diff options
Diffstat (limited to 'web')
27 files changed, 276 insertions, 154 deletions
diff --git a/web/react/components/create_comment.jsx b/web/react/components/create_comment.jsx index 99f553c0c..5097b3aa5 100644 --- a/web/react/components/create_comment.jsx +++ b/web/react/components/create_comment.jsx @@ -43,6 +43,12 @@ export default class CreateComment extends React.Component { submitting: false }; } + componentDidUpdate(prevProps, prevState) { + if (prevState.uploadsInProgress < this.state.uploadsInProgress) { + $('.post-right__scroll').scrollTop($('.post-right__scroll')[0].scrollHeight); + $('.post-right__scroll').perfectScrollbar('update'); + } + } handleSubmit(e) { e.preventDefault(); diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 595643027..0cd14747d 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -52,6 +52,12 @@ export default class CreatePost extends React.Component { componentDidUpdate(prevProps, prevState) { if (prevState.previews.length !== this.state.previews.length) { this.resizePostHolder(); + return; + } + + if (prevState.uploadsInProgress !== this.state.uploadsInProgress) { + this.resizePostHolder(); + return; } } getCurrentDraft() { @@ -79,7 +85,7 @@ export default class CreatePost extends React.Component { return; } - let post = {}; + const post = {}; post.filenames = []; post.message = this.state.messageText; @@ -99,20 +105,20 @@ export default class CreatePost extends React.Component { this.state.channelId, post.message, false, - function handleCommandSuccess(data) { + (data) => { PostStore.storeDraft(data.channel_id, null); this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); if (data.goto_location.length > 0) { window.location.href = data.goto_location; } - }.bind(this), - function handleCommandError(err) { - let state = {}; + }, + (err) => { + const state = {}; state.serverError = err.message; state.submitting = false; this.setState(state); - }.bind(this) + } ); } else { post.channel_id = this.state.channelId; @@ -133,10 +139,10 @@ export default class CreatePost extends React.Component { this.setState({messageText: '', submitting: false, postError: null, previews: [], serverError: null}); Client.createPost(post, channel, - function handlePostSuccess(data) { + (data) => { AsyncClient.getPosts(); - let member = ChannelStore.getMember(channel.id); + const member = ChannelStore.getMember(channel.id); member.msg_count = channel.total_msg_count; member.last_viewed_at = Date.now(); ChannelStore.setChannelMember(member); @@ -146,8 +152,8 @@ export default class CreatePost extends React.Component { post: data }); }, - function handlePostError(err) { - let state = {}; + (err) => { + const state = {}; if (err.message === 'Invalid RootId parameter') { if ($('#post_deleted').length > 0) { @@ -161,7 +167,7 @@ export default class CreatePost extends React.Component { state.submitting = false; this.setState(state); - }.bind(this) + } ); } } @@ -179,9 +185,9 @@ export default class CreatePost extends React.Component { } } handleUserInput(messageText) { - this.setState({messageText: messageText}); + this.setState({messageText}); - let draft = PostStore.getCurrentDraft(); + const draft = PostStore.getCurrentDraft(); draft.message = messageText; PostStore.storeCurrentDraft(draft); } @@ -191,7 +197,7 @@ export default class CreatePost extends React.Component { $(window).trigger('resize'); } handleUploadStart(clientIds, channelId) { - let draft = PostStore.getDraft(channelId); + const draft = PostStore.getDraft(channelId); draft.uploadsInProgress = draft.uploadsInProgress.concat(clientIds); PostStore.storeDraft(channelId, draft); @@ -199,7 +205,7 @@ export default class CreatePost extends React.Component { this.setState({uploadsInProgress: draft.uploadsInProgress}); } handleFileUploadComplete(filenames, clientIds, channelId) { - let draft = PostStore.getDraft(channelId); + const draft = PostStore.getDraft(channelId); // remove each finished file from uploads for (let i = 0; i < clientIds.length; i++) { @@ -217,7 +223,7 @@ export default class CreatePost extends React.Component { } handleUploadError(err, clientId) { if (clientId !== -1) { - let draft = PostStore.getDraft(this.state.channelId); + const draft = PostStore.getDraft(this.state.channelId); const index = draft.uploadsInProgress.indexOf(clientId); if (index !== -1) { @@ -237,8 +243,8 @@ export default class CreatePost extends React.Component { Utils.setCaretPosition(React.findDOMNode(this.refs.textbox.refs.message), newText.length); } removePreview(id) { - let previews = this.state.previews; - let uploadsInProgress = this.state.uploadsInProgress; + const previews = Object.assign([], this.state.previews); + const uploadsInProgress = this.state.uploadsInProgress; // id can either be the path of an uploaded file or the client id of an in progress upload let index = previews.indexOf(id); @@ -253,12 +259,12 @@ export default class CreatePost extends React.Component { } } - let draft = PostStore.getCurrentDraft(); + const draft = PostStore.getCurrentDraft(); draft.previews = previews; draft.uploadsInProgress = uploadsInProgress; PostStore.storeCurrentDraft(draft); - this.setState({previews: previews, uploadsInProgress: uploadsInProgress}); + this.setState({previews, uploadsInProgress}); } componentDidMount() { ChannelStore.addChangeListener(this.onChange); @@ -272,7 +278,7 @@ export default class CreatePost extends React.Component { if (this.state.channelId !== channelId) { const draft = this.getCurrentDraft(); - this.setState({channelId: channelId, messageText: draft.messageText, initialText: draft.messageText, submitting: false, serverError: null, postError: null, previews: draft.previews, uploadsInProgress: draft.uploadsInProgress}); + this.setState({channelId, messageText: draft.messageText, initialText: draft.messageText, submitting: false, serverError: null, postError: null, previews: draft.previews, uploadsInProgress: draft.uploadsInProgress}); } } getFileCount(channelId) { diff --git a/web/react/components/delete_channel_modal.jsx b/web/react/components/delete_channel_modal.jsx index 4efb9cb23..44c54db72 100644 --- a/web/react/components/delete_channel_modal.jsx +++ b/web/react/components/delete_channel_modal.jsx @@ -4,6 +4,7 @@ const Client = require('../utils/client.jsx'); const AsyncClient = require('../utils/async_client.jsx'); const ChannelStore = require('../stores/channel_store.jsx'); +var TeamStore = require('../stores/team_store.jsx'); export default class DeleteChannelModal extends React.Component { constructor(props) { @@ -24,7 +25,7 @@ export default class DeleteChannelModal extends React.Component { Client.deleteChannel(this.state.channelId, function handleDeleteSuccess() { AsyncClient.getChannels(true); - window.location.href = '/'; + window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/town-square'; }, function handleDeleteError(err) { AsyncClient.dispatchError(err, 'handleDelete'); diff --git a/web/react/components/error_bar.jsx b/web/react/components/error_bar.jsx index 87d94a41d..05726e860 100644 --- a/web/react/components/error_bar.jsx +++ b/web/react/components/error_bar.jsx @@ -2,10 +2,6 @@ // See License.txt for license information. var ErrorStore = require('../stores/error_store.jsx'); -var utils = require('../utils/utils.jsx'); -var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); -var Constants = require('../utils/constants.jsx'); -var ActionTypes = Constants.ActionTypes; export default class ErrorBar extends React.Component { constructor() { @@ -13,70 +9,79 @@ export default class ErrorBar extends React.Component { this.onErrorChange = this.onErrorChange.bind(this); this.handleClose = this.handleClose.bind(this); + this.prevTimer = null; - this.state = this.getStateFromStores(); - if (this.state.message) { - setTimeout(this.handleClose, 10000); + this.state = ErrorStore.getLastError(); + if (this.state && this.state.message) { + this.prevTimer = setTimeout(this.handleClose, 10000); } } - getStateFromStores() { - var error = ErrorStore.getLastError(); - if (!error || error.message === 'There appears to be a problem with your internet connection') { - return {message: null}; - } - return {message: error.message}; - } componentDidMount() { ErrorStore.addChangeListener(this.onErrorChange); $('body').css('padding-top', $(React.findDOMNode(this)).outerHeight()); - $(window).resize(function onResize() { - if (this.state.message) { + $(window).resize(() => { + if (this.state && this.state.message) { $('body').css('padding-top', $(React.findDOMNode(this)).outerHeight()); } - }.bind(this)); + }); } + componentWillUnmount() { ErrorStore.removeChangeListener(this.onErrorChange); } + onErrorChange() { - var newState = this.getStateFromStores(); - if (!utils.areStatesEqual(newState, this.state)) { - if (newState.message) { - setTimeout(this.handleClose, 10000); - } + var newState = ErrorStore.getLastError(); + + if (this.prevTimer != null) { + clearInterval(this.prevTimer); + this.prevTimer = null; + } + if (newState) { this.setState(newState); + this.prevTimer = setTimeout(this.handleClose, 10000); + } else { + this.setState({message: null}); } } + handleClose(e) { if (e) { e.preventDefault(); } - AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_ERROR, - err: null - }); + ErrorStore.storeLastError(null); + ErrorStore.emitChange(); $('body').css('padding-top', '0'); } + render() { - if (this.state.message) { - return ( - <div className='error-bar'> - <span>{this.state.message}</span> - <a - href='#' - className='error-bar__close' - onClick={this.handleClose} - > - × - </a> - </div> - ); + if (!this.state) { + return <div/>; + } + + if (!this.state.message) { + return <div/>; + } + + if (this.state.connErrorCount < 7) { + return <div/>; } - return <div/>; + return ( + <div className='error-bar'> + <span>{this.state.message}</span> + <a + href='#' + className='error-bar__close' + onClick={this.handleClose} + > + × + </a> + </div> + ); } } diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx index da9874b0b..bdb50cd9e 100644 --- a/web/react/components/navbar.jsx +++ b/web/react/components/navbar.jsx @@ -262,7 +262,7 @@ export default class Navbar extends React.Component { return ( <div className='navbar-brand'> <a - href='/' + href={TeamStore.getCurrentTeamUrl() + '/channels/town-square'} className='heading' > {channelTitle} diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx index c38edf6a2..824e7ef39 100644 --- a/web/react/components/post_info.jsx +++ b/web/react/components/post_info.jsx @@ -5,6 +5,8 @@ var UserStore = require('../stores/user_store.jsx'); var utils = require('../utils/utils.jsx'); var Constants = require('../utils/constants.jsx'); +var Tooltip = ReactBootstrap.Tooltip; +var OverlayTrigger = ReactBootstrap.OverlayTrigger; export default class PostInfo extends React.Component { constructor(props) { @@ -148,15 +150,19 @@ export default class PostInfo extends React.Component { var dropdown = this.createDropdown(); + let tooltip = <Tooltip>{utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}</Tooltip>; + return ( <ul className='post-header post-info'> <li className='post-header-col'> - <time - className='post-profile-time' - title={`${utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}`} + <OverlayTrigger + placement='top' + overlay={tooltip} > - {utils.displayDateTime(post.create_at)} - </time> + <time className='post-profile-time'> + {utils.displayDateTime(post.create_at)} + </time> + </OverlayTrigger> </li> <li className='post-header-col post-header__reply'> <div className='dropdown'> diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index 218922b67..3e1e075bb 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -105,18 +105,18 @@ export default class PostList extends React.Component { UserStore.addStatusesChangeListener(this.onTimeChange); SocketStore.addChangeListener(this.onSocketChange); - var postHolder = $(React.findDOMNode(this.refs.postlist)); + const postHolder = $(React.findDOMNode(this.refs.postlist)); - $(window).on('resize.' + this.props.channelId, function resize() { + $(window).resize(() => { this.resize(); if (!this.scrolled) { this.scrollToBottom(); } - }.bind(this)); + }); - postHolder.on('scroll', function scroll() { - var position = postHolder.scrollTop() + postHolder.height() + 14; - var bottom = postHolder[0].scrollHeight; + postHolder.on('scroll', () => { + const position = postHolder.scrollTop() + postHolder.height() + 14; + const bottom = postHolder[0].scrollHeight; if (position >= bottom) { this.scrolled = false; @@ -128,7 +128,7 @@ export default class PostList extends React.Component { this.userHasSeenNew = true; } this.isUserScroll = true; - }.bind(this)); + }); $('.post-list__content div .post').removeClass('post--last'); $('.post-list__content div:last-child .post').addClass('post--last'); @@ -146,7 +146,7 @@ export default class PostList extends React.Component { UserStore.removeStatusesChangeListener(this.onTimeChange); SocketStore.removeChangeListener(this.onSocketChange); $('body').off('click.userpopover'); - $(window).off('resize.' + this.props.channelId); + $(window).off('resize'); var postHolder = $(React.findDOMNode(this.refs.postlist)); postHolder.off('scroll'); } diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index 8311747ee..495159efc 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -84,7 +84,7 @@ export default class SignupUserComplete extends React.Component { if (this.props.hash > 0) { BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: 'finished'})); } - window.location.href = '/'; + window.location.href = '/' + this.props.teamName + '/channels/town-square'; }.bind(this), function emailLoginFailure(err) { if (err.message === 'Login failed because email address has not been verified') { diff --git a/web/react/components/team_signup_password_page.jsx b/web/react/components/team_signup_password_page.jsx index b26d9f6ce..105e4817a 100644 --- a/web/react/components/team_signup_password_page.jsx +++ b/web/react/components/team_signup_password_page.jsx @@ -53,7 +53,7 @@ export default class TeamSignupPasswordPage extends React.Component { props.state.wizard = 'finished'; props.updateParent(props.state, true); - window.location.href = '/'; + window.location.href = '/' + teamSignup.team.name + '/channels/town-square'; }.bind(this), function loginFail(err) { if (err.message === 'Login failed because email address has not been verified') { diff --git a/web/react/components/team_signup_with_sso.jsx b/web/react/components/team_signup_with_sso.jsx index 2849b4cbb..a4972dd8d 100644 --- a/web/react/components/team_signup_with_sso.jsx +++ b/web/react/components/team_signup_with_sso.jsx @@ -42,7 +42,7 @@ export default class SSOSignUpPage extends React.Component { if (data.follow_link) { window.location.href = data.follow_link; } else { - window.location.href = '/'; + window.location.href = '/' + team.name + '/channels/town-square'; } }, function fail(err) { diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx index ea8126bec..5f5316013 100644 --- a/web/react/components/textbox.jsx +++ b/web/react/components/textbox.jsx @@ -5,7 +5,6 @@ const AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); const PostStore = require('../stores/post_store.jsx'); const CommandList = require('./command_list.jsx'); const ErrorStore = require('../stores/error_store.jsx'); -const AsyncClient = require('../utils/async_client.jsx'); const Utils = require('../utils/utils.jsx'); const Constants = require('../utils/constants.jsx'); @@ -18,7 +17,6 @@ export default class Textbox extends React.Component { this.getStateFromStores = this.getStateFromStores.bind(this); this.onListenerChange = this.onListenerChange.bind(this); this.onRecievedError = this.onRecievedError.bind(this); - this.onTimerInterrupt = this.onTimerInterrupt.bind(this); this.updateMentionTab = this.updateMentionTab.bind(this); this.handleChange = this.handleChange.bind(this); this.handleKeyPress = this.handleKeyPress.bind(this); @@ -35,8 +33,7 @@ export default class Textbox extends React.Component { this.state = { mentionText: '-1', mentions: [], - connection: '', - timerInterrupt: null + connection: '' }; this.caret = -1; @@ -44,6 +41,7 @@ export default class Textbox extends React.Component { this.doProcessMentions = false; this.mentions = []; } + getStateFromStores() { const error = ErrorStore.getLastError(); @@ -53,6 +51,7 @@ export default class Textbox extends React.Component { return {message: null}; } + componentDidMount() { PostStore.addAddMentionListener(this.onListenerChange); ErrorStore.addChangeListener(this.onRecievedError); @@ -60,46 +59,28 @@ export default class Textbox extends React.Component { this.resize(); this.updateMentionTab(null); } + componentWillUnmount() { PostStore.removeAddMentionListener(this.onListenerChange); ErrorStore.removeChangeListener(this.onRecievedError); } + onListenerChange(id, username) { if (id === this.props.id) { this.addMention(username); } } - onRecievedError() { - const errorState = this.getStateFromStores(); - if (this.state.timerInterrupt !== null) { - window.clearInterval(this.state.timerInterrupt); - this.setState({timerInterrupt: null}); - } + onRecievedError() { + const errorState = ErrorStore.getLastError(); - if (errorState.message === 'There appears to be a problem with your internet connection') { + if (errorState && errorState.connErrorCount > 0) { this.setState({connection: 'bad-connection'}); - const timerInterrupt = window.setInterval(this.onTimerInterrupt, 5000); - this.setState({timerInterrupt: timerInterrupt}); } else { this.setState({connection: ''}); } } - onTimerInterrupt() { - // Since these should only happen when you have no connection and slightly briefly after any - // performance hit should not matter - if (this.state.connection === 'bad-connection') { - AppDispatcher.handleServerAction({ - type: ActionTypes.RECIEVED_ERROR, - err: null - }); - - AsyncClient.updateLastViewedAt(); - } - window.clearInterval(this.state.timerInterrupt); - this.setState({timerInterrupt: null}); - } componentDidUpdate() { if (this.caret >= 0) { Utils.setCaretPosition(React.findDOMNode(this.refs.message), this.caret); @@ -111,6 +92,7 @@ export default class Textbox extends React.Component { } this.resize(); } + componentWillReceiveProps(nextProps) { if (!this.addedMention) { this.checkForNewMention(nextProps.messageText); @@ -122,19 +104,22 @@ export default class Textbox extends React.Component { this.addedMention = false; this.refs.commands.getSuggestedCommands(nextProps.messageText); } + updateMentionTab(mentionText) { // using setTimeout so dispatch isn't called during an in progress dispatch - setTimeout(function updateMentionTabAfterTimeout() { + setTimeout(() => { AppDispatcher.handleViewAction({ type: ActionTypes.RECIEVED_MENTION_DATA, id: this.props.id, mention_text: mentionText }); - }.bind(this), 1); + }, 1); } + handleChange() { this.props.onUserInput(React.findDOMNode(this.refs.message).value); } + handleKeyPress(e) { const text = React.findDOMNode(this.refs.message).value; @@ -157,6 +142,7 @@ export default class Textbox extends React.Component { this.props.onKeyPress(e); } + handleKeyDown(e) { if (Utils.getSelectedText(React.findDOMNode(this.refs.message)) !== '') { this.doProcessMentions = true; @@ -166,6 +152,7 @@ export default class Textbox extends React.Component { this.handleBackspace(e); } } + handleBackspace() { const text = React.findDOMNode(this.refs.message).value; if (text.indexOf('/') === 0) { @@ -185,6 +172,7 @@ export default class Textbox extends React.Component { this.doProcessMentions = true; } } + checkForNewMention(text) { const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message)); @@ -211,6 +199,7 @@ export default class Textbox extends React.Component { const name = preText.substring(atIndex + 1, preText.length).toLowerCase(); this.updateMentionTab(name); } + addMention(name) { const caret = Utils.getCaretPosition(React.findDOMNode(this.refs.message)); @@ -233,11 +222,13 @@ export default class Textbox extends React.Component { this.props.onUserInput(`${prefix}@${name} ${suffix}`); } + addCommand(cmd) { const elm = React.findDOMNode(this.refs.message); elm.value = cmd; this.handleChange(); } + resize() { const e = React.findDOMNode(this.refs.message); const w = React.findDOMNode(this.refs.wrapper); @@ -264,21 +255,25 @@ export default class Textbox extends React.Component { this.props.onHeightChange(); } } + handleFocus() { const elm = React.findDOMNode(this.refs.message); if (elm.title === elm.value) { elm.value = ''; } } + handleBlur() { const elm = React.findDOMNode(this.refs.message); if (elm.value === '') { elm.value = elm.title; } } + handlePaste() { this.doProcessMentions = true; } + render() { return ( <div diff --git a/web/react/components/user_settings/manage_incoming_hooks.jsx b/web/react/components/user_settings/manage_incoming_hooks.jsx index 12c041c7f..1bbfbd162 100644 --- a/web/react/components/user_settings/manage_incoming_hooks.jsx +++ b/web/react/components/user_settings/manage_incoming_hooks.jsx @@ -134,7 +134,7 @@ export default class ManageIncomingHooks extends React.Component { } else if (hooks.length > 0) { displayHooks = hooks; } else { - displayHooks = <label>{' None'}</label>; + displayHooks = <label>{': None'}</label>; } const existingHooks = ( diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx index e645878c1..a7fecb689 100644 --- a/web/react/components/view_image.jsx +++ b/web/react/components/view_image.jsx @@ -1,10 +1,11 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. -var Client = require('../utils/client.jsx'); -var Utils = require('../utils/utils.jsx'); -var ViewImagePopoverBar = require('./view_image_popover_bar.jsx'); -var Modal = ReactBootstrap.Modal; +const Client = require('../utils/client.jsx'); +const Utils = require('../utils/utils.jsx'); +const Constants = require('../utils/constants.jsx'); +const ViewImagePopoverBar = require('./view_image_popover_bar.jsx'); +const Modal = ReactBootstrap.Modal; export default class ViewImageModal extends React.Component { constructor(props) { @@ -200,11 +201,20 @@ export default class ViewImageModal extends React.Component { </a> ); } else if (fileType === 'video' || fileType === 'audio') { + let width = Constants.WEB_VIDEO_WIDTH; + let height = Constants.WEB_VIDEO_HEIGHT; + if (Utils.isMobile()) { + width = Constants.MOBILE_VIDEO_WIDTH; + height = Constants.MOBILE_VIDEO_HEIGHT; + } + content = ( <video ref='video' data-setup='{}' controls='controls' + width={width} + height={height} > <source src={Utils.getWindowLocationOrigin() + '/api/v1/files/get' + filename} /> </video> diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx index 07207c556..74259194a 100644 --- a/web/react/pages/channel.jsx +++ b/web/react/pages/channel.jsx @@ -26,6 +26,7 @@ var ChannelInviteModal = require('../components/channel_invite_modal.jsx'); var TeamMembersModal = require('../components/team_members.jsx'); var DirectChannelModal = require('../components/more_direct_channels.jsx'); var ErrorBar = require('../components/error_bar.jsx'); +var ErrorStore = require('../stores/error_store.jsx'); var ChannelLoader = require('../components/channel_loader.jsx'); var MentionList = require('../components/mention_list.jsx'); var ChannelInfoModal = require('../components/channel_info_modal.jsx'); @@ -234,6 +235,11 @@ function setupChannelPage(props) { <RegisterAppModal />, document.getElementById('register_app_modal') ); + + if (global.window.config.SendEmailNotifications === 'false') { + ErrorStore.storeLastError({message: 'Preview Mode: Email notifications have not been configured'}); + ErrorStore.emitChange(); + } } global.window.setup_channel_page = setupChannelPage; diff --git a/web/react/stores/error_store.jsx b/web/react/stores/error_store.jsx index 597c88cff..ece7d8522 100644 --- a/web/react/stores/error_store.jsx +++ b/web/react/stores/error_store.jsx @@ -48,7 +48,7 @@ class ErrorStoreClass extends EventEmitter { var ErrorStore = new ErrorStoreClass(); -ErrorStore.dispatchToken = AppDispatcher.register(function registry(payload) { +ErrorStore.dispatchToken = AppDispatcher.register((payload) => { var action = payload.action; switch (action.type) { case ActionTypes.RECIEVED_ERROR: diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx index ae74059d1..1d853f979 100644 --- a/web/react/stores/socket_store.jsx +++ b/web/react/stores/socket_store.jsx @@ -3,6 +3,7 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var UserStore = require('./user_store.jsx'); +var ErrorStore = require('./error_store.jsx'); var EventEmitter = require('events').EventEmitter; var Constants = require('../utils/constants.jsx'); @@ -21,6 +22,7 @@ class SocketStoreClass extends EventEmitter { this.addChangeListener = this.addChangeListener.bind(this); this.removeChangeListener = this.removeChangeListener.bind(this); this.sendMessage = this.sendMessage.bind(this); + this.failCount = 0; this.initialize(); } @@ -37,27 +39,43 @@ class SocketStoreClass extends EventEmitter { protocol = 'wss://'; } var connUrl = protocol + location.host + '/api/v1/websocket'; - console.log('connecting to ' + connUrl); //eslint-disable-line no-console + if (this.failCount === 0) { + console.log('websocket connecting to ' + connUrl); //eslint-disable-line no-console + } conn = new WebSocket(connUrl); - conn.onclose = function closeConn(evt) { - console.log('websocket closed'); //eslint-disable-line no-console - console.log(evt); //eslint-disable-line no-console + conn.onopen = () => { + if (this.failCount > 0) { + console.log('websocket re-established connection'); //eslint-disable-line no-console + } + + this.failCount = 0; + ErrorStore.storeLastError(null); + ErrorStore.emitChange(); + }; + + conn.onclose = () => { conn = null; setTimeout( - function reconnect() { + () => { this.initialize(); - }.bind(this), + }, 3000 ); - }.bind(this); + }; + + conn.onerror = (evt) => { + if (this.failCount === 0) { + console.log('websocket error ' + evt); //eslint-disable-line no-console + } + + this.failCount = this.failCount + 1; - conn.onerror = function connError(evt) { - console.log('websocket error'); //eslint-disable-line no-console - console.log(evt); //eslint-disable-line no-console + ErrorStore.storeLastError({connErrorCount: this.failCount, message: 'We cannot reach the Mattermost service. The service may be down or misconfigured. Please contact an administrator to make sure the WebSocket port is configured properly.'}); + ErrorStore.emitChange(); }; - conn.onmessage = function connMessage(evt) { + conn.onmessage = (evt) => { AppDispatcher.handleServerAction({ type: ActionTypes.RECIEVED_MSG, msg: JSON.parse(evt.data) @@ -86,7 +104,7 @@ class SocketStoreClass extends EventEmitter { var SocketStore = new SocketStoreClass(); -SocketStore.dispatchToken = AppDispatcher.register(function registry(payload) { +SocketStore.dispatchToken = AppDispatcher.register((payload) => { var action = payload.action; switch (action.type) { diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 4effa7307..715e26197 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -27,7 +27,7 @@ function handleError(methodName, xhr, status, err) { msg = 'error in ' + methodName + ' status=' + status + ' statusCode=' + xhr.status + ' err=' + err; if (xhr.status === 0) { - e = {message: 'There appears to be a problem with your internet connection'}; + e = {message: 'There appears to be a problem with your internet connection', connErrorCount: 1}; } else { e = {message: 'We received an unexpected status code from the server (' + xhr.status + ')'}; } diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 8c9e1ee85..7497d8450 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -72,6 +72,10 @@ module.exports = { MAX_FILE_SIZE: 50000000, // 50 MB THUMBNAIL_WIDTH: 128, THUMBNAIL_HEIGHT: 100, + WEB_VIDEO_WIDTH: 640, + WEB_VIDEO_HEIGHT: 480, + MOBILE_VIDEO_WIDTH: 480, + MOBILE_VIDEO_HEIGHT: 360, DEFAULT_CHANNEL: 'town-square', OFFTOPIC_CHANNEL: 'off-topic', GITLAB_SERVICE: 'gitlab', @@ -130,6 +134,7 @@ module.exports = { mentionColor: '#ffffff', centerChannelBg: '#ffffff', centerChannelColor: '#333333', + newMessageSeparator: '#FF8800', linkColor: '#2389d7', buttonBg: '#2389d7', buttonColor: '#FFFFFF' @@ -150,12 +155,13 @@ module.exports = { mentionColor: '#bfcde8', centerChannelBg: '#f2f4f8', centerChannelColor: '#333333', + newMessageSeparator: '#FF8800', linkColor: '#2f81b7', buttonBg: '#1dacfc', buttonColor: '#FFFFFF' }, - dark: { - type: 'Dark', + mattermostDark: { + type: 'Mattermost Dark', sidebarBg: '#1B2C3E', sidebarText: '#bbbbbb', sidebarUnreadText: '#fff', @@ -170,9 +176,31 @@ module.exports = { mentionColor: '#FFFFFF', centerChannelBg: '#2F3E4E', centerChannelColor: '#DDDDDD', + newMessageSeparator: '#5de5da', linkColor: '#A4FFEB', buttonBg: '#1dacfc', buttonColor: '#FFFFFF' + }, + windows10: { + type: 'Windows 10 Dark', + sidebarBg: '#171717', + sidebarText: '#eee', + sidebarUnreadText: '#fff', + sidebarTextHoverBg: '#302e30', + sidebarTextHoverColor: '#fff', + sidebarTextActiveBg: '#484748', + sidebarTextActiveColor: '#FFFFFF', + sidebarHeaderBg: '#1f1f1f', + sidebarHeaderTextColor: '#FFFFFF', + onlineIndicator: '#0177e7', + mentionBj: '#0177e7', + mentionColor: '#FFFFFF', + centerChannelBg: '#1F1F1F', + centerChannelColor: '#DDDDDD', + newMessageSeparator: '#CC992D', + linkColor: '#0177e7', + buttonBg: '#0177e7', + buttonColor: '#FFFFFF' } }, THEME_ELEMENTS: [ @@ -233,6 +261,10 @@ module.exports = { uiName: 'Center Channel Text' }, { + id: 'newMessageSeparator', + uiName: 'New message separator' + }, + { id: 'linkColor', uiName: 'Link Color' }, diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index 50438c6cf..61dcae6d8 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -4,6 +4,7 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var ChannelStore = require('../stores/channel_store.jsx'); var UserStore = require('../stores/user_store.jsx'); +var TeamStore = require('../stores/team_store.jsx'); var Constants = require('../utils/constants.jsx'); var ActionTypes = Constants.ActionTypes; var AsyncClient = require('./async_client.jsx'); @@ -113,7 +114,7 @@ export function notifyMe(title, body, channel) { if (channel) { switchChannel(channel); } else { - window.location.href = '/'; + window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/town-square'; } }; setTimeout(function closeNotificationOnTimeout() { @@ -314,7 +315,6 @@ function getYoutubeEmbed(link) { $('.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); } if (global.window.config.GoogleDeveloperKey) { @@ -616,8 +616,9 @@ export function applyTheme(theme) { } if (theme.centerChannelColor) { - changeCss('.app__content', 'color:' + theme.centerChannelColor, 2); + changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .loading-screen .loading__content .round', 'color:' + theme.centerChannelColor, 1); changeCss('#post-create', 'color:' + theme.centerChannelColor, 2); + changeCss('.post-body hr', 'background:' + theme.centerChannelColor, 1); changeCss('.channel-header .heading', 'color:' + theme.centerChannelColor, 1); changeCss('.markdown__table tbody tr:nth-child(2n)', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); changeCss('.channel-header__info>div.dropdown .header-dropdown__icon', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1); @@ -630,8 +631,8 @@ export function applyTheme(theme) { changeCss('.search-bar__container .search__form .search-bar', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2) + '; color: ' + theme.centerChannelColor, 2); changeCss('.search-bar__container .search__form', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); changeCss('.channel-intro .channel-intro__content', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1); - changeCss('.date-separator .separator__text, .new-separator .separator__text', 'color:' + theme.centerChannelColor, 2); - changeCss('.date-separator .separator__hr, .new-separator .separator__hr, .post-right__container .post.post--root hr, .search-item-container', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); + changeCss('.date-separator .separator__text', 'color:' + theme.centerChannelColor, 2); + changeCss('.date-separator .separator__hr, .post-right__container .post.post--root hr, .search-item-container', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); changeCss('.channel-intro', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1); changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1); changeCss('.post.current--user .post-body, .post.post--comment.other--root.current--user .post-comment, .post.post--comment.other--root .post-comment, .post.same--root .post-body', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2); @@ -645,6 +646,11 @@ export function applyTheme(theme) { changeCss('.sidebar--right', 'color:' + theme.centerChannelColor, 2); } + if (theme.newMessageSeparator) { + changeCss('.new-separator .separator__text', 'color:' + theme.newMessageSeparator, 1); + changeCss('.new-separator .separator__hr', 'border-color:' + changeOpacity(theme.newMessageSeparator, 0.5), 1); + } + if (theme.linkColor) { changeCss('a, a:focus, a:hover', 'color:' + theme.linkColor, 1); changeCss('.post .comment-icon__container', 'fill:' + theme.linkColor, 1); diff --git a/web/sass-files/sass/partials/_loading.scss b/web/sass-files/sass/partials/_loading.scss index d71055722..ab8601df6 100644 --- a/web/sass-files/sass/partials/_loading.scss +++ b/web/sass-files/sass/partials/_loading.scss @@ -37,4 +37,4 @@ to { opacity: 0.1; } } } -} +}
\ No newline at end of file diff --git a/web/sass-files/sass/partials/_markdown.scss b/web/sass-files/sass/partials/_markdown.scss index de92e9d20..122586354 100644 --- a/web/sass-files/sass/partials/_markdown.scss +++ b/web/sass-files/sass/partials/_markdown.scss @@ -2,10 +2,20 @@ font-weight: 700; } .markdown__paragraph-inline { - display: inline; - + .markdown__paragraph-inline { - margin-left: 4px; - } + display: inline; + + .markdown__paragraph-inline { + margin-left: 4px; + } +} +.post-body { + hr { + height: 4px; + padding: 0; + margin: 15px 0 16px; + background-color: #e7e7e7; + border: 0 none; + @include opacity(0.2); + } } .markdown__table { background: #fff; @@ -21,6 +31,26 @@ } } } +blockquote { + border: none; + position: relative; + font-size: 16px; + padding: 10px 10px 10px 38px; + margin-bottom: 0; + &:before { + font-family: FontAwesome; + font-weight: normal; + font-style: normal; + display: inline-block; + text-decoration: inherit; + content: "\f10d"; + left: 8px; + top: 5px; + position: absolute; + font-size: 20px; + @include opacity(0.6); + } +} pre { border: none; background-color: #f7f7f7; diff --git a/web/sass-files/sass/partials/_mentions.scss b/web/sass-files/sass/partials/_mentions.scss index a86cb8a73..83cdde53b 100644 --- a/web/sass-files/sass/partials/_mentions.scss +++ b/web/sass-files/sass/partials/_mentions.scss @@ -66,8 +66,4 @@ .mention-highlight { background-color:#fff2bb; color: #333; -} - -.mention-link { - color:$primary-color; }
\ No newline at end of file diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index e362e8f7a..7532875d6 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -229,12 +229,13 @@ body.ios { right: 0; position: absolute; top: 1px; - color: #999; + color: #444; + @include opacity(0.5); @include single-transition(all, 0.15s); font-size: 16px; padding: 7px 9px 6px; &:hover, &:active { - color: #444; + @include opacity(0.9); box-shadow: none; } } @@ -256,7 +257,7 @@ body.ios { line-height: 18px; display: inline-block; font-size: 13px; - color: #777; + @include opacity(0.6); } } } diff --git a/web/sass-files/sass/partials/_videos.scss b/web/sass-files/sass/partials/_videos.scss index de18aa08a..9e1ce29b7 100644 --- a/web/sass-files/sass/partials/_videos.scss +++ b/web/sass-files/sass/partials/_videos.scss @@ -1,6 +1,7 @@ .video-div { position:relative; max-width: 480px; + margin-bottom: 8px; .video-thumbnail { max-width: 100%; height: auto; @@ -19,18 +20,15 @@ } .video-type { - color:grey; + @include opacity(0.8); font-size:15px; - font-weight:200; margin:0px; padding:0px; } .video-uploader { - font-size:15px; - margin-top:3px; - margin-bottom:0px; - padding:0px; + font-size: 13px; + margin: 0 0 15px; } .video-title { diff --git a/web/static/images/themes/dark.png b/web/static/images/themes/mattermost dark.png Binary files differindex 832f64d2e..832f64d2e 100644 --- a/web/static/images/themes/dark.png +++ b/web/static/images/themes/mattermost dark.png diff --git a/web/static/images/themes/windows 10 dark.png b/web/static/images/themes/windows 10 dark.png Binary files differnew file mode 100644 index 000000000..d65304820 --- /dev/null +++ b/web/static/images/themes/windows 10 dark.png diff --git a/web/web.go b/web/web.go index 1e435d47f..da7eff13d 100644 --- a/web/web.go +++ b/web/web.go @@ -145,8 +145,14 @@ func root(c *api.Context, w http.ResponseWriter, r *http.Request) { return } - page := NewHtmlTemplatePage("signup_team", "Signup") - page.Render(c, w) + if len(c.Session.UserId) == 0 { + page := NewHtmlTemplatePage("signup_team", "Signup") + page.Render(c, w) + } else { + page := NewHtmlTemplatePage("home", "Home") + page.Props["TeamURL"] = c.GetTeamURL() + page.Render(c, w) + } } func signup(c *api.Context, w http.ResponseWriter, r *http.Request) { |