summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md5
-rw-r--r--api/post.go14
-rw-r--r--api/team.go2
-rw-r--r--api/user.go2
-rw-r--r--config/config.json3
-rw-r--r--config/config_docker.json3
-rw-r--r--store/sql_post_store.go5
-rw-r--r--store/sql_store.go12
-rw-r--r--utils/config.go1
-rw-r--r--utils/mail.go13
-rw-r--r--web/react/components/channel_header.jsx106
-rw-r--r--web/react/components/edit_channel_modal.jsx2
-rw-r--r--web/react/components/get_link_modal.jsx2
-rw-r--r--web/react/components/loading_screen.jsx24
-rw-r--r--web/react/components/member_list_item.jsx2
-rw-r--r--web/react/components/member_list_team.jsx2
-rw-r--r--web/react/components/mention.jsx10
-rw-r--r--web/react/components/mention_list.jsx100
-rw-r--r--web/react/components/more_channels.jsx15
-rw-r--r--web/react/components/new_channel.jsx2
-rw-r--r--web/react/components/post.jsx13
-rw-r--r--web/react/components/post_body.jsx17
-rw-r--r--web/react/components/post_list.jsx105
-rw-r--r--web/react/components/post_right.jsx2
-rw-r--r--web/react/components/search_bar.jsx37
-rw-r--r--web/react/components/search_results.jsx73
-rw-r--r--web/react/components/sidebar.jsx18
-rw-r--r--web/react/components/sidebar_header.jsx11
-rw-r--r--web/react/components/signup_user_complete.jsx33
-rw-r--r--web/react/components/user_profile.jsx4
-rw-r--r--web/react/components/user_settings.jsx14
-rw-r--r--web/react/utils/utils.jsx39
-rw-r--r--web/sass-files/sass/partials/_base.scss10
-rw-r--r--web/sass-files/sass/partials/_files.scss3
-rw-r--r--web/sass-files/sass/partials/_headers.scss53
-rw-r--r--web/sass-files/sass/partials/_loading.scss50
-rw-r--r--web/sass-files/sass/partials/_mentions.scss4
-rw-r--r--web/sass-files/sass/partials/_modal.scss1
-rw-r--r--web/sass-files/sass/partials/_popover.scss9
-rw-r--r--web/sass-files/sass/partials/_post.scss3
-rw-r--r--web/sass-files/sass/partials/_responsive.scss12
-rw-r--r--web/sass-files/sass/partials/_settings.scss4
-rw-r--r--web/sass-files/sass/partials/_sidebar--right.scss8
-rw-r--r--web/sass-files/sass/partials/_variables.scss8
-rw-r--r--web/sass-files/sass/styles.scss3
-rw-r--r--web/static/images/dropdown-icon.pngbin2877 -> 0 bytes
46 files changed, 506 insertions, 353 deletions
diff --git a/README.md b/README.md
index 04cbf718d..a4f95dbdf 100644
--- a/README.md
+++ b/README.md
@@ -137,8 +137,5 @@ To contribute to this open source project please review the Mattermost Contribut
License
-------
-Most Mattermost source files are made available under the terms of the GNU Affero General Public License (AGPL). See individual files for details.
-
-As an exception, Admin Tools and Configuration Files are are made available under the terms of the Apache License, version 2.0. See LICENSE.txt for more information.
-
+Mattermost is licensed under an "Apache-wrapped AGPL" model, which means you can run and link to the system using Configuration Files and Admin Tools licensed under Apache, version 2.0, as described in the LICENSE file, as an explicit exception to the terms of the GNU Affero General Public License (AGPL) that applies to most of the remaining source files. See individual files for details.
diff --git a/api/post.go b/api/post.go
index 650f47062..02f997166 100644
--- a/api/post.go
+++ b/api/post.go
@@ -14,6 +14,7 @@ import (
"strconv"
"strings"
"time"
+ "path/filepath"
)
func InitPost(r *mux.Router) {
@@ -437,6 +438,19 @@ func fireAndForgetNotifications(post *model.Post, teamId, teamUrl string) {
message := model.NewMessage(teamId, post.ChannelId, post.UserId, model.ACTION_POSTED)
message.Add("post", post.ToJson())
+
+ if len(post.Filenames) != 0 {
+ message.Add("otherFile", "true")
+
+ for _, filename := range post.Filenames {
+ ext := filepath.Ext(filename)
+ if model.IsFileExtImage(ext) {
+ message.Add("image", "true")
+ break
+ }
+ }
+ }
+
if len(mentionedUsers) != 0 {
message.Add("mentions", model.ArrayToJson(mentionedUsers))
}
diff --git a/api/team.go b/api/team.go
index c4a0ca181..f9aeecd7e 100644
--- a/api/team.go
+++ b/api/team.go
@@ -68,7 +68,7 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if utils.Cfg.ServiceSettings.Mode == utils.MODE_DEV {
+ if utils.Cfg.ServiceSettings.Mode == utils.MODE_DEV || utils.Cfg.EmailSettings.ByPassEmail {
m["follow_link"] = bodyPage.Props["Link"]
}
diff --git a/api/user.go b/api/user.go
index 5b052e826..0c63868b3 100644
--- a/api/user.go
+++ b/api/user.go
@@ -293,7 +293,7 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if !user.EmailVerified {
+ if !user.EmailVerified && !utils.Cfg.EmailSettings.ByPassEmail {
c.Err = model.NewAppError("login", "Login failed because email address has not been verified", extraInfo)
c.Err.StatusCode = http.StatusForbidden
return
diff --git a/config/config.json b/config/config.json
index b0a019e8d..d5bb7e553 100644
--- a/config/config.json
+++ b/config/config.json
@@ -54,11 +54,12 @@
"ProfileHeight": 128
},
"EmailSettings": {
+ "ByPassEmail" : true,
"SMTPUsername": "",
"SMTPPassword": "",
"SMTPServer": "",
"UseTLS": false,
- "FeedbackEmail": "feedback@xxxxxxmustbefilledin.com",
+ "FeedbackEmail": "",
"FeedbackName": "",
"ApplePushServer": "",
"ApplePushCertPublic": "",
diff --git a/config/config_docker.json b/config/config_docker.json
index 85f0d9c73..91ed0ef10 100644
--- a/config/config_docker.json
+++ b/config/config_docker.json
@@ -54,9 +54,10 @@
"ProfileHeight": 128
},
"EmailSettings": {
+ "ByPassEmail" : true,
"SMTPUsername": "",
"SMTPPassword": "",
- "SMTPServer": "localhost:25",
+ "SMTPServer": "",
"UseTLS": false,
"FeedbackEmail": "",
"FeedbackName": "",
diff --git a/store/sql_post_store.go b/store/sql_post_store.go
index 7ada515d7..5b9ebfdf2 100644
--- a/store/sql_post_store.go
+++ b/store/sql_post_store.go
@@ -35,6 +35,11 @@ func NewSqlPostStore(sqlStore *SqlStore) PostStore {
}
func (s SqlPostStore) UpgradeSchemaIfNeeded() {
+
+ // These execs are for upgrading currently created databases to full utf8mb4 compliance
+ // Will be removed as seen fit for upgrading
+ s.GetMaster().Exec("ALTER TABLE Posts charset=utf8mb4")
+ s.GetMaster().Exec("ALTER TABLE Posts MODIFY COLUMN Message varchar(4000) CHARACTER SET utf8mb4")
}
func (s SqlPostStore) CreateIndexesIfNotExists() {
diff --git a/store/sql_store.go b/store/sql_store.go
index a0a1a9f23..7a2d059b9 100644
--- a/store/sql_store.go
+++ b/store/sql_store.go
@@ -24,6 +24,7 @@ import (
sqltrace "log"
"math/rand"
"os"
+ "strings"
"time"
)
@@ -81,7 +82,14 @@ func NewSqlStore() Store {
func setupConnection(con_type string, driver string, dataSource string, maxIdle int, maxOpen int, trace bool) *gorp.DbMap {
- db, err := dbsql.Open(driver, dataSource)
+ charset := ""
+ if strings.Index(dataSource, "?") > -1 {
+ charset = "&charset=utf8mb4,utf8"
+ } else {
+ charset = "?charset=utf8mb4,utf8"
+ }
+
+ db, err := dbsql.Open(driver, dataSource+charset)
if err != nil {
l4g.Critical("Failed to open sql connection to '%v' err:%v", dataSource, err)
time.Sleep(time.Second)
@@ -104,7 +112,7 @@ func setupConnection(con_type string, driver string, dataSource string, maxIdle
if driver == "sqlite3" {
dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.SqliteDialect{}}
} else if driver == "mysql" {
- dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8"}}
+ dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.MySQLDialect{Engine: "InnoDB", Encoding: "UTF8MB4"}}
} else {
l4g.Critical("Failed to create dialect specific driver")
time.Sleep(time.Second)
diff --git a/utils/config.go b/utils/config.go
index eb2ae3050..97cb99f3e 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -77,6 +77,7 @@ type ImageSettings struct {
}
type EmailSettings struct {
+ ByPassEmail bool
SMTPUsername string
SMTPPassword string
SMTPServer string
diff --git a/utils/mail.go b/utils/mail.go
index 3cd37ffef..0fe7042b7 100644
--- a/utils/mail.go
+++ b/utils/mail.go
@@ -8,14 +8,14 @@ import (
"crypto/tls"
"fmt"
"github.com/mattermost/platform/model"
+ "html"
"net"
"net/mail"
"net/smtp"
- "html"
)
func CheckMailSettings() *model.AppError {
- if len(Cfg.EmailSettings.SMTPServer) == 0 {
+ if len(Cfg.EmailSettings.SMTPServer) == 0 || Cfg.EmailSettings.ByPassEmail {
return model.NewAppError("CheckMailSettings", "No email settings present, mail will not be sent", "")
}
conn, err := connectToSMTPServer()
@@ -79,6 +79,10 @@ func newSMTPClient(conn net.Conn) (*smtp.Client, *model.AppError) {
func SendMail(to, subject, body string) *model.AppError {
+ if len(Cfg.EmailSettings.SMTPServer) == 0 || Cfg.EmailSettings.ByPassEmail {
+ return nil
+ }
+
fromMail := mail.Address{"", Cfg.EmailSettings.FeedbackEmail}
toMail := mail.Address{"", to}
@@ -95,11 +99,6 @@ func SendMail(to, subject, body string) *model.AppError {
}
message += "\r\n<html><body>" + body + "</body></html>"
- if len(Cfg.EmailSettings.SMTPServer) == 0 {
- l4g.Warn("Skipping sending of email because EmailSettings are not configured")
- return nil
- }
-
conn, err1 := connectToSMTPServer()
if err1 != nil {
return err1
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index 68de80228..836454b46 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.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 PostStore = require('../stores/post_store.jsx');
@@ -16,7 +17,7 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-var ExtraMembers = React.createClass({
+var PopoverListMembers = React.createClass({
componentDidMount: function() {
var originalLeave = $.fn.popover.Constructor.prototype.leave;
$.fn.popover.Constructor.prototype.leave = function(obj) {
@@ -35,30 +36,29 @@ var ExtraMembers = React.createClass({
$("#member_popover").popover({placement : 'bottom', trigger: 'click', html: true});
$('body').on('click', function (e) {
- if ($(e.target.parentNode.parentNode)[0] !== $("#member_popover")[0] && $(e.target).parents('.popover.in').length === 0) {
+ if ($(e.target.parentNode.parentNode)[0] !== $("#member_popover")[0] && $(e.target).parents('.popover.in').length === 0) {
$("#member_popover").popover('hide');
}
});
-
},
+
render: function() {
- var count = this.props.members.length == 0 ? "-" : this.props.members.length;
- count = this.props.members.length > 19 ? "20+" : count;
- var data_content = "";
- var sortedMembers = this.props.members;
+ var popoverHtml = '';
+ var members = this.props.members;
+ var count = (members.length > 20) ? "20+" : (members.length || '-');
- if(sortedMembers) {
- sortedMembers.sort(function(a,b) {
+ if (members) {
+ members.sort(function(a,b) {
return a.username.localeCompare(b.username);
- })
+ });
- sortedMembers.forEach(function(m) {
- data_content += "<div style='white-space: nowrap'>" + m.username + "</div>";
+ members.forEach(function(m) {
+ popoverHtml += "<div class='text--nowrap'>" + m.username + "</div>";
});
}
return (
- <div style={{"cursor" : "pointer"}} id="member_popover" data-toggle="popover" data-content={data_content} data-original-title="Members" >
+ <div id="member_popover" data-toggle="popover" data-content={popoverHtml} data-original-title="Members" >
<div id="member_tooltip" data-toggle="tooltip" title="View Channel Members">
{count} <span className="glyphicon glyphicon-user" aria-hidden="true"></span>
</div>
@@ -78,6 +78,7 @@ function getStateFromStores() {
}
module.exports = React.createClass({
+ displayName: 'ChannelHeader',
componentDidMount: function() {
ChannelStore.addChangeListener(this._onChange);
ChannelStore.addExtraInfoChangeListener(this._onChange);
@@ -99,7 +100,7 @@ module.exports = React.createClass({
$(".channel-header__info .description").popover({placement : 'bottom', trigger: 'hover', html: true, delay: {show: 500, hide: 500}});
},
_onSocketChange: function(msg) {
- if(msg.action === "new_user") {
+ if (msg.action === "new_user") {
AsyncClient.getChannelExtraInfo(true);
}
},
@@ -107,15 +108,14 @@ module.exports = React.createClass({
return getStateFromStores();
},
handleLeave: function(e) {
- var self = this;
Client.leaveChannel(this.state.channel.id,
function(data) {
var townsquare = ChannelStore.getByName('town-square');
utils.switchChannel(townsquare);
- }.bind(this),
+ },
function(err) {
AsyncClient.dispatchError(err, "handleLeave");
- }.bind(this)
+ }
);
},
searchMentions: function(e) {
@@ -131,52 +131,29 @@ module.exports = React.createClass({
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_SEARCH_TERM,
term: terms,
- do_search: false
+ do_search: true,
+ is_mention_search: true
});
-
- Client.search(
- terms,
- function(data) {
- AppDispatcher.handleServerAction({
- type: ActionTypes.RECIEVED_SEARCH,
- results: data,
- is_mention_search: true
- });
- },
- function(err) {
- dispatchError(err, "search");
- }
- );
},
+
render: function() {
if (this.state.channel == null) {
- return (
- <div></div>
- );
+ return null;
}
var description = utils.textToJsx(this.state.channel.description, {"singleline": true, "noMentionHighlight": true});
var popoverContent = React.renderToString(<MessageWrapper message={this.state.channel.description}/>);
- var channelTitle = "";
+ var channelTitle = this.state.channel.display_name;
var channelName = this.state.channel.name;
var currentId = UserStore.getCurrentId();
var isAdmin = this.state.memberChannel.roles.indexOf("admin") > -1 || this.state.memberTeam.roles.indexOf("admin") > -1;
- var searchForm = <th className="search-bar__container"><NavbarSearchBox /></th>;
- var isDirect = false;
-
- if (this.state.channel.type === 'O') {
- channelTitle = this.state.channel.display_name;
- } else if (this.state.channel.type === 'P') {
- channelTitle = this.state.channel.display_name;
- } else if (this.state.channel.type === 'D') {
- isDirect = true;
+ var isDirect = (this.state.channel.type === 'D');
+
+ if (isDirect) {
if (this.state.users.length > 1) {
- if (this.state.users[0].id === UserStore.getCurrentId()) {
- channelTitle = <UserProfile userId={this.state.users[1].id} overwriteName={this.state.users[1].full_name ? this.state.users[1].full_name : this.state.users[1].username} />;
- } else {
- channelTitle = <UserProfile userId={this.state.users[0].id} overwriteName={this.state.users[0].full_name ? this.state.users[0].full_name : this.state.users[0].username} />;
- }
+ var contact = this.state.users[((this.state.users[0].id === currentId) ? 1 : 0)];
+ channelTitle = <UserProfile userId={contact.id} overwriteName={contact.full_name || contact.username} />;
}
}
@@ -196,21 +173,21 @@ module.exports = React.createClass({
<li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_invite" href="#">Add Members</a></li>
{ isAdmin ?
<li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_members" href="#">Manage Members</a></li>
- : ""
+ : null
}
- <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#edit_channel" data-desc={this.state.channel.description} data-title={this.state.channel.display_name} data-channelid={this.state.channel.id}>Set Channel Description...</a></li>
- <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#channel_notifications" data-title={this.state.channel.display_name} data-channelid={this.state.channel.id}>Notification Preferences</a></li>
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#edit_channel" data-desc={this.state.channel.description} data-title={channelTitle} data-channelid={this.state.channel.id}>Set Channel Description...</a></li>
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#channel_notifications" data-title={channelTitle} data-channelid={this.state.channel.id}>Notification Preferences</a></li>
{ isAdmin && channelName != Constants.DEFAULT_CHANNEL ?
- <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#rename_channel" data-display={this.state.channel.display_name} data-name={this.state.channel.name} data-channelid={this.state.channel.id}>Rename Channel...</a></li>
- : ""
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#rename_channel" data-display={channelTitle} data-name={this.state.channel.name} data-channelid={this.state.channel.id}>Rename Channel...</a></li>
+ : null
}
{ isAdmin && channelName != Constants.DEFAULT_CHANNEL ?
- <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#delete_channel" data-title={this.state.channel.display_name} data-channelid={this.state.channel.id}>Delete Channel...</a></li>
- : ""
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#delete_channel" data-title={channelTitle} data-channelid={this.state.channel.id}>Delete Channel...</a></li>
+ : null
}
{ channelName != Constants.DEFAULT_CHANNEL ?
<li role="presentation"><a role="menuitem" href="#" onClick={this.handleLeave}>Leave Channel</a></li>
- : ""
+ : null
}
</ul>
</div>
@@ -220,14 +197,13 @@ module.exports = React.createClass({
<a href="#"><strong className="heading">{channelTitle}</strong></a>
}
</th>
- <th><ExtraMembers members={this.state.users} channelId={this.state.channel.id} /></th>
- { searchForm }
+ <th><PopoverListMembers members={this.state.users} channelId={this.state.channel.id} /></th>
+ <th className="search-bar__container"><NavbarSearchBox /></th>
<th>
- <div className="dropdown" style={{"marginLeft":"5px", "marginRight":"10px"}}>
+ <div className="dropdown channel-header__links">
<a href="#" className="dropdown-toggle theme" type="button" id="channel_header_right_dropdown" data-toggle="dropdown" aria-expanded="true">
- <i className="fa fa-caret-down"></i>
- </a>
- <ul className="dropdown-menu" role="menu" aria-labelledby="channel_header_right_dropdown" style={{"left": "-150px"}}>
+ <span dangerouslySetInnerHTML={{__html: " <svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>"}} /> </a>
+ <ul className="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="channel_header_right_dropdown">
<li role="presentation"><a role="menuitem" href="#" onClick={this.searchMentions}>Recent Mentions</a></li>
</ul>
</div>
@@ -237,5 +213,3 @@ module.exports = React.createClass({
);
}
});
-
-
diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx
index 255654fd5..c0818959a 100644
--- a/web/react/components/edit_channel_modal.jsx
+++ b/web/react/components/edit_channel_modal.jsx
@@ -43,7 +43,7 @@ module.exports = React.createClass({
<h4 className="modal-title" ref="title">Edit {this.state.title} Description</h4>
</div>
<div className="modal-body">
- <textarea className="form-control" style={{resize: "none"}} rows="6" ref="channelDesc" maxLength="1024" value={this.state.description} onChange={this.handleUserInput}></textarea>
+ <textarea className="form-control no-resize" rows="6" ref="channelDesc" maxLength="1024" value={this.state.description} onChange={this.handleUserInput}></textarea>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
diff --git a/web/react/components/get_link_modal.jsx b/web/react/components/get_link_modal.jsx
index bbfdce63a..af5314e64 100644
--- a/web/react/components/get_link_modal.jsx
+++ b/web/react/components/get_link_modal.jsx
@@ -38,7 +38,7 @@ module.exports = React.createClass({
<div className="modal-body">
<p>{"The link below is used for open " + strings.TeamPlural + " or if you allowed your " + strings.Team + " members to sign up using their " + strings.Company + " email addresses."}
</p>
- <textarea className="form-control" style={{resize: "none"}} readOnly="true" value={this.state.value}></textarea>
+ <textarea className="form-control no-resize" readOnly="true" value={this.state.value}></textarea>
</div>
<div className="modal-footer">
<button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
diff --git a/web/react/components/loading_screen.jsx b/web/react/components/loading_screen.jsx
new file mode 100644
index 000000000..5905e519b
--- /dev/null
+++ b/web/react/components/loading_screen.jsx
@@ -0,0 +1,24 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+module.exports = React.createClass({
+ displayName: "LoadingScreen",
+ propTypes: {
+ position: React.PropTypes.oneOf(['absolute', 'fixed', 'relative', 'static', 'inherit'])
+ },
+ getDefaultProps: function() {
+ return { position: 'relative' };
+ },
+ render: function() {
+ return (
+ <div className="loading-screen" style={{position: this.props.position}}>
+ <div className="loading__content">
+ <h3>Loading</h3>
+ <div className="round round-1"></div>
+ <div className="round round-2"></div>
+ <div className="round round-3"></div>
+ </div>
+ </div>
+ );
+ }
+});
diff --git a/web/react/components/member_list_item.jsx b/web/react/components/member_list_item.jsx
index cf8c71d7e..a5279909b 100644
--- a/web/react/components/member_list_item.jsx
+++ b/web/react/components/member_list_item.jsx
@@ -49,7 +49,7 @@ module.exports = React.createClass({
</div>
);
} else {
- invite = <div className="member-role text-capitalize" style={{marginRight: 15}}>{member.roles || 'Member'}</div>;
+ invite = <div className="member-role text-capitalize">{member.roles || 'Member'}<span className="caret hidden"></span></div>;
}
return (
diff --git a/web/react/components/member_list_team.jsx b/web/react/components/member_list_team.jsx
index aa53c5db6..89b5e49d5 100644
--- a/web/react/components/member_list_team.jsx
+++ b/web/react/components/member_list_team.jsx
@@ -59,7 +59,7 @@ var MemberListTeamItem = React.createClass({
return {};
},
render: function() {
- var server_error = this.state.server_error ? <div style={{ clear: "both" }} className="has-error"><label className='has-error control-label'>{this.state.server_error}</label></div> : null;
+ var server_error = this.state.server_error ? <div className="has-error"><label className='has-error control-label'>{this.state.server_error}</label></div> : null;
var user = this.props.user;
var currentRoles = "Member";
var timestamp = UserStore.getCurrentUser().update_at;
diff --git a/web/react/components/mention.jsx b/web/react/components/mention.jsx
index 520b81cbb..114dc183f 100644
--- a/web/react/components/mention.jsx
+++ b/web/react/components/mention.jsx
@@ -6,16 +6,22 @@ module.exports = React.createClass({
handleClick: function() {
this.props.handleClick(this.props.username);
},
+ getInitialState: function() {
+ return null;
+ },
render: function() {
+ var self = this;
var icon;
var timestamp = UserStore.getCurrentUser().update_at;
- if (this.props.id != null) {
+ if (this.props.id === "allmention" || this.props.id === "channelmention") {
+ icon = <span><i className="mention-img fa fa-users fa-2x"></i></span>;
+ } else if (this.props.id != null) {
icon = <span><img className="mention-img" src={"/api/v1/users/" + this.props.id + "/image?time=" + timestamp}/></span>;
} else {
icon = <span><i className="mention-img fa fa-users fa-2x"></i></span>;
}
return (
- <div className="mentions-name" onClick={this.handleClick}>
+ <div className={"mentions-name " + this.props.isFocused} id={this.props.id + "_mentions"} onClick={this.handleClick} onMouseEnter={this.props.handleMouseEnter}>
<div className="pull-left">{icon}</div>
<div className="pull-left mention-align"><span>@{this.props.username}</span><span className="mention-fullname">{this.props.secondary_text}</span></div>
</div>
diff --git a/web/react/components/mention_list.jsx b/web/react/components/mention_list.jsx
index 103ff29bb..c5ff82346 100644
--- a/web/react/components/mention_list.jsx
+++ b/web/react/components/mention_list.jsx
@@ -17,14 +17,37 @@ module.exports = React.createClass({
displayName: "MentionList",
componentDidMount: function() {
PostStore.addMentionDataChangeListener(this._onChange);
-
var self = this;
- $('body').on('keypress.mentionlist', '#'+this.props.id,
+
+ $('body').on('keydown.mentionlist', '#'+this.props.id,
function(e) {
- if (!self.isEmpty() && self.state.mentionText != '-1' && e.which === 13) {
+ if (!self.isEmpty() && self.state.mentionText != '-1' && (e.which === 13 || e.which === 9)) {
+ e.stopPropagation();
+ e.preventDefault();
+ self.addCurrentMention();
+ }
+ else if (!self.isEmpty() && self.state.mentionText != '-1' && (e.which === 38 || e.which === 40)) {
e.stopPropagation();
e.preventDefault();
- self.addFirstMention();
+
+ var tempSelectedMention = -1;
+ if (e.which === 38) {
+ if (self.getSelection(self.state.selectedMention - 1))
+ self.setState({ selectedMention: self.state.selectedMention - 1, selectedUsername: self.refs['mention' + (self.state.selectedMention - 1)].props.username });
+ else {
+ while (self.getSelection(++tempSelectedMention))
+ ; //Need to find the top of the list
+ self.setState({ selectedMention: tempSelectedMention - 1, selectedUsername: self.refs['mention' + (tempSelectedMention - 1)].props.username });
+ }
+ }
+ else if (e.which === 40) {
+ if (self.getSelection(self.state.selectedMention + 1))
+ self.setState({ selectedMention: self.state.selectedMention + 1, selectedUsername: self.refs['mention' + (self.state.selectedMention + 1)].props.username });
+ else
+ self.setState({ selectedMention: 0, selectedUsername: self.refs.mention0.props.username });
+ }
+
+ self.scrollToMention(e.which, tempSelectedMention);
}
}
);
@@ -37,7 +60,28 @@ module.exports = React.createClass({
},
componentWillUnmount: function() {
PostStore.removeMentionDataChangeListener(this._onChange);
- $('body').off('keypress.mentionlist', '#'+this.props.id);
+ $('body').off('keydown.mentionlist', '#'+this.props.id);
+ },
+ componentDidUpdate: function() {
+ if (this.state.mentionText != "-1") {
+ if (this.state.selectedUsername !== "" && (!this.getSelection(this.state.selectedMention) || this.state.selectedUsername !== this.refs['mention' + this.state.selectedMention].props.username)) {
+ var tempSelectedMention = -1;
+ var foundMatch = false;
+ while (tempSelectedMention < this.state.selectedMention && this.getSelection(++tempSelectedMention)) {
+ if (this.state.selectedUsername === this.refs['mention' + tempSelectedMention].props.username) {
+ this.setState({ selectedMention: tempSelectedMention });
+ foundMatch = true;
+ break;
+ }
+ }
+ if (this.getSelection(0) && !foundMatch) {
+ this.setState({ selectedMention: 0, selectedUsername: this.refs.mention0.props.username });
+ }
+ }
+ }
+ else if (this.state.selectedMention !== 0) {
+ this.setState({ selectedMention: 0, selectedUsername: "" });
+ }
},
_onChange: function(id, mentionText, excludeList) {
if (id !== this.props.id) return;
@@ -45,6 +89,7 @@ module.exports = React.createClass({
var newState = this.state;
if (mentionText != null) newState.mentionText = mentionText;
if (excludeList != null) newState.excludeUsers = excludeList;
+
this.setState(newState);
},
handleClick: function(name) {
@@ -56,6 +101,21 @@ module.exports = React.createClass({
this.setState({ mentionText: '-1' });
},
+ handleMouseEnter: function(listId) {
+ this.setState({ selectedMention: listId, selectedUsername: this.refs['mention' + listId].props.username });
+ },
+ getSelection: function(listId) {
+ if (!this.refs['mention' + listId])
+ return false;
+ else
+ return true;
+ },
+ addCurrentMention: function() {
+ if (!this.getSelection(this.state.selectedMention))
+ this.addFirstMention();
+ else
+ this.refs['mention' + this.state.selectedMention].handleClick();
+ },
addFirstMention: function() {
if (!this.refs.mention0) return;
this.refs.mention0.handleClick();
@@ -63,6 +123,23 @@ module.exports = React.createClass({
isEmpty: function() {
return (!this.refs.mention0);
},
+ scrollToMention: function(keyPressed, ifLoopUp) {
+ var direction = keyPressed === 38 ? "up" : "down";
+ var scrollAmount = 0;
+
+ if (direction === "up" && ifLoopUp !== -1)
+ scrollAmount = $("#mentionsbox").height() * 100; //Makes sure that it scrolls all the way to the bottom
+ else if (direction === "down" && this.state.selectedMention === 0)
+ scrollAmount = 0;
+ else if (direction === "up")
+ scrollAmount = "-=" + ($('#'+this.refs['mention' + this.state.selectedMention].props.id +"_mentions").innerHeight() - 5);
+ else if (direction === "down")
+ scrollAmount = "+=" + ($('#'+this.refs['mention' + this.state.selectedMention].props.id +"_mentions").innerHeight() - 5);
+
+ $("#mentionsbox").animate({
+ scrollTop: scrollAmount
+ }, 75);
+ },
alreadyMentioned: function(username) {
var excludeUsers = this.state.excludeUsers;
for (var i = 0; i < excludeUsers.length; i++) {
@@ -73,9 +150,10 @@ module.exports = React.createClass({
return false;
},
getInitialState: function() {
- return { excludeUsers: [], mentionText: "-1" };
+ return { excludeUsers: [], mentionText: "-1", selectedMention: 0, selectedUsername: "" };
},
render: function() {
+ var self = this;
var mentionText = this.state.mentionText;
if (mentionText === '-1') return null;
@@ -89,12 +167,14 @@ module.exports = React.createClass({
all.username = "all";
all.full_name = "";
all.secondary_text = "Notifies everyone in the team";
+ all.id = "allmention";
users.push(all);
var channel = {};
channel.username = "channel";
channel.full_name = "";
channel.secondary_text = "Notifies everyone in the channel";
+ channel.id = "channelmention";
users.push(channel);
users.sort(function(a,b) {
@@ -118,17 +198,21 @@ module.exports = React.createClass({
if (firstName.lastIndexOf(mentionText,0) === 0
|| lastName.lastIndexOf(mentionText,0) === 0 || users[i].username.lastIndexOf(mentionText,0) === 0) {
- mentions[i+1] = (
+ mentions[index] = (
<Mention
ref={'mention' + index}
username={users[i].username}
secondary_text={users[i].secondary_text}
id={users[i].id}
+ listId={index}
+ isFocused={this.state.selectedMention === index ? "mentions-focus" : ""}
+ handleMouseEnter={function(value) { return function() { self.handleMouseEnter(value); } }(index)}
handleClick={this.handleClick} />
);
index++;
}
}
+
var numMentions = Object.keys(mentions).length;
if (numMentions < 1) return null;
@@ -144,7 +228,7 @@ module.exports = React.createClass({
return (
<div className="mentions--top" style={style}>
- <div ref="mentionlist" className="mentions-box">
+ <div ref="mentionlist" className="mentions-box" id="mentionsbox">
{ mentions }
</div>
</div>
diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx
index c3ddc76f3..007476f9b 100644
--- a/web/react/components/more_channels.jsx
+++ b/web/react/components/more_channels.jsx
@@ -7,6 +7,7 @@ var client = require('../utils/client.jsx');
var asyncClient = require('../utils/async_client.jsx');
var UserStore = require('../stores/user_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
+var LoadingScreen = require('./loading_screen.jsx');
function getStateFromStores() {
return {
@@ -16,6 +17,8 @@ function getStateFromStores() {
}
module.exports = React.createClass({
+ displayName: "MoreChannelsModal",
+
componentDidMount: function() {
ChannelStore.addMoreChangeListener(this._onChange);
$(this.refs.modal.getDOMNode()).on('shown.bs.modal', function (e) {
@@ -90,7 +93,7 @@ module.exports = React.createClass({
<p className="more-channel-name">{channel.display_name}</p>
<p className="more-channel-description">{channel.description}</p>
</td>
- <td className="td--action"><button onClick={outter.handleJoin.bind(outter, channel.id)} className="pull-right btn btn-primary">Join</button></td>
+ <td className="td--action"><button onClick={outter.handleJoin.bind(outter, channel.id)} className="btn btn-primary">Join</button></td>
</tr>
)
})}
@@ -100,15 +103,7 @@ module.exports = React.createClass({
<p className="primary-message">No more channels to join</p>
<p className="secondary-message">Click 'Create New Channel' to make a new one</p>
</div>)
- : <div ref="loadingscreen" className="loading-screen loading-screen--channel">
- <div className="loading__content">
- <h3>Loading</h3>
- <div id="round_1" className="round"></div>
- <div id="round_2" className="round"></div>
- <div id="round_3" className="round"></div>
- </div>
- </div>
- }
+ : <LoadingScreen /> }
{ server_error }
</div>
<div className="modal-footer">
diff --git a/web/react/components/new_channel.jsx b/web/react/components/new_channel.jsx
index 160241c1c..069e0d6b1 100644
--- a/web/react/components/new_channel.jsx
+++ b/web/react/components/new_channel.jsx
@@ -122,7 +122,7 @@ module.exports = React.createClass({
</div>
<div className="form-group">
<label className='control-label'>Description</label>
- <textarea className="form-control" style={{resize: "none"}} ref="channel_desc" rows="3" placeholder="Description" maxLength="1024"></textarea>
+ <textarea className="form-control no-resize" ref="channel_desc" rows="3" placeholder="Description" maxLength="1024"></textarea>
</div>
{ server_error }
</form>
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index 726fb1b02..5457e1cd3 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -10,6 +10,7 @@ var UserStore = require('../stores/user_store.jsx');
var ActionTypes = Constants.ActionTypes;
module.exports = React.createClass({
+ displayName: "Post",
componentDidMount: function() {
$('.modal').on('show.bs.modal', function () {
$('.modal-body').css('overflow-y', 'auto');
@@ -19,7 +20,7 @@ module.exports = React.createClass({
handleCommentClick: function(e) {
e.preventDefault();
- data = {};
+ var data = {};
data.order = [this.props.post.id];
data.posts = this.props.posts;
@@ -48,7 +49,6 @@ module.exports = React.createClass({
var commentCount = 0;
var commentRootId = parentPost ? post.root_id : post.id;
- var rootUser = "";
for (var postId in posts) {
if (posts[postId].root_id == commentRootId) {
commentCount += 1;
@@ -57,12 +57,7 @@ module.exports = React.createClass({
var error = this.state.error ? <div className='form-group has-error'><label className='control-label'>{ this.state.error }</label></div> : null;
- if (this.props.sameRoot){
- rootUser = "same--root";
- }
- else {
- rootUser = "other--root";
- }
+ var rootUser = this.props.sameRoot ? "same--root" : "other--root";
var postType = "";
if (type != "Post"){
@@ -83,7 +78,7 @@ module.exports = React.createClass({
<div className="post-profile-img__container">
<img className="post-profile-img" src={"/api/v1/users/" + post.user_id + "/image?time=" + timestamp} height="36" width="36" />
</div>
- : "" }
+ : null }
<div className="post__content">
<PostHeader post={post} sameRoot={this.props.sameRoot} commentCount={commentCount} handleCommentClick={this.handleCommentClick} isLastComment={this.props.isLastComment} />
<PostBody post={post} sameRoot={this.props.sameRoot} parentPost={parentPost} posts={posts} handleCommentClick={this.handleCommentClick} />
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index 7d5ef4d33..d9678df30 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -71,11 +71,22 @@ module.exports = React.createClass({
name = <a className="theme" onClick={function(){ utils.searchForTerm(profile.username); }}>{profile.username}</a>;
}
- var message = parentPost.message;
+ var message = ""
+ if(parentPost.message) {
+ message = utils.replaceHtmlEntities(parentPost.message)
+ } else if (parentPost.filenames.length) {
+ message = parentPost.filenames[0].split('/').pop();
+
+ if (parentPost.filenames.length === 2) {
+ message += " plus 1 other file";
+ } else if (parentPost.filenames.length > 2) {
+ message += " plus " + (parentPost.filenames.length - 1) + " other files";
+ }
+ }
comment = (
<p className="post-link">
- <span>Commented on {name}{apostrophe} message: <a className="theme" onClick={this.props.handleCommentClick}>{utils.replaceHtmlEntities(message)}</a></span>
+ <span>Commented on {name}{apostrophe} message: <a className="theme" onClick={this.props.handleCommentClick}>{message}</a></span>
</p>
);
@@ -120,7 +131,7 @@ module.exports = React.createClass({
return (
<div className="post-body">
{ comment }
- <p key={post.Id+"_message"} className={postClass}><span>{inner}</span></p>
+ <p key={post.id+"_message"} className={postClass}><span>{inner}</span></p>
{ filenames && filenames.length > 0 ?
<div className="post-image__columns">
{ postFiles }
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index e6e028209..98491976f 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -8,6 +8,7 @@ var UserProfile = require( './user_profile.jsx' );
var AsyncClient = require('../utils/async_client.jsx');
var CreatePost = require('./create_post.jsx');
var Post = require('./post.jsx');
+var LoadingScreen = require('./loading_screen.jsx');
var SocketStore = require('../stores/socket_store.jsx');
var utils = require('../utils/utils.jsx');
var Client = require('../utils/client.jsx');
@@ -26,37 +27,8 @@ function getStateFromStores() {
};
}
-function changeColor(col, amt) {
-
- var usePound = false;
-
- if (col[0] == "#") {
- col = col.slice(1);
- usePound = true;
- }
-
- var num = parseInt(col,16);
-
- var r = (num >> 16) + amt;
-
- if (r > 255) r = 255;
- else if (r < 0) r = 0;
-
- var b = ((num >> 8) & 0x00FF) + amt;
-
- if (b > 255) b = 255;
- else if (b < 0) b = 0;
-
- var g = (num & 0x0000FF) + amt;
-
- if (g > 255) g = 255;
- else if (g < 0) g = 0;
-
- return (usePound?"#":"") + String("000000" + (g | (b << 8) | (r << 16)).toString(16)).slice(-6);
-
-}
-
module.exports = React.createClass({
+ displayName: "PostList",
scrollPosition: 0,
preventScrollTrigger: false,
gotMorePosts: false,
@@ -69,7 +41,7 @@ module.exports = React.createClass({
utils.changeCss('a.theme', 'color:'+user.props.theme+'; fill:'+user.props.theme+'!important;');
utils.changeCss('div.theme', 'background-color:'+user.props.theme+';');
utils.changeCss('.btn.btn-primary', 'background: ' + user.props.theme+';');
- utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + changeColor(user.props.theme, -10) +';');
+ utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + utils.changeColor(user.props.theme, -10) +';');
utils.changeCss('.modal .modal-header', 'background: ' + user.props.theme+';');
utils.changeCss('.mention', 'background: ' + user.props.theme+';');
utils.changeCss('.mention-link', 'color: ' + user.props.theme+';');
@@ -161,24 +133,20 @@ module.exports = React.createClass({
$('body').off('click.userpopover');
},
resize: function() {
+ var post_holder = $(".post-list-holder-by-time")[0];
+ this.preventScrollTrigger = true;
if (this.gotMorePosts) {
this.gotMorePosts = false;
- var post_holder = $(".post-list-holder-by-time")[0];
- this.preventScrollTrigger = true;
$(post_holder).scrollTop($(post_holder).scrollTop() + (post_holder.scrollHeight-this.oldScrollHeight) );
- $(post_holder).perfectScrollbar('update');
} else {
- var post_holder = $(".post-list-holder-by-time")[0];
- this.preventScrollTrigger = true;
if ($("#new_message")[0] && !this.scrolledToNew) {
$(post_holder).scrollTop($(post_holder).scrollTop() + $("#new_message").offset().top - 63);
- $(post_holder).perfectScrollbar('update');
this.scrolledToNew = true;
} else {
$(post_holder).scrollTop(post_holder.scrollHeight);
- $(post_holder).perfectScrollbar('update');
}
}
+ $(post_holder).perfectScrollbar('update');
},
_onChange: function() {
var newState = getStateFromStores();
@@ -297,7 +265,7 @@ module.exports = React.createClass({
},
function(err) {
$(self.refs.loadmore.getDOMNode()).text("Load more messages");
- dispatchError(err, "getPosts");
+ AsyncClient.dispatchError(err, "getPosts");
}
);
},
@@ -347,7 +315,10 @@ module.exports = React.createClass({
<div className="channel-intro-profile">
<strong><UserProfile userId={teammate.id} /></strong>
</div>
- <p className="channel-intro-text">{"This is the start of your private message history with " + teammate_name + "." }<br/>{"Private messages and files shared here are not shown to people outside this area."}</p>
+ <p className="channel-intro-text">
+ {"This is the start of your private message history with " + teammate_name + "." }<br/>
+ {"Private messages and files shared here are not shown to people outside this area."}
+ </p>
</div>
);
} else {
@@ -410,7 +381,7 @@ module.exports = React.createClass({
{ channel.type === 'P' ? " Only invited members can see this private group." : " Any member can join and read this channel." }
<br/>
<a className="intro-links" href="#" style={userStyle} data-toggle="modal" data-target="#edit_channel" data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}><i className="fa fa-pencil"></i>Set a description</a>
- <a className="intro-links" style={userStyle} data-toggle="modal" data-target="#channel_invite"><i className="fa fa-user-plus"></i>Invite others to this {ui_type}</a>
+ <a className="intro-links" href="#" style={userStyle} data-toggle="modal" data-target="#channel_invite"><i className="fa fa-user-plus"></i>Invite others to this {ui_type}</a>
</p>
</div>
);
@@ -420,37 +391,35 @@ module.exports = React.createClass({
var postCtls = [];
- if (posts != undefined) {
+ if (posts) {
var previousPostDay = posts[order[order.length-1]] ? utils.getDateForUnixTicks(posts[order[order.length-1]].create_at): new Date();
- var currentPostDay = new Date();
+ var currentPostDay;
for (var i = order.length-1; i >= 0; i--) {
var post = posts[order[i]];
- var parentPost;
+ var parentPost = post.parent_id ? posts[post.parent_id] : null;
- if (post.parent_id) {
- parentPost = posts[post.parent_id];
- } else {
- parentPost = null;
- }
+ var sameUser = '';
+ var sameRoot = false;
+ var hideProfilePic = false;
+ var prevPost = (i < order.length - 1) ? posts[order[i + 1]] : null;
- var sameUser = i < order.length-1 && posts[order[i+1]].user_id === post.user_id && post.create_at - posts[order[i+1]].create_at <= 1000*60*5 ? "same--user" : "";
- var sameRoot = i < order.length-1 && post.root_id != "" && (posts[order[i+1]].id === post.root_id || posts[order[i+1]].root_id === post.root_id) ? true : false;
+ if (prevPost) {
+ sameUser = (prevPost.user_id === post.user_id) && (post.create_at - prevPost.create_at <= 1000*60*5) ? "same--user" : "";
+ sameRoot = utils.isComment(post) && (prevPost.id === post.root_id || prevPost.root_id === post.root_id);
- // we only hide the profile pic if the previous post is not a comment, the current post is not a comment, and the previous post was made by the same user as the current post
- var hideProfilePic = i < order.length-1 && posts[order[i+1]].user_id === post.user_id && posts[order[i+1]].root_id === '' && post.root_id === '';
+ // we only hide the profile pic if the previous post is not a comment, the current post is not a comment, and the previous post was made by the same user as the current post
+ hideProfilePic = (prevPost.user_id === post.user_id) && !utils.isComment(prevPost) && !utils.isComment(post);
+ }
// check if it's the last comment in a consecutive string of comments on the same post
- var isLastComment = false;
- if (utils.isComment(post)) {
- // it is the last comment if it is last post in the channel or the next post has a different root post
- isLastComment = (i === 0 || posts[order[i-1]].root_id != post.root_id);
- }
+ // it is the last comment if it is last post in the channel or the next post has a different root post
+ var isLastComment = utils.isComment(post) && (i === 0 || posts[order[i-1]].root_id != post.root_id);
var postCtl = <Post sameUser={sameUser} sameRoot={sameRoot} post={post} parentPost={parentPost} key={post.id} posts={posts} hideProfilePic={hideProfilePic} isLastComment={isLastComment} />;
currentPostDay = utils.getDateForUnixTicks(post.create_at);
- if(currentPostDay.getDate() !== previousPostDay.getDate() || currentPostDay.getMonth() !== previousPostDay.getMonth() || currentPostDay.getFullYear() !== previousPostDay.getFullYear()) {
+ if (currentPostDay.toDateString() != previousPostDay.toDateString()) {
postCtls.push(
<div className="date-separator">
<hr className="separator__hr" />
@@ -469,20 +438,10 @@ module.exports = React.createClass({
);
}
postCtls.push(postCtl);
- previousPostDay = utils.getDateForUnixTicks(post.create_at);
+ previousPostDay = currentPostDay;
}
- }
- else {
- postCtls.push(
- <div ref="loadingscreen" className="loading-screen">
- <div className="loading__content">
- <h3>Loading</h3>
- <div id="round_1" className="round"></div>
- <div id="round_2" className="round"></div>
- <div id="round_3" className="round"></div>
- </div>
- </div>
- );
+ } else {
+ postCtls.push(<LoadingScreen position="absolute" />);
}
return (
@@ -497,5 +456,3 @@ module.exports = React.createClass({
);
}
});
-
-
diff --git a/web/react/components/post_right.jsx b/web/react/components/post_right.jsx
index 408fbf83a..dca05051d 100644
--- a/web/react/components/post_right.jsx
+++ b/web/react/components/post_right.jsx
@@ -43,7 +43,7 @@ RhsHeaderPost = React.createClass({
});
},
render: function() {
- var back = this.props.fromSearch ? <a href="#" onClick={this.handleBack} style={{color:"black"}}>{"< "}</a> : "";
+ var back = this.props.fromSearch ? <a href="#" onClick={this.handleBack} className="sidebar--right__back"><i className="fa fa-chevron-left"></i></a> : "";
return (
<div className="sidebar--right__header">
diff --git a/web/react/components/search_bar.jsx b/web/react/components/search_bar.jsx
index cddb738f9..ab7e99d60 100644
--- a/web/react/components/search_bar.jsx
+++ b/web/react/components/search_bar.jsx
@@ -3,6 +3,7 @@
var client = require('../utils/client.jsx');
+var AsyncClient = require('../utils/async_client.jsx');
var PostStore = require('../stores/post_store.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var utils = require('../utils/utils.jsx');
@@ -10,14 +11,14 @@ var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
function getSearchTermStateFromStores() {
- term = PostStore.getSearchTerm();
- if (!term) term = "";
+ var term = PostStore.getSearchTerm() || '';
return {
search_term: term
};
}
module.exports = React.createClass({
+ displayName: 'SearchBar',
componentDidMount: function() {
PostStore.addSearchTermChangeListener(this._onChange);
},
@@ -58,14 +59,14 @@ module.exports = React.createClass({
e.target.select();
},
performSearch: function(terms, isMentionSearch) {
- if (terms.length > 0) {
- $("#search-spinner").removeClass("hidden");
+ if (terms.length) {
+ this.setState({isSearching: true});
client.search(
terms,
function(data) {
- $("#search-spinner").addClass("hidden");
- if(utils.isMobile()) {
- $('#search')[0].value = "";
+ this.setState({isSearching: false});
+ if (utils.isMobile()) {
+ React.findDOMNode(this.refs.search).value = '';
}
AppDispatcher.handleServerAction({
@@ -73,18 +74,17 @@ module.exports = React.createClass({
results: data,
is_mention_search: isMentionSearch
});
- },
+ }.bind(this),
function(err) {
- $("#search-spinner").addClass("hidden");
- dispatchError(err, "search");
- }
+ this.setState({isSearching: false});
+ AsyncClient.dispatchError(err, "search");
+ }.bind(this)
);
}
},
handleSubmit: function(e) {
e.preventDefault();
- terms = this.state.search_term.trim();
- this.performSearch(terms);
+ this.performSearch(this.state.search_term.trim());
},
getInitialState: function() {
return getSearchTermStateFromStores();
@@ -95,8 +95,15 @@ module.exports = React.createClass({
<div className="sidebar__collapse" onClick={this.handleClose}></div>
<span className="glyphicon glyphicon-search sidebar__search-icon"></span>
<form role="form" className="search__form relative-div" onSubmit={this.handleSubmit}>
- <input type="text" className="form-control search-bar-box" ref="search" id="search" placeholder="Search" value={this.state.search_term} onFocus={this.handleUserFocus} onChange={this.handleUserInput} />
- <span id="search-spinner" className="glyphicon glyphicon-refresh glyphicon-refresh-animate hidden"></span>
+ <input
+ type="text"
+ ref="search"
+ className="form-control search-bar-box"
+ placeholder="Search"
+ value={this.state.search_term}
+ onFocus={this.handleUserFocus}
+ onChange={this.handleUserInput} />
+ {this.state.isSearching ? <span className={"glyphicon glyphicon-refresh glyphicon-refresh-animate"}></span> : null}
</form>
</div>
);
diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx
index 156cf0120..1fd974642 100644
--- a/web/react/components/search_results.jsx
+++ b/web/react/components/search_results.jsx
@@ -8,12 +8,13 @@ var UserStore = require('../stores/user_store.jsx');
var UserProfile = require( './user_profile.jsx' );
var SearchBox =require('./search_bar.jsx');
var utils = require('../utils/utils.jsx');
-var client =require('../utils/client.jsx');
+var client = require('../utils/client.jsx');
+var AsyncClient = require('../utils/async_client.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
-RhsHeaderSearch = React.createClass({
+var RhsHeaderSearch = React.createClass({
handleClose: function(e) {
e.preventDefault();
@@ -32,13 +33,13 @@ RhsHeaderSearch = React.createClass({
return (
<div className="sidebar--right__header">
<span className="sidebar--right__title">{title}</span>
- <button type="button" className="sidebar--right__close" aria-label="Close" onClick={this.handleClose}></button>
+ <button type="button" className="sidebar--right__close" aria-label="Close" title="Close" onClick={this.handleClose}></button>
</div>
);
}
});
-SearchItem = React.createClass({
+var SearchItem = React.createClass({
handleClick: function(e) {
e.preventDefault();
@@ -62,15 +63,16 @@ SearchItem = React.createClass({
});
},
function(err) {
- dispatchError(err, "getPost");
+ AsyncClient.dispatchError(err, "getPost");
}
);
- var postChannel = ChannelStore.get(this.props.post.channel_id);
- var teammate = postChannel.type === 'D' ? utils.getDirectTeammate(this.props.post.channel_id).username : "";
+ var postChannel = ChannelStore.get(this.props.post.channel_id);
+ var teammate = postChannel.type === 'D' ? utils.getDirectTeammate(this.props.post.channel_id).username : "";
- utils.switchChannel(postChannel,teammate);
+ utils.switchChannel(postChannel, teammate);
},
+
render: function() {
var message = utils.textToJsx(this.props.post.message, {searchTerm: this.props.term, noMentionHighlight: !this.props.isMentionSearch});
@@ -79,14 +81,10 @@ SearchItem = React.createClass({
var timestamp = UserStore.getCurrentUser().update_at;
if (channel) {
- if (channel.type === 'D') {
- channelName = "Private Message";
- } else {
- channelName = channel.display_name;
- }
+ channelName = (channel.type === 'D') ? "Private Message" : channel.display_name;
}
- return (
+ return (
<div className="search-item-container post" onClick={this.handleClick}>
<div className="search-channel__name">{ channelName }</div>
<div className="post-profile-img__container">
@@ -95,7 +93,11 @@ SearchItem = React.createClass({
<div className="post__content">
<ul className="post-header">
<li className="post-header-col"><strong><UserProfile userId={this.props.post.user_id} /></strong></li>
- <li className="post-header-col"><time className="search-item-time">{ utils.displayDate(this.props.post.create_at)+' '+utils.displayTime(this.props.post.create_at) }</time></li>
+ <li className="post-header-col">
+ <time className="search-item-time">
+ { utils.displayDate(this.props.post.create_at) + ' ' + utils.displayTime(this.props.post.create_at) }
+ </time>
+ </li>
</ul>
<div className="search-item-snippet"><span>{message}</span></div>
</div>
@@ -104,11 +106,13 @@ SearchItem = React.createClass({
}
});
+
function getStateFromStores() {
return { results: PostStore.getSearchResults() };
}
module.exports = React.createClass({
+ displayName: 'SearchResults',
componentDidMount: function() {
PostStore.addSearchChangeListener(this._onChange);
this.resize();
@@ -144,41 +148,24 @@ module.exports = React.createClass({
var results = this.state.results;
var currentId = UserStore.getCurrentId();
- var searchForm = currentId == null ? null : <SearchBox />;
-
- if (results == null) {
- return (
- <div className="sidebar--right__header">
- <div className="sidebar__heading">Search Results</div>
- </div>
- );
- }
+ var searchForm = currentId ? <SearchBox /> : null;
+ var noResults = (!results || !results.order || !results.order.length);
+ var searchTerm = PostStore.getSearchTerm();
- if (!results.order || results.order.length == 0) {
- return (
- <div className="sidebar--right__content">
- <div className="search-bar__container">{searchForm}</div>
- <div className="sidebar-right__body">
- <RhsHeaderSearch />
- <div id="search-items-container" className="search-items-container">
- <div className="sidebar--right__subheader">No results</div>
- </div>
- </div>
- </div>
- );
- }
-
- var self = this;
return (
<div className="sidebar--right__content">
<div className="search-bar__container sidebar--right__search-header">{searchForm}</div>
<div className="sidebar-right__body">
<RhsHeaderSearch isMentionSearch={this.props.isMentionSearch} />
<div id="search-items-container" className="search-items-container">
- {results.order.map(function(id) {
- var post = results.posts[id];
- return <SearchItem key={post.id} post={post} term={PostStore.getSearchTerm()} isMentionSearch={self.props.isMentionSearch} />
- })}
+
+ { noResults ? <div className="sidebar--right__subheader">No results</div>
+ : results.order.map(function(id) {
+ var post = results.posts[id];
+ return <SearchItem key={post.id} post={post} term={searchTerm} isMentionSearch={this.props.isMentionSearch} />
+ }, this)
+ }
+
</div>
</div>
</div>
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 934a4d22a..cae9425d3 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -249,11 +249,27 @@ var SidebarLoggedIn = React.createClass({
var repRegex = new RegExp("<br>", "g");
var post = JSON.parse(msg.props.post);
+ var msgProps = msg.props;
var msg = post.message.replace(repRegex, "\n").replace(/\n+/g, " ").replace("<mention>", "").replace("</mention>", "");
+
if (msg.length > 50) {
msg = msg.substring(0,49) + "...";
}
- utils.notifyMe(title, username + " wrote: " + msg, channel);
+
+ if (msg.length === 0) {
+ if (msgProps.image) {
+ utils.notifyMe(title, username + " uploaded an image", channel);
+ }
+ else if (msgProps.otherFile) {
+ utils.notifyMe(title, username + " uploaded a file", channel);
+ }
+ else {
+ utils.notifyMe(title, username + " did something new", channel);
+ }
+ }
+ else {
+ utils.notifyMe(title, username + " wrote: " + msg, channel);
+ }
if (!user.notify_props || user.notify_props.desktop_sound === "true") {
utils.ding();
}
diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx
index 0b59d2036..83ad4470e 100644
--- a/web/react/components/sidebar_header.jsx
+++ b/web/react/components/sidebar_header.jsx
@@ -77,7 +77,7 @@ var NavbarDropdown = React.createClass({
for (var i = 0; i < this.state.teams.length; i++) {
var domain = this.state.teams[i];
- if (domain == utils.getSubDomain())
+ if (domain == utils.getSubDomain())
continue;
if (teams.length == 0)
@@ -91,7 +91,7 @@ var NavbarDropdown = React.createClass({
<ul className="nav navbar-nav navbar-right">
<li className="dropdown">
<a href="#" className="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false">
- <i className="dropdown__icon"></i>
+ <span className="dropdown__icon" dangerouslySetInnerHTML={{__html: " <svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>"}} />
</a>
<ul className="dropdown-menu" role="menu">
<li><a href="#" data-toggle="modal" data-target="#user_settings1">Account Settings</a></li>
@@ -121,10 +121,15 @@ module.exports = React.createClass({
},
render: function() {
var teamName = this.props.teamName ? this.props.teamName : config.SiteName;
+ var me = UserStore.getCurrentUser()
return (
<div className="team__header theme">
- <a className="team__name" href="/channels/town-square">{ teamName }</a>
+ <img className="user__picture" src={"/api/v1/users/" + me.id + "/image?time=" + me.update_at} />
+ <div className="header__info">
+ <div className="user__name">@{me.username}</div>
+ <a className="team__name" href="/channels/town-square">{ teamName }</a>
+ </div>
<NavbarDropdown teamType={this.props.teamType} />
</div>
);
diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx
index fa0c26017..d1053c778 100644
--- a/web/react/components/signup_user_complete.jsx
+++ b/web/react/components/signup_user_complete.jsx
@@ -46,25 +46,24 @@ module.exports = React.createClass({
function(data) {
client.track('signup', 'signup_user_02_complete');
- if (data.email_verified) {
- client.loginByEmail(this.props.domain, this.state.user.email, this.state.user.password,
- function(data) {
- UserStore.setLastDomain(this.props.domain);
- UserStore.setLastEmail(this.state.user.email);
- UserStore.setCurrentUser(data);
- if (this.props.hash > 0)
- BrowserStore.setGlobalItem(this.props.hash, {wizard: "finished"});
- window.location.href = '/channels/town-square';
- }.bind(this),
- function(err) {
+ client.loginByEmail(this.props.domain, this.state.user.email, this.state.user.password,
+ function(data) {
+ UserStore.setLastDomain(this.props.domain);
+ UserStore.setLastEmail(this.state.user.email);
+ UserStore.setCurrentUser(data);
+ if (this.props.hash > 0)
+ BrowserStore.setGlobalItem(this.props.hash, JSON.stringify({wizard: "finished"}));
+ window.location.href = '/channels/town-square';
+ }.bind(this),
+ function(err) {
+ if (err.message == "Login failed because email address has not been verified") {
+ window.location.href = "/verify?email="+ encodeURIComponent(this.state.user.email) + "&domain=" + encodeURIComponent(this.props.domain);
+ } else {
this.state.server_error = err.message;
this.setState(this.state);
- }.bind(this)
- );
- }
- else {
- window.location.href = "/verify?email="+ encodeURIComponent(this.state.user.email) + "&domain=" + encodeURIComponent(this.props.domain);
- }
+ }
+ }.bind(this)
+ );
}.bind(this),
function(err) {
this.state.server_error = err.message;
diff --git a/web/react/components/user_profile.jsx b/web/react/components/user_profile.jsx
index 89d0a80ff..65f025919 100644
--- a/web/react/components/user_profile.jsx
+++ b/web/react/components/user_profile.jsx
@@ -53,7 +53,7 @@ module.exports = React.createClass({
var name = this.props.overwriteName ? this.props.overwriteName : this.state.profile.username;
- var data_content = "<img style='margin: 10px' src='/api/v1/users/" + this.state.profile.id + "/image?time=" + this.state.profile.update_at + "' height='128' width='128' />";
+ var data_content = "<img class='user-popover__image' src='/api/v1/users/" + this.state.profile.id + "/image?time=" + this.state.profile.update_at + "' height='128' width='128' />";
if (!config.ShowEmail) {
data_content += "<div class='text-nowrap'>Email not shared</div>";
} else {
@@ -61,7 +61,7 @@ module.exports = React.createClass({
}
return (
- <div style={{"cursor" : "pointer", "display" : "inline-block"}} className="user-popover" id={"profile_" + this.uniqueId} data-toggle="popover" data-content={data_content} data-original-title={this.state.profile.username} >
+ <div className="user-popover" id={"profile_" + this.uniqueId} data-toggle="popover" data-content={data_content} data-original-title={this.state.profile.username} >
{ name }
</div>
);
diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx
index b4c3747af..17afadce2 100644
--- a/web/react/components/user_settings.jsx
+++ b/web/react/components/user_settings.jsx
@@ -560,7 +560,7 @@ var AuditTab = React.createClass({
<div className="user-settings">
<h3 className="tab-header">Activity Log</h3>
<div className="divider-dark first"/>
- <div className="table-responsive" style={{ maxWidth: "560px", maxHeight: "300px" }}>
+ <div className="table-responsive">
<table className="table-condensed small">
<thead>
<tr>
@@ -576,11 +576,11 @@ var AuditTab = React.createClass({
this.state.audits.map(function(value, index) {
return (
<tr key={ "" + index }>
- <td style={{ whiteSpace: "nowrap" }}>{ new Date(value.create_at).toLocaleString() }</td>
- <td style={{ whiteSpace: "nowrap" }}>{ value.action.replace("/api/v1", "") }</td>
- <td style={{ whiteSpace: "nowrap" }}>{ value.ip_address }</td>
- <td style={{ whiteSpace: "nowrap" }}>{ value.session_id }</td>
- <td style={{ whiteSpace: "nowrap" }}>{ value.extra_info }</td>
+ <td className="text-nowrap">{ new Date(value.create_at).toLocaleString() }</td>
+ <td className="text-nowrap">{ value.action.replace("/api/v1", "") }</td>
+ <td className="text-nowrap">{ value.ip_address }</td>
+ <td className="text-nowrap">{ value.session_id }</td>
+ <td className="text-nowrap">{ value.extra_info }</td>
</tr>
);
}, this)
@@ -626,7 +626,7 @@ var SecurityTab = React.createClass({
client.updatePassword(data,
function(data) {
- this.updateSection("");
+ this.props.updateSection("");
AsyncClient.getMe();
this.setState({ current_password: '', new_password: '', confirm_password: '' });
}.bind(this),
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index b8747b8fc..6cae7fe89 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -225,7 +225,7 @@ module.exports.extractLinks = function(text) {
}
return { "links": links, "text": text };
-}
+}
module.exports.escapeRegExp = function(string) {
return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
@@ -678,13 +678,13 @@ module.exports.isValidUsername = function (name) {
error = "First character must be a letter.";
}
- else
+ else
{
var lowerName = name.toLowerCase().trim();
- for (var i = 0; i < Constants.RESERVED_USERNAMES.length; i++)
+ for (var i = 0; i < Constants.RESERVED_USERNAMES.length; i++)
{
- if (lowerName === Constants.RESERVED_USERNAMES[i])
+ if (lowerName === Constants.RESERVED_USERNAMES[i])
{
error = "Cannot use a reserved word as a username.";
break;
@@ -789,3 +789,34 @@ module.exports.getHomeLink = function() {
parts[0] = "www";
return window.location.protocol + "//" + parts.join(".");
}
+
+
+module.exports.changeColor =function(col, amt) {
+
+ var usePound = false;
+
+ if (col[0] == "#") {
+ col = col.slice(1);
+ usePound = true;
+ }
+
+ var num = parseInt(col,16);
+
+ var r = (num >> 16) + amt;
+
+ if (r > 255) r = 255;
+ else if (r < 0) r = 0;
+
+ var b = ((num >> 8) & 0x00FF) + amt;
+
+ if (b > 255) b = 255;
+ else if (b < 0) b = 0;
+
+ var g = (num & 0x0000FF) + amt;
+
+ if (g > 255) g = 255;
+ else if (g < 0) g = 0;
+
+ return (usePound?"#":"") + String("000000" + (g | (b << 8) | (r << 16)).toString(16)).slice(-6);
+
+};
diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss
index fd6225bdd..1fb970075 100644
--- a/web/sass-files/sass/partials/_base.scss
+++ b/web/sass-files/sass/partials/_base.scss
@@ -10,6 +10,9 @@ body {
height: 100%;
&.white {
background: #fff;
+ > .container-fluid {
+ overflow: auto;
+ }
.inner__wrap {
> .row.content {
min-height: 100%;
@@ -53,6 +56,9 @@ div.theme {
.form-control {
@include border-radius(2px);
+ &.no-resize {
+ resize: none;
+ }
}
.form-group {
@@ -126,6 +132,10 @@ div.theme {
to { transform: scale(1) rotate(360deg);}
}
+.glyphicon-refresh-animate {
+ @include animation(spin .7s infinite linear);
+}
+
.black-bg {
background-color: black !important;
}
diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss
index 79142176e..56d03e171 100644
--- a/web/sass-files/sass/partials/_files.scss
+++ b/web/sass-files/sass/partials/_files.scss
@@ -32,6 +32,7 @@
}
}
.preview-img {
+ display: block;
height: auto;
max-width: 100%;
}
@@ -137,7 +138,7 @@
border: 1px solid #E2E2E2;
background-color: #FFF;
background-repeat: no-repeat;
- background-position: left center;
+ background-position: top left;
}
a {
text-decoration: none;
diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss
index 1ec1109a5..d876d8b37 100644
--- a/web/sass-files/sass/partials/_headers.scss
+++ b/web/sass-files/sass/partials/_headers.scss
@@ -75,14 +75,16 @@
// Team Header in Sidebar
.sidebar--left, .sidebar--menu {
.team__header {
- padding: 0 15px 0 15px;
+ padding: 10px;
@include legacy-pie-clearfix;
a {
color: #fff;
}
.navbar-right {
font-size: 0.85em;
- margin: 16px -5px 0;
+ position: absolute;
+ top: 20px;
+ right: 22px;
.dropdown-toggle {
padding: 0 10px;
}
@@ -93,24 +95,35 @@
}
}
.dropdown__icon {
- background: url("../images/dropdown-icon.png");
- width: 4px;
- height: 16px;
- @include background-size(100% 100%);
- display: inline-block;
+ fill: #fff;
}
}
- .team__name {
+ .user__picture {
+ width: 36px;
+ height: 36px;
float: left;
- line-height: 50px;
+ @include border-radius(36px);
+ }
+ .header__info {
+ padding-left: 42px;
+ color: #fff;
+ }
+ .team__name, .user__name {
+ display: block;
+ line-height: 18px;
font-weight: 600;
- font-size: 1.2em;
+ font-size: 16px;
max-width: 80%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-decoration: none;
}
+ .user__name {
+ font-size: 14px;
+ font-weight: 400;
+ color: #eee;
+ }
> .nav {
> li {
> a {
@@ -146,7 +159,7 @@
#member_popover {
width: 45px;
color: #999;
-
+ cursor: pointer;
}
&.alt {
margin: 0;
@@ -213,3 +226,21 @@
top: 1px;
}
}
+
+.channel-header__links {
+ height: 32px;
+ vertical-align: top;
+ display: inline-block;
+ width: 15px;
+ margin: 9px 3px 3px 0;
+ &:hover {
+ svg {
+ fill: #888;
+ }
+ }
+ svg {
+ vertical-align: top;
+ margin-top: 8px;
+ fill: #AAA;
+ }
+} \ No newline at end of file
diff --git a/web/sass-files/sass/partials/_loading.scss b/web/sass-files/sass/partials/_loading.scss
index bc819e8f5..d71055722 100644
--- a/web/sass-files/sass/partials/_loading.scss
+++ b/web/sass-files/sass/partials/_loading.scss
@@ -2,13 +2,8 @@
display: table;
width: 100%;
height: 100%;
- position: absolute;
- @include box-sizing(border-box);
+ padding: 60px;
text-align: center;
- &.loading-screen--channel {
- position: relative;
- padding: 4em 0 3.5em;
- }
.loading__content {
display: table-cell;
vertical-align: middle;
@@ -19,11 +14,7 @@
margin: 0 0.2em 0;
display: inline-block;
}
- }
-}
-.loading-screen {
- .loading__content {
.round {
background-color: #444;
width: 4px;
@@ -32,43 +23,18 @@
margin: 0 2px;
opacity: 0.1;
@include border-radius(10px);
- -moz-animation: move 0.75s infinite linear;
- -webkit-animation: move 0.75s infinite linear;
- }
-
- #round_1 {
- -moz-animation-delay: .2s;
- -webkit-animation-delay: .2s;
+ @include animation(move 0.75s infinite linear);
}
- #round_2 {
- -moz-animation-delay: .4s;
- -webkit-animation-delay: .4s;
- }
-
- #round_3 {
- -moz-animation-delay: .6s;
- -webkit-animation-delay: .6s;
- }
-
- @-moz-keyframes move {
- 0% {
- opacity: 1;
+ @for $i from 1 through 3 {
+ .round-#{$i} {
+ @include animation-delay(.2s*$i);
}
-
- 100% {
- opacity: 0.1;
- };
}
- @-webkit-keyframes move {
- 0% {
- opacity: 1;
- }
-
- 100% {
- opacity: 0.1;
- };
+ @include keyframes(move) {
+ from { opacity: 1; }
+ to { opacity: 0.1; }
}
}
}
diff --git a/web/sass-files/sass/partials/_mentions.scss b/web/sass-files/sass/partials/_mentions.scss
index 7e8c1869a..1396f21a1 100644
--- a/web/sass-files/sass/partials/_mentions.scss
+++ b/web/sass-files/sass/partials/_mentions.scss
@@ -37,6 +37,10 @@
}
}
+.mentions-focus {
+ background-color: #E6F2FA;
+}
+
.mentions-text {
font-color:black;
}
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index 707e71cf0..1b0338884 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -154,6 +154,7 @@
background: #FFF;
position: relative;
max-width: 90%;
+ min-height: 50px;
min-width: 280px;
@include border-radius(3px);
display: table;
diff --git a/web/sass-files/sass/partials/_popover.scss b/web/sass-files/sass/partials/_popover.scss
new file mode 100644
index 000000000..fa1b44841
--- /dev/null
+++ b/web/sass-files/sass/partials/_popover.scss
@@ -0,0 +1,9 @@
+.user-popover {
+ cursor: pointer;
+ display: inline-block;
+}
+
+.user-popover__image {
+ margin: 0 0 10px;
+ @include border-radius(128px);
+} \ 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 d0c536363..465c50296 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -119,6 +119,7 @@ body.ios {
table-layout: fixed;
width: 100%;
min-height: 100%;
+ height: 100%;
.post-list__content {
display: table-cell;
vertical-align: bottom;
@@ -424,4 +425,4 @@ body.ios {
width: 40px;
}
}
-}
+} \ No newline at end of file
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index 9c0c09ee3..1f9643175 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -413,7 +413,7 @@
}
}
.footer, .footer-pane, .footer-push {
- height: 185px;
+ height: auto;
}
.footer-pane {
.footer-link {
@@ -436,10 +436,9 @@
.form-control {
background: none;
color: #fff;
- border-bottom: 1px solid #fff;
border-bottom: 1px solid rgba(#fff, 0.7);
border-radius: 0;
- padding: 0 0 0 23px;
+ padding: 0 10px 0 23px;
}
::-webkit-input-placeholder {
color: #fff;
@@ -534,6 +533,11 @@
.sidebar--right__close {
display: none;
}
+ .search__form {
+ .glyphicon {
+ color: #fff;
+ }
+ }
}
.inner__wrap {
&.move--right {
@@ -570,6 +574,8 @@
.modal {
.modal-image {
.image-wrapper {
+ font-size: 12px;
+ max-width: 280px;
.modal-close {
@include opacity(1);
}
diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss
index af759c650..e60bc290e 100644
--- a/web/sass-files/sass/partials/_settings.scss
+++ b/web/sass-files/sass/partials/_settings.scss
@@ -1,6 +1,10 @@
.user-settings {
background: #fff;
min-height:300px;
+ .table-responsive {
+ max-width: 560px;
+ max-height: 300px;
+ }
}
.settings-modal {
diff --git a/web/sass-files/sass/partials/_sidebar--right.scss b/web/sass-files/sass/partials/_sidebar--right.scss
index a0e82fd2f..d02a92448 100644
--- a/web/sass-files/sass/partials/_sidebar--right.scss
+++ b/web/sass-files/sass/partials/_sidebar--right.scss
@@ -10,6 +10,14 @@
&.move--left {
right: 0;
}
+ .sidebar--right__back {
+ color: #666;
+ width: 20px;
+ text-align: center;
+ margin: 0 0 0 -6px;
+ font-size: 12px;
+ display: inline-block;
+ }
.sidebar-right__body {
border-left: $border-gray;
border-top: $border-gray;
diff --git a/web/sass-files/sass/partials/_variables.scss b/web/sass-files/sass/partials/_variables.scss
index eb1f3eef3..5d883ab44 100644
--- a/web/sass-files/sass/partials/_variables.scss
+++ b/web/sass-files/sass/partials/_variables.scss
@@ -7,10 +7,4 @@ $primary-color: #2389D7;
$primary-color--hover: darken(#2389D7, 5%);
$body-bg: #e9e9e9;
$header-bg: #f9f9f9;
-$border-gray: 1px solid #ddd;
-
-// Animation
-.glyphicon-refresh-animate {
- -animation: spin .7s infinite linear;
- -webkit-animation: spin2 .7s infinite linear;
-} \ No newline at end of file
+$border-gray: 1px solid #ddd; \ No newline at end of file
diff --git a/web/sass-files/sass/styles.scss b/web/sass-files/sass/styles.scss
index 9cc26320c..294f6122a 100644
--- a/web/sass-files/sass/styles.scss
+++ b/web/sass-files/sass/styles.scss
@@ -15,6 +15,7 @@
@import "partials/headers";
@import "partials/footer";
@import "partials/content";
+@import "partials/popover";
@import "partials/post";
@import "partials/post_right";
@import "partials/navbar";
@@ -29,7 +30,7 @@
@import "partials/modal";
@import "partials/mentions";
@import "partials/error";
-@import "partials/loading";
+@import "partials/loading";
// Responsive Css
@import "partials/responsive";
diff --git a/web/static/images/dropdown-icon.png b/web/static/images/dropdown-icon.png
deleted file mode 100644
index 5c271cfc7..000000000
--- a/web/static/images/dropdown-icon.png
+++ /dev/null
Binary files differ