diff options
21 files changed, 145 insertions, 57 deletions
diff --git a/api/channel.go b/api/channel.go index 8264b3e74..88db27def 100644 --- a/api/channel.go +++ b/api/channel.go @@ -655,6 +655,10 @@ func addChannelMember(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("name=" + channel.Name + " user_id=" + userId) + message := model.NewMessage(c.Session.TeamId, "", userId, model.ACTION_USER_ADDED) + + store.PublishAndForget(message) + <-Srv.Store.Channel().UpdateLastViewedAt(id, oUser.Id) w.Write([]byte(cm.ToJson())) } diff --git a/model/message.go b/model/message.go index 47f598af8..52ee69e8f 100644 --- a/model/message.go +++ b/model/message.go @@ -15,6 +15,7 @@ const ( ACTION_POST_DELETED = "post_deleted" ACTION_VIEWED = "viewed" ACTION_NEW_USER = "new_user" + ACTION_USER_ADDED = "user_added" ) type Message struct { diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx index 1b389aa1d..b7cb248db 100644 --- a/web/react/components/channel_loader.jsx +++ b/web/react/components/channel_loader.jsx @@ -12,8 +12,6 @@ var Constants = require('../utils/constants.jsx'); module.exports = React.createClass({ componentDidMount: function() { - // Initalize stores - BrowserStore.initalize(); /* Start initial aysnc loads */ AsyncClient.getMe(); diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx index 9ff67ae1b..94be2acd6 100644 --- a/web/react/components/invite_member_modal.jsx +++ b/web/react/components/invite_member_modal.jsx @@ -162,25 +162,31 @@ module.exports = React.createClass({ invite_sections[index] = ( <div key={"key" + index}> <div> - <button type="button" className="btn remove__member" onClick={this.removeInviteFields.bind(this, index)}>×</button> + <button type="button" className="btn btn-link remove__member" onClick={this.removeInviteFields.bind(this, index)}><span className="fa fa-trash"></span></button> </div> <div className={ email_error ? "form-group invite has-error" : "form-group invite" }> <input onKeyUp={this.displayNameKeyUp} type="text" ref={"email"+index} className="form-control" placeholder="email@domain.com" maxLength="64" /> { email_error } </div> + <div className="row--invite"> { config.AllowInviteNames ? - <div className={ first_name_error ? "form-group invite has-error" : "form-group invite" }> - <input type="text" className="form-control" ref={"first_name"+index} placeholder="First name" maxLength="64" /> - { first_name_error } + <div className="col-sm-6"> + <div className={ first_name_error ? "form-group has-error" : "form-group" }> + <input type="text" className="form-control" ref={"first_name"+index} placeholder="First name" maxLength="64" /> + { first_name_error } + </div> </div> : "" } { config.AllowInviteNames ? - <div className={ last_name_error ? "form-group invite has-error" : "form-group invite" }> - <input type="text" className="form-control" ref={"last_name"+index} placeholder="Last name" maxLength="64" /> - { last_name_error } + <div className="col-sm-6"> + <div className={ last_name_error ? "form-group has-error" : "form-group" }> + <input type="text" className="form-control" ref={"last_name"+index} placeholder="Last name" maxLength="64" /> + { last_name_error } + </div> </div> : "" } </div> + </div> ); } @@ -203,7 +209,7 @@ module.exports = React.createClass({ <button type="button" className="btn btn-default" onClick={this.addInviteFields}>Add another</button> <br/> <br/> - <label className='control-label'>People invited automatically join Town Square channel.</label> + <span>People invited automatically join Town Square channel.</span> </div> <div className="modal-footer"> <button type="button" className="btn btn-default" data-dismiss="modal">Close</button> diff --git a/web/react/components/mention_list.jsx b/web/react/components/mention_list.jsx index 3fac41073..ba2c53612 100644 --- a/web/react/components/mention_list.jsx +++ b/web/react/components/mention_list.jsx @@ -129,7 +129,7 @@ module.exports = React.createClass({ if (numMentions < 1) return (<div/>); - var height = (numMentions*37) + 2; + var height = (numMentions*36) + 4; var width = $('#'+this.props.id).parent().width(); var bottom = $(window).height() - $('#'+this.props.id).offset().top; var left = $('#'+this.props.id).offset().left; @@ -137,7 +137,7 @@ module.exports = React.createClass({ return ( <div className="mentions--top" style={{height: height, width: width, bottom: bottom, left: left}}> - <div ref="mentionlist" className="mentions-box" style={{maxHeight: max_height, height: height, width: width}}> + <div ref="mentionlist" className="mentions-box" style={{height: height, width: width}}> { mentions } </div> </div> diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index d6dc9ce30..177e4a1db 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -78,6 +78,7 @@ module.exports = React.createClass({ PostStore.addChangeListener(this._onChange); ChannelStore.addChangeListener(this._onChange); + UserStore.addStatusesChangeListener(this._onChange); SocketStore.addChangeListener(this._onSocketChange); $(".post-list-holder-by-time").perfectScrollbar(); @@ -157,6 +158,7 @@ module.exports = React.createClass({ componentWillUnmount: function() { PostStore.removeChangeListener(this._onChange); ChannelStore.removeChangeListener(this._onChange); + UserStore.removeStatusesChangeListener(this._onChange); SocketStore.removeChangeListener(this._onSocketChange); $('body').off('click.userpopover'); }, @@ -193,6 +195,9 @@ module.exports = React.createClass({ this.scrolledToNew = false; } this.setState(newState); + } else { + // Updates the timestamp on each post + this.forceUpdate() } }, _onSocketChange: function(msg) { diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 0e4d38fe0..2095978e8 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -263,6 +263,10 @@ var SidebarLoggedIn = React.createClass({ if (ChannelStore.getCurrentId() != msg.channel_id) { AsyncClient.getChannels(true); } + } else if (msg.action == "user_added") { + if (UserStore.getCurrentId() === msg.user_id) { + AsyncClient.getChannels(true); + } } }, updateTitle: function() { diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx index 8334b345b..60c8ffae6 100644 --- a/web/react/components/sidebar_right.jsx +++ b/web/react/components/sidebar_right.jsx @@ -16,10 +16,16 @@ module.exports = React.createClass({ componentDidMount: function() { PostStore.addSearchChangeListener(this._onSearchChange); PostStore.addSelectedPostChangeListener(this._onSelectedChange); + UserStore.addStatusesChangeListener(this._onChange); }, componentWillUnmount: function() { PostStore.removeSearchChangeListener(this._onSearchChange); PostStore.removeSelectedPostChangeListener(this._onSelectedChange); + UserStore.removeStatusesChangeListener(this._onChange); + }, + _onChange: function() { + // Updates the timestamp on each post + this.forceUpdate(); }, _onSelectedChange: function(from_search) { if (this.isMounted()) { diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx index 124e617bd..fb96cc99f 100644 --- a/web/react/components/signup_user_complete.jsx +++ b/web/react/components/signup_user_complete.jsx @@ -130,7 +130,7 @@ module.exports = React.createClass({ </div> { email } <label className="control-label">Password</label> - <div className={ name_error ? "form-group has-error" : "form-group" }> + <div className={ password_error ? "form-group has-error" : "form-group" }> <input type="password" ref="password" className="form-control" placeholder="" maxLength="128" /> { password_error } </div> diff --git a/web/react/stores/browser_store.jsx b/web/react/stores/browser_store.jsx index b3e54cad5..82cf9a942 100644 --- a/web/react/stores/browser_store.jsx +++ b/web/react/stores/browser_store.jsx @@ -1,44 +1,52 @@ // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // See License.txt for license information. - var UserStore = require('../stores/user_store.jsx'); // Also change model/utils.go ETAG_ROOT_VERSION var BROWSER_STORE_VERSION = '.1'; -module.exports.initalize = function() { +var _initialized = false; + +function _initialize() { var currentVersion = localStorage.getItem("local_storage_version"); if (currentVersion !== BROWSER_STORE_VERSION) { localStorage.clear(); sessionStorage.clear(); localStorage.setItem("local_storage_version", BROWSER_STORE_VERSION); } + _initialized = true; } module.exports.setItem = function(name, value) { + if (!_initialized) _initialize(); var user_id = UserStore.getCurrentId(); localStorage.setItem(user_id + "_" + name, value); }; module.exports.getItem = function(name) { + if (!_initialized) _initialize(); var user_id = UserStore.getCurrentId(); return localStorage.getItem(user_id + "_" + name); }; module.exports.removeItem = function(name) { + if (!_initialized) _initialize(); var user_id = UserStore.getCurrentId(); localStorage.removeItem(user_id + "_" + name); }; module.exports.setGlobalItem = function(name, value) { + if (!_initialized) _initialize(); localStorage.setItem(name, value); }; module.exports.getGlobalItem = function(name) { + if (!_initialized) _initialize(); return localStorage.getItem(name); }; module.exports.removeGlobalItem = function(name) { + if (!_initialized) _initialize(); localStorage.removeItem(name); }; @@ -53,7 +61,7 @@ module.exports.actionOnItemsWithPrefix = function (prefix, action) { var user_id = UserStore.getCurrentId(); var id_len = user_id.length; var prefix_len = prefix.length; - for (key in localStorage) { + for (var key in localStorage) { if (key.substring(id_len, id_len + prefix_len) === prefix) { var userkey = key.substring(id_len); action(userkey, BrowserStore.getItem(key)); @@ -70,8 +78,8 @@ module.exports.isLocalStorageSupported = function() { localStorage.removeItem("testLocal", '1'); return true; - } + } catch (e) { return false; } -} +}; diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx index 6db2db2b1..3ae7e1e5c 100644 --- a/web/react/utils/async_client.jsx +++ b/web/react/utils/async_client.jsx @@ -55,7 +55,7 @@ module.exports.getChannels = function(force, updateLastViewed, checkVersion) { if (checkVersion) { var serverVersion = xhr.getResponseHeader("X-Version-ID"); - if (UserStore.getLastVersion() == undefined) { + if (!UserStore.getLastVersion()) { UserStore.setLastVersion(serverVersion); } diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss index cf28e44e8..fd6225bdd 100644 --- a/web/sass-files/sass/partials/_base.scss +++ b/web/sass-files/sass/partials/_base.scss @@ -154,9 +154,4 @@ div.theme { text-decoration: none; padding: 0 10px; } -} - -.invite { - width: 90%; -} - +}
\ No newline at end of file diff --git a/web/sass-files/sass/partials/_mentions.scss b/web/sass-files/sass/partials/_mentions.scss index d6e2ab368..da46866c8 100644 --- a/web/sass-files/sass/partials/_mentions.scss +++ b/web/sass-files/sass/partials/_mentions.scss @@ -11,12 +11,13 @@ position: absolute; z-index: 1060; .mentions-box { + max-height: 303px; position:absolute; background-color:#fff; border: $border-gray; - overflow-x: hidden; - overflow-y: scroll; - bottom:0; + overflow-x: hidden; + overflow-y: scroll; + bottom:0; } } @@ -24,13 +25,15 @@ position:relative; width:100%; background-color:#fff; - height:37px; + height:36px; padding:2px; z-index:101; - cursor: pointer; - &:hover { - background-color:#e8eaed; - } + line-height: 36px; + font-size: 13px; + cursor: pointer; + &:hover { + background-color: #E6F2FA; + } } .mentions-text { @@ -38,15 +41,20 @@ } .mention-img { - margin-right:10px; - height:32px; - width:32px; - border-radius: 10%; + margin-right: 6px; + height: 32px; + width: 32px; + line-height: 36px; + display: block; + font-size: 20px; + text-align: center; + color: #555; + @include border-radius(3px); } .mention-fullname { - color: grey; - padding-left: 10px; + color: grey; + padding-left: 10px; } .mention-highlight { @@ -55,9 +63,4 @@ .mention-link { color:$primary-color; -} - -.mention-align { - position:relative; - top:5px; -} +}
\ No newline at end of file diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss index 637f908ca..707e71cf0 100644 --- a/web/sass-files/sass/partials/_modal.scss +++ b/web/sass-files/sass/partials/_modal.scss @@ -15,6 +15,10 @@ } .remove__member { float: right; + color: #E56565; + font-size: 20px; + line-height: 0; + padding: 6px; } .modal-dialog { max-width: 95%; @@ -268,3 +272,19 @@ } } } + +// Invite New Member +.invite { + margin-right: 40px; +} + +.row--invite { + margin-right: 40px; + @include clearfix; + .col-sm-6 { + padding: 0 0 0 15px; + &:first-child { + padding-left: 0; + } + } +}
\ No newline at end of file diff --git a/web/sass-files/sass/partials/_perfect-scrollbar.scss b/web/sass-files/sass/partials/_perfect-scrollbar.scss index f253d0792..f38c6062f 100755 --- a/web/sass-files/sass/partials/_perfect-scrollbar.scss +++ b/web/sass-files/sass/partials/_perfect-scrollbar.scss @@ -3,7 +3,7 @@ .ps-container.ps-active-x > .ps-scrollbar-x-rail, .ps-container.ps-active-y > .ps-scrollbar-y-rail { display: block; } .ps-container.ps-in-scrolling { - pointer-events: none; } + } .ps-container.ps-in-scrolling.ps-x > .ps-scrollbar-x-rail { background-color: #eee; opacity: 0.9; @@ -85,7 +85,7 @@ /* there must be 'right' for ps-scrollbar-y */ width: 8px; } .ps-container:hover.ps-in-scrolling { - pointer-events: none; } + } .ps-container:hover.ps-in-scrolling.ps-x > .ps-scrollbar-x-rail { background-color: #eee; opacity: 0.9; diff --git a/web/sass-files/sass/partials/_post_right.scss b/web/sass-files/sass/partials/_post_right.scss index 0d3e75689..8816393c8 100644 --- a/web/sass-files/sass/partials/_post_right.scss +++ b/web/sass-files/sass/partials/_post_right.scss @@ -21,10 +21,21 @@ } .post-create__container { - margin-top: 1em; + margin-top: 10px; + .textarea-wrapper { + min-height: 100px; + } .custom-textarea { min-height: 100px; } + .msg-typing { + color: #555; + float: left; + padding-top: 17px; + } + .post-create-footer { + padding-top: 10px; + } } } diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss index 0eeaa3615..9c0c09ee3 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -276,6 +276,11 @@ } } } + .row--invite { + .col-sm-6 { + padding: 0; + } + } .settings-modal { &.display--content { .modal-header { diff --git a/web/sass-files/sass/partials/_sidebar--right.scss b/web/sass-files/sass/partials/_sidebar--right.scss index 7a91caec9..a0e82fd2f 100644 --- a/web/sass-files/sass/partials/_sidebar--right.scss +++ b/web/sass-files/sass/partials/_sidebar--right.scss @@ -27,12 +27,6 @@ margin: 3px 0 0; } } - .post-create__container { - margin-top: 10px; - .post-create-footer { - padding-top: 10px; - } - } .sidebar__overlay { width: 100%; height: 100%; diff --git a/web/sass-files/sass/partials/_signup.scss b/web/sass-files/sass/partials/_signup.scss index 8917ebb9f..a714aa44f 100644 --- a/web/sass-files/sass/partials/_signup.scss +++ b/web/sass-files/sass/partials/_signup.scss @@ -20,7 +20,7 @@ font-size: em(28px); } h3 { - font-weight: 600; + font-weight: 600; margin: 0 0 1.3em 0; font-size: 1.4em; } @@ -91,6 +91,8 @@ .has-error { .control-label { margin-top: 5px; + font-size: 14px; + font-weight: 600; } } .reset-form { @@ -114,6 +116,6 @@ } .signup-team-login { - padding-bottom: 10px; - font-weight: 700; + padding-bottom: 10px; + font-weight: 700; }
\ No newline at end of file diff --git a/web/static/config/manifest.json b/web/static/config/manifest.json new file mode 100644 index 000000000..6110122c2 --- /dev/null +++ b/web/static/config/manifest.json @@ -0,0 +1,13 @@ +{ + "name": "Mattermost", + "icons": [ + { + "src": "../static/iamges/icon50x50.gif", + "sizes": "50x50", + "type": "image/png" + } + ], + "start_url": "/", + "display": "standalone", + "orientation": "portrait" +}
\ No newline at end of file diff --git a/web/templates/head.html b/web/templates/head.html index abcb30700..ead648571 100644 --- a/web/templates/head.html +++ b/web/templates/head.html @@ -5,6 +5,19 @@ <title>{{ .Title }}</title> + <!-- iOS add to homescreen --> + <meta name="apple-mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> + <meta name="mobile-web-app-capable" content="yes" /> + <meta name="apple-mobile-web-app-title" content="{{ .Title }}"> + <meta name="application-name" content="{{ .Title }}"> + <meta name="format-detection" content="telephone=no"> + <!-- iOS add to homescreen --> + + <!-- Android add to homescreen --> + <link rel="manifest" href="/static/config/manifest.json"> + <!-- Android add to homescreen --> + <link rel="stylesheet" href="/static/css/bootstrap-3.3.1.min.css"> <link rel="stylesheet" href="/static/css/jasny-bootstrap.min.css" rel="stylesheet"> |