Jeremie Pardou-Piquemal 31db176328 Refactor Chat
2023-01-01 18:08:55 +01:00

245 lines
5.9 KiB
JavaScript

import React from 'react';
import PropTypes from 'prop-types';
import sanitizeHtml from 'sanitize-html';
import { CornerDownRight } from 'react-feather';
import { hasTouchSupport } from '@/utils/dom';
import FileTransfer from '@/components/FileTransfer';
export const Chat = ({ sendEncryptedMessage, showNotice, userId, username, clearActivities, translations }) => {
const [message, setMessage] = React.useState('');
const [shiftKeyDown, setShiftKeyDown] = React.useState(false);
const textInputRef = React.useRef();
const touchSupport = hasTouchSupport;
const canSend = message.trim().length;
const commands = [
{
command: 'nick',
description: 'Changes nickname.',
parameters: ['{username}'],
usage: '/nick {username}',
scope: 'global',
action: params => {
// eslint-disable-line
let newUsername = params.join(' ') || ''; // eslint-disable-line
// Remove things that aren't digits or chars
newUsername = newUsername.replace(/[^A-Za-z0-9]/g, '-');
const errors = [];
if (!newUsername.trim().length) {
errors.push('Username cannot be blank');
}
if (newUsername.toString().length > 16) {
errors.push('Username cannot be greater than 16 characters');
}
if (!newUsername.match(/^[A-Z]/i)) {
errors.push('Username must start with a letter');
}
if (errors.length) {
return showNotice({
message: `${errors.join(', ')}`,
level: 'error',
});
}
sendEncryptedMessage({
type: 'CHANGE_USERNAME',
payload: {
id: userId,
newUsername,
currentUsername: username,
},
});
},
},
{
command: 'help',
description: 'Shows a list of commands.',
parameters: [],
usage: '/help',
scope: 'local',
action: () => {
const validCommands = commands.map(command => `/${command.command}`);
showNotice({
message: `Valid commands: ${validCommands.sort().join(', ')}`,
level: 'info',
});
},
},
{
command: 'me',
description: 'Invoke virtual action',
parameters: ['{action}'],
usage: '/me {action}',
scope: 'global',
action: params => {
const actionMessage = params.join(' ');
if (!actionMessage.trim().length) {
return false;
}
sendEncryptedMessage({
type: 'USER_ACTION',
payload: {
action: actionMessage,
},
});
},
},
{
command: 'clear',
description: 'Clears the chat screen',
parameters: [],
usage: '/clear',
scope: 'local',
action: () => {
clearActivities();
},
},
];
const handleKeyUp = e => {
if (e.key === 'Shift') {
setShiftKeyDown(false);
}
};
const handleKeyPress = e => {
if (e.key === 'Shift') {
setShiftKeyDown(true);
}
if (e.key === 'Enter' && !hasTouchSupport && !shiftKeyDown) {
e.preventDefault();
if (canSend) {
sendMessage();
} else {
setMessage('');
}
}
};
const executeCommand = command => {
const commandToExecute = commands.find(cmnd => cmnd.command === command.command);
if (commandToExecute) {
const { params } = command;
const commandResult = commandToExecute.action(params);
return commandResult;
}
return null;
};
const handleSendClick = () => {
sendMessage();
textInputRef.current.focus();
};
const handleFormSubmit = evt => {
evt.preventDefault();
sendMessage();
};
const parseCommand = message => {
const commandTrigger = {
command: null,
params: [],
};
if (message.charAt(0) === '/') {
const parsedCommand = message.replace('/', '').split(' ');
commandTrigger.command = sanitizeHtml(parsedCommand[0]) || null;
// Get params
if (parsedCommand.length >= 2) {
for (let i = 1; i < parsedCommand.length; i++) {
commandTrigger.params.push(parsedCommand[i]);
}
}
return commandTrigger;
}
return false;
};
const sendMessage = () => {
if (!canSend) {
return;
}
const isCommand = parseCommand(message);
if (isCommand) {
const res = executeCommand(isCommand);
if (res === false) {
return;
}
} else {
sendEncryptedMessage({
type: 'TEXT_MESSAGE',
payload: {
text: message,
timestamp: Date.now(),
},
});
}
setMessage('');
};
const handleInputChange = evt => {
setMessage(evt.target.value);
};
return (
<form onSubmit={handleFormSubmit} className="chat-preflight-container">
<textarea
rows="1"
onKeyUp={handleKeyUp}
onKeyDown={handleKeyPress}
ref={textInputRef}
autoFocus
className="chat"
value={message}
placeholder={translations.typePlaceholder}
onChange={handleInputChange}
/>
<div className="input-controls">
<FileTransfer sendEncryptedMessage={sendEncryptedMessage} />
{touchSupport && (
<button
onClick={handleSendClick}
className={`icon is-right send btn btn-link ${canSend ? 'active' : ''}`}
title="Send"
>
<CornerDownRight className={canSend ? '' : 'disabled'} />
</button>
)}
</div>
</form>
);
};
Chat.propTypes = {
sendEncryptedMessage: PropTypes.func.isRequired,
showNotice: PropTypes.func.isRequired,
userId: PropTypes.string.isRequired,
username: PropTypes.string.isRequired,
clearActivities: PropTypes.func.isRequired,
focusChat: PropTypes.bool.isRequired,
scrollToBottom: PropTypes.func.isRequired,
translations: PropTypes.object.isRequired,
};
export default Chat;