From 877e91a43e470bb0a62f2905f1604b130a378c36 Mon Sep 17 00:00:00 2001 From: Oswaldo Acauan Date: Mon, 23 May 2016 10:09:47 -0300 Subject: [PATCH 1/9] Rename to new structure --- bigbluebutton-html5/imports/startup/client/routes.js | 2 +- .../imports/ui/components/chat/{Chat.jsx => component.jsx} | 0 .../ui/components/chat/{ChatContainer.jsx => container.jsx} | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename bigbluebutton-html5/imports/ui/components/chat/{Chat.jsx => component.jsx} (100%) rename bigbluebutton-html5/imports/ui/components/chat/{ChatContainer.jsx => container.jsx} (95%) diff --git a/bigbluebutton-html5/imports/startup/client/routes.js b/bigbluebutton-html5/imports/startup/client/routes.js index e2b7872313..968ab7829a 100755 --- a/bigbluebutton-html5/imports/startup/client/routes.js +++ b/bigbluebutton-html5/imports/startup/client/routes.js @@ -7,7 +7,7 @@ import { createHistory } from 'history'; import AppContainer from '../../ui/components/app/container'; import {setCredentials, subscribeForData} from '../../ui/components/app/service'; import UserListContainer from '../../ui/components/user-list/UserListContainer'; -import ChatContainer from '../../ui/components/chat/ChatContainer'; +import ChatContainer from '../../ui/components/chat/container'; const browserHistory = useRouterHistory(createHistory)({ basename: '/html5client', diff --git a/bigbluebutton-html5/imports/ui/components/chat/Chat.jsx b/bigbluebutton-html5/imports/ui/components/chat/component.jsx similarity index 100% rename from bigbluebutton-html5/imports/ui/components/chat/Chat.jsx rename to bigbluebutton-html5/imports/ui/components/chat/component.jsx diff --git a/bigbluebutton-html5/imports/ui/components/chat/ChatContainer.jsx b/bigbluebutton-html5/imports/ui/components/chat/container.jsx similarity index 95% rename from bigbluebutton-html5/imports/ui/components/chat/ChatContainer.jsx rename to bigbluebutton-html5/imports/ui/components/chat/container.jsx index 453be391c8..92d90bd755 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/ChatContainer.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/container.jsx @@ -1,7 +1,7 @@ import React, { Component, PropTypes } from 'react'; import { createContainer } from 'meteor/react-meteor-data'; -import Chat from './Chat'; +import Chat from './component'; class ChatContainer extends Component { constructor(props) { From 1d8a36564df4720380782944e7aca3bbccf72836 Mon Sep 17 00:00:00 2001 From: Oswaldo Acauan Date: Wed, 1 Jun 2016 13:33:19 -0300 Subject: [PATCH 2/9] Add base components and style for Chat --- bigbluebutton-html5/client/main.css | 10 ++ .../imports/ui/components/app/styles.scss | 4 +- .../imports/ui/components/button/styles.scss | 10 +- .../imports/ui/components/chat/component.jsx | 35 ++++- .../imports/ui/components/chat/container.jsx | 33 +++-- .../chat/message-form/component.jsx | 89 ++++++++++++ .../components/chat/message-form/styles.scss | 43 ++++++ .../chat/message-list/component.jsx | 50 +++++++ .../message-list-item/component.jsx | 59 ++++++++ .../message-list-item/styles.scss | 65 +++++++++ .../components/chat/message-list/styles.scss | 12 ++ .../imports/ui/components/chat/service.js | 137 ++++++++++++++++++ .../imports/ui/components/chat/styles.scss | 17 +++ .../stylesheets/placeholders/_scrollable.scss | 16 ++ .../ui/stylesheets/variables/_all.scss | 1 + .../ui/stylesheets/variables/general.scss | 11 ++ .../ui/stylesheets/variables/palette.scss | 1 + .../ui/stylesheets/variables/typography.scss | 13 ++ bigbluebutton-html5/package.json | 3 +- 19 files changed, 586 insertions(+), 23 deletions(-) create mode 100644 bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx create mode 100644 bigbluebutton-html5/imports/ui/components/chat/message-form/styles.scss create mode 100644 bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx create mode 100644 bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx create mode 100644 bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss create mode 100644 bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss create mode 100644 bigbluebutton-html5/imports/ui/components/chat/service.js create mode 100644 bigbluebutton-html5/imports/ui/stylesheets/placeholders/_scrollable.scss create mode 100644 bigbluebutton-html5/imports/ui/stylesheets/variables/general.scss diff --git a/bigbluebutton-html5/client/main.css b/bigbluebutton-html5/client/main.css index 6ed797b135..6ba6d769ac 100644 --- a/bigbluebutton-html5/client/main.css +++ b/bigbluebutton-html5/client/main.css @@ -21,6 +21,16 @@ a { overflow: hidden; } +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0,0,0,0); + border: 0; +} /* DEBUG ONLY * { diff --git a/bigbluebutton-html5/imports/ui/components/app/styles.scss b/bigbluebutton-html5/imports/ui/components/app/styles.scss index 3d9e09fe44..9a4723f01c 100644 --- a/bigbluebutton-html5/imports/ui/components/app/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/app/styles.scss @@ -124,12 +124,12 @@ $actionsbar-height: 50px; // TODO: Change to ActionsBar real height } @include mq($medium-up) { - flex: 0 20vw; + flex: 0 25vw; order: 1; } @include mq($xlarge-up) { - flex-basis: 15vw; + flex-basis: 20vw; } } diff --git a/bigbluebutton-html5/imports/ui/components/button/styles.scss b/bigbluebutton-html5/imports/ui/components/button/styles.scss index 5a5e0e9caf..6bad25a1ef 100644 --- a/bigbluebutton-html5/imports/ui/components/button/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/button/styles.scss @@ -16,19 +16,19 @@ $btn-danger-color: $color-white; $btn-danger-bg: $color-danger; $btn-danger-border: $color-danger; -$btn-border-size: 2px; -$btn-border-radius: .2rem; +$btn-border-size: $border-size; +$btn-border-radius: $border-radius; $btn-font-weight: 600; $btn-spacing: .35rem; $btn-sm-font-size: $font-size-small * .85; -$btn-sm-padding: .25rem .75rem; +$btn-sm-padding: $sm-padding-y $sm-padding-x; $btn-md-font-size: $font-size-base * .85; -$btn-md-padding: .375rem 1rem; +$btn-md-padding: $md-padding-y $md-padding-x; $btn-lg-font-size: $font-size-large * .85; -$btn-lg-padding: .5rem 1.25rem; +$btn-lg-padding: $lg-padding-y $lg-padding-x; /* Base * ========== diff --git a/bigbluebutton-html5/imports/ui/components/chat/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/component.jsx index 25e827921a..d49994bd1b 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/component.jsx @@ -1,12 +1,41 @@ import React, { Component } from 'react'; import { Link } from 'react-router'; +import styles from './styles'; + +import MessageForm from './message-form/component'; +import MessageList from './message-list/component'; +import Icon from '../icon/component'; + +const ELEMENT_ID = 'chat-messages'; export default class Chat extends Component { + constructor(props) { + super(props); + } + render() { + const { + title, + messages, + } = this.props; + return ( -
- You are chatting with {this.props.currentChat} -
+
+
+ + {title} + +
+ + +
); } } diff --git a/bigbluebutton-html5/imports/ui/components/chat/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/container.jsx index 92d90bd755..3923d51b7c 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/container.jsx @@ -2,30 +2,39 @@ import React, { Component, PropTypes } from 'react'; import { createContainer } from 'meteor/react-meteor-data'; import Chat from './component'; +import ChatService from './service'; + +const PUBLIC_CHAT_KEY = 'public'; class ChatContainer extends Component { constructor(props) { super(props); - this.state = { - currentChat: null, - }; - } - - componentDidMount() { - const chatID = this.props.params.id || 'public'; - this.setState({ currentChat: chatID }); } render() { - const { chatID, ...props } = this.props.params; return ( - + {this.props.children} ); } } -export default createContainer(() => { - return {}; +export default createContainer(({ params }) => { + const chatID = params.chatID || PUBLIC_CHAT_KEY; + + let messages = []; + + if (chatID === PUBLIC_CHAT_KEY) { + title = 'Public Chat'; + messages = ChatService.getPublicMessages(); + } else { + title = 'Username'; + messages = ChatService.getPrivateMessages(chatID); + } + + return { + title, + messages, + }; }, ChatContainer); diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx new file mode 100644 index 0000000000..950e78fb46 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx @@ -0,0 +1,89 @@ +import React, { Component, PropTypes } from 'react'; +import { findDOMNode } from 'react-dom'; +import cx from 'classnames'; +import styles from './styles'; + +import Button from '../../button/component'; +import TextareaAutosize from 'react-autosize-textarea'; + +const propTypes = { +}; + +const defaultProps = { +}; + +export default class MessageForm extends Component { + constructor(props) { + super(props); + + this.state = { + message: '', + }; + + this.handleMessageChange = this.handleMessageChange.bind(this); + this.handleMessageKeyUp = this.handleMessageKeyUp.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + } + + handleMessageKeyUp(e) { + if (e.keyCode === 13 && !e.shiftKey) { + this.refs.btnSubmit.click(); + + // FIX: I dont know why the live bellow dont trigger the handleSubmit function + // this.refs.form.submit(); + } + } + + handleMessageChange(e) { + this.setState({ message: e.target.value }); + } + + handleSubmit(e) { + alert('it works!'); + e.preventDefault(); + + const message = this.state.message.trim(); + + if (!message) { + return; + } + + this.setState({ message: '' }); + } + + render() { + return ( +
+
+
+ + + + ); + } +} + +MessageForm.propTypes = propTypes; +MessageForm.defaultProps = defaultProps; diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.scss new file mode 100644 index 0000000000..d4cd5ffe16 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/chat/message-form/styles.scss @@ -0,0 +1,43 @@ +@import "imports/ui/stylesheets/variables/_all"; + +.form { + flex-grow: 0; + flex-shrink: 0; + align-self: flex-end; + display: flex; + flex-direction: row; + width: 100%; +} + +.actions { + display: flex; + align-items: center; + justify-content: center; + flex-grow: 0; + flex-shrink: 0; + border: $border-size solid $color-gray-lighter; + background: #fff; + border-radius: $border-radius 0 0 $border-radius; + color: $color-gray-lighter; + padding: $sm-padding-y $sm-padding-x; +} + +.input { + flex: 1; + background: #fff; + border: $border-size solid $color-gray-lighter; + border-left: none; + background-clip: padding-box; + margin: 0; + color: $color-gray; + -webkit-appearance: none; + box-shadow: none; + outline: 0; + padding: $sm-padding-y $sm-padding-x; + resize: none; + transition: none; + border-radius: 0 $border-radius $border-radius 0; + font-size: $font-size-base * .90; + min-height: 4rem; + max-height: 10rem; +} diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx new file mode 100644 index 0000000000..8ceb720789 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/component.jsx @@ -0,0 +1,50 @@ +import React, { Component, PropTypes } from 'react'; +import { findDOMNode } from 'react-dom'; +import styles from './styles'; + +import MessageListItem from './message-list-item/component'; + +const propTypes = { + messages: PropTypes.array.isRequired, +}; + +export default class MessageList extends Component { + _scrollBottom() { + const node = findDOMNode(this); + node.scrollTop = node.scrollHeight; + } + + componentWillUpdate() { + const node = findDOMNode(this); + this.shouldScrollBottom = node.scrollTop + node.offsetHeight === node.scrollHeight; + } + + componentDidUpdate() { + if (this.shouldScrollBottom) { + this._scrollBottom(); + } + } + + componentDidMount() { + this._scrollBottom(); + } + + render() { + const { messages } = this.props; + return ( +
+ {messages.map((message, index) => ( + + ))} +
+ ); + } +} + +MessageList.propTypes = propTypes; diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx new file mode 100644 index 0000000000..9351956e36 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx @@ -0,0 +1,59 @@ +import React, { Component, PropTypes } from 'react'; +import { FormattedTime } from 'react-intl'; +import styles from './styles'; + +const propTypes = { + user: React.PropTypes.shape({ + name: React.PropTypes.string.isRequired, + }).isRequired, + message: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.arrayOf(React.PropTypes.string), + ]).isRequired, + time: PropTypes.number.isRequired, +}; + +const defaultProps = { +}; + +export default class MessageListItem extends Component { + render() { + const { + user, + message, + time, + } = this.props; + + const dateTime = new Date(time); + + let messageTexts = message; + + if (!Array.isArray(message)) { + messageTexts = [message]; + } + + return ( +
+
+ lel +
+
+
+
+ {user.name} +
+ +
+ {messageTexts.map((text, i) => ( +

{text}

+ ))} +
+
+ ); + } +} + +MessageListItem.propTypes = propTypes; +MessageListItem.defaultProps = defaultProps; diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss new file mode 100644 index 0000000000..0e396c9226 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss @@ -0,0 +1,65 @@ +@import "imports/ui/stylesheets/variables/_all"; + +.item { + display: flex; + flex-flow: row; + flex: 1; + margin-bottom: $line-height-computed; + font-size: $font-size-base * .90; +} + +.avatar { + flex-basis: 1.7rem; + flex-shrink: 0; + flex-grow: 0; +} + +.content { + flex: 1; + display: flex; + flex-flow: column; + overflow: hidden; + margin-left: $line-height-computed / 2; +} + +.meta { + display: flex; + flex: 1; + flex-flow: row; + + & + .message { + margin-top: 0; + } +} + +.name { + display: flex; + min-width: 0; + font-weight: 600; + color: $color-gray; + + > span { + @extend %text-elipsis; + } +} + +.time { + flex-shrink: 0; + flex-grow: 0; + flex-basis: 3.5rem; + color: $color-gray-light; + text-transform: uppercase; + font-size: 75%; + margin-left: $line-height-computed / 2; + + > span { + vertical-align: sub; + } +} + +.message { + flex: 1; + margin-top: $line-height-computed / 2; + margin-bottom: 0; + color: $color-text; +} diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss new file mode 100644 index 0000000000..110de8583b --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss @@ -0,0 +1,12 @@ +@import "imports/ui/stylesheets/variables/_all"; +@import "imports/ui/stylesheets/placeholders/_scrollable"; + +.messageList { + @extend %scrollbox-vertical; + flex-flow: column; + flex-grow: 1; + flex-grow: 1; + flex-shrink: 1; + margin-right: -$line-height-computed; + padding-right: $line-height-computed; +} diff --git a/bigbluebutton-html5/imports/ui/components/chat/service.js b/bigbluebutton-html5/imports/ui/components/chat/service.js new file mode 100644 index 0000000000..3269553ff2 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/components/chat/service.js @@ -0,0 +1,137 @@ +import Chats from '/imports/api/chat'; +import Users from '/imports/api/users'; + +const SYSTEM_CHAT_TYPE = 'SYSTEM_MESSAGE'; +const PUBLIC_CHAT_TYPE = 'PUBLIC_CHAT'; +const PRIVATE_CHAT_TYPE = 'PRIVATE_CHAT'; + +/* +Message payload +{ + "_id": "ZFrNuy7B7NHFHYsLj", + "meetingId": "183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1464098422087", + "message": { + "chat_type": "PUBLIC_CHAT", + "message": "asdasdasd", + "to_username": "public_chat_username", + "from_tz_offset": "180", + "from_color": "0", + "to_userid": "public_chat_userid", + "from_userid": "zdnypeqfydjl_2", + "from_time": "1464098638588", + "from_username": "Hi!",. + "from_lang": null + } +} +*/ + +const getPublicMessages = () => { + let messages = Chats.find({ + chat_type: PUBLIC_CHAT_TYPE, + }); + + return [ + { + content: 'Lorem ipsum dolor.', + time: 1464098638588, + sender: { + id: '123', + name: 'João da Silva Perreira Roberto Sauro', + }, + }, { + content: 'Lorem ipsum dolor.', + time: 1464098638588, + sender: { + id: '123', + name: 'João da Silva Perreira Roberto Sauro', + }, + }, { + content: 'Lorem ipsum dolor.', + time: 1464098638588, + sender: { + id: '123', + name: 'João da Silva Perreira Roberto Sauro', + }, + }, { + content: 'Lorem ipsum dolor.', + time: 1464098638588, + sender: { + id: '123', + name: 'João da Silva Perreira Roberto Sauro', + }, + }, { + content: 'Lorem ipsum dolor.', + time: 1464098638588, + sender: { + id: '123', + name: 'João da Silva Perreira Roberto Sauro', + }, + }, { + content: 'Lorem ipsum dolor.', + time: 1464098638588, + sender: { + id: '123', + name: 'João da Silva Perreira Roberto Sauro', + }, + }, { + content: 'Lorem ipsum dolor.', + time: 1464098638588, + sender: { + id: '123', + name: 'João da Silva Perreira Roberto Sauro', + }, + }, { + content: 'Lorem ipsum dolor.', + time: 1464098638588, + sender: { + id: '123', + name: 'João da Silva Perreira Roberto Sauro', + }, + }, + { + content: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quidem ex, ullam debitis aperiam, voluptate sunt?', + time: 1464098638588, + sender: { + id: '123', + name: 'Marcio Perinha', + }, + }, + { + content: [ + 'Lorem ipsum dolor sit amet, consectetur adipisicing elit.', + 'Nam adipisci sed ut reiciendis soluta, dicta quam nulla tenetur impedit obcaecati ex.', + 'Iure totam incidunt placeat earum nisi pariatur velit id quis sint provident perspiciatis facere iste unde exercitationem nihil debitis, mollitia, perferendis, nostrum consectetur eos ea! Quidem, illo, consequuntur. Quos enim nobis, necessitatibus dolorem et dolorum sit earum ipsam dicta eius? Velit eos nihil, dolores fuga enim quia maiores architecto vitae illum nostrum corrupti quod adipisci, nesciunt neque accusantium ex soluta corporis iste sunt doloribus perferendis minus, a ut?', + 'Maxime repudiandae dolor id itaque explicabo sit deleniti error laudantium voluptatem.', + ], + time: 1464098638588, + sender: { + id: '123', + name: 'João da Silva', + }, + }, + ]; +}; + +const getPrivateMessages = (userID) => { + let messages = Chats.find({ + chat_type: PRIVATE_CHAT_TYPE, + from_userid: userID, + }); + + return messages.fetch(); +}; + +const sendMessage = (toUserID, message) => { + let messages = Chats.find({ + chat_type: PRIVATE_CHAT_TYPE, + from_userid: userID, + }); + + return messages; +}; + +export default { + getPublicMessages, + getPrivateMessages, + sendMessage, +}; diff --git a/bigbluebutton-html5/imports/ui/components/chat/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/styles.scss index e69de29bb2..29a69970b9 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/styles.scss @@ -0,0 +1,17 @@ +@import "imports/ui/stylesheets/variables/_all"; + +.chat { + background-color: #fff; + margin: $line-height-computed; + display: flex; + flex-grow: 1; + flex-direction: column; +} + +.closeChat { + text-decoration: none; +} + +.header { + margin-bottom: $line-height-computed; +} diff --git a/bigbluebutton-html5/imports/ui/stylesheets/placeholders/_scrollable.scss b/bigbluebutton-html5/imports/ui/stylesheets/placeholders/_scrollable.scss new file mode 100644 index 0000000000..d16005da51 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/stylesheets/placeholders/_scrollable.scss @@ -0,0 +1,16 @@ +%scrollbox-vertical { + overflow-y: auto; + background: + /* Shadow covers */ + linear-gradient(white 30%, rgba(255,255,255,0)), + linear-gradient(rgba(255,255,255,0), white 70%) 0 100%, + + /* Shadows */ + radial-gradient(farthest-side at 50% 0, rgba(0,0,0,.2), rgba(0,0,0,0)), + radial-gradient(farthest-side at 50% 100%, rgba(0,0,0,.2), rgba(0,0,0,0)) 0 100%; + + background-repeat: no-repeat; + background-color: white; + background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px; + background-attachment: local, local, scroll, scroll; +} diff --git a/bigbluebutton-html5/imports/ui/stylesheets/variables/_all.scss b/bigbluebutton-html5/imports/ui/stylesheets/variables/_all.scss index 7afa57ddc1..7813d0ab94 100644 --- a/bigbluebutton-html5/imports/ui/stylesheets/variables/_all.scss +++ b/bigbluebutton-html5/imports/ui/stylesheets/variables/_all.scss @@ -1,3 +1,4 @@ +@import "./general"; @import "./breakpoints"; @import "./palette"; @import "./typography"; diff --git a/bigbluebutton-html5/imports/ui/stylesheets/variables/general.scss b/bigbluebutton-html5/imports/ui/stylesheets/variables/general.scss new file mode 100644 index 0000000000..33e4d87964 --- /dev/null +++ b/bigbluebutton-html5/imports/ui/stylesheets/variables/general.scss @@ -0,0 +1,11 @@ +$border-size: 2px; +$border-radius: .2rem; + +$sm-padding-x: .75rem; +$sm-padding-y: .25rem; + +$md-padding-x: 1rem; +$md-padding-y: .375rem; + +$lg-padding-x: 1.25rem; +$lg-padding-y: .5rem; diff --git a/bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss b/bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss index dca4b5bb43..8811f88589 100644 --- a/bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss +++ b/bigbluebutton-html5/imports/ui/stylesheets/variables/palette.scss @@ -3,6 +3,7 @@ $color-white: #F3F6F9 !default; $color-gray: #353B42 !default; $color-gray-dark: #2A2D33 !default; $color-gray-light: #8B9AA8 !default; +$color-gray-lighter: lighten($color-gray-light, 25%) !default; $color-primary: #299AD5 !default; $color-success: #4DC0A2 !default; diff --git a/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss b/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss index 4fbba09dba..4340296f1b 100644 --- a/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss +++ b/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss @@ -15,3 +15,16 @@ $headings-font-family: inherit !default; $headings-font-weight: 500 !default; $headings-line-height: 1.1 !default; $headings-color: inherit !default; + +/* + * Placeholders + * =============== + */ + +%text-elipsis { + min-width: 0; + display: inline-block; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/bigbluebutton-html5/package.json b/bigbluebutton-html5/package.json index 280b8d417b..93930e71d4 100644 --- a/bigbluebutton-html5/package.json +++ b/bigbluebutton-html5/package.json @@ -17,7 +17,8 @@ "react-intl": "^2.1.2", "react-modal": "^1.2.1", "react-router": "^2.4.0", - "underscore": "~1.8.3" + "underscore": "~1.8.3", + "react-autosize-textarea": "~0.3.1" }, "devDependencies": { "autoprefixer": "^6.3.6", From f4fbc658e0a5acb513053f355bce0d193db0541a Mon Sep 17 00:00:00 2001 From: Oswaldo Acauan Date: Thu, 2 Jun 2016 10:00:57 -0300 Subject: [PATCH 3/9] Fetch chat messages from server instead of mock --- .../message-list-item/component.jsx | 48 ++++- .../message-list-item/styles.scss | 9 + .../components/chat/message-list/styles.scss | 1 + .../imports/ui/components/chat/service.js | 200 +++++++++--------- .../ui/components/user-avatar/component.jsx | 21 +- .../user-list/chat-list-item/component.jsx | 3 +- .../ui/components/user-list/component.jsx | 43 +++- .../user-list/user-list-item/component.jsx | 12 +- .../imports/utils/regex-weburl.js | 110 ++++++++++ 9 files changed, 315 insertions(+), 132 deletions(-) create mode 100644 bigbluebutton-html5/imports/utils/regex-weburl.js diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx index 9351956e36..774b2eb111 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/component.jsx @@ -1,16 +1,24 @@ import React, { Component, PropTypes } from 'react'; import { FormattedTime } from 'react-intl'; +import cx from 'classnames'; + +import UserAvatar from '../../../user-avatar/component'; + import styles from './styles'; const propTypes = { user: React.PropTypes.shape({ name: React.PropTypes.string.isRequired, - }).isRequired, + isPresenter: React.PropTypes.bool.isRequired, + isVoiceUser: React.PropTypes.bool.isRequired, + isModerator: React.PropTypes.bool.isRequired, + image: React.PropTypes.string, + }), message: React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.arrayOf(React.PropTypes.string), ]).isRequired, - time: PropTypes.number.isRequired, + time: PropTypes.number.isRequired, }; const defaultProps = { @@ -26,16 +34,14 @@ export default class MessageListItem extends Component { const dateTime = new Date(time); - let messageTexts = message; - - if (!Array.isArray(message)) { - messageTexts = [message]; + if (!user) { + return this.renderSystemMessage(); } return (
- lel +
@@ -46,8 +52,32 @@ export default class MessageListItem extends Component {
- {messageTexts.map((text, i) => ( -

{text}

+ {message.map((text, i) => ( +

+

+ ))} +
+
+ ); + } + + renderSystemMessage() { + const { + message, + } = this.props; + + return ( +
+
+ {message.map((text, i) => ( +

+

))}
diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss index 0e396c9226..01695ea79b 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/message-list-item/styles.scss @@ -8,6 +8,15 @@ font-size: $font-size-base * .90; } +.systemMessage { + font-weight: 600; + + .item + &, + & + .item { + margin-bottom: $line-height-computed * 1.5; + } +} + .avatar { flex-basis: 1.7rem; flex-shrink: 0; diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss index 110de8583b..3c23a6b180 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss @@ -9,4 +9,5 @@ flex-shrink: 1; margin-right: -$line-height-computed; padding-right: $line-height-computed; + padding-bottom: $line-height-computed; } diff --git a/bigbluebutton-html5/imports/ui/components/chat/service.js b/bigbluebutton-html5/imports/ui/components/chat/service.js index 3269553ff2..1bb085f723 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/service.js +++ b/bigbluebutton-html5/imports/ui/components/chat/service.js @@ -1,124 +1,114 @@ import Chats from '/imports/api/chat'; import Users from '/imports/api/users'; +import LocalStorage from '/imports/ui/services/storage'; + +import RegexWebUrl from '/imports/utils/regex-weburl'; + +const BREAK_TAG = '
'; +const GROUPING_MESSAGES_WINDOW = 60000; const SYSTEM_CHAT_TYPE = 'SYSTEM_MESSAGE'; const PUBLIC_CHAT_TYPE = 'PUBLIC_CHAT'; const PRIVATE_CHAT_TYPE = 'PRIVATE_CHAT'; -/* -Message payload -{ - "_id": "ZFrNuy7B7NHFHYsLj", - "meetingId": "183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1464098422087", - "message": { - "chat_type": "PUBLIC_CHAT", - "message": "asdasdasd", - "to_username": "public_chat_username", - "from_tz_offset": "180", - "from_color": "0", - "to_userid": "public_chat_userid", - "from_userid": "zdnypeqfydjl_2", - "from_time": "1464098638588", - "from_username": "Hi!",. - "from_lang": null +const CURRENT_USER_ID = LocalStorage.get('userID'); + +/* TODO: Same map is done in the user-list/service we should share this someway */ + +const mapUser = (user) => ({ + id: user.userid, + name: user.name, + isPresenter: user.presenter, + isModerator: user.role === 'MODERATOR', + isCurrent: user.userid === CURRENT_USER_ID, + isVoiceUser: user.voiceUser.joined, + isMuted: user.voiceUser.muted, + isListenOnly: user.listenOnly, + isSharingWebcam: user.webcam_stream.length, +}); + +const parseMessage = (message) => { + message = message || ''; + + // Replace \r and \n to
+ message = message.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + BREAK_TAG + '$2'); + + // Replace flash links to html valid ones + message = message.split(`$1'); + + return message; +}; + +const mapMessage = (message) => { + let mappedMessage = { + content: [parseMessage(message.message)], + time: +(message.from_time), //+ message.from_tz_offset, + sender: null, + }; + + if (message.from_userid !== SYSTEM_CHAT_TYPE) { + let user = Users.findOne({ userId: message.from_userid }); + mappedMessage.sender = mapUser(user.user); } -} -*/ + + return mappedMessage; +}; + +const reduceMessages = (previous, current, index, array) => { + let lastMessage = previous[previous.length - 1]; + + if (!lastMessage || !lastMessage.sender || !current.sender) { // Skip system messages + console.log(current.content); + return previous.concat(current); + } + + // Check if the last message is from the same user and time discrepancy + // between the two messages exceeds window and then group current message + // with the last one + + if (lastMessage.sender.id === current.sender.id + && (current.time - lastMessage.time) <= GROUPING_MESSAGES_WINDOW) { + lastMessage.content = lastMessage.content.concat(current.content); + return previous; + } else { + return previous.concat(current); + } +}; const getPublicMessages = () => { - let messages = Chats.find({ - chat_type: PUBLIC_CHAT_TYPE, - }); + let publicMessages = Chats.find({ + 'message.chat_type': { $in: [PUBLIC_CHAT_TYPE, SYSTEM_CHAT_TYPE] }, + }, { + sort: ['message.from_time'], + }) + .fetch(); - return [ - { - content: 'Lorem ipsum dolor.', - time: 1464098638588, - sender: { - id: '123', - name: 'João da Silva Perreira Roberto Sauro', - }, - }, { - content: 'Lorem ipsum dolor.', - time: 1464098638588, - sender: { - id: '123', - name: 'João da Silva Perreira Roberto Sauro', - }, - }, { - content: 'Lorem ipsum dolor.', - time: 1464098638588, - sender: { - id: '123', - name: 'João da Silva Perreira Roberto Sauro', - }, - }, { - content: 'Lorem ipsum dolor.', - time: 1464098638588, - sender: { - id: '123', - name: 'João da Silva Perreira Roberto Sauro', - }, - }, { - content: 'Lorem ipsum dolor.', - time: 1464098638588, - sender: { - id: '123', - name: 'João da Silva Perreira Roberto Sauro', - }, - }, { - content: 'Lorem ipsum dolor.', - time: 1464098638588, - sender: { - id: '123', - name: 'João da Silva Perreira Roberto Sauro', - }, - }, { - content: 'Lorem ipsum dolor.', - time: 1464098638588, - sender: { - id: '123', - name: 'João da Silva Perreira Roberto Sauro', - }, - }, { - content: 'Lorem ipsum dolor.', - time: 1464098638588, - sender: { - id: '123', - name: 'João da Silva Perreira Roberto Sauro', - }, - }, - { - content: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quidem ex, ullam debitis aperiam, voluptate sunt?', - time: 1464098638588, - sender: { - id: '123', - name: 'Marcio Perinha', - }, - }, - { - content: [ - 'Lorem ipsum dolor sit amet, consectetur adipisicing elit.', - 'Nam adipisci sed ut reiciendis soluta, dicta quam nulla tenetur impedit obcaecati ex.', - 'Iure totam incidunt placeat earum nisi pariatur velit id quis sint provident perspiciatis facere iste unde exercitationem nihil debitis, mollitia, perferendis, nostrum consectetur eos ea! Quidem, illo, consequuntur. Quos enim nobis, necessitatibus dolorem et dolorum sit earum ipsam dicta eius? Velit eos nihil, dolores fuga enim quia maiores architecto vitae illum nostrum corrupti quod adipisci, nesciunt neque accusantium ex soluta corporis iste sunt doloribus perferendis minus, a ut?', - 'Maxime repudiandae dolor id itaque explicabo sit deleniti error laudantium voluptatem.', - ], - time: 1464098638588, - sender: { - id: '123', - name: 'João da Silva', - }, - }, - ]; + let systemMessage = Chats.findOne({ 'message.chat_type': SYSTEM_CHAT_TYPE }); + + return publicMessages + .map(m => m.message) + .map(mapMessage) + .reduce(reduceMessages, []); }; const getPrivateMessages = (userID) => { let messages = Chats.find({ - chat_type: PRIVATE_CHAT_TYPE, - from_userid: userID, - }); + 'message.chat_type': PRIVATE_CHAT_TYPE, + $or: [ + { 'message.to_userid': userID }, + { 'message.from_userid': userID }, + ], + }, { + sort: ['message.from_time'], + }).fetch(); - return messages.fetch(); + return messages + .map(m => m.message) + .map(mapMessage) + .reduce(reduceMessages, []); }; const sendMessage = (toUserID, message) => { diff --git a/bigbluebutton-html5/imports/ui/components/user-avatar/component.jsx b/bigbluebutton-html5/imports/ui/components/user-avatar/component.jsx index 74385b1ab9..20101577ea 100644 --- a/bigbluebutton-html5/imports/ui/components/user-avatar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-avatar/component.jsx @@ -2,6 +2,19 @@ import React, { Component, PropTypes } from 'react'; import styles from './styles.scss'; import cx from 'classnames'; +const propTypes = { + user: React.PropTypes.shape({ + name: React.PropTypes.string.isRequired, + isPresenter: React.PropTypes.bool.isRequired, + isVoiceUser: React.PropTypes.bool.isRequired, + isModerator: React.PropTypes.bool.isRequired, + image: React.PropTypes.string, + }).isRequired, +}; + +const defaultProps = { +}; + export default class UserAvatar extends Component { render() { let user = this.props.user; @@ -10,14 +23,18 @@ export default class UserAvatar extends Component { avatarClasses[styles.presenter] = user.isPresenter; avatarClasses[styles.voiceUser] = user.isVoiceUser; avatarClasses[styles.moderator] = user.isModerator; + // avatarClasses[styles.image] = user.image; - return ( + return (
{user.name.slice(0, 1)}
- ) + ); } } + +UserAvatar.propTypes = propTypes; +UserAvatar.defaultProps = defaultProps; diff --git a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx index 3918aeba59..7ebd4582d8 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/chat-list-item/component.jsx @@ -5,9 +5,8 @@ import classNames from 'classnames'; export default class ChatListItem extends Component { render() { - return ( -
  • +
  • diff --git a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx index 6d8456b172..dc4d4d6bdf 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/component.jsx @@ -1,11 +1,28 @@ import React, { Component } from 'react'; import styles from './styles.scss'; import { Link } from 'react-router'; -import UserListItem from './user-list-item/component.jsx'; -import ChatListItem from './chat-list-item/component.jsx'; +import { withRouter } from 'react-router'; import { FormattedMessage } from 'react-intl'; -export default class UserList extends Component { +import UserListItem from './user-list-item/component.jsx'; +import ChatListItem from './chat-list-item/component.jsx'; + +class UserList extends Component { + constructor(props) { + super(props); + + this.handleMessageClick = this.handleMessageClick.bind(this); + this.handleParticipantDblClick = this.handleParticipantDblClick.bind(this); + } + + handleMessageClick(chatID) { + this.props.router.push(`/users/chat/${chatID}`); + } + + handleParticipantDblClick(userID) { + this.props.router.push(`/users/chat/${userID}`); + } + render() { return (
    @@ -26,7 +43,7 @@ export default class UserList extends Component { />
    - ) + ); } renderContent() { @@ -49,10 +66,10 @@ export default class UserList extends Component { />
      - + this.handleMessageClick('public')} />
    - ) + ); } renderParticipants() { @@ -68,10 +85,20 @@ export default class UserList extends Component {
      - {this.props.users.map(user => )} + {this.props.users.map(user => ( + this.handleParticipantDblClick(user.id)} + accessibilityLabel={'Status abc'} + accessible={true} + key={user.id} + user={user} + /> + ))}
    - ) + ); } } + +export default withRouter(UserList); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/component.jsx index 52048b71b3..9db2df6c59 100644 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-item/component.jsx @@ -9,7 +9,7 @@ let cx = classNames.bind(styles); export default class ChatListItem extends Component { render() { return ( -
  • +
  • {this.renderUserName()} {this.renderUserIcons()} @@ -21,7 +21,7 @@ export default class ChatListItem extends Component { let user = this.props.user; let userNameSub = null; - if(user.isPresenter) { + if (user.isPresenter) { userNameSub = (

    ); - } else if(user.isCurrent) { + } else if (user.isCurrent) { userNameSub = (

    ( {userNameSub} - ) + ); } renderUserIcons() { @@ -58,7 +58,7 @@ export default class ChatListItem extends Component { let audioChatIcon = null; if (user.isVoiceUser || user.isListenOnly) { - if(user.isMuted) { + if (user.isMuted) { audioChatIcon = 'icon-bbb-audio-off'; } else { audioChatIcon = user.isListenOnly ? 'icon-bbb-listen' : 'icon-bbb-audio'; @@ -77,6 +77,6 @@ export default class ChatListItem extends Component { - ) + ); } } diff --git a/bigbluebutton-html5/imports/utils/regex-weburl.js b/bigbluebutton-html5/imports/utils/regex-weburl.js new file mode 100644 index 0000000000..380edb707e --- /dev/null +++ b/bigbluebutton-html5/imports/utils/regex-weburl.js @@ -0,0 +1,110 @@ +// +// Regular Expression for URL validation +// +// Author: Diego Perini +// Updated: 2010/12/05 +// License: MIT +// +// Copyright (c) 2010-2013 Diego Perini (http://www.iport.it) +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// the regular expression composed & commented +// could be easily tweaked for RFC compliance, +// it was expressly modified to fit & satisfy +// these test for an URL shortener: +// +// http://mathiasbynens.be/demo/url-regex +// +// Notes on possible differences from a standard/generic validation: +// +// - utf-8 char class take in consideration the full Unicode range +// - TLDs have been made mandatory so single names like "localhost" fails +// - protocols have been restricted to ftp, http and https only as requested +// +// Changes: +// +// - IP address dotted notation validation, range: 1.0.0.0 - 223.255.255.255 +// first and last IP address of each class is considered invalid +// (since they are broadcast/network addresses) +// +// - Added exclusion of private, reserved and/or local networks ranges +// +// - Made starting path slash optional (http://example.com?foo=bar) +// +// - Allow a dot (.) at the end of hostnames (http://example.com.) +// +// Compressed one-line versions: +// +// Javascript version +// +// /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$/i +// +// PHP version +// +// _^(?:(?:https?|ftp)://)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.(?:[a-z\x{00a1}-\x{ffff}0-9]-*)*[a-z\x{00a1}-\x{ffff}0-9]+)*(?:\.(?:[a-z\x{00a1}-\x{ffff}]{2,}))\.?)(?::\d{2,5})?(?:[/?#]\S*)?$_iuS +// + +export default new RegExp( + + // protocol identifier + '(?:(?:https?|ftp)://)' + + + // user:pass authentication + '(?:\\S+(?::\\S*)?@)?' + + '(?:' + + + // IP address exclusion + // private & local networks + '(?!(?:10|127)(?:\\.\\d{1,3}){3})' + + '(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' + + '(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})' + + + // IP address dotted notation octets + // excludes loopback network 0.0.0.0 + // excludes reserved space >= 224.0.0.0 + // excludes network & broacast addresses + // (first & last IP address of each class) + '(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])' + + '(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}' + + '(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))' + + '|' + + + // host name + '(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)' + + + // domain name + '(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*' + + + // TLD identifier + '(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))' + + + // TLD may end with dot + '\\.?' + + ')' + + + // port number + '(?::\\d{2,5})?' + + + // resource path + '(?:[/?#]\\S*)?', 'img' +); From f39b7171af92e90caff464a166020d7f3fd03e91 Mon Sep 17 00:00:00 2001 From: Oswaldo Acauan Date: Thu, 2 Jun 2016 10:46:35 -0300 Subject: [PATCH 4/9] Move session/auth logic to separated services --- .../imports/api/phone/index.js | 6 ++-- .../imports/api/verto/index.js | 7 +++-- .../imports/ui/components/app/service.js | 28 +++++-------------- .../imports/ui/components/chat/service.js | 4 +-- .../ui/components/deskshare/service.js | 4 +-- .../imports/ui/components/polling/service.js | 2 +- .../imports/ui/services/api/index.js | 8 ++---- .../imports/ui/services/auth/index.js | 27 ++++++++++++++++++ .../imports/ui/services/storage/index.js | 14 ++++------ 9 files changed, 53 insertions(+), 47 deletions(-) create mode 100644 bigbluebutton-html5/imports/ui/services/auth/index.js diff --git a/bigbluebutton-html5/imports/api/phone/index.js b/bigbluebutton-html5/imports/api/phone/index.js index 022b7507f1..b9ab1999af 100755 --- a/bigbluebutton-html5/imports/api/phone/index.js +++ b/bigbluebutton-html5/imports/api/phone/index.js @@ -1,6 +1,6 @@ import Users from '/imports/api/users'; import Meetings from '/imports/api/meetings'; -import {getInStorage, setInStorage} from '/imports/ui/components/app/service'; +import Auth from '/imports/ui/services/auth'; import {callServer} from '/imports/ui/services/api'; import {clientConfig} from '/config'; import {createVertoUserName, joinVertoAudio} from '/imports/api/verto'; @@ -12,7 +12,7 @@ function getVoiceBridge() { } function amIListenOnly() { - const uid = getInStorage('userID'); + const uid = Auth.getUser(); return Users.findOne({ userId: uid }).user.listenOnly; } @@ -88,7 +88,7 @@ function joinVoiceCall(options) { window.BBB = {}; window.BBB.getMyUserInfo = function (callback) { - const uid = getInStorage('userID'); + const uid = Auth.getUser(); const result = { myUserID: uid, myUsername: Users.findOne({ userId: uid }).user.name, diff --git a/bigbluebutton-html5/imports/api/verto/index.js b/bigbluebutton-html5/imports/api/verto/index.js index e977df7cc1..d2972c4ddf 100755 --- a/bigbluebutton-html5/imports/api/verto/index.js +++ b/bigbluebutton-html5/imports/api/verto/index.js @@ -1,8 +1,9 @@ -import {clientConfig} from '/config'; -import {getVoiceBridge} from '/imports/api/phone'; +import Auth from '/imports/ui/services/auth'; +import { clientConfig } from '/config'; +import { getVoiceBridge } from '/imports/api/phone'; function createVertoUserName() { - const uid = getInStorage('userID'); + const uid = Auth.getUser(); const uName = Users.findOne({ userId: uid }).user.name; const conferenceUsername = 'FreeSWITCH User - ' + encodeURIComponent(uName); return conferenceUsername; diff --git a/bigbluebutton-html5/imports/ui/components/app/service.js b/bigbluebutton-html5/imports/ui/components/app/service.js index acc5437962..c6e29ae318 100755 --- a/bigbluebutton-html5/imports/ui/components/app/service.js +++ b/bigbluebutton-html5/imports/ui/components/app/service.js @@ -1,26 +1,17 @@ import { Meteor } from 'meteor/meteor'; + +import Auth from '/imports/ui/services/auth'; + import Users from '/imports/api/users'; import Chat from '/imports/api/chat'; import Meetings from '/imports/api/meetings'; import Cursor from '/imports/api/cursor'; import Polls from '/imports/api/polls'; -function setInStorage(key, value) { - if (!!value) { - console.log('in setInStorage', key, value); - localStorage.setItem(key, value); - } -}; - -function getInStorage(key) { - return localStorage.getItem(key); -}; - function setCredentials(nextState, replace) { - if (!!nextState && !!nextState.params) { - setInStorage('meetingID', nextState.params.meetingID); - setInStorage('userID', nextState.params.userID); - setInStorage('authToken', nextState.params.authToken); + if (nextState && Object.keys(nextState.params).length) { + const { meetingID, userID, authToken } = nextState.params; + Auth.setCredentials(meetingID, userID, authToken); } }; @@ -47,11 +38,7 @@ function subscribeForData() { }; function subscribeFor(collectionName) { - const credentials = { - meetingId: getInStorage('meetingID'), - requesterUserId: getInStorage('userID'), - requesterToken: getInStorage('authToken'), - }; + const credentials = Auth.getCredentials(); // console.log("subscribingForData", collectionName, meetingID, userID, authToken); @@ -74,5 +61,4 @@ export { pollExists, subscribeForData, setCredentials, - getInStorage, }; diff --git a/bigbluebutton-html5/imports/ui/components/chat/service.js b/bigbluebutton-html5/imports/ui/components/chat/service.js index 1bb085f723..3045795230 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/service.js +++ b/bigbluebutton-html5/imports/ui/components/chat/service.js @@ -1,6 +1,6 @@ import Chats from '/imports/api/chat'; import Users from '/imports/api/users'; -import LocalStorage from '/imports/ui/services/storage'; +import Auth from '/imports/ui/services/auth'; import RegexWebUrl from '/imports/utils/regex-weburl'; @@ -11,7 +11,7 @@ const SYSTEM_CHAT_TYPE = 'SYSTEM_MESSAGE'; const PUBLIC_CHAT_TYPE = 'PUBLIC_CHAT'; const PRIVATE_CHAT_TYPE = 'PRIVATE_CHAT'; -const CURRENT_USER_ID = LocalStorage.get('userID'); +const CURRENT_USER_ID = Auth.getUser(); /* TODO: Same map is done in the user-list/service we should share this someway */ diff --git a/bigbluebutton-html5/imports/ui/components/deskshare/service.js b/bigbluebutton-html5/imports/ui/components/deskshare/service.js index 81d63575b9..2608890721 100755 --- a/bigbluebutton-html5/imports/ui/components/deskshare/service.js +++ b/bigbluebutton-html5/imports/ui/components/deskshare/service.js @@ -1,6 +1,6 @@ import Deskshare from '/imports/api/deskshare'; import {conferenceUsername, joinVertoAudio, watchVertoVideo} from '/imports/api/verto'; -import {getInStorage} from '/imports/ui/components/app/service'; +import Auth from '/imports/ui/services/auth'; import {getVoiceBridge} from '/imports/api/phone'; // when the meeting information has been updated check to see if it was @@ -16,7 +16,7 @@ function videoIsBroadcasting() { if (ds.broadcasting) { console.log('Deskshare is now broadcasting'); - if (ds.startedBy != getInStorage('userID')) { + if (ds.startedBy != Auth.getUser()) { console.log('deskshare wasn\'t initiated by me'); presenterDeskshareHasStarted(); return true; diff --git a/bigbluebutton-html5/imports/ui/components/polling/service.js b/bigbluebutton-html5/imports/ui/components/polling/service.js index 3dc7d27bb2..b3b357176f 100755 --- a/bigbluebutton-html5/imports/ui/components/polling/service.js +++ b/bigbluebutton-html5/imports/ui/components/polling/service.js @@ -1,5 +1,5 @@ import { Polls } from '/imports/api/polls'; -import {callServer} from '/imports/ui/services/api'; +import { callServer } from '/imports/ui/services/api'; let mapPolls = function () { let poll = Polls.findOne({}); diff --git a/bigbluebutton-html5/imports/ui/services/api/index.js b/bigbluebutton-html5/imports/ui/services/api/index.js index 8a6b4a6461..14e096c629 100755 --- a/bigbluebutton-html5/imports/ui/services/api/index.js +++ b/bigbluebutton-html5/imports/ui/services/api/index.js @@ -1,4 +1,4 @@ -import {getInStorage} from '/imports/ui/components/app/service'; +import { getCredentials } from '/imports/ui/services/auth'; function callServer(name) { if (!name || !(typeof (name) === 'string' || name instanceof String) || name.length === 0 || @@ -7,11 +7,7 @@ function callServer(name) { return false; } - const credentials = { - meetingId: getInStorage('meetingID'), - requesterUserId: getInStorage('userID'), - requesterToken: getInStorage('authToken'), - }; + const credentials = getCredentials(); // slice off the first element. That is the function name but we already have that. const args = Array.prototype.slice.call(arguments, 1); diff --git a/bigbluebutton-html5/imports/ui/services/auth/index.js b/bigbluebutton-html5/imports/ui/services/auth/index.js new file mode 100644 index 0000000000..2c557367be --- /dev/null +++ b/bigbluebutton-html5/imports/ui/services/auth/index.js @@ -0,0 +1,27 @@ +import Storage from '/imports/ui/services/storage'; + +setCredentials = (meeting, user, token) => { + Storage.set('meetingID', meeting); + Storage.set('userID', user); + Storage.set('authToken', token); +}; + +getCredentials = () => ({ + meetingId: Storage.get('meetingID'), + requesterUserId: Storage.get('userID'), + requesterToken: Storage.get('authToken'), +}); + +getMeeting = () => getCredentials().requesterUserId; + +getUser = () => getCredentials().requesterUserId; + +getToken = () => getCredentials().requesterUserId; + +export default { + setCredentials, + getCredentials, + getMeeting, + getUser, + getToken, +}; diff --git a/bigbluebutton-html5/imports/ui/services/storage/index.js b/bigbluebutton-html5/imports/ui/services/storage/index.js index 01ba6859da..8ecff31d93 100644 --- a/bigbluebutton-html5/imports/ui/services/storage/index.js +++ b/bigbluebutton-html5/imports/ui/services/storage/index.js @@ -1,15 +1,11 @@ const STORAGE = localStorage; -const PREFIX = 'bbb_'; +const PREFIX = 'BBB_'; -function get(key) { - return STORAGE.getItem(key); -} +const get = (key) => STORAGE.getItem(`${PREFIX}${key}`); -function set(key) { - STORAGE.setItem(key); -} +const set = (key, value) => STORAGE.setItem(`${PREFIX}${key}`, value); export default { get, - set -} + set, +}; From 8d74db562a72f55c8b224d35befa66832e889e19 Mon Sep 17 00:00:00 2001 From: Oswaldo Acauan Date: Thu, 2 Jun 2016 10:48:39 -0300 Subject: [PATCH 5/9] Fix getters on auth service --- bigbluebutton-html5/imports/ui/services/auth/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/services/auth/index.js b/bigbluebutton-html5/imports/ui/services/auth/index.js index 2c557367be..d7fb08e475 100644 --- a/bigbluebutton-html5/imports/ui/services/auth/index.js +++ b/bigbluebutton-html5/imports/ui/services/auth/index.js @@ -12,11 +12,11 @@ getCredentials = () => ({ requesterToken: Storage.get('authToken'), }); -getMeeting = () => getCredentials().requesterUserId; +getMeeting = () => getCredentials().meetingId; getUser = () => getCredentials().requesterUserId; -getToken = () => getCredentials().requesterUserId; +getToken = () => getCredentials().requesterToken; export default { setCredentials, From d642cb1c99e482e5e3e29e6ffa8ab3b809a456d7 Mon Sep 17 00:00:00 2001 From: Oswaldo Acauan Date: Thu, 2 Jun 2016 15:40:27 -0300 Subject: [PATCH 6/9] Add chat send message functionality --- .../server/methods/sendChatMessagetoServer.js | 13 +++- .../server/modifiers/addChatToCollection.js | 21 +++-- .../imports/ui/components/app/service.js | 2 +- .../imports/ui/components/chat/component.jsx | 7 +- .../imports/ui/components/chat/container.jsx | 7 +- .../chat/message-form/component.jsx | 2 +- .../components/chat/message-list/styles.scss | 4 +- .../imports/ui/components/chat/service.js | 77 +++++++++++-------- .../imports/ui/services/auth/index.js | 10 +-- .../ui/stylesheets/mixins/_scrollable.scss | 27 +++++++ .../stylesheets/placeholders/_scrollable.scss | 16 ---- 11 files changed, 120 insertions(+), 66 deletions(-) create mode 100644 bigbluebutton-html5/imports/ui/stylesheets/mixins/_scrollable.scss delete mode 100644 bigbluebutton-html5/imports/ui/stylesheets/placeholders/_scrollable.scss diff --git a/bigbluebutton-html5/imports/api/chat/server/methods/sendChatMessagetoServer.js b/bigbluebutton-html5/imports/api/chat/server/methods/sendChatMessagetoServer.js index 18a3b13546..742f467049 100755 --- a/bigbluebutton-html5/imports/api/chat/server/methods/sendChatMessagetoServer.js +++ b/bigbluebutton-html5/imports/api/chat/server/methods/sendChatMessagetoServer.js @@ -5,6 +5,8 @@ import { translateHTML5ToFlash } from '/imports/startup/server/helpers'; import { logger } from '/imports/startup/server/logger'; import { redisConfig } from '/config'; +import RegexWebUrl from '/imports/utils/regex-weburl'; + Meteor.methods({ // meetingId: the id of the meeting // chatObject: the object including info on the chat message, including the text @@ -32,7 +34,7 @@ Meteor.methods({ }; if (isAllowedTo(action(), credentials) && chatObject.from_userid === requesterUserId) { - chatObject.message = translateHTML5ToFlash(chatObject.message); + chatObject.message = parseMessage(chatObject.message); let message = { payload: { message: chatObject, @@ -46,3 +48,12 @@ Meteor.methods({ } }, }); + +const parseMessage = (message) => { + message = message || ''; + + // Replace flash links to flash valid ones + message.replace(RegexWebUrl, "$&"); + + return message; +}; diff --git a/bigbluebutton-html5/imports/api/chat/server/modifiers/addChatToCollection.js b/bigbluebutton-html5/imports/api/chat/server/modifiers/addChatToCollection.js index ab184b0ca2..e00f7f366e 100755 --- a/bigbluebutton-html5/imports/api/chat/server/modifiers/addChatToCollection.js +++ b/bigbluebutton-html5/imports/api/chat/server/modifiers/addChatToCollection.js @@ -1,6 +1,8 @@ import Chat from '/imports/api/chat'; import { logger } from '/imports/startup/server/logger'; +const BREAK_TAG = '
    '; + export function addChatToCollection(meetingId, messageObject) { let id; @@ -8,7 +10,7 @@ export function addChatToCollection(meetingId, messageObject) { // (this is the time_from that the Flash client outputs) messageObject.from_time = messageObject.from_time.toString().split('.').join('').split('E')[0]; if ((messageObject.from_userid != null) && (messageObject.to_userid != null)) { - messageObject.message = translateFlashToHTML5(messageObject.message); + messageObject.message = parseMessage(messageObject.message); return id = Chat.upsert({ meetingId: meetingId, 'message.message': messageObject.message, @@ -43,10 +45,15 @@ export function addChatToCollection(meetingId, messageObject) { } }; -// translate '
    ' breakline character to '\r' carriage return character for HTML5 -const translateFlashToHTML5 = function (message) { - let result; - result = message; - result = result.replace(new RegExp(BREAK_LINE, 'g'), CARRIAGE_RETURN); - return result; +const parseMessage = (message) => { + message = message || ''; + + // Replace \r and \n to
    + message = message.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + BREAK_TAG + '$2'); + + // Replace flash links to html valid ones + message = message.split(` - + ); } diff --git a/bigbluebutton-html5/imports/ui/components/chat/container.jsx b/bigbluebutton-html5/imports/ui/components/chat/container.jsx index 3923d51b7c..41d081950c 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/container.jsx @@ -26,15 +26,18 @@ export default createContainer(({ params }) => { let messages = []; if (chatID === PUBLIC_CHAT_KEY) { - title = 'Public Chat'; messages = ChatService.getPublicMessages(); + title = 'Public'; } else { - title = 'Username'; messages = ChatService.getPrivateMessages(chatID); + title = ChatService.getChatTitle(chatID); } return { title, messages, + actions: { + handleSendMessage: message => ChatService.sendMessage(chatID, message), + }, }; }, ChatContainer); 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 950e78fb46..0ce2ae50d1 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx @@ -39,7 +39,6 @@ export default class MessageForm extends Component { } handleSubmit(e) { - alert('it works!'); e.preventDefault(); const message = this.state.message.trim(); @@ -49,6 +48,7 @@ export default class MessageForm extends Component { } this.setState({ message: '' }); + this.props.handleSendMessage(message); } render() { diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss b/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss index 3c23a6b180..92555617e0 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss +++ b/bigbluebutton-html5/imports/ui/components/chat/message-list/styles.scss @@ -1,8 +1,8 @@ @import "imports/ui/stylesheets/variables/_all"; -@import "imports/ui/stylesheets/placeholders/_scrollable"; +@import "imports/ui/stylesheets/mixins/_scrollable"; .messageList { - @extend %scrollbox-vertical; + @include scrollbox-vertical(); flex-flow: column; flex-grow: 1; flex-grow: 1; diff --git a/bigbluebutton-html5/imports/ui/components/chat/service.js b/bigbluebutton-html5/imports/ui/components/chat/service.js index 3045795230..6029b6a84d 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/service.js +++ b/bigbluebutton-html5/imports/ui/components/chat/service.js @@ -2,16 +2,15 @@ import Chats from '/imports/api/chat'; import Users from '/imports/api/users'; import Auth from '/imports/ui/services/auth'; -import RegexWebUrl from '/imports/utils/regex-weburl'; +import { callServer } from '/imports/ui/services/api'; -const BREAK_TAG = '
    '; const GROUPING_MESSAGES_WINDOW = 60000; const SYSTEM_CHAT_TYPE = 'SYSTEM_MESSAGE'; const PUBLIC_CHAT_TYPE = 'PUBLIC_CHAT'; const PRIVATE_CHAT_TYPE = 'PRIVATE_CHAT'; -const CURRENT_USER_ID = Auth.getUser(); +const PUBLIC_CHAT_ID = 'public'; /* TODO: Same map is done in the user-list/service we should share this someway */ @@ -20,38 +19,22 @@ const mapUser = (user) => ({ name: user.name, isPresenter: user.presenter, isModerator: user.role === 'MODERATOR', - isCurrent: user.userid === CURRENT_USER_ID, + isCurrent: user.userid === Auth.getUser(), isVoiceUser: user.voiceUser.joined, isMuted: user.voiceUser.muted, isListenOnly: user.listenOnly, isSharingWebcam: user.webcam_stream.length, }); -const parseMessage = (message) => { - message = message || ''; - - // Replace \r and \n to
    - message = message.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + BREAK_TAG + '$2'); - - // Replace flash links to html valid ones - message = message.split(`
    $1'); - - return message; -}; - const mapMessage = (message) => { let mappedMessage = { - content: [parseMessage(message.message)], + content: [message.message], time: +(message.from_time), //+ message.from_tz_offset, sender: null, }; - if (message.from_userid !== SYSTEM_CHAT_TYPE) { - let user = Users.findOne({ userId: message.from_userid }); - mappedMessage.sender = mapUser(user.user); + if (message.chat_type !== SYSTEM_CHAT_TYPE) { + mappedMessage.sender = getUser(message.from_userid); } return mappedMessage; @@ -61,7 +44,6 @@ const reduceMessages = (previous, current, index, array) => { let lastMessage = previous[previous.length - 1]; if (!lastMessage || !lastMessage.sender || !current.sender) { // Skip system messages - console.log(current.content); return previous.concat(current); } @@ -78,6 +60,15 @@ const reduceMessages = (previous, current, index, array) => { } }; +const getUser = (userID) => { + const user = Users.findOne({ userId: userID }); + if (user) { + return mapUser(user.user); + } else { + throw `User ${userID} not found`; + } +}; + const getPublicMessages = () => { let publicMessages = Chats.find({ 'message.chat_type': { $in: [PUBLIC_CHAT_TYPE, SYSTEM_CHAT_TYPE] }, @@ -111,17 +102,43 @@ const getPrivateMessages = (userID) => { .reduce(reduceMessages, []); }; -const sendMessage = (toUserID, message) => { - let messages = Chats.find({ - chat_type: PRIVATE_CHAT_TYPE, - from_userid: userID, - }); +const getChatTitle = (userID) => { + const user = getUser(userID); + return user.name; +}; - return messages; +const sendMessage = (receiverID, message) => { + const isPublic = receiverID === PUBLIC_CHAT_ID; + + const sender = getUser(Auth.getUser()); + const receiver = !isPublic ? getUser(receiverID) : { + id: 'public_chat_userid', + name: 'public_chat_username', + }; + + /* FIX: Why we need all this payload to send a message? + * The server only really needs the message, from_userid, to_userid and from_lang + */ + + let messagePayload = { + message: message, + chat_type: isPublic ? PUBLIC_CHAT_TYPE : PRIVATE_CHAT_TYPE, + from_userid: sender.id, + from_username: sender.name, + from_tz_offset: (new Date()).getTimezoneOffset(), + to_username: receiver.name, + to_userid: receiver.id, + from_lang: window.navigator.userLanguage || window.navigator.language, + from_time: Date.now(), + from_color: 0, + }; + + return callServer('sendChatMessagetoServer', messagePayload); }; export default { getPublicMessages, getPrivateMessages, + getChatTitle, sendMessage, }; diff --git a/bigbluebutton-html5/imports/ui/services/auth/index.js b/bigbluebutton-html5/imports/ui/services/auth/index.js index d7fb08e475..815ce8936c 100644 --- a/bigbluebutton-html5/imports/ui/services/auth/index.js +++ b/bigbluebutton-html5/imports/ui/services/auth/index.js @@ -1,22 +1,22 @@ import Storage from '/imports/ui/services/storage'; -setCredentials = (meeting, user, token) => { +export const setCredentials = (meeting, user, token) => { Storage.set('meetingID', meeting); Storage.set('userID', user); Storage.set('authToken', token); }; -getCredentials = () => ({ +export const getCredentials = () => ({ meetingId: Storage.get('meetingID'), requesterUserId: Storage.get('userID'), requesterToken: Storage.get('authToken'), }); -getMeeting = () => getCredentials().meetingId; +export const getMeeting = () => getCredentials().meetingId; -getUser = () => getCredentials().requesterUserId; +export const getUser = () => getCredentials().requesterUserId; -getToken = () => getCredentials().requesterToken; +export const getToken = () => getCredentials().requesterToken; export default { setCredentials, diff --git a/bigbluebutton-html5/imports/ui/stylesheets/mixins/_scrollable.scss b/bigbluebutton-html5/imports/ui/stylesheets/mixins/_scrollable.scss new file mode 100644 index 0000000000..2f17278dec --- /dev/null +++ b/bigbluebutton-html5/imports/ui/stylesheets/mixins/_scrollable.scss @@ -0,0 +1,27 @@ +@mixin scrollbox-vertical($bg-color: white) { + overflow-y: auto; + background: + /* Shadow covers */ + linear-gradient($bg-color 30%, rgba(255,255,255,0)), + linear-gradient(rgba(255,255,255,0), $bg-color 70%) 0 100%, + + /* Shadows */ + radial-gradient(farthest-side at 50% 0, rgba(0,0,0,.2), rgba(0,0,0,0)), + radial-gradient(farthest-side at 50% 100%, rgba(0,0,0,.2), rgba(0,0,0,0)) 0 100%; + + background-repeat: no-repeat; + background-color: transparent; + background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px; + background-attachment: local, local, scroll, scroll; + + // Fancy scroll + &::-webkit-scrollbar{width:5px;height:5px} + &::-webkit-scrollbar-button{width:0;height:0} + &::-webkit-scrollbar-thumb{background:rgba(0,0,0,.25);border:none;border-radius:50px} + &::-webkit-scrollbar-thumb:hover{background:rgba(0,0,0,.5)} + &::-webkit-scrollbar-thumb:active{background:rgba(0,0,0,.25)} + &::-webkit-scrollbar-track{background:rgba(0,0,0,.25);border:none;border-radius:50px} + &::-webkit-scrollbar-track:hover{background:rgba(0,0,0,.25)} + &::-webkit-scrollbar-track:active{background:rgba(0,0,0,.25)} + &::-webkit-scrollbar-corner{background:0 0} +} diff --git a/bigbluebutton-html5/imports/ui/stylesheets/placeholders/_scrollable.scss b/bigbluebutton-html5/imports/ui/stylesheets/placeholders/_scrollable.scss deleted file mode 100644 index d16005da51..0000000000 --- a/bigbluebutton-html5/imports/ui/stylesheets/placeholders/_scrollable.scss +++ /dev/null @@ -1,16 +0,0 @@ -%scrollbox-vertical { - overflow-y: auto; - background: - /* Shadow covers */ - linear-gradient(white 30%, rgba(255,255,255,0)), - linear-gradient(rgba(255,255,255,0), white 70%) 0 100%, - - /* Shadows */ - radial-gradient(farthest-side at 50% 0, rgba(0,0,0,.2), rgba(0,0,0,0)), - radial-gradient(farthest-side at 50% 100%, rgba(0,0,0,.2), rgba(0,0,0,0)) 0 100%; - - background-repeat: no-repeat; - background-color: white; - background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px; - background-attachment: local, local, scroll, scroll; -} From f204e6007a2a9dc5ae4a584cf73bd4f0064630b4 Mon Sep 17 00:00:00 2001 From: Oswaldo Acauan Date: Thu, 2 Jun 2016 16:08:17 -0300 Subject: [PATCH 7/9] Sanitize chat messages client and server-side --- .../server/methods/sendChatMessagetoServer.js | 34 ++++++++++++++----- .../chat/message-form/component.jsx | 8 ++++- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/bigbluebutton-html5/imports/api/chat/server/methods/sendChatMessagetoServer.js b/bigbluebutton-html5/imports/api/chat/server/methods/sendChatMessagetoServer.js index 742f467049..a6a02e6138 100755 --- a/bigbluebutton-html5/imports/api/chat/server/methods/sendChatMessagetoServer.js +++ b/bigbluebutton-html5/imports/api/chat/server/methods/sendChatMessagetoServer.js @@ -7,6 +7,31 @@ import { redisConfig } from '/config'; import RegexWebUrl from '/imports/utils/regex-weburl'; +const HTML_SAFE_MAP = { + '<': '<', + '>': '>', + '"': '"', + "'": ''', +}; + +const parseMessage = (message) => { + message = message || ''; + + message = message.trim(); + + // Sanitize. See: http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript/ + console.log('antes', message); + + message = message.replace(/[<>'"]/g, (c) => HTML_SAFE_MAP[c]); + + console.log('depois', message); + + // Replace flash links to flash valid ones + message = message.replace(RegexWebUrl, "$&"); + + return message; +}; + Meteor.methods({ // meetingId: the id of the meeting // chatObject: the object including info on the chat message, including the text @@ -48,12 +73,3 @@ Meteor.methods({ } }, }); - -const parseMessage = (message) => { - message = message || ''; - - // Replace flash links to flash valid ones - message.replace(RegexWebUrl, "$&"); - - return message; -}; 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 0ce2ae50d1..8ef87d5fcd 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx @@ -41,7 +41,13 @@ export default class MessageForm extends Component { handleSubmit(e) { e.preventDefault(); - const message = this.state.message.trim(); + 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) { return; From b77b8ce971919b58feab28849a6dc5a9e0b49a56 Mon Sep 17 00:00:00 2001 From: Oswaldo Acauan Date: Thu, 2 Jun 2016 16:34:17 -0300 Subject: [PATCH 8/9] Fix Welcome message not being parsed --- .../server/methods/sendChatMessagetoServer.js | 13 ++++---- .../server/modifiers/addChatToCollection.js | 30 ++++++++++--------- .../api/users/server/modifiers/userJoined.js | 19 ++++++++++-- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/bigbluebutton-html5/imports/api/chat/server/methods/sendChatMessagetoServer.js b/bigbluebutton-html5/imports/api/chat/server/methods/sendChatMessagetoServer.js index a6a02e6138..084f11553a 100755 --- a/bigbluebutton-html5/imports/api/chat/server/methods/sendChatMessagetoServer.js +++ b/bigbluebutton-html5/imports/api/chat/server/methods/sendChatMessagetoServer.js @@ -19,12 +19,11 @@ const parseMessage = (message) => { message = message.trim(); + // Replace
    with \n\r + message = message.replace(//gi, '\n\r'); + // Sanitize. See: http://shebang.brandonmintern.com/foolproof-html-escaping-in-javascript/ - console.log('antes', message); - - message = message.replace(/[<>'"]/g, (c) => HTML_SAFE_MAP[c]); - - console.log('depois', message); + message = message.replace(/[<>'"]/g, c => HTML_SAFE_MAP[c]); // Replace flash links to flash valid ones message = message.replace(RegexWebUrl, "$&"); @@ -44,6 +43,7 @@ Meteor.methods({ const chatType = chatObject.chat_type; const recipient = chatObject.to_userid; let eventName = null; + const action = function () { if (chatType === 'PUBLIC_CHAT') { eventName = 'send_public_chat_message'; @@ -58,8 +58,9 @@ Meteor.methods({ } }; + chatObject.message = parseMessage(chatObject.message); + if (isAllowedTo(action(), credentials) && chatObject.from_userid === requesterUserId) { - chatObject.message = parseMessage(chatObject.message); let message = { payload: { message: chatObject, diff --git a/bigbluebutton-html5/imports/api/chat/server/modifiers/addChatToCollection.js b/bigbluebutton-html5/imports/api/chat/server/modifiers/addChatToCollection.js index e00f7f366e..8d203a052f 100755 --- a/bigbluebutton-html5/imports/api/chat/server/modifiers/addChatToCollection.js +++ b/bigbluebutton-html5/imports/api/chat/server/modifiers/addChatToCollection.js @@ -3,14 +3,29 @@ import { logger } from '/imports/startup/server/logger'; const BREAK_TAG = '
    '; +const parseMessage = (message) => { + message = message || ''; + + // Replace \r and \n to
    + message = message.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + BREAK_TAG + '$2'); + + // Replace flash links to html valid ones + message = message.split(` { + message = message || ''; + + // Replace \r and \n to
    + message = message.replace(/([^>\r\n]?)(\r\n|\n\r|\r|\n)/g, '$1' + BREAK_TAG + '$2'); + + // Replace flash links to html valid ones + message = message.split(`
    - + ); } @@ -93,3 +114,5 @@ export default class MessageForm extends Component { MessageForm.propTypes = propTypes; MessageForm.defaultProps = defaultProps; + +export default injectIntl(MessageForm);