Can send a message from text input using submit button
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { SmileOutlined } from '@ant-design/icons';
|
||||
import { Button, Input } from 'antd';
|
||||
import { useRef, useState } from 'react';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import ContentEditable from 'react-contenteditable';
|
||||
import WebsocketService from '../../../services/websocket-service';
|
||||
import { websocketServiceAtom } from '../../stores/ClientConfigStore';
|
||||
import { getCaretPosition, convertToText, convertOnPaste } from '../chat';
|
||||
import { MessageType } from '../../../interfaces/socket-events';
|
||||
import s from './ChatTextField.module.scss';
|
||||
|
||||
interface Props {
|
||||
@@ -22,8 +24,27 @@ export default function ChatTextField(props: Props) {
|
||||
// large is 40px
|
||||
const size = 'small';
|
||||
|
||||
const sendMessage = () => {
|
||||
if (!websocketService) {
|
||||
console.log('websocketService is not defined');
|
||||
return;
|
||||
}
|
||||
|
||||
const message = convertToText(value);
|
||||
websocketService.send({ type: MessageType.CHAT, body: message });
|
||||
setValue('');
|
||||
};
|
||||
|
||||
const handleChange = evt => {
|
||||
text.current = evt.target.value;
|
||||
setValue(evt.target.value);
|
||||
};
|
||||
|
||||
const handleKeyDown = event => {
|
||||
const key = event && event.key;
|
||||
|
||||
if (key === 'Enter') {
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -33,11 +54,14 @@ export default function ChatTextField(props: Props) {
|
||||
style={{ width: '60%', maxHeight: '50px', padding: '5px' }}
|
||||
html={text.current}
|
||||
onChange={handleChange}
|
||||
onKeyDown={e => {
|
||||
handleKeyDown(e);
|
||||
}}
|
||||
/>
|
||||
<Button type="default" ghost title="Emoji" onClick={() => setShowEmojis(!showEmojis)}>
|
||||
<SmileOutlined style={{ color: 'rgba(0,0,0,.45)' }} />
|
||||
</Button>
|
||||
<Button size={size} type="primary">
|
||||
<Button size={size} type="primary" onClick={sendMessage}>
|
||||
Submit
|
||||
</Button>
|
||||
</Input.Group>
|
||||
|
||||
191
web/components/chat/chat.js
Normal file
191
web/components/chat/chat.js
Normal file
@@ -0,0 +1,191 @@
|
||||
// import {
|
||||
// CHAT_INITIAL_PLACEHOLDER_TEXT,
|
||||
// CHAT_PLACEHOLDER_TEXT,
|
||||
// CHAT_PLACEHOLDER_OFFLINE,
|
||||
// } from './constants.js';
|
||||
|
||||
// Taken from https://stackoverflow.com/a/46902361
|
||||
export function getCaretPosition(node) {
|
||||
const selection = window.getSelection();
|
||||
|
||||
if (selection.rangeCount === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const range = selection.getRangeAt(0);
|
||||
const preCaretRange = range.cloneRange();
|
||||
const tempElement = document.createElement('div');
|
||||
|
||||
preCaretRange.selectNodeContents(node);
|
||||
preCaretRange.setEnd(range.endContainer, range.endOffset);
|
||||
tempElement.appendChild(preCaretRange.cloneContents());
|
||||
|
||||
return tempElement.innerHTML.length;
|
||||
}
|
||||
|
||||
// Might not need this anymore
|
||||
// Pieced together from parts of https://stackoverflow.com/questions/6249095/how-to-set-caretcursor-position-in-contenteditable-element-div
|
||||
export function setCaretPosition(editableDiv, position) {
|
||||
const range = document.createRange();
|
||||
const sel = window.getSelection();
|
||||
range.selectNode(editableDiv);
|
||||
range.setStart(editableDiv.childNodes[0], position);
|
||||
range.collapse(true);
|
||||
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(range);
|
||||
}
|
||||
|
||||
// export function generatePlaceholderText(isEnabled, hasSentFirstChatMessage) {
|
||||
// if (isEnabled) {
|
||||
// return hasSentFirstChatMessage
|
||||
// ? CHAT_PLACEHOLDER_TEXT
|
||||
// : CHAT_INITIAL_PLACEHOLDER_TEXT;
|
||||
// }
|
||||
// return CHAT_PLACEHOLDER_OFFLINE;
|
||||
// }
|
||||
|
||||
export function extraUserNamesFromMessageHistory(messages) {
|
||||
const list = [];
|
||||
if (messages) {
|
||||
messages
|
||||
.filter(m => m.user && m.user.displayName)
|
||||
.forEach(message => {
|
||||
if (!list.includes(message.user.displayName)) {
|
||||
list.push(message.user.displayName);
|
||||
}
|
||||
});
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
// utils from https://gist.github.com/nathansmith/86b5d4b23ed968a92fd4
|
||||
/*
|
||||
You would call this after getting an element's
|
||||
`.innerHTML` value, while the user is typing.
|
||||
*/
|
||||
export function convertToText(str = '') {
|
||||
// Ensure string.
|
||||
let value = String(str);
|
||||
|
||||
// Convert encoding.
|
||||
value = value.replace(/ /gi, ' ');
|
||||
value = value.replace(/&/gi, '&');
|
||||
|
||||
// Replace `<br>`.
|
||||
value = value.replace(/<br>/gi, '\n');
|
||||
|
||||
// Replace `<div>` (from Chrome).
|
||||
value = value.replace(/<div>/gi, '\n');
|
||||
|
||||
// Replace `<p>` (from IE).
|
||||
value = value.replace(/<p>/gi, '\n');
|
||||
|
||||
// Cleanup the emoji titles.
|
||||
value = value.replace(/\u200C{2}/gi, '');
|
||||
|
||||
// Trim each line.
|
||||
value = value
|
||||
.split('\n')
|
||||
.map((line = '') => line.trim())
|
||||
.join('\n');
|
||||
|
||||
// No more than 2x newline, per "paragraph".
|
||||
value = value.replace(/\n\n+/g, '\n\n');
|
||||
|
||||
// Clean up spaces.
|
||||
value = value.replace(/[ ]+/g, ' ');
|
||||
value = value.trim();
|
||||
|
||||
// Expose string.
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
You would call this when a user pastes from
|
||||
the clipboard into a `contenteditable` area.
|
||||
*/
|
||||
export function convertOnPaste(event = { preventDefault() {} }, emojiList) {
|
||||
// Prevent paste.
|
||||
event.preventDefault();
|
||||
|
||||
// Set later.
|
||||
let value = '';
|
||||
|
||||
// Does method exist?
|
||||
const hasEventClipboard = !!(
|
||||
event.clipboardData &&
|
||||
typeof event.clipboardData === 'object' &&
|
||||
typeof event.clipboardData.getData === 'function'
|
||||
);
|
||||
|
||||
// Get clipboard data?
|
||||
if (hasEventClipboard) {
|
||||
value = event.clipboardData.getData('text/plain');
|
||||
}
|
||||
|
||||
// Insert into temp `<textarea>`, read back out.
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.innerHTML = value;
|
||||
value = textarea.innerText;
|
||||
|
||||
// Clean up text.
|
||||
value = convertToText(value);
|
||||
|
||||
const HTML = emojify(value, emojiList);
|
||||
|
||||
// Insert text.
|
||||
if (typeof document.execCommand === 'function') {
|
||||
document.execCommand('insertHTML', false, HTML);
|
||||
}
|
||||
}
|
||||
|
||||
export function createEmojiMarkup(data, isCustom) {
|
||||
const emojiUrl = isCustom ? data.emoji : data.url;
|
||||
const emojiName = (
|
||||
isCustom ? data.name : data.url.split('\\').pop().split('/').pop().split('.').shift()
|
||||
).toLowerCase();
|
||||
return `<img class="emoji" alt=":${emojiName}:" title=":${emojiName}:" src="${emojiUrl}"/>`;
|
||||
}
|
||||
|
||||
// trim html white space characters from ends of messages for more accurate counting
|
||||
export function trimNbsp(html) {
|
||||
return html.replace(/^(?: |\s)+|(?: |\s)+$/gi, '');
|
||||
}
|
||||
|
||||
export function emojify(HTML, emojiList) {
|
||||
const textValue = convertToText(HTML);
|
||||
|
||||
for (let lastPos = textValue.length; lastPos >= 0; lastPos--) {
|
||||
const endPos = textValue.lastIndexOf(':', lastPos);
|
||||
if (endPos <= 0) {
|
||||
break;
|
||||
}
|
||||
const startPos = textValue.lastIndexOf(':', endPos - 1);
|
||||
if (startPos === -1) {
|
||||
break;
|
||||
}
|
||||
const typedEmoji = textValue.substring(startPos + 1, endPos).trim();
|
||||
const emojiIndex = emojiList.findIndex(
|
||||
emojiItem => emojiItem.name.toLowerCase() === typedEmoji.toLowerCase(),
|
||||
);
|
||||
|
||||
if (emojiIndex != -1) {
|
||||
const emojiImgElement = createEmojiMarkup(emojiList[emojiIndex], true);
|
||||
HTML = HTML.replace(`:${typedEmoji}:`, emojiImgElement);
|
||||
}
|
||||
}
|
||||
return HTML;
|
||||
}
|
||||
|
||||
// MODERATOR UTILS
|
||||
export function checkIsModerator(message) {
|
||||
const { user } = message;
|
||||
const { scopes } = user;
|
||||
|
||||
if (!scopes || scopes.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return scopes.includes('MODERATOR');
|
||||
}
|
||||
@@ -75,12 +75,11 @@ export function ClientConfigStore() {
|
||||
const [websocketService, setWebsocketService] =
|
||||
useRecoilState<WebsocketService>(websocketServiceAtom);
|
||||
|
||||
// let websocketService: WebsocketService;
|
||||
let ws: WebsocketService;
|
||||
|
||||
const updateClientConfig = async () => {
|
||||
try {
|
||||
const config = await ClientConfigService.getConfig();
|
||||
// console.log(`ClientConfig: ${JSON.stringify(config)}`);
|
||||
setClientConfig(config);
|
||||
setAppState(AppState.Online);
|
||||
} catch (error) {
|
||||
@@ -133,7 +132,7 @@ export function ClientConfigStore() {
|
||||
const startChat = async () => {
|
||||
setChatState(ChatState.Loading);
|
||||
try {
|
||||
const ws = new WebsocketService(accessToken, '/ws');
|
||||
ws = new WebsocketService(accessToken, '/ws');
|
||||
ws.handleMessage = handleMessage;
|
||||
setWebsocketService(ws);
|
||||
} catch (error) {
|
||||
|
||||
@@ -32,7 +32,7 @@ export const Example = Template.bind({});
|
||||
export const LongerMessage = Template.bind({});
|
||||
LongerMessage.args = {
|
||||
value:
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
|
||||
'Lorem ipsum dolor sit amet, <img src="https://watch.owncast.online/img/emoji/bluntparrot.gif" width="40px" /> consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
|
||||
};
|
||||
|
||||
LongerMessage.parameters = {
|
||||
|
||||
Reference in New Issue
Block a user