diff options
Diffstat (limited to 'web/react')
-rw-r--r-- | web/react/components/command_list.jsx | 6 | ||||
-rw-r--r-- | web/react/components/create_comment.jsx | 7 | ||||
-rw-r--r-- | web/react/components/create_post.jsx | 7 | ||||
-rw-r--r-- | web/react/components/edit_post_modal.jsx | 6 | ||||
-rw-r--r-- | web/react/components/post_info.jsx | 17 | ||||
-rw-r--r-- | web/react/components/post_list.jsx | 42 | ||||
-rw-r--r-- | web/react/components/setting_item_max.jsx | 2 | ||||
-rw-r--r-- | web/react/components/sidebar_header.jsx | 2 | ||||
-rw-r--r-- | web/react/components/user_settings_general.jsx | 237 | ||||
-rw-r--r-- | web/react/components/user_settings_notifications.jsx | 16 | ||||
-rw-r--r-- | web/react/stores/channel_store.jsx | 1 | ||||
-rw-r--r-- | web/react/utils/utils.jsx | 95 |
12 files changed, 294 insertions, 144 deletions
diff --git a/web/react/components/command_list.jsx b/web/react/components/command_list.jsx index 5efe98dc6..27264ff6e 100644 --- a/web/react/components/command_list.jsx +++ b/web/react/components/command_list.jsx @@ -48,15 +48,15 @@ module.exports = React.createClass({ if (this.state.suggestions[i].suggestion != this.state.cmd) { suggestions.push( <div key={i} className="command-name" onClick={this.handleClick.bind(this, i)}> - <div className="pull-left"><strong>{ this.state.suggestions[i].suggestion }</strong></div> - <div className="command-desc pull-right">{ this.state.suggestions[i].description }</div> + <div className="command__title"><strong>{ this.state.suggestions[i].suggestion }</strong></div> + <div className="command__desc">{ this.state.suggestions[i].description }</div> </div> ); } } return ( - <div ref="mentionlist" className="command-box" style={{height:(this.state.suggestions.length*37)+2}}> + <div ref="mentionlist" className="command-box" style={{height:(this.state.suggestions.length*56)+2}}> { suggestions } </div> ); diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index f6e34fda9..c2b7e222f 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -104,17 +104,14 @@ module.exports = React.createClass({ this.lastTime = t; } }, - handleUserInput: function(message) { - var messageText = utils.truncateText(message); - var newPostError = utils.checkMessageLengthError(messageText, this.state.postError, 'Comment length cannot exceed ' + Constants.MAX_POST_LEN + ' characters'); - + handleUserInput: function(messageText) { var draft = PostStore.getCommentDraft(this.props.rootId); draft.message = messageText; PostStore.storeCommentDraft(this.props.rootId, draft); $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight); $('.post-right__scroll').perfectScrollbar('update'); - this.setState({messageText: messageText, postError: newPostError}); + this.setState({messageText: messageText}); }, handleUploadStart: function(clientIds, channelId) { var draft = PostStore.getCommentDraft(this.props.rootId); diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 73210c855..b9142223f 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -130,12 +130,9 @@ module.exports = React.createClass({ this.lastTime = t; } }, - handleUserInput: function(message) { - var messageText = utils.truncateText(message); - var newPostError = utils.checkMessageLengthError(messageText, this.state.postError, 'Message length cannot exceed ' + Constants.MAX_POST_LEN + ' characters'); - + handleUserInput: function(messageText) { this.resizePostHolder(); - this.setState({messageText: messageText, postError: newPostError}); + this.setState({messageText: messageText}); var draft = PostStore.getCurrentDraft(); draft['message'] = messageText; diff --git a/web/react/components/edit_post_modal.jsx b/web/react/components/edit_post_modal.jsx index df692e1bb..1c5a1ed5e 100644 --- a/web/react/components/edit_post_modal.jsx +++ b/web/react/components/edit_post_modal.jsx @@ -38,10 +38,8 @@ module.exports = React.createClass({ $("#edit_post").modal('hide'); $(this.state.refocusId).focus(); }, - handleEditInput: function(editText) { - var editMessage = utils.truncateText(editText); - var newError = utils.checkMessageLengthError(editMessage, this.state.error, 'New message length cannot exceed ' + Constants.MAX_POST_LEN + ' characters'); - this.setState({editText: editMessage, error: newError}); + handleEditInput: function(editMessage) { + this.setState({editText: editMessage}); }, handleEditKeyPress: function(e) { if (e.which == 13 && !e.shiftKey && !e.altKey) { diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx index 73e897f62..c80b287a3 100644 --- a/web/react/components/post_info.jsx +++ b/web/react/components/post_info.jsx @@ -43,13 +43,16 @@ export default class PostInfo extends React.Component { if (isOwner) { dropdownContents.push( - <li role='presentation'> + <li + key='editPost' + role='presentation' + > <a href='#' role='menuitem' data-toggle='modal' data-target='#edit_post' - data-refoucsid="#post_textbox" + data-refoucsid='#post_textbox' data-title={type} data-message={post.message} data-postid={post.id} @@ -64,7 +67,10 @@ export default class PostInfo extends React.Component { if (isOwner || isAdmin) { dropdownContents.push( - <li role='presentation'> + <li + key='deletePost' + role='presentation' + > <a href='#' role='menuitem' @@ -83,7 +89,10 @@ export default class PostInfo extends React.Component { if (this.props.allowReply === 'true') { dropdownContents.push( - <li role='presentation'> + <li + key='replyLink' + role='presentation' + > <a className='reply-link theme' href='#' diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index 865a22dbd..c1e6e490d 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -31,9 +31,11 @@ export default class PostList extends React.Component { this.onSocketChange = this.onSocketChange.bind(this); this.createChannelIntroMessage = this.createChannelIntroMessage.bind(this); this.loadMorePosts = this.loadMorePosts.bind(this); + this.loadFirstPosts = this.loadFirstPosts.bind(this); this.state = this.getStateFromStores(); this.state.numToDisplay = Constants.POST_CHUNK_SIZE; + this.state.isFirstLoadComplete = false; } getStateFromStores() { var channel = ChannelStore.getCurrent(); @@ -157,7 +159,10 @@ export default class PostList extends React.Component { }); this.scrollToBottom(); - setTimeout(this.scrollToBottom, 100); + + if (this.state.channel.id != null) { + this.loadFirstPosts(this.state.channel.id); + } } componentDidUpdate(prevProps, prevState) { $('.post-list__content div .post').removeClass('post--last'); @@ -229,9 +234,26 @@ export default class PostList extends React.Component { postHolder.removeClass('hide-scroll'); } } + loadFirstPosts(id) { + Client.getPosts( + id, + PostStore.getLatestUpdate(id), + function success() { + this.setState({isFirstLoadComplete: true}); + }.bind(this), + function fail() { + this.setState({isFirstLoadComplete: true}); + }.bind(this) + ); + } onChange() { var newState = this.getStateFromStores(); + // Special case where the channel wasn't yet set in componentDidMount + if (!this.state.isFirstLoadComplete && this.state.channel.id == null && newState.channel.id != null) { + this.loadFirstPosts(newState.channel.id); + } + if (!utils.areStatesEqual(newState, this.state)) { if (this.state.channel.id !== newState.channel.id) { PostStore.clearUnseenDeletedPosts(this.state.channel.id); @@ -379,6 +401,14 @@ export default class PostList extends React.Component { > <i className='fa fa-pencil'></i>Set a description </a> + <a + className='intro-links' + href='#' + data-toggle='modal' + data-target='#channel_invite' + > + <i className='fa fa-user-plus'></i>Invite others to this channel + </a> </div> ); } @@ -511,9 +541,15 @@ export default class PostList extends React.Component { if (post.user_id !== userId && post.create_at > this.state.lastViewed && !renderedLastViewed) { renderedLastViewed = true; + + // Temporary fix to solve ie10/11 rendering issue + let newSeparatorId = ''; + if (!utils.isBrowserIE()) { + newSeparatorId = 'new_message'; + } postCtls.push( <div - id='new_message' + id={newSeparatorId} key='unviewed' className='new-separator' > @@ -605,7 +641,7 @@ export default class PostList extends React.Component { } var postCtls = []; - if (posts) { + if (posts && this.state.isFirstLoadComplete) { postCtls = this.createPosts(posts, order); } else { postCtls.push(<LoadingScreen position='absolute' />); diff --git a/web/react/components/setting_item_max.jsx b/web/react/components/setting_item_max.jsx index 1599041b0..b978cdb0c 100644 --- a/web/react/components/setting_item_max.jsx +++ b/web/react/components/setting_item_max.jsx @@ -5,6 +5,7 @@ module.exports = React.createClass({ render: function() { var clientError = this.props.client_error ? <div className='form-group'><label className='col-sm-12 has-error'>{ this.props.client_error }</label></div> : null; var server_error = this.props.server_error ? <div className='form-group'><label className='col-sm-12 has-error'>{ this.props.server_error }</label></div> : null; + var extraInfo = this.props.extraInfo ? this.props.extraInfo : null; var inputs = this.props.inputs; @@ -15,6 +16,7 @@ module.exports = React.createClass({ <ul className="setting-list"> <li className="setting-list-item"> {inputs} + {extraInfo} </li> <li className="setting-list-item"> <hr /> diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx index d5d16816f..af65b7e1d 100644 --- a/web/react/components/sidebar_header.jsx +++ b/web/react/components/sidebar_header.jsx @@ -85,7 +85,7 @@ var NavbarDropdown = React.createClass({ } }); } - teams.push(<li key='newTeam_li'><a key='newTeam_a' href={utils.getWindowLocationOrigin() + '/signup_team' }>Create a New Team</a></li>); + teams.push(<li key='newTeam_li'><a key='newTeam_a' target="_blank" href={utils.getWindowLocationOrigin() + '/signup_team' }>Create a New Team</a></li>); return ( <ul className='nav navbar-nav navbar-right'> diff --git a/web/react/components/user_settings_general.jsx b/web/react/components/user_settings_general.jsx index fed11fbe9..ddd2fb607 100644 --- a/web/react/components/user_settings_general.jsx +++ b/web/react/components/user_settings_general.jsx @@ -11,10 +11,32 @@ var AsyncClient = require('../utils/async_client.jsx'); var utils = require('../utils/utils.jsx'); var assign = require('object-assign'); -module.exports = React.createClass({ - displayName: 'GeneralTab', - submitActive: false, - submitUsername: function(e) { +export default class UserSettingsGeneralTab extends React.Component { + constructor(props) { + super(props); + this.submitActive = false; + + this.submitUsername = this.submitUsername.bind(this); + this.submitNickname = this.submitNickname.bind(this); + this.submitName = this.submitName.bind(this); + this.submitEmail = this.submitEmail.bind(this); + this.submitUser = this.submitUser.bind(this); + this.submitPicture = this.submitPicture.bind(this); + + this.updateUsername = this.updateUsername.bind(this); + this.updateFirstName = this.updateFirstName.bind(this); + this.updateLastName = this.updateLastName.bind(this); + this.updateNickname = this.updateNickname.bind(this); + this.updateEmail = this.updateEmail.bind(this); + this.updatePicture = this.updatePicture.bind(this); + this.updateSection = this.updateSection.bind(this); + + this.handleClose = this.handleClose.bind(this); + this.setupInitialState = this.setupInitialState.bind(this); + + this.state = this.setupInitialState(props); + } + submitUsername(e) { e.preventDefault(); var user = this.props.user; @@ -37,8 +59,8 @@ module.exports = React.createClass({ user.username = username; this.submitUser(user); - }, - submitNickname: function(e) { + } + submitNickname(e) { e.preventDefault(); var user = UserStore.getCurrentUser(); @@ -52,8 +74,8 @@ module.exports = React.createClass({ user.nickname = nickname; this.submitUser(user); - }, - submitName: function(e) { + } + submitName(e) { e.preventDefault(); var user = UserStore.getCurrentUser(); @@ -69,8 +91,8 @@ module.exports = React.createClass({ user.last_name = lastName; this.submitUser(user); - }, - submitEmail: function(e) { + } + submitEmail(e) { e.preventDefault(); var user = UserStore.getCurrentUser(); @@ -88,15 +110,15 @@ module.exports = React.createClass({ user.email = email; this.submitUser(user); - }, - submitUser: function(user) { + } + submitUser(user) { client.updateUser(user, - function() { + function updateSuccess() { this.updateSection(''); AsyncClient.getMe(); }.bind(this), - function(err) { - var state = this.getInitialState(); + function updateFailure(err) { + var state = this.setupInitialState(this.props); if (err.message) { state.serverError = err.message; } else { @@ -105,8 +127,8 @@ module.exports = React.createClass({ this.setState(state); }.bind(this) ); - }, - submitPicture: function(e) { + } + submitPicture(e) { e.preventDefault(); if (!this.state.picture) { @@ -129,34 +151,34 @@ module.exports = React.createClass({ this.setState({loadingPicture: true}); client.uploadProfileImage(formData, - function() { + function imageUploadSuccess() { this.submitActive = false; AsyncClient.getMe(); window.location.reload(); }.bind(this), - function(err) { - var state = this.getInitialState(); + function imageUploadFailure(err) { + var state = this.setupInitialState(this.props); state.serverError = err; this.setState(state); }.bind(this) ); - }, - updateUsername: function(e) { + } + updateUsername(e) { this.setState({username: e.target.value}); - }, - updateFirstName: function(e) { + } + updateFirstName(e) { this.setState({firstName: e.target.value}); - }, - updateLastName: function(e) { + } + updateLastName(e) { this.setState({lastName: e.target.value}); - }, - updateNickname: function(e) { + } + updateNickname(e) { this.setState({nickname: e.target.value}); - }, - updateEmail: function(e) { + } + updateEmail(e) { this.setState({email: e.target.value}); - }, - updatePicture: function(e) { + } + updatePicture(e) { if (e.target.files && e.target.files[0]) { this.setState({picture: e.target.files[0]}); @@ -165,34 +187,33 @@ module.exports = React.createClass({ } else { this.setState({picture: null}); } - }, - updateSection: function(section) { - this.setState(assign({}, this.getInitialState(), {clientError: ''})); + } + updateSection(section) { + this.setState(assign({}, this.setupInitialState(this.props), {clientError: '', serverError: '', emailError: ''})); this.submitActive = false; this.props.updateSection(section); - }, - handleClose: function() { - $(this.getDOMNode()).find('.form-control').each(function() { + } + handleClose() { + $(this.getDOMNode()).find('.form-control').each(function clearForms() { this.value = ''; }); - this.setState(assign({}, this.getInitialState(), {clientError: null, serverError: null, emailError: null})); + this.setState(assign({}, this.setupInitialState(this.props), {clientError: null, serverError: null, emailError: null})); this.props.updateSection(''); - }, - componentDidMount: function() { + } + componentDidMount() { $('#user_settings').on('hidden.bs.modal', this.handleClose); - }, - componentWillUnmount: function() { + } + componentWillUnmount() { $('#user_settings').off('hidden.bs.modal', this.handleClose); - }, - getInitialState: function() { - var user = this.props.user; + } + setupInitialState(props) { + var user = props.user; var emailEnabled = !ConfigStore.getSettingAsBoolean('ByPassEmail', false); - return {username: user.username, firstName: user.first_name, lastName: user.last_name, nickname: user.nickname, - email: user.email, picture: null, loadingPicture: false, emailEnabled: emailEnabled}; - }, - render: function() { + email: user.email, picture: null, loadingPicture: false, emailEnabled: emailEnabled}; + } + render() { var user = this.props.user; var clientError = null; @@ -214,19 +235,35 @@ module.exports = React.createClass({ if (this.props.activeSection === 'name') { inputs.push( - <div className='form-group'> + <div + key='firstNameSetting' + className='form-group' + > <label className='col-sm-5 control-label'>First Name</label> <div className='col-sm-7'> - <input className='form-control' type='text' onChange={this.updateFirstName} value={this.state.firstName}/> + <input + className='form-control' + type='text' + onChange={this.updateFirstName} + value={this.state.firstName} + /> </div> </div> ); inputs.push( - <div className='form-group'> + <div + key='lastNameSetting' + className='form-group' + > <label className='col-sm-5 control-label'>Last Name</label> <div className='col-sm-7'> - <input className='form-control' type='text' onChange={this.updateLastName} value={this.state.lastName}/> + <input + className='form-control' + type='text' + onChange={this.updateLastName} + value={this.state.lastName} + /> </div> </div> ); @@ -238,7 +275,7 @@ module.exports = React.createClass({ submit={this.submitName} server_error={serverError} client_error={clientError} - updateSection={function(e) { + updateSection={function clearSection(e) { self.updateSection(''); e.preventDefault(); }} @@ -259,7 +296,7 @@ module.exports = React.createClass({ <SettingItemMin title='Full Name' describe={fullName} - updateSection={function() { + updateSection={function updateNameSection() { self.updateSection('name'); }} /> @@ -268,11 +305,24 @@ module.exports = React.createClass({ var nicknameSection; if (this.props.activeSection === 'nickname') { + let nicknameLabel = 'Nickname'; + if (utils.isMobile()) { + nicknameLabel = ''; + } + inputs.push( - <div className='form-group'> - <label className='col-sm-5 control-label'>{utils.isMobile() ? '' : 'Nickname'}</label> + <div + key='nicknameSetting' + className='form-group' + > + <label className='col-sm-5 control-label'>{nicknameLabel}</label> <div className='col-sm-7'> - <input className='form-control' type='text' onChange={this.updateNickname} value={this.state.nickname}/> + <input + className='form-control' + type='text' + onChange={this.updateNickname} + value={this.state.nickname} + /> </div> </div> ); @@ -284,7 +334,7 @@ module.exports = React.createClass({ submit={this.submitNickname} server_error={serverError} client_error={clientError} - updateSection={function(e) { + updateSection={function clearSection(e) { self.updateSection(''); e.preventDefault(); }} @@ -295,7 +345,7 @@ module.exports = React.createClass({ <SettingItemMin title='Nickname' describe={UserStore.getCurrentUser().nickname} - updateSection={function() { + updateSection={function updateNicknameSection() { self.updateSection('nickname'); }} /> @@ -304,11 +354,24 @@ module.exports = React.createClass({ var usernameSection; if (this.props.activeSection === 'username') { + let usernameLabel = 'Username'; + if (utils.isMobile()) { + usernameLabel = ''; + } + inputs.push( - <div className='form-group'> - <label className='col-sm-5 control-label'>{utils.isMobile() ? '' : 'Username'}</label> + <div + key='usernameSetting' + className='form-group' + > + <label className='col-sm-5 control-label'>{usernameLabel}</label> <div className='col-sm-7'> - <input className='form-control' type='text' onChange={this.updateUsername} value={this.state.username}/> + <input + className='form-control' + type='text' + onChange={this.updateUsername} + value={this.state.username} + /> </div> </div> ); @@ -320,7 +383,7 @@ module.exports = React.createClass({ submit={this.submitUsername} server_error={serverError} client_error={clientError} - updateSection={function(e) { + updateSection={function clearSection(e) { self.updateSection(''); e.preventDefault(); }} @@ -331,7 +394,7 @@ module.exports = React.createClass({ <SettingItemMin title='Username' describe={UserStore.getCurrentUser().username} - updateSection={function() { + updateSection={function updateUsernameSection() { self.updateSection('username'); }} /> @@ -346,11 +409,16 @@ module.exports = React.createClass({ } inputs.push( - <div> + <div key='emailSetting'> <div className='form-group'> <label className='col-sm-5 control-label'>Primary Email</label> <div className='col-sm-7'> - <input className='form-control' type='text' onChange={this.updateEmail} value={this.state.email}/> + <input + className='form-control' + type='text' + onChange={this.updateEmail} + value={this.state.email} + /> </div> </div> {helpText} @@ -364,7 +432,7 @@ module.exports = React.createClass({ submit={this.submitEmail} server_error={serverError} client_error={emailError} - updateSection={function(e) { + updateSection={function clearSection(e) { self.updateSection(''); e.preventDefault(); }} @@ -375,7 +443,7 @@ module.exports = React.createClass({ <SettingItemMin title='Email' describe={UserStore.getCurrentUser().email} - updateSection={function() { + updateSection={function updateEmailSection() { self.updateSection('email'); }} /> @@ -391,7 +459,7 @@ module.exports = React.createClass({ src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update} server_error={serverError} client_error={clientError} - updateSection={function(e) { + updateSection={function clearSection(e) { self.updateSection(''); e.preventDefault(); }} @@ -410,7 +478,7 @@ module.exports = React.createClass({ <SettingItemMin title='Profile Picture' describe={minMessage} - updateSection={function() { + updateSection={function updatePictureSection() { self.updateSection('picture'); }} /> @@ -419,8 +487,21 @@ module.exports = React.createClass({ return ( <div> <div className='modal-header'> - <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>×</span></button> - <h4 className='modal-title' ref='title'><i className='modal-back'></i>General Settings</h4> + <button + type='button' + className='close' + data-dismiss='modal' + aria-label='Close' + > + <span aria-hidden='true'>×</span> + </button> + <h4 + className='modal-title' + ref='title' + > + <i className='modal-back'></i> + General Settings + </h4> </div> <div className='user-settings'> <h3 className='tab-header'>General Settings</h3> @@ -439,4 +520,10 @@ module.exports = React.createClass({ </div> ); } -}); +} + +UserSettingsGeneralTab.propTypes = { + user: React.PropTypes.object, + updateSection: React.PropTypes.func, + activeSection: React.PropTypes.string +}; diff --git a/web/react/components/user_settings_notifications.jsx b/web/react/components/user_settings_notifications.jsx index b89f72987..ba0bda78e 100644 --- a/web/react/components/user_settings_notifications.jsx +++ b/web/react/components/user_settings_notifications.jsx @@ -1,6 +1,7 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. +var ChannelStore = require('../stores/channel_store.jsx'); var UserStore = require('../stores/user_store.jsx'); var SettingItemMin = require('./setting_item_min.jsx'); var SettingItemMax = require('./setting_item_max.jsx'); @@ -67,7 +68,11 @@ function getNotificationsStateFromStores() { } } - return {notifyLevel: desktop, enableEmail: email, soundNeeded: soundNeeded, enableSound: sound, usernameKey: usernameKey, mentionKey: mentionKey, customKeys: customKeys, customKeysChecked: customKeys.length > 0, firstNameKey: firstNameKey, allKey: allKey, channelKey: channelKey}; + var curChannel = ChannelStore.getCurrent().display_name; + + return {notifyLevel: desktop, enableEmail: email, soundNeeded: soundNeeded, enableSound: sound, + usernameKey: usernameKey, mentionKey: mentionKey, customKeys: customKeys, customKeysChecked: customKeys.length > 0, + firstNameKey: firstNameKey, allKey: allKey, channelKey: channelKey, curChannel: curChannel}; } export default class NotificationsTab extends React.Component { @@ -141,10 +146,12 @@ export default class NotificationsTab extends React.Component { } componentDidMount() { UserStore.addChangeListener(this.onListenerChange); + ChannelStore.addChangeListener(this.onListenerChange); $('#user_settings').on('hidden.bs.modal', this.handleClose); } componentWillUnmount() { UserStore.removeChangeListener(this.onListenerChange); + ChannelStore.removeChangeListener(this.onListenerChange); $('#user_settings').off('hidden.bs.modal', this.handleClose); this.props.updateSection(''); } @@ -265,6 +272,12 @@ export default class NotificationsTab extends React.Component { e.preventDefault(); }; + let extraInfo = ( + <div className='setting-list__hint'> + These settings will override the global notification settings for the <b>{this.state.curChannel}</b> channel + </div> + ) + desktopSection = ( <SettingItemMax title='Send desktop notifications' @@ -272,6 +285,7 @@ export default class NotificationsTab extends React.Component { submit={this.handleSubmit} server_error={serverError} updateSection={handleUpdateDesktopSection} + extraInfo={extraInfo} /> ); } else { diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx index f7c23841c..678d50bbd 100644 --- a/web/react/stores/channel_store.jsx +++ b/web/react/stores/channel_store.jsx @@ -270,4 +270,5 @@ ChannelStore.dispatchToken = AppDispatcher.register(function(payload) { } }); +ChannelStore.setMaxListeners(11); module.exports = ChannelStore; diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 34a0d55da..a1dc72ae2 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -260,10 +260,40 @@ module.exports.escapeRegExp = function(string) { return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); }; +function handleYoutubeTime(link) { + var timeRegex = /[\\?&]t=([0-9hms]+)/; + + var time = link.trim().match(timeRegex); + if (!time || !time[1]) { + return ''; + } + + var hours = time[1].match(/([0-9]+)h/); + var minutes = time[1].match(/([0-9]+)m/); + var seconds = time[1].match(/([0-9]+)s/); + + var ticks = 0; + + if (hours && hours[1]) { + ticks += parseInt(hours[1], 10) * 3600; + } + + if (minutes && minutes[1]) { + ticks += parseInt(minutes[1], 10) * 60; + } + + if (seconds && seconds[1]) { + ticks += parseInt(seconds[1], 10); + } + + return '&start=' + ticks.toString(); +} + function getYoutubeEmbed(link) { var regex = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|watch\?(?:[a-zA-Z-_]+=[a-zA-Z0-9-_]+&)+v=)([^#\&\?]*).*/; var youtubeId = link.trim().match(regex)[1]; + var time = handleYoutubeTime(link); function onClick(e) { var div = $(e.target).closest('.video-thumbnail__container')[0]; @@ -271,7 +301,8 @@ function getYoutubeEmbed(link) { iframe.setAttribute('src', 'https://www.youtube.com/embed/' + div.id + - '?autoplay=1&autohide=1&border=0&wmode=opaque&fs=1&enablejsapi=1'); + '?autoplay=1&autohide=1&border=0&wmode=opaque&fs=1&enablejsapi=1' + + time); iframe.setAttribute('width', '480px'); iframe.setAttribute('height', '360px'); iframe.setAttribute('type', 'text/html'); @@ -286,6 +317,7 @@ function getYoutubeEmbed(link) { return; } var metadata = data.items[0].snippet; + $('.video-type.' + youtubeId).html("Youtube - ") $('.video-uploader.' + youtubeId).html(metadata.channelTitle); $('.video-title.' + youtubeId).find('a').html(metadata.title); $('.post-list-holder-by-time').scrollTop($('.post-list-holder-by-time')[0].scrollHeight); @@ -303,9 +335,11 @@ function getYoutubeEmbed(link) { return ( <div className='post-comment'> - <h4 className='video-type'>YouTube</h4> + <h4> + <span className={'video-type ' + youtubeId}>YouTube</span> + <span className={'video-title ' + youtubeId}><a href={link}></a></span> + </h4> <h4 className={'video-uploader ' + youtubeId}></h4> - <h4 className={'video-title ' + youtubeId}><a href={link}></a></h4> <div className='video-div embed-responsive-item' id={youtubeId} onClick={onClick}> <div className='embed-responsive embed-responsive-4by3 video-div__placeholder'> <div id={youtubeId} className='video-thumbnail__container'> @@ -456,9 +490,21 @@ module.exports.textToJsx = function(text, options) { var mentionRegex = /^(?:@)([a-z0-9_]+)$/gi; // looks loop invariant but a weird JS bug needs it to be redefined here var explicitMention = mentionRegex.exec(trimWord); - if ((trimWord.toLowerCase().indexOf(searchTerm) > -1 || word.toLowerCase().indexOf(searchTerm) > -1) && searchTerm != '') { - - highlightSearchClass = ' search-highlight'; + if (searchTerm !== '') { + let searchWords = searchTerm.split(' '); + for (let idx in searchWords) { + let searchWord = searchWords[idx]; + if (searchWord === word.toLowerCase() || searchWord === trimWord.toLowerCase()) { + highlightSearchClass = ' search-highlight'; + break; + } else if (searchWord.charAt(searchWord.length - 1) === '*') { + let searchWordPrefix = searchWord.slice(0,-1); + if (trimWord.toLowerCase().indexOf(searchWordPrefix) > -1 || word.toLowerCase().indexOf(searchWordPrefix) > -1) { + highlightSearchClass = ' search-highlight'; + break; + } + } + } } if (explicitMention && @@ -1001,43 +1047,6 @@ module.exports.isBrowserEdge = function() { return window.naviagtor && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('edge') > -1; }; -// Gets text length consistent with maxlength property of textarea html tag -module.exports.getLengthOfTextInTextarea = function(messageText) { - // Need to get length with carriage returns counting as two characters to match textbox maxlength behavior - // unless ie10/ie11/edge which already do - - var len = messageText.length; - if (!module.exports.isBrowserIE() && !module.exports.isBrowserEdge()) { - len = messageText.replace(/\r(?!\n)|\n(?!\r)/g, '--').length; - } - - return len; -}; - -module.exports.checkMessageLengthError = function(message, currentError, newError) { - var updatedError = currentError; - var len = module.exports.getLengthOfTextInTextarea(message); - - if (!currentError && len >= Constants.MAX_POST_LEN) { - updatedError = newError; - } else if (currentError === newError && len < Constants.MAX_POST_LEN) { - updatedError = ''; - } - - return updatedError; -}; - -// Necessary due to issues with textarea max length and pasting newlines -module.exports.truncateText = function(message) { - var lengthDifference = module.exports.getLengthOfTextInTextarea(message) - message.length; - - if (lengthDifference > 0) { - return message.substring(0, Constants.MAX_POST_LEN - lengthDifference); - } - - return message.substring(0, Constants.MAX_POST_LEN); -}; - // Used to get the id of the other user from a DM channel module.exports.getUserIdFromChannelName = function(channel) { var ids = channel.name.split('__'); |