Merge remote-tracking branch 'upstream/master' into Acl
* upstream/master: HTML5 - added new line at the end of the file Add check for min/max length for chat message HTML5 - fixed setting's dropdown re-render issue
This commit is contained in:
commit
9b41e3a475
@ -35,6 +35,8 @@ class Chat extends Component {
|
||||
lastReadMessageTime,
|
||||
partnerIsLoggedOut,
|
||||
isChatLocked,
|
||||
minMessageLength,
|
||||
maxMessageLength,
|
||||
actions,
|
||||
intl,
|
||||
} = this.props;
|
||||
@ -81,6 +83,8 @@ class Chat extends Component {
|
||||
chatAreaId={ELEMENT_ID}
|
||||
chatTitle={title}
|
||||
chatName={chatName}
|
||||
minMessageLength={minMessageLength}
|
||||
maxMessageLength={maxMessageLength}
|
||||
handleSendMessage={actions.handleSendMessage}
|
||||
/>
|
||||
</div>
|
||||
|
@ -99,13 +99,14 @@ export default injectIntl(createContainer(({ params, intl }) => {
|
||||
partnerIsLoggedOut,
|
||||
isChatLocked,
|
||||
scrollPosition,
|
||||
minMessageLength: CHAT_CONFIG.min_message_length,
|
||||
maxMessageLength: CHAT_CONFIG.max_message_length,
|
||||
actions: {
|
||||
|
||||
handleClosePrivateChat: chatID => ChatService.closePrivateChat(chatID),
|
||||
|
||||
handleSendMessage: message => {
|
||||
ChatService.updateScrollPosition(chatID, null);
|
||||
let sentMessage = ChatService.sendMessage(chatID, message);
|
||||
return ChatService.sendMessage(chatID, message);
|
||||
},
|
||||
|
||||
handleScrollUpdate: position => ChatService.updateScrollPosition(chatID, position),
|
||||
|
@ -27,6 +27,12 @@ const messages = defineMessages({
|
||||
id: 'app.chat.inputPlaceholder',
|
||||
description: 'Chat message input placeholder',
|
||||
},
|
||||
errorMinMessageLength: {
|
||||
id: 'app.chat.errorMinMessageLength',
|
||||
},
|
||||
errorMaxMessageLength: {
|
||||
id: 'app.chat.errorMaxMessageLength',
|
||||
},
|
||||
});
|
||||
|
||||
class MessageForm extends Component {
|
||||
@ -35,6 +41,8 @@ class MessageForm extends Component {
|
||||
|
||||
this.state = {
|
||||
message: '',
|
||||
error: '',
|
||||
hasErrors: false,
|
||||
};
|
||||
|
||||
this.handleMessageChange = this.handleMessageChange.bind(this);
|
||||
@ -44,7 +52,6 @@ class MessageForm extends Component {
|
||||
}
|
||||
|
||||
handleMessageKeyDown(e) {
|
||||
|
||||
//TODO Prevent send message pressing enter on mobile and/or virtual keyboard
|
||||
if (e.keyCode === 13 && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
@ -59,73 +66,98 @@ class MessageForm extends Component {
|
||||
}
|
||||
|
||||
handleMessageChange(e) {
|
||||
this.setState({ message: e.target.value });
|
||||
const { intl } = this.props;
|
||||
|
||||
const message = e.target.value;
|
||||
let error = '';
|
||||
|
||||
const { minMessageLength, maxMessageLength, } = this.props;
|
||||
|
||||
if (message.length < minMessageLength) {
|
||||
error = intl.formatMessage(messages.errorMinMessageLength,
|
||||
{ 0: minMessageLength - message.length });
|
||||
}
|
||||
|
||||
if (message.length > maxMessageLength) {
|
||||
error = intl.formatMessage(messages.errorMaxMessageLength,
|
||||
{ 0: message.length - maxMessageLength });
|
||||
}
|
||||
|
||||
this.setState({
|
||||
message,
|
||||
error,
|
||||
});
|
||||
}
|
||||
|
||||
handleSubmit(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const { disabled } = this.props;
|
||||
const { disabled, minMessageLength, maxMessageLength, } = this.props;
|
||||
let message = this.state.message.trim();
|
||||
|
||||
if (disabled) {
|
||||
if (disabled
|
||||
|| message.length === 0
|
||||
|| message.length < minMessageLength
|
||||
|| message.length > maxMessageLength) {
|
||||
this.setState({ hasErrors: true, });
|
||||
return false;
|
||||
}
|
||||
|
||||
let message = this.state.message.trim();
|
||||
|
||||
// Sanitize. See: http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript/
|
||||
|
||||
let div = document.createElement('div');
|
||||
div.appendChild(document.createTextNode(message));
|
||||
message = div.innerHTML;
|
||||
|
||||
if (message) {
|
||||
this.props.handleSendMessage(message);
|
||||
}
|
||||
|
||||
this.setState({ message: '' });
|
||||
return this.props.handleSendMessage(message)
|
||||
.then(() => this.setState({
|
||||
message: '',
|
||||
hasErrors: false,
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, chatTitle, chatName, disabled } = this.props;
|
||||
const { intl, chatTitle, chatName, disabled,
|
||||
minMessageLength, maxMessageLength, } = this.props;
|
||||
|
||||
const { hasErrors, error } = this.state;
|
||||
|
||||
return (
|
||||
<form
|
||||
ref="form"
|
||||
className={cx(this.props.className, styles.form)}
|
||||
onSubmit={this.handleSubmit}>
|
||||
{
|
||||
// <MessageFormActions
|
||||
// onClick={() => alert('Not supported yet...')}
|
||||
// className={styles.actions}
|
||||
// disabled={disabled}
|
||||
// label={'More actions'}
|
||||
// />
|
||||
}
|
||||
<TextareaAutosize
|
||||
className={styles.input}
|
||||
id="message-input"
|
||||
placeholder={intl.formatMessage(messages.inputPlaceholder, { 0: chatName })}
|
||||
aria-controls={this.props.chatAreaId}
|
||||
aria-label={intl.formatMessage(messages.inputLabel, { 0: chatTitle })}
|
||||
autoCorrect="off"
|
||||
autoComplete="off"
|
||||
spellCheck="true"
|
||||
disabled={disabled}
|
||||
value={this.state.message}
|
||||
onChange={this.handleMessageChange}
|
||||
onKeyDown={this.handleMessageKeyDown}
|
||||
/>
|
||||
<Button
|
||||
className={styles.sendButton}
|
||||
aria-label={intl.formatMessage(messages.submitLabel)}
|
||||
type="submit"
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage(messages.submitLabel)}
|
||||
hideLabel={true}
|
||||
icon={'send'}
|
||||
onClick={this.handleMessageKeyDown({ keyCode: 13 })}
|
||||
/>
|
||||
<div className={styles.wrapper}>
|
||||
<TextareaAutosize
|
||||
className={styles.input}
|
||||
id="message-input"
|
||||
placeholder={intl.formatMessage(messages.inputPlaceholder, { 0: chatName })}
|
||||
aria-controls={this.props.chatAreaId}
|
||||
aria-label={intl.formatMessage(messages.inputLabel, { 0: chatTitle })}
|
||||
aria-invalid={ hasErrors ? 'true' : 'false' }
|
||||
aria-describedby={ hasErrors ? 'message-input-error' : null }
|
||||
autoCorrect="off"
|
||||
autoComplete="off"
|
||||
spellCheck="true"
|
||||
disabled={disabled}
|
||||
value={this.state.message}
|
||||
onChange={this.handleMessageChange}
|
||||
onKeyDown={this.handleMessageKeyDown}
|
||||
/>
|
||||
<Button
|
||||
className={styles.sendButton}
|
||||
aria-label={intl.formatMessage(messages.submitLabel)}
|
||||
type="submit"
|
||||
disabled={disabled}
|
||||
label={intl.formatMessage(messages.submitLabel)}
|
||||
hideLabel={true}
|
||||
icon="send"
|
||||
onClick={() => null}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.info}>
|
||||
{ hasErrors ? <span id="message-input-error">{error}</span> : null }
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
@ -4,9 +4,14 @@
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
align-self: flex-end;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
margin-bottom: -$sm-padding-x;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.actions {
|
||||
@ -72,3 +77,14 @@
|
||||
color: $color-gray-light;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
font-size: $font-size-base * .75;
|
||||
color: $color-gray-light;
|
||||
text-align: right;
|
||||
padding: $border-size 0;
|
||||
|
||||
&:before {
|
||||
content: "\00a0"; // non-breaking space
|
||||
}
|
||||
}
|
||||
|
@ -192,9 +192,7 @@ const sendMessage = (receiverID, message) => {
|
||||
Storage.setItem(CLOSED_CHAT_LIST_KEY, _.without(currentClosedChats, receiver.id));
|
||||
}
|
||||
|
||||
makeCall('sendChat', messagePayload);
|
||||
|
||||
return messagePayload;
|
||||
return makeCall('sendChat', messagePayload);
|
||||
};
|
||||
|
||||
const getScrollPosition = (receiverID) => {
|
||||
|
@ -67,6 +67,25 @@ const intlMessages = defineMessages({
|
||||
class SettingsDropdown extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isSettingOpen: false,
|
||||
};
|
||||
|
||||
this.onActionsShow = this.onActionsShow.bind(this);
|
||||
this.onActionsHide = this.onActionsHide.bind(this);
|
||||
}
|
||||
|
||||
onActionsShow() {
|
||||
this.setState({
|
||||
isSettingOpen: true,
|
||||
});
|
||||
}
|
||||
|
||||
onActionsHide() {
|
||||
this.setState({
|
||||
isSettingOpen: false,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -83,7 +102,10 @@ class SettingsDropdown extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<Dropdown autoFocus={true}>
|
||||
<Dropdown isOpen={this.state.isSettingOpen}
|
||||
onShow={this.onActionsShow}
|
||||
onHide={this.onActionsHide}
|
||||
autoFocus={true}>
|
||||
<DropdownTrigger placeInTabOrder={true}>
|
||||
<Button
|
||||
label={intl.formatMessage(intlMessages.optionsLabel)}
|
||||
|
@ -83,15 +83,6 @@ const userNameSubTransition = {
|
||||
};
|
||||
|
||||
class UserListItem extends Component {
|
||||
componentDidMount() {
|
||||
const { addEventListener } = window;
|
||||
addEventListener('click', this.handleClickOutsideDropdown, false);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { removeEventListener } = window;
|
||||
removeEventListener('click', this.handleClickOutsideDropdown, false);
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -8,7 +8,7 @@ $font-size-base: 1rem !default;
|
||||
$font-size-large: 1.25rem !default;
|
||||
$font-size-small: .875rem !default;
|
||||
|
||||
$line-height-base: 1.428571429 !default; // 20/14
|
||||
$line-height-base: 1.25 !default; // 20/16
|
||||
$line-height-computed: floor(($font-size-base * $line-height-base)) !default;
|
||||
|
||||
$headings-font-family: inherit !default;
|
||||
|
@ -1,5 +1,7 @@
|
||||
# Chat service configurations
|
||||
chat:
|
||||
min_message_length: 1
|
||||
max_message_length: 5000
|
||||
grouping_messages_window: 60000
|
||||
# Chat types
|
||||
type_system: 'SYSTEM_MESSAGE'
|
||||
|
@ -10,6 +10,8 @@
|
||||
"app.chat.submitLabel": "Send Message",
|
||||
"app.chat.inputLabel": "Message input for chat {0}",
|
||||
"app.chat.inputPlaceholder": "Message {0}",
|
||||
"app.chat.errorMinMessageLength": "The message is {0} characters(s) too short",
|
||||
"app.chat.errorMaxMessageLength": "The message is {0} characters(s) too long",
|
||||
"app.chat.titlePublic": "Public Chat",
|
||||
"app.chat.titlePrivate": "Private Chat with {0}",
|
||||
"app.chat.partnerDisconnected": "{0} has left the meeting",
|
||||
|
Loading…
Reference in New Issue
Block a user