summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/context.go10
-rw-r--r--api/post.go11
-rw-r--r--api/post_test.go14
-rw-r--r--api/templates/reset_body.html2
-rw-r--r--api/user.go2
-rw-r--r--model/user.go1
-rw-r--r--store/sql_user_store.go8
-rw-r--r--store/store.go2
-rw-r--r--web/react/components/channel_header.jsx44
-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/member_list_item.jsx2
-rw-r--r--web/react/components/member_list_team.jsx2
-rw-r--r--web/react/components/navbar.jsx159
-rw-r--r--web/react/components/new_channel.jsx2
-rw-r--r--web/react/components/post_info.jsx3
-rw-r--r--web/react/components/post_list.jsx3
-rw-r--r--web/react/components/post_right.jsx2
-rw-r--r--web/react/components/search_bar.jsx2
-rw-r--r--web/react/components/sidebar_header.jsx50
-rw-r--r--web/react/components/user_profile.jsx4
-rw-r--r--web/react/components/user_settings.jsx21
-rw-r--r--web/react/stores/channel_store.jsx53
-rw-r--r--web/react/stores/user_store.jsx2
-rw-r--r--web/react/utils/utils.jsx31
-rw-r--r--web/sass-files/sass/partials/_base.scss8
-rw-r--r--web/sass-files/sass/partials/_files.scss1
-rw-r--r--web/sass-files/sass/partials/_headers.scss31
-rw-r--r--web/sass-files/sass/partials/_modal.scss3
-rw-r--r--web/sass-files/sass/partials/_navbar.scss1
-rw-r--r--web/sass-files/sass/partials/_popover.scss9
-rw-r--r--web/sass-files/sass/partials/_post.scss2
-rw-r--r--web/sass-files/sass/partials/_responsive.scss13
-rw-r--r--web/sass-files/sass/partials/_search.scss13
-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/styles.scss3
-rw-r--r--web/static/config/config.js4
-rw-r--r--web/static/images/dropdown-icon.pngbin2877 -> 0 bytes
-rw-r--r--web/templates/head.html3
40 files changed, 309 insertions, 228 deletions
diff --git a/api/context.go b/api/context.go
index bea0fbeff..054e42e2e 100644
--- a/api/context.go
+++ b/api/context.go
@@ -265,6 +265,16 @@ func (c *Context) IsSystemAdmin() bool {
return false
}
+func (c *Context) IsTeamAdmin(userId string) bool {
+ if uresult := <-Srv.Store.User().Get(userId); uresult.Err != nil {
+ c.Err = uresult.Err
+ return false
+ } else {
+ user := uresult.Data.(*model.User)
+ return strings.Contains(c.Session.Roles, model.ROLE_ADMIN) && user.TeamId == c.Session.TeamId
+ }
+}
+
func (c *Context) RemoveSessionCookie(w http.ResponseWriter) {
sessionCache.Remove(c.Session.Id)
diff --git a/api/post.go b/api/post.go
index 02f997166..efca2f570 100644
--- a/api/post.go
+++ b/api/post.go
@@ -634,16 +634,17 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) {
cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId)
pchan := Srv.Store.Post().Get(postId)
- if !c.HasPermissionsToChannel(cchan, "deletePost") {
- return
- }
-
if result := <-pchan; result.Err != nil {
c.Err = result.Err
return
} else {
+
post := result.Data.(*model.PostList).Posts[postId]
+ if !c.HasPermissionsToChannel(cchan, "deletePost") && !c.IsTeamAdmin(post.UserId){
+ return
+ }
+
if post == nil {
c.SetInvalidParam("deletePost", "postId")
return
@@ -655,7 +656,7 @@ func deletePost(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if post.UserId != c.Session.UserId {
+ if post.UserId != c.Session.UserId && !strings.Contains(c.Session.Roles,model.ROLE_ADMIN) {
c.Err = model.NewAppError("deletePost", "You do not have the appropriate permissions", "")
c.Err.StatusCode = http.StatusForbidden
return
diff --git a/api/post_test.go b/api/post_test.go
index 970307759..5009ff54d 100644
--- a/api/post_test.go
+++ b/api/post_test.go
@@ -483,6 +483,10 @@ func TestDeletePosts(t *testing.T) {
team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+ userAdmin := &model.User{TeamId: team.Id, Email: team.Email, FullName: "Corey Hulen", Password: "pwd"}
+ userAdmin = Client.Must(Client.CreateUser(userAdmin, "")).Data.(*model.User)
+ store.Must(Srv.Store.User().VerifyEmail(userAdmin.Id))
+
user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", FullName: "Corey Hulen", Password: "pwd"}
user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user1.Id))
@@ -521,8 +525,16 @@ func TestDeletePosts(t *testing.T) {
r2 := Client.Must(Client.GetPosts(channel1.Id, 0, 10, "")).Data.(*model.PostList)
if len(r2.Posts) != 4 {
- t.Fatal("should have returned 5 items")
+ t.Fatal("should have returned 4 items")
}
+
+ time.Sleep(10 * time.Millisecond)
+ post4 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
+ post4 = Client.Must(Client.CreatePost(post4)).Data.(*model.Post)
+
+ Client.LoginByEmail(team.Domain, userAdmin.Email, "pwd")
+
+ Client.Must(Client.DeletePost(channel1.Id, post4.Id))
}
func TestEmailMention(t *testing.T) {
diff --git a/api/templates/reset_body.html b/api/templates/reset_body.html
index 3a5d62ab4..4b5976663 100644
--- a/api/templates/reset_body.html
+++ b/api/templates/reset_body.html
@@ -18,7 +18,7 @@
<tr>
<td style="border-bottom: 1px solid #ddd; padding: 0 0 20px;">
<h2 style="font-weight: normal; margin-top: 10px;">You requested a password reset</h2>
- <p>To change your password, click below.<br>If you did not mean to reset your password, please ignore this email and your password will remain the same.</p>
+ <p>To change your password, click "Reset Password" below.<br>If you did not mean to reset your password, please ignore this email and your password will remain the same.</p>
<p style="margin: 20px 0 15px">
<a href="{{.Props.ResetUrl}}" style="background: #2389D7; border-radius: 3px; color: #fff; border: none; outline: none; min-width: 200px; padding: 15px 25px; font-size: 14px; font-family: inherit; cursor: pointer; -webkit-appearance: none;text-decoration: none;">Reset Password</a>
</p>
diff --git a/api/user.go b/api/user.go
index 0c63868b3..3c0062f8c 100644
--- a/api/user.go
+++ b/api/user.go
@@ -729,7 +729,7 @@ func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- Srv.Store.User().UpdateUpdateAt(c.Session.UserId)
+ Srv.Store.User().UpdateLastPictureUpdate(c.Session.UserId)
c.LogAudit("")
}
diff --git a/model/user.go b/model/user.go
index b94ceb899..c5a4d846d 100644
--- a/model/user.go
+++ b/model/user.go
@@ -45,6 +45,7 @@ type User struct {
Props StringMap `json:"props"`
NotifyProps StringMap `json:"notify_props"`
LastPasswordUpdate int64 `json:"last_password_update"`
+ LastPictureUpdate int64 `json:"last_picture_update"`
}
// IsValid validates the user and returns an error if it isn't configured
diff --git a/store/sql_user_store.go b/store/sql_user_store.go
index 77470946c..665e4d697 100644
--- a/store/sql_user_store.go
+++ b/store/sql_user_store.go
@@ -37,6 +37,7 @@ func NewSqlUserStore(sqlStore *SqlStore) UserStore {
}
func (s SqlUserStore) UpgradeSchemaIfNeeded() {
+ s.CreateColumnIfNotExists("Users","LastPictureUpdate", "LastPasswordUpdate", "bigint(20)", "0")
}
func (us SqlUserStore) CreateIndexesIfNotExists() {
@@ -120,6 +121,7 @@ func (us SqlUserStore) Update(user *model.User, allowActiveUpdate bool) StoreCha
user.AuthData = oldUser.AuthData
user.Password = oldUser.Password
user.LastPasswordUpdate = oldUser.LastPasswordUpdate
+ user.LastPictureUpdate = oldUser.LastPictureUpdate
user.TeamId = oldUser.TeamId
user.LastActivityAt = oldUser.LastActivityAt
user.LastPingAt = oldUser.LastPingAt
@@ -150,13 +152,15 @@ func (us SqlUserStore) Update(user *model.User, allowActiveUpdate bool) StoreCha
return storeChannel
}
-func (us SqlUserStore) UpdateUpdateAt(userId string) StoreChannel {
+func (us SqlUserStore) UpdateLastPictureUpdate(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
- if _, err := us.GetMaster().Exec("UPDATE Users SET UpdateAt = ? WHERE Id = ?", model.GetMillis(), userId); err != nil {
+ curTime := model.GetMillis()
+
+ if _, err := us.GetMaster().Exec("UPDATE Users SET LastPictureUpdate = ?, UpdateAt = ? WHERE Id = ?", curTime, curTime, userId); err != nil {
result.Err = model.NewAppError("SqlUserStore.UpdateUpdateAt", "We couldn't update the update_at", "user_id="+userId)
} else {
result.Data = userId
diff --git a/store/store.go b/store/store.go
index 0ed045788..9faa6a9d7 100644
--- a/store/store.go
+++ b/store/store.go
@@ -77,7 +77,7 @@ type PostStore interface {
type UserStore interface {
Save(user *model.User) StoreChannel
Update(user *model.User, allowRoleUpdate bool) StoreChannel
- UpdateUpdateAt(userId string) StoreChannel
+ UpdateLastPictureUpdate(userId string) StoreChannel
UpdateLastPingAt(userId string, time int64) StoreChannel
UpdateLastActivityAt(userId string, time int64) StoreChannel
UpdateUserAndSessionActivity(userId string, sessionId string, time int64) StoreChannel
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index fe381a59e..2e430489f 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -53,12 +53,12 @@ var PopoverListMembers = React.createClass({
});
members.forEach(function(m) {
- popoverHtml += "<div style='white-space: nowrap'>" + m.username + "</div>";
+ popoverHtml += "<div class='text--nowrap'>" + m.username + "</div>";
});
}
return (
- <div style={{cursor : "pointer"}} id="member_popover" data-toggle="popover" data-content={popoverHtml} 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>
@@ -142,10 +142,10 @@ module.exports = React.createClass({
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 = this.state.channel.display_name;
- var channelName = this.state.channel.name;
+ var channel = this.state.channel;
+ var description = utils.textToJsx(channel.description, {"singleline": true, "noMentionHighlight": true});
+ var popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>);
+ var channelTitle = channel.display_name;
var currentId = UserStore.getCurrentId();
var isAdmin = this.state.memberChannel.roles.indexOf("admin") > -1 || this.state.memberTeam.roles.indexOf("admin") > -1;
var isDirect = (this.state.channel.type === 'D');
@@ -169,23 +169,26 @@ module.exports = React.createClass({
<span className="glyphicon glyphicon-chevron-down header-dropdown__icon"></span>
</a>
<ul className="dropdown-menu" role="menu" aria-labelledby="channel_header_dropdown">
- <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_info" data-channelid={this.state.channel.id} href="#">View Info</a></li>
- <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_info" data-channelid={channel.id} href="#">View Info</a></li>
+ { !ChannelStore.isDefault(channel) ?
+ <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_invite" href="#">Add Members</a></li>
+ : null
+ }
+ { isAdmin && !ChannelStore.isDefault(channel) ?
<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={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={channelTitle} 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="#edit_channel" data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set Channel Description...</a></li>
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#channel_notifications" data-title={channel.display_name} data-channelid={channel.id}>Notification Preferences</a></li>
+ { isAdmin && !ChannelStore.isDefault(channel) ?
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#rename_channel" data-display={channel.display_name} data-name={channel.name} data-channelid={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={channelTitle} data-channelid={this.state.channel.id}>Delete Channel...</a></li>
+ { isAdmin && !ChannelStore.isDefault(channel) ?
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#delete_channel" data-title={channel.display_name} data-channelid={channel.id}>Delete Channel...</a></li>
: null
}
- { channelName != Constants.DEFAULT_CHANNEL ?
+ { !ChannelStore.isDefault(channel) ?
<li role="presentation"><a role="menuitem" href="#" onClick={this.handleLeave}>Leave Channel</a></li>
: null
}
@@ -197,14 +200,13 @@ module.exports = React.createClass({
<a href="#"><strong className="heading">{channelTitle}</strong></a>
}
</th>
- <th><PopoverListMembers members={this.state.users} channelId={this.state.channel.id} /></th>
+ <th><PopoverListMembers members={this.state.users} channelId={channel.id} /></th>
<th className="search-bar__container"><NavbarSearchBox /></th>
<th>
- <div className="dropdown" style={{marginLeft:5, marginRight:10}}>
+ <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>
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/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/navbar.jsx b/web/react/components/navbar.jsx
index 35f7d9044..78cf7d8b8 100644
--- a/web/react/components/navbar.jsx
+++ b/web/react/components/navbar.jsx
@@ -2,21 +2,20 @@
// See License.txt for license information.
-var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx');
-var Sidebar = require('./sidebar.jsx');
var UserStore = require('../stores/user_store.jsx');
-var SocketStore = require('../stores/socket_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
-var Constants = require('../utils/constants.jsx');
+
var UserProfile = require('./user_profile.jsx');
var MessageWrapper = require('./message_wrapper.jsx');
+
+var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
+var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
function getCountsStateFromStores() {
-
var count = 0;
var channels = ChannelStore.getAll();
var members = ChannelStore.getAllMembers();
@@ -34,7 +33,7 @@ function getCountsStateFromStores() {
}
});
- return { count: count }
+ return { count: count };
}
var NotifyCounts = React.createClass({
@@ -54,11 +53,10 @@ var NotifyCounts = React.createClass({
return getCountsStateFromStores();
},
render: function() {
- if (this.state.count == 0) {
- return (<span></span>);
- }
- else {
- return (<span className="badge badge-notify">{ this.state.count }</span>);
+ if (this.state.count) {
+ return <span className="badge badge-notify">{ this.state.count }</span>;
+ } else {
+ return null;
}
}
});
@@ -66,25 +64,25 @@ var NotifyCounts = React.createClass({
var NavbarLoginForm = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
- var state = { }
+ var state = { };
var domain = this.refs.domain.getDOMNode().value.trim();
if (!domain) {
- state.server_error = "A domain is required"
+ state.server_error = "A domain is required";
this.setState(state);
return;
}
var email = this.refs.email.getDOMNode().value.trim();
if (!email) {
- state.server_error = "An email is required"
+ state.server_error = "An email is required";
this.setState(state);
return;
}
var password = this.refs.password.getDOMNode().value.trim();
if (!password) {
- state.server_error = "A password is required"
+ state.server_error = "A password is required";
this.setState(state);
return;
}
@@ -105,7 +103,7 @@ var NavbarLoginForm = React.createClass({
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?domain=' + encodeURIComponent(domain) + '&email=' + encodeURIComponent(email);
@@ -159,13 +157,14 @@ function getStateFromStores() {
}
module.exports = React.createClass({
+ displayName: 'Navbar',
+
componentDidMount: function() {
ChannelStore.addChangeListener(this._onChange);
ChannelStore.addExtraInfoChangeListener(this._onChange);
- var self = this;
- $('.inner__wrap').click(self.hideSidebars);
+ $('.inner__wrap').click(this.hideSidebars);
- $('body').on('click.infopopover', function(e){
+ $('body').on('click.infopopover', function(e) {
if ($(e.target).attr('data-toggle') !== 'popover'
&& $(e.target).parents('.popover.in').length === 0) {
$('.info-popover').popover('hide');
@@ -181,13 +180,13 @@ module.exports = React.createClass({
},
handleLeave: function(e) {
client.leaveChannel(this.state.channel.id,
- function(data) {
+ function() {
AsyncClient.getChannels(true);
window.location.href = '/channels/town-square';
- }.bind(this),
+ },
function(err) {
AsyncClient.dispatchError(err, "handleLeave");
- }.bind(this)
+ }
);
},
hideSidebars: function(e) {
@@ -204,7 +203,7 @@ module.exports = React.createClass({
});
if (e.target.className != 'navbar-toggle' && e.target.className != 'icon-bar') {
- $('.inner__wrap').removeClass('move--right').removeClass('move--left').removeClass('move--left-small');
+ $('.inner__wrap').removeClass('move--right move--left move--left-small');
$('.sidebar--left').removeClass('move--right');
$('.sidebar--right').removeClass('move--left');
$('.sidebar--menu').removeClass('move--left');
@@ -229,24 +228,22 @@ module.exports = React.createClass({
render: function() {
var currentId = UserStore.getCurrentId();
- var channelName = "";
var popoverContent = "";
var channelTitle = this.props.teamName;
var isAdmin = false;
var isDirect = false;
var description = ""
+ var channel = this.state.channel;
- if (this.state.channel) {
- var channel = this.state.channel;
- description = utils.textToJsx(this.state.channel.description, {"singleline": true, "noMentionHighlight": true});
- popoverContent = React.renderToString(<MessageWrapper message={this.state.channel.description}/>);
- channelName = this.state.channel.name;
+ if (channel) {
+ description = utils.textToJsx(channel.description, {"singleline": true, "noMentionHighlight": true});
+ popoverContent = React.renderToString(<MessageWrapper message={channel.description}/>);
isAdmin = this.state.member.roles.indexOf("admin") > -1;
if (channel.type === 'O') {
- channelTitle = this.state.channel.display_name;
+ channelTitle = channel.display_name;
} else if (channel.type === 'P') {
- channelTitle = this.state.channel.display_name;
+ channelTitle = channel.display_name;
} else if (channel.type === 'D') {
isDirect = true;
if (this.state.users.length > 1) {
@@ -258,12 +255,11 @@ module.exports = React.createClass({
}
}
- if(this.state.channel.description.length == 0){
- popoverContent = React.renderToString(<div>No channel description yet. <br /><a href='#' data-toggle='modal' data-desc={this.state.channel.description} data-title={this.state.channel.display_name} data-channelid={this.state.channel.id} data-target='#edit_channel'>Click here</a> to add one.</div>);
+ if (channel.description.length == 0) {
+ popoverContent = React.renderToString(<div>No channel description yet. <br /><a href='#' data-toggle='modal' data-desc={channel.description} data-title={channel.display_name} data-channelid={channel.id} data-target='#edit_channel'>Click here</a> to add one.</div>);
}
}
- var loginForm = currentId == null ? <NavbarLoginForm /> : null;
var navbar_collapse_button = currentId != null ? null :
<button type="button" className="navbar-toggle" data-toggle="collapse" data-target="#navbar-collapse-1">
<span className="sr-only">Toggle sidebar</span>
@@ -292,60 +288,61 @@ module.exports = React.createClass({
{ navbar_collapse_button }
{ sidebar_collapse_button }
{ right_sidebar_collapse_button }
- { !isDirect && this.state.channel ?
- <div className="navbar-brand">
- <div className="dropdown">
- <div data-toggle="popover" data-content={popoverContent} className="description info-popover"></div>
- <a href="#" className="dropdown-toggle theme" type="button" id="channel_header_dropdown" data-toggle="dropdown" aria-expanded="true">
- <strong className="heading">{channelTitle} </strong>
- <span className="glyphicon glyphicon-chevron-down header-dropdown__icon"></span>
- </a>
- <ul className="dropdown-menu" role="menu" aria-labelledby="channel_header_dropdown">
- <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>
- : ""
- }
- <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>
- { 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>
- : ""
- }
- { 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>
- : ""
- }
- { channelName != Constants.DEFAULT_CHANNEL ?
- <li role="presentation"><a role="menuitem" href="#" onClick={this.handleLeave}>Leave Channel</a></li>
- : ""
- }
- </ul>
+ { !isDirect && channel ?
+ <div className="navbar-brand">
+ <div className="dropdown">
+ <div data-toggle="popover" data-content={popoverContent} className="description info-popover"></div>
+ <a href="#" className="dropdown-toggle theme" type="button" id="channel_header_dropdown" data-toggle="dropdown" aria-expanded="true">
+ <span className="heading">{channelTitle} </span>
+ <span className="glyphicon glyphicon-chevron-down header-dropdown__icon"></span>
+ </a>
+ <ul className="dropdown-menu" role="menu" aria-labelledby="channel_header_dropdown">
+ { !ChannelStore.isDefault(channel) ?
+ <li role="presentation"><a role="menuitem" data-toggle="modal" data-target="#channel_invite" href="#">Add Members</a></li>
+ : null
+ }
+ { isAdmin && !ChannelStore.isDefault(channel) ?
+ <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={channel.description} data-title={channel.display_name} data-channelid={channel.id}>Set Channel Description...</a></li>
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#channel_notifications" data-title={channel.display_name} data-channelid={channel.id}>Notification Preferences</a></li>
+ { isAdmin && !ChannelStore.isDefault(channel) ?
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#rename_channel" data-display={channel.display_name} data-name={channel.name} data-channelid={channel.id}>Rename Channel...</a></li>
+ : null
+ }
+ { isAdmin && !ChannelStore.isDefault(channel) ?
+ <li role="presentation"><a role="menuitem" href="#" data-toggle="modal" data-target="#delete_channel" data-title={channel.display_name} data-channelid={channel.id}>Delete Channel...</a></li>
+ : null
+ }
+ { !ChannelStore.isDefault(channel) ?
+ <li role="presentation"><a role="menuitem" href="#" onClick={this.handleLeave}>Leave Channel</a></li>
+ : null
+ }
+ </ul>
+ </div>
</div>
- </div>
- : "" }
- { isDirect && this.state.channel ?
+ : null
+ }
+ { isDirect && channel ?
<div className="navbar-brand">
- <strong>
- <a href="#"><strong className="heading">{channelTitle}</strong></a>
- </strong>
+ <a href="#" className="heading">{ channelTitle }</a>
</div>
- : "" }
- { !this.state.channel ?
+ : null }
+ { !channel ?
<div className="navbar-brand">
- <strong>
- <a href="/"><strong className="heading">{ channelTitle }</strong></a>
- </strong>
+ <a href="/" className="heading">{ channelTitle }</a>
</div>
- : "" }
- </div>
- <div className="collapse navbar-collapse" id="navbar-collapse-1">
- { loginForm }
+ : null }
</div>
+ { !currentId ?
+ <div className="collapse navbar-collapse" id="navbar-collapse-1">
+ <NavbarLoginForm />
+ </div>
+ : null
+ }
</div>
</nav>
);
-}
+ }
});
-
-
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_info.jsx b/web/react/components/post_info.jsx
index cf01747f0..48efa95ba 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -11,6 +11,7 @@ module.exports = React.createClass({
render: function() {
var post = this.props.post;
var isOwner = UserStore.getCurrentId() == post.user_id;
+ var isAdmin = UserStore.getCurrentUser().roles.indexOf("admin") > -1
var type = "Post"
if (post.root_id.length > 0) {
@@ -36,7 +37,7 @@ module.exports = React.createClass({
<ul className="dropdown-menu" role="menu">
{ isOwner ? <li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#edit_post" data-title={type} data-message={post.message} data-postid={post.id} data-channelid={post.channel_id} data-comments={type === "Post" ? this.props.commentCount : 0}>Edit</a></li>
: "" }
- { isOwner ? <li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#delete_post" data-title={type} data-postid={post.id} data-channelid={post.channel_id} data-comments={type === "Post" ? this.props.commentCount : 0}>Delete</a></li>
+ { isOwner || isAdmin ? <li role="presentation"><a href="#" role="menuitem" data-toggle="modal" data-target="#delete_post" data-title={type} data-postid={post.id} data-channelid={post.channel_id} data-comments={type === "Post" ? this.props.commentCount : 0}>Delete</a></li>
: "" }
{ this.props.allowReply === "true" ? <li role="presentation"><a className="reply-link theme" href="#" onClick={this.props.handleCommentClick}>Reply</a></li>
: "" }
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 98491976f..573799a19 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -6,7 +6,6 @@ var ChannelStore = require('../stores/channel_store.jsx');
var UserStore = require('../stores/user_store.jsx');
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');
@@ -341,7 +340,7 @@ module.exports = React.createClass({
}
}
- if (channel.name === Constants.DEFAULT_CHANNEL) {
+ if (ChannelStore.isDefault(channel)) {
more_messages = (
<div className="channel-intro">
<h4 className="channel-intro-title">Welcome</h4>
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 ab7e99d60..f21f0cd58 100644
--- a/web/react/components/search_bar.jsx
+++ b/web/react/components/search_bar.jsx
@@ -92,7 +92,7 @@ module.exports = React.createClass({
render: function() {
return (
<div>
- <div className="sidebar__collapse" onClick={this.handleClose}></div>
+ <div className="sidebar__collapse" onClick={this.handleClose}>Cancel</div>
<span className="glyphicon glyphicon-search sidebar__search-icon"></span>
<form role="form" className="search__form relative-div" onSubmit={this.handleSubmit}>
<input
diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx
index 54858a04d..45c9ca629 100644
--- a/web/react/components/sidebar_header.jsx
+++ b/web/react/components/sidebar_header.jsx
@@ -37,17 +37,13 @@ var NavbarDropdown = React.createClass({
var invite_link = "";
var manage_link = "";
var rename_link = "";
- var currentUser = UserStore.getCurrentUser()
+ var currentUser = UserStore.getCurrentUser();
var isAdmin = false;
if (currentUser != null) {
isAdmin = currentUser.roles.indexOf("admin") > -1;
- invite_link = (
- <li>
- <a href="#" data-toggle="modal" data-target="#invite_member">Invite New Member</a>
- </li>
- );
+ invite_link = ( <li> <a href="#" data-toggle="modal" data-target="#invite_member">Invite New Member</a> </li>);
if (this.props.teamType == "O") {
team_link = (
@@ -59,16 +55,8 @@ var NavbarDropdown = React.createClass({
}
if (isAdmin) {
- manage_link = (
- <li>
- <a href="#" data-toggle="modal" data-target="#team_members">Manage Team</a>
- </li>
- );
- rename_link = (
- <li>
- <a href="#" data-toggle="modal" data-target="#rename_team_link">Rename</a>
- </li>
- );
+ manage_link = ( <li> <a href="#" data-toggle="modal" data-target="#team_members">Manage Team</a> </li>);
+ rename_link = ( <li> <a href="#" data-toggle="modal" data-target="#rename_team_link">Rename</a> </li>);
}
var teams = [];
@@ -91,11 +79,11 @@ 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>
- { isAdmin ? <li><a href="#" data-toggle="modal" data-target="#team_settings">Team Settings</a></li> : "" }
+ { isAdmin ? <li><a href="#" data-toggle="modal" data-target="#team_settings">Team Settings</a></li> : null }
{ invite_link }
{ team_link }
{ manage_link }
@@ -113,28 +101,30 @@ var NavbarDropdown = React.createClass({
});
module.exports = React.createClass({
- handleSubmit: function(e) {
- e.preventDefault();
- },
- getInitialState: function() {
- return { };
+ displayName: 'SidebarHeader',
+
+ getDefaultProps: function() {
+ return {
+ teamName: config.SiteName
+ };
},
+
render: function() {
- var teamName = this.props.teamName ? this.props.teamName : config.SiteName;
- var me = UserStore.getCurrentUser()
+ var me = UserStore.getCurrentUser();
+
+ if (!me) {
+ return null;
+ }
return (
<div className="team__header theme">
<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 className="user__name">{'@' + me.username}</div>
+ <a className="team__name" href="/channels/town-square">{this.props.teamName}</a>
</div>
<NavbarDropdown teamType={this.props.teamType} />
</div>
);
}
});
-
-
-
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 06d8d0208..38e4b1aea 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)
@@ -820,6 +820,7 @@ var GeneralTab = React.createClass({
client.uploadProfileImage(formData,
function(data) {
this.submitActive = false;
+ AsyncClient.getMe();
window.location.reload();
}.bind(this),
function(err) {
@@ -989,7 +990,7 @@ var GeneralTab = React.createClass({
<SettingPicture
title="Profile Picture"
submit={this.submitPicture}
- src={"/api/v1/users/" + user.id + "/image"}
+ src={"/api/v1/users/" + user.id + "/image?time=" + user.last_picture_update}
server_error={server_error}
client_error={client_error}
updateSection={function(e){self.updateSection("");e.preventDefault();}}
@@ -1000,10 +1001,14 @@ var GeneralTab = React.createClass({
);
} else {
+ var minMessage = "Click Edit to upload an image.";
+ if (user.last_picture_update) {
+ minMessage = "Image last updated " + utils.displayDate(user.last_picture_update)
+ }
pictureSection = (
<SettingItemMin
title="Profile Picture"
- describe="Picture inside."
+ describe={minMessage}
updateSection={function(){self.updateSection("picture");}}
/>
);
diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx
index 4a27e5f17..a97f13391 100644
--- a/web/react/stores/channel_store.jsx
+++ b/web/react/stores/channel_store.jsx
@@ -44,40 +44,24 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
removeExtraInfoChangeListener: function(callback) {
this.removeListener(EXTRA_INFO_EVENT, callback);
},
- get: function(id) {
- var current = null;
- var c = this._getChannels();
-
- c.some(function(channel) {
- if (channel.id == id) {
- current = channel;
- return true;
+ findFirstBy: function(field, value) {
+ var channels = this._getChannels();
+ for (var i = 0; i < channels.length; i++) {
+ if (channels[i][field] == value) {
+ return channels[i];
}
- return false;
- });
+ }
- return current;
+ return null;
+ },
+ get: function(id) {
+ return this.findFirstBy('id', id);
},
getMember: function(id) {
- var current = null;
return this.getAllMembers()[id];
},
getByName: function(name) {
- var current = null;
- var c = this._getChannels();
-
- c.some(function(channel) {
- if (channel.name == name) {
- current = channel;
- return true;
- }
-
- return false;
-
- });
-
- return current;
-
+ return this.findFirstBy('name', name);
},
getAll: function() {
return this._getChannels();
@@ -120,7 +104,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
getCurrent: function() {
var currentId = this.getCurrentId();
- if (currentId != null)
+ if (currentId)
return this.get(currentId);
else
return null;
@@ -128,7 +112,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
getCurrentMember: function() {
var currentId = ChannelStore.getCurrentId();
- if (currentId != null)
+ if (currentId)
return this.getAllMembers()[currentId];
else
return null;
@@ -143,7 +127,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
var currentId = ChannelStore.getCurrentId();
var extra = null;
- if (currentId != null)
+ if (currentId)
extra = this._getExtraInfos()[currentId];
if (extra == null)
@@ -154,7 +138,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
getExtraInfo: function(channel_id) {
var extra = null;
- if (channel_id != null)
+ if (channel_id)
extra = this._getExtraInfos()[channel_id];
if (extra == null)
@@ -192,7 +176,10 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
},
_getExtraInfos: function() {
return BrowserStore.getItem("extra_infos", {});
- }
+ },
+ isDefault: function(channel) {
+ return channel.name == Constants.DEFAULT_CHANNEL;
+ }
});
ChannelStore.dispatchToken = AppDispatcher.register(function(payload) {
@@ -231,4 +218,4 @@ ChannelStore.dispatchToken = AppDispatcher.register(function(payload) {
}
});
-module.exports = ChannelStore;
+module.exports = ChannelStore; \ No newline at end of file
diff --git a/web/react/stores/user_store.jsx b/web/react/stores/user_store.jsx
index 93ddfec70..dd207ca80 100644
--- a/web/react/stores/user_store.jsx
+++ b/web/react/stores/user_store.jsx
@@ -177,7 +177,7 @@ var UserStore = assign({}, EventEmitter.prototype, {
},
getCurrentMentionKeys: function() {
var user = this.getCurrentUser();
- if (user.notify_props && user.notify_props.mention_keys) {
+ if (user && user.notify_props && user.notify_props.mention_keys) {
var keys = user.notify_props.mention_keys.split(',');
if (user.full_name.length > 0 && user.notify_props.first_name === "true") {
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 19c074606..7186251e7 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -198,7 +198,13 @@ module.exports.getTimestamp = function() {
}
var testUrlMatch = function(text) {
- var urlMatcher = new Autolinker.matchParser.MatchParser;
+ var urlMatcher = new Autolinker.matchParser.MatchParser({
+ urls: true,
+ emails: false,
+ twitter: false,
+ phone: false,
+ hashtag: false,
+ });
var result = [];
var replaceFn = function(match) {
var linkData = {};
@@ -303,18 +309,25 @@ var getYoutubeEmbed = function(link) {
};
var success = function(data) {
- $('.video-uploader.'+youtubeId).html(data.data.uploader);
- $('.video-title.'+youtubeId).find('a').html(data.data.title);
+ if(!data.items.length || !data.items[0].snippet) {
+ return;
+ }
+ var metadata = data.items[0].snippet;
+ $('.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);
$(".post-list-holder-by-time").perfectScrollbar('update');
};
- $.ajax({
- async: true,
- url: 'https://gdata.youtube.com/feeds/api/videos/'+youtubeId+'?v=2&alt=jsonc',
- type: 'GET',
- success: success
- });
+ if(config.GoogleDeveloperKey) {
+ $.ajax({
+ async: true,
+ url: "https://www.googleapis.com/youtube/v3/videos",
+ type: 'GET',
+ data: {part:"snippet", id:youtubeId, key:config.GoogleDeveloperKey},
+ success: success
+ });
+ }
return (
<div className="post-comment">
diff --git a/web/sass-files/sass/partials/_base.scss b/web/sass-files/sass/partials/_base.scss
index 8f4ff7b60..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 {
@@ -127,7 +133,7 @@ div.theme {
}
.glyphicon-refresh-animate {
- @include animation(spin .7s infinite linear);
+ @include animation(spin .7s infinite linear);
}
.black-bg {
diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss
index c584c240d..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%;
}
diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss
index 338f5ceb4..c2740891a 100644
--- a/web/sass-files/sass/partials/_headers.scss
+++ b/web/sass-files/sass/partials/_headers.scss
@@ -95,11 +95,7 @@
}
}
.dropdown__icon {
- background: url("../images/dropdown-icon.png");
- width: 4px;
- height: 16px;
- @include background-size(100% 100%);
- display: inline-block;
+ fill: #fff;
}
}
.user__picture {
@@ -161,9 +157,10 @@
font-size: 14px;
line-height: 50px;
#member_popover {
+ margin-right: 5px;
width: 45px;
color: #999;
-
+ cursor: pointer;
}
&.alt {
margin: 0;
@@ -230,3 +227,25 @@
top: 1px;
}
}
+
+.channel-header__links {
+ height: 32px;
+ vertical-align: top;
+ display: inline-block;
+ width: 15px;
+ margin: 9px 4px 3px 0;
+ &:hover {
+ svg {
+ fill: #888;
+ }
+ }
+ a {
+ height: 100%;
+ display: block;
+ }
+ svg {
+ vertical-align: top;
+ margin-top: 8px;
+ fill: #AAA;
+ }
+} \ 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 707e71cf0..f359037c5 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -154,7 +154,8 @@
background: #FFF;
position: relative;
max-width: 90%;
- min-width: 280px;
+ min-height: 50px;
+ min-width: 320px;
@include border-radius(3px);
display: table;
margin: 0 auto;
diff --git a/web/sass-files/sass/partials/_navbar.scss b/web/sass-files/sass/partials/_navbar.scss
index 62864afb7..6d8f11ce3 100644
--- a/web/sass-files/sass/partials/_navbar.scss
+++ b/web/sass-files/sass/partials/_navbar.scss
@@ -43,6 +43,7 @@
font-size: 16px;
.heading {
margin-right: 3px;
+ font-weight: 600;
color: #fff;
}
.header-dropdown__icon {
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 40ed40b49..465c50296 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -425,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 e01739240..a33d69378 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 {
@@ -432,13 +432,16 @@
color: #fff;
.search__form {
border: none;
- padding: 0 10px 0 30px;
+ padding: 0 60px 0 25px;
.form-control {
+ line-height: 31px;
background: none;
color: #fff;
- border-bottom: 1px solid rgba(#fff, 0.7);
border-radius: 0;
- padding: 0 10px 0 23px;
+ padding: 0 10px 0;
+ @include input-placeholder {
+ color: rgba(#fff, 0.6);
+ }
}
::-webkit-input-placeholder {
color: #fff;
@@ -574,6 +577,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/_search.scss b/web/sass-files/sass/partials/_search.scss
index 8d51d00c0..d4a4da243 100644
--- a/web/sass-files/sass/partials/_search.scss
+++ b/web/sass-files/sass/partials/_search.scss
@@ -2,21 +2,20 @@
padding: 8px 8px 8px 0;
}
.sidebar__collapse {
- width: 20px;
- height: 30px;
+ width: auto;
+ height: auto;
position: absolute;
- top: 10px;
- left: 6px;
+ top: 17px;
+ right: 15px;
cursor: pointer;
- background: url("../images/arrow-left.png") center no-repeat;
- @include background-size(10px 15px);
z-index: 5;
display: none;
}
.sidebar__search-icon {
position: absolute;
- left: 40px;
+ left: 15px;
top: 18px;
+ font-size: 16px;
@include opacity(0.8);
display: none;
}
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/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/config/config.js b/web/static/config/config.js
index 45c713da2..0d564b77e 100644
--- a/web/static/config/config.js
+++ b/web/static/config/config.js
@@ -16,6 +16,10 @@ var config = {
RequireInviteNames: false,
AllowSignupDomainsWizard: false,
+ // Google Developer Key (for Youtube API links)
+ // Leave blank to disable
+ GoogleDeveloperKey: "",
+
// Privacy switches
ShowEmail: true,
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
diff --git a/web/templates/head.html b/web/templates/head.html
index ead648571..5b423d487 100644
--- a/web/templates/head.html
+++ b/web/templates/head.html
@@ -31,7 +31,6 @@
<link rel="stylesheet" href="/static/css/styles.css">
<script src="/static/js/perfect-scrollbar-0.6.3.jquery.js"></script>
- <script src="/static/js/bundle.js"></script>
<script type="text/javascript" src="https://www.google.com/jsapi?autoload={'modules':[{'name':'visualization','version':'1','packages':['annotationchart']}]}"></script>
@@ -102,5 +101,7 @@
}
</script>
<!-- Snowplow stops plowing -->
+
+ <script src="/static/js/bundle.js"></script>
</head>
{{end}}