mirror of
https://github.com/darkwire/darkwire.io.git
synced 2025-08-01 14:43:22 +00:00
Refactor Chat
This commit is contained in:
parent
7eb31f13e3
commit
31db176328
@ -1,171 +1,134 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import sanitizeHtml from 'sanitize-html';
|
import sanitizeHtml from 'sanitize-html';
|
||||||
import { CornerDownRight } from 'react-feather';
|
import { CornerDownRight } from 'react-feather';
|
||||||
|
|
||||||
import { getSelectedText, hasTouchSupport } from '@/utils/dom';
|
import { hasTouchSupport } from '@/utils/dom';
|
||||||
|
|
||||||
import FileTransfer from '@/components/FileTransfer';
|
import FileTransfer from '@/components/FileTransfer';
|
||||||
|
|
||||||
export class Chat extends Component {
|
export const Chat = ({ sendEncryptedMessage, showNotice, userId, username, clearActivities, translations }) => {
|
||||||
constructor(props) {
|
const [message, setMessage] = React.useState('');
|
||||||
super(props);
|
const [shiftKeyDown, setShiftKeyDown] = React.useState(false);
|
||||||
this.state = {
|
const textInputRef = React.useRef();
|
||||||
message: '',
|
|
||||||
touchSupport: hasTouchSupport,
|
|
||||||
shiftKeyDown: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
this.commands = [
|
const touchSupport = hasTouchSupport;
|
||||||
{
|
|
||||||
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
|
const canSend = message.trim().length;
|
||||||
newUsername = newUsername.replace(/[^A-Za-z0-9]/g, '-');
|
|
||||||
|
|
||||||
const errors = [];
|
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
|
||||||
|
|
||||||
if (!newUsername.trim().length) {
|
// Remove things that aren't digits or chars
|
||||||
errors.push('Username cannot be blank');
|
newUsername = newUsername.replace(/[^A-Za-z0-9]/g, '-');
|
||||||
}
|
|
||||||
|
|
||||||
if (newUsername.toString().length > 16) {
|
const errors = [];
|
||||||
errors.push('Username cannot be greater than 16 characters');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newUsername.match(/^[A-Z]/i)) {
|
if (!newUsername.trim().length) {
|
||||||
errors.push('Username must start with a letter');
|
errors.push('Username cannot be blank');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errors.length) {
|
if (newUsername.toString().length > 16) {
|
||||||
return this.props.showNotice({
|
errors.push('Username cannot be greater than 16 characters');
|
||||||
message: `${errors.join(', ')}`,
|
}
|
||||||
level: 'error',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.sendEncryptedMessage({
|
if (!newUsername.match(/^[A-Z]/i)) {
|
||||||
type: 'CHANGE_USERNAME',
|
errors.push('Username must start with a letter');
|
||||||
payload: {
|
}
|
||||||
id: this.props.userId,
|
|
||||||
newUsername,
|
if (errors.length) {
|
||||||
currentUsername: this.props.username,
|
return showNotice({
|
||||||
},
|
message: `${errors.join(', ')}`,
|
||||||
|
level: 'error',
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
},
|
|
||||||
{
|
|
||||||
command: 'help',
|
|
||||||
description: 'Shows a list of commands.',
|
|
||||||
paramaters: [],
|
|
||||||
usage: '/help',
|
|
||||||
scope: 'local',
|
|
||||||
action: params => {
|
|
||||||
// eslint-disable-line
|
|
||||||
const validCommands = this.commands.map(command => `/${command.command}`);
|
|
||||||
this.props.showNotice({
|
|
||||||
message: `Valid commands: ${validCommands.sort().join(', ')}`,
|
|
||||||
level: 'info',
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
command: 'me',
|
|
||||||
description: 'Invoke virtual action',
|
|
||||||
paramaters: ['{action}'],
|
|
||||||
usage: '/me {action}',
|
|
||||||
scope: 'global',
|
|
||||||
action: params => {
|
|
||||||
// eslint-disable-line
|
|
||||||
const actionMessage = params.join(' ');
|
|
||||||
if (!actionMessage.trim().length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.props.sendEncryptedMessage({
|
sendEncryptedMessage({
|
||||||
type: 'USER_ACTION',
|
type: 'CHANGE_USERNAME',
|
||||||
payload: {
|
payload: {
|
||||||
action: actionMessage,
|
id: userId,
|
||||||
},
|
newUsername,
|
||||||
});
|
currentUsername: username,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
{
|
|
||||||
command: 'clear',
|
|
||||||
description: 'Clears the chat screen',
|
|
||||||
paramaters: [],
|
|
||||||
usage: '/clear',
|
|
||||||
scope: 'local',
|
|
||||||
action: (params = null) => {
|
|
||||||
// eslint-disable-line
|
|
||||||
this.props.clearActivities();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
if (!hasTouchSupport) {
|
|
||||||
// Disable for now due to vary issues:
|
|
||||||
// Paste not working, shift+enter line breaks
|
|
||||||
// autosize(this.textInput);
|
|
||||||
this.textInput.addEventListener('autosize:resized', () => {
|
|
||||||
this.props.scrollToBottom();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
if (nextProps.focusChat) {
|
|
||||||
if (!getSelectedText()) {
|
|
||||||
// Don't focus for now, evaluate UX benfits
|
|
||||||
// this.textInput.focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(nextProps, nextState) {
|
|
||||||
if (!nextState.message.trim().length) {
|
|
||||||
// autosize.update(this.textInput)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeyUp(e) {
|
|
||||||
if (e.key === 'Shift') {
|
|
||||||
this.setState({
|
|
||||||
shiftKeyDown: false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
handleKeyPress(e) {
|
|
||||||
if (e.key === 'Shift') {
|
|
||||||
this.setState({
|
|
||||||
shiftKeyDown: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Fix when autosize is enabled - line breaks require shift+enter twice
|
|
||||||
if (e.key === 'Enter' && !hasTouchSupport && !this.state.shiftKeyDown) {
|
|
||||||
e.preventDefault();
|
|
||||||
if (this.canSend()) {
|
|
||||||
this.sendMessage();
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
message: '',
|
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
executeCommand(command) {
|
const executeCommand = command => {
|
||||||
const commandToExecute = this.commands.find(cmnd => cmnd.command === command.command);
|
const commandToExecute = commands.find(cmnd => cmnd.command === command.command);
|
||||||
|
|
||||||
if (commandToExecute) {
|
if (commandToExecute) {
|
||||||
const { params } = command;
|
const { params } = command;
|
||||||
@ -175,19 +138,19 @@ export class Chat extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
};
|
||||||
|
|
||||||
handleSendClick() {
|
const handleSendClick = () => {
|
||||||
this.sendMessage.bind(this);
|
sendMessage();
|
||||||
this.textInput.focus();
|
textInputRef.current.focus();
|
||||||
}
|
};
|
||||||
|
|
||||||
handleFormSubmit(evt) {
|
const handleFormSubmit = evt => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
this.sendMessage();
|
sendMessage();
|
||||||
}
|
};
|
||||||
|
|
||||||
parseCommand(message) {
|
const parseCommand = message => {
|
||||||
const commandTrigger = {
|
const commandTrigger = {
|
||||||
command: null,
|
command: null,
|
||||||
params: [],
|
params: [],
|
||||||
@ -207,23 +170,22 @@ export class Chat extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
sendMessage() {
|
const sendMessage = () => {
|
||||||
if (!this.canSend()) {
|
if (!canSend) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { message } = this.state;
|
const isCommand = parseCommand(message);
|
||||||
const isCommand = this.parseCommand(message);
|
|
||||||
|
|
||||||
if (isCommand) {
|
if (isCommand) {
|
||||||
const res = this.executeCommand(isCommand);
|
const res = executeCommand(isCommand);
|
||||||
if (res === false) {
|
if (res === false) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.props.sendEncryptedMessage({
|
sendEncryptedMessage({
|
||||||
type: 'TEXT_MESSAGE',
|
type: 'TEXT_MESSAGE',
|
||||||
payload: {
|
payload: {
|
||||||
text: message,
|
text: message,
|
||||||
@ -232,55 +194,41 @@ export class Chat extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({
|
setMessage('');
|
||||||
message: '',
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
handleInputChange(evt) {
|
const handleInputChange = evt => {
|
||||||
this.setState({
|
setMessage(evt.target.value);
|
||||||
message: evt.target.value,
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
canSend() {
|
return (
|
||||||
return this.state.message.trim().length;
|
<form onSubmit={handleFormSubmit} className="chat-preflight-container">
|
||||||
}
|
<textarea
|
||||||
|
rows="1"
|
||||||
render() {
|
onKeyUp={handleKeyUp}
|
||||||
const touchSupport = this.state.touchSupport;
|
onKeyDown={handleKeyPress}
|
||||||
|
ref={textInputRef}
|
||||||
return (
|
autoFocus
|
||||||
<form onSubmit={this.handleFormSubmit.bind(this)} className="chat-preflight-container">
|
className="chat"
|
||||||
<textarea
|
value={message}
|
||||||
rows="1"
|
placeholder={translations.typePlaceholder}
|
||||||
onKeyUp={this.handleKeyUp.bind(this)}
|
onChange={handleInputChange}
|
||||||
onKeyDown={this.handleKeyPress.bind(this)}
|
/>
|
||||||
ref={input => {
|
<div className="input-controls">
|
||||||
this.textInput = input;
|
<FileTransfer sendEncryptedMessage={sendEncryptedMessage} />
|
||||||
}}
|
{touchSupport && (
|
||||||
autoFocus
|
<button
|
||||||
className="chat"
|
onClick={handleSendClick}
|
||||||
value={this.state.message}
|
className={`icon is-right send btn btn-link ${canSend ? 'active' : ''}`}
|
||||||
placeholder={this.props.translations.typePlaceholder}
|
title="Send"
|
||||||
onChange={this.handleInputChange.bind(this)}
|
>
|
||||||
/>
|
<CornerDownRight className={canSend ? '' : 'disabled'} />
|
||||||
<div className="input-controls">
|
</button>
|
||||||
<FileTransfer sendEncryptedMessage={this.props.sendEncryptedMessage} />
|
)}
|
||||||
{touchSupport && (
|
</div>
|
||||||
<button
|
</form>
|
||||||
onClick={this.handleSendClick.bind(this)}
|
);
|
||||||
className={`icon is-right send btn btn-link ${this.canSend() ? 'active' : ''}`}
|
};
|
||||||
title="Send"
|
|
||||||
>
|
|
||||||
<CornerDownRight className={this.canSend() ? '' : 'disabled'} />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Chat.propTypes = {
|
Chat.propTypes = {
|
||||||
sendEncryptedMessage: PropTypes.func.isRequired,
|
sendEncryptedMessage: PropTypes.func.isRequired,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user