diff --git a/bigbluebutton-html5/imports/ui/components/chat/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/component.jsx index 6c1cec82e9bb723415fa38b2ed420d569d87211c..4de0c264927ebb3da354186eb5e6273e039a9698 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/component.jsx @@ -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> diff --git a/bigbluebutton-html5/imports/ui/components/chat/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/container.jsx index e4705b2976db00564f20aa027d1b1e351e93f8c6..e114314e66ce6c013456ea97c1465d233fa677a4 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/container.jsx @@ -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), diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx index 2c0dd9e2a99d1841ea9758851a93d2f0ca390479..c839510da98a183a4da7c5bfb52bbd4173219c58 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx @@ -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> ); } diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.scss index a75cdfd355095041e84ca7a8db4d728e932576ba..9fbc08f17b8aa5709df66a27c79dcf90e1027536 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.scss @@ -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 + } +} diff --git a/bigbluebutton-html5/imports/ui/components/chat/service.js b/bigbluebutton-html5/imports/ui/components/chat/service.js index 4f1a6fe6b54eef79020000c71b1bc5b7b56af85b..603762d9bac912106a6aeb77d944d70cc685a5b8 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/service.js +++ b/bigbluebutton-html5/imports/ui/components/chat/service.js @@ -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) => { diff --git a/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss b/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss index 4340296f1b03fb0796a4bbe2eacaa451d15d2b87..4cfc353281a56a4cc77a323579839637324d4ed9 100644 --- a/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss +++ b/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss @@ -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; diff --git a/bigbluebutton-html5/private/config/public/chat.yaml b/bigbluebutton-html5/private/config/public/chat.yaml index 2ae01424b0deffc9b1b12cd6ae89b1d873c276d7..277ff5842daf1d2a7f2fb2b76ee0f354f5e6dc40 100755 --- a/bigbluebutton-html5/private/config/public/chat.yaml +++ b/bigbluebutton-html5/private/config/public/chat.yaml @@ -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' diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json index 25ca747596d3e46e643f7fdc4813a21027d3ca96..a2302553931bd505dde2c1663cf79ad336ab5186 100755 --- a/bigbluebutton-html5/private/locales/en.json +++ b/bigbluebutton-html5/private/locales/en.json @@ -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",