Этот коммит содержится в:
=Corey Hulen
2015-07-15 10:22:05 -08:00
родитель 252d0f3924 38f9e140e9
Коммит e017babc5d
40 изменённых файлов: 421 добавлений и 497 удалений

Просмотреть файл

@@ -13,9 +13,9 @@ import (
"github.com/mattermost/platform/model" "github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils" "github.com/mattermost/platform/utils"
"github.com/nfnt/resize" "github.com/nfnt/resize"
_ "golang.org/x/image/bmp"
"image" "image"
_ "image/gif" _ "image/gif"
_ "golang.org/x/image/bmp"
"image/jpeg" "image/jpeg"
"io" "io"
"net/http" "net/http"
@@ -157,7 +157,7 @@ func fireAndForgetHandleImages(filenames []string, fileData [][]byte, teamId, ch
go func() { go func() {
var thumbnail image.Image var thumbnail image.Image
if imgConfig.Width > int(utils.Cfg.ImageSettings.ThumbnailWidth) { if imgConfig.Width > int(utils.Cfg.ImageSettings.ThumbnailWidth) {
thumbnail = resize.Resize(utils.Cfg.ImageSettings.ThumbnailWidth, utils.Cfg.ImageSettings.ThumbnailHeight, img, resize.NearestNeighbor) thumbnail = resize.Resize(utils.Cfg.ImageSettings.ThumbnailWidth, utils.Cfg.ImageSettings.ThumbnailHeight, img, resize.Lanczos3)
} else { } else {
thumbnail = img thumbnail = img
} }
@@ -182,7 +182,7 @@ func fireAndForgetHandleImages(filenames []string, fileData [][]byte, teamId, ch
go func() { go func() {
var preview image.Image var preview image.Image
if imgConfig.Width > int(utils.Cfg.ImageSettings.PreviewWidth) { if imgConfig.Width > int(utils.Cfg.ImageSettings.PreviewWidth) {
preview = resize.Resize(utils.Cfg.ImageSettings.PreviewWidth, utils.Cfg.ImageSettings.PreviewHeight, img, resize.NearestNeighbor) preview = resize.Resize(utils.Cfg.ImageSettings.PreviewWidth, utils.Cfg.ImageSettings.PreviewHeight, img, resize.Lanczos3)
} else { } else {
preview = img preview = img
} }

Просмотреть файл

@@ -14,6 +14,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"path/filepath"
) )
func InitPost(r *mux.Router) { 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 := model.NewMessage(teamId, post.ChannelId, post.UserId, model.ACTION_POSTED)
message.Add("post", post.ToJson()) 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 { if len(mentionedUsers) != 0 {
message.Add("mentions", model.ArrayToJson(mentionedUsers)) message.Add("mentions", model.ArrayToJson(mentionedUsers))
} }

Просмотреть файл

@@ -729,6 +729,8 @@ func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} }
Srv.Store.User().UpdateUpdateAt(c.Session.UserId)
c.LogAudit("") c.LogAudit("")
} }

Просмотреть файл

@@ -10,7 +10,7 @@ DOCKER SETUP
3. Add a line to your /etc/hosts that goes `<Docker IP> dockerhost` 3. Add a line to your /etc/hosts that goes `<Docker IP> dockerhost`
4. Run `boot2docker shellinit` and copy the export statements to your ~/.bash_profile 4. Run `boot2docker shellinit` and copy the export statements to your ~/.bash_profile
Any issues? Please let us know on our forums at: http://bit.ly/1MY1kul Any issues? Please let us know on our forums at: http://forum.mattermost.org
GO SETUP GO SETUP
@@ -39,4 +39,4 @@ MATTERMOST SETUP
6. Then do `cd platform` and `make test`. Provided the test runs fine, you now have a complete build environment. 6. Then do `cd platform` and `make test`. Provided the test runs fine, you now have a complete build environment.
7. Use `make run` to run your code 7. Use `make run` to run your code
Any issues? Please let us know on our forums at: http://bit.ly/1MY1kul Any issues? Please let us know on our forums at: http://forum.mattermost.org

Просмотреть файл

@@ -35,6 +35,11 @@ func NewSqlPostStore(sqlStore *SqlStore) PostStore {
} }
func (s SqlPostStore) UpgradeSchemaIfNeeded() { 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() { func (s SqlPostStore) CreateIndexesIfNotExists() {

Просмотреть файл

@@ -24,6 +24,7 @@ import (
sqltrace "log" sqltrace "log"
"math/rand" "math/rand"
"os" "os"
"strings"
"time" "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 { 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 { if err != nil {
l4g.Critical("Failed to open sql connection to '%v' err:%v", dataSource, err) l4g.Critical("Failed to open sql connection to '%v' err:%v", dataSource, err)
time.Sleep(time.Second) time.Sleep(time.Second)
@@ -104,7 +112,7 @@ func setupConnection(con_type string, driver string, dataSource string, maxIdle
if driver == "sqlite3" { if driver == "sqlite3" {
dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.SqliteDialect{}} dbmap = &gorp.DbMap{Db: db, TypeConverter: mattermConverter{}, Dialect: gorp.SqliteDialect{}}
} else if driver == "mysql" { } 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 { } else {
l4g.Critical("Failed to create dialect specific driver") l4g.Critical("Failed to create dialect specific driver")
time.Sleep(time.Second) time.Sleep(time.Second)

Просмотреть файл

@@ -150,6 +150,25 @@ func (us SqlUserStore) Update(user *model.User, allowActiveUpdate bool) StoreCha
return storeChannel return storeChannel
} }
func (us SqlUserStore) UpdateUpdateAt(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 {
result.Err = model.NewAppError("SqlUserStore.UpdateUpdateAt", "We couldn't update the update_at", "user_id="+userId)
} else {
result.Data = userId
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (us SqlUserStore) UpdateLastPingAt(userId string, time int64) StoreChannel { func (us SqlUserStore) UpdateLastPingAt(userId string, time int64) StoreChannel {
storeChannel := make(StoreChannel) storeChannel := make(StoreChannel)

Просмотреть файл

@@ -77,6 +77,7 @@ type PostStore interface {
type UserStore interface { type UserStore interface {
Save(user *model.User) StoreChannel Save(user *model.User) StoreChannel
Update(user *model.User, allowRoleUpdate bool) StoreChannel Update(user *model.User, allowRoleUpdate bool) StoreChannel
UpdateUpdateAt(userId string) StoreChannel
UpdateLastPingAt(userId string, time int64) StoreChannel UpdateLastPingAt(userId string, time int64) StoreChannel
UpdateLastActivityAt(userId string, time int64) StoreChannel UpdateLastActivityAt(userId string, time int64) StoreChannel
UpdateUserAndSessionActivity(userId string, sessionId string, time int64) StoreChannel UpdateUserAndSessionActivity(userId string, sessionId string, time int64) StoreChannel

Просмотреть файл

@@ -56,13 +56,13 @@ module.exports = React.createClass({
$(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) { $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
var newState = {}; var newState = {};
if(BrowserStore.getItem('edit_state_transfer')) { if(BrowserStore.getItem('edit_state_transfer')) {
newState = JSON.parse(BrowserStore.getItem('edit_state_transfer')); newState = BrowserStore.getItem('edit_state_transfer');
BrowserStore.removeItem('edit_state_transfer'); BrowserStore.removeItem('edit_state_transfer');
} else { } else {
var button = e.relatedTarget; var button = e.relatedTarget;
newState = { title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments') }; newState = { title: $(button).attr('data-title'), channel_id: $(button).attr('data-channelid'), post_id: $(button).attr('data-postid'), comments: $(button).attr('data-comments') };
} }
self.setState(newState) self.setState(newState);
}); });
PostStore.addSelectedPostChangeListener(this._onChange); PostStore.addSelectedPostChangeListener(this._onChange);
}, },

Просмотреть файл

@@ -4,6 +4,7 @@
var Client = require('../utils/client.jsx'); var Client = require('../utils/client.jsx');
var AsyncClient = require('../utils/async_client.jsx'); var AsyncClient = require('../utils/async_client.jsx');
var Textbox = require('./textbox.jsx'); var Textbox = require('./textbox.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
module.exports = React.createClass({ module.exports = React.createClass({
handleEdit: function(e) { handleEdit: function(e) {
@@ -13,14 +14,14 @@ module.exports = React.createClass({
if (updatedPost.message.length === 0) { if (updatedPost.message.length === 0) {
var tempState = this.state; var tempState = this.state;
delete tempState.editText; delete tempState.editText;
BrowserStore.setItem('edit_state_transfer', JSON.stringify(tempState)); BrowserStore.setItem('edit_state_transfer', tempState);
$("#edit_post").modal('hide'); $("#edit_post").modal('hide');
$("#delete_post").modal('show'); $("#delete_post").modal('show');
return; return;
} }
updatedPost.id = this.state.post_id updatedPost.id = this.state.post_id;
updatedPost.channel_id = this.state.channel_id updatedPost.channel_id = this.state.channel_id;
Client.updatePost(updatedPost, Client.updatePost(updatedPost,
function(data) { function(data) {

24
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>
);
}
});

Просмотреть файл

@@ -23,6 +23,7 @@ module.exports = React.createClass({
var member = this.props.member; var member = this.props.member;
var isAdmin = this.props.isAdmin; var isAdmin = this.props.isAdmin;
var isMemberAdmin = member.roles.indexOf("admin") > -1; var isMemberAdmin = member.roles.indexOf("admin") > -1;
var timestamp = UserStore.getCurrentUser().update_at;
var invite; var invite;
if (member.invited && this.props.handleInvite) { if (member.invited && this.props.handleInvite) {
@@ -53,7 +54,7 @@ module.exports = React.createClass({
return ( return (
<div className="row member-div"> <div className="row member-div">
<img className="post-profile-img pull-left" src={"/api/v1/users/" + member.id + "/image"} height="36" width="36" /> <img className="post-profile-img pull-left" src={"/api/v1/users/" + member.id + "/image?time=" + timestamp} height="36" width="36" />
<span className="member-name">{member.username}</span> <span className="member-name">{member.username}</span>
<span className="member-email">{member.email}</span> <span className="member-email">{member.email}</span>
{ invite } { invite }

Просмотреть файл

@@ -61,7 +61,8 @@ var MemberListTeamItem = React.createClass({
render: function() { 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 style={{ clear: "both" }} className="has-error"><label className='has-error control-label'>{this.state.server_error}</label></div> : null;
var user = this.props.user; var user = this.props.user;
var currentRoles = "Member" var currentRoles = "Member";
var timestamp = UserStore.getCurrentUser().update_at;
if (user.roles.length > 0) { if (user.roles.length > 0) {
currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1); currentRoles = user.roles.charAt(0).toUpperCase() + user.roles.slice(1);
@@ -83,7 +84,7 @@ var MemberListTeamItem = React.createClass({
return ( return (
<div className="row member-div"> <div className="row member-div">
<img className="post-profile-img pull-left" src={"/api/v1/users/" + user.id + "/image"} height="36" width="36" /> <img className="post-profile-img pull-left" src={"/api/v1/users/" + user.id + "/image?time=" + timestamp} height="36" width="36" />
<span className="member-name">{user.full_name.trim() ? user.full_name : user.username}</span> <span className="member-name">{user.full_name.trim() ? user.full_name : user.username}</span>
<span className="member-email">{user.full_name.trim() ? user.username : email}</span> <span className="member-email">{user.full_name.trim() ? user.username : email}</span>
<div className="dropdown member-drop"> <div className="dropdown member-drop">

Просмотреть файл

@@ -1,5 +1,6 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
var UserStore = require("../stores/user_store.jsx");
module.exports = React.createClass({ module.exports = React.createClass({
handleClick: function() { handleClick: function() {
@@ -7,8 +8,9 @@ module.exports = React.createClass({
}, },
render: function() { render: function() {
var icon; var icon;
var timestamp = UserStore.getCurrentUser().update_at;
if (this.props.id != null) { if (this.props.id != null) {
icon = <span><img className="mention-img" src={"/api/v1/users/" + this.props.id + "/image"}/></span>; icon = <span><img className="mention-img" src={"/api/v1/users/" + this.props.id + "/image?time=" + timestamp}/></span>;
} else { } else {
icon = <span><i className="mention-img fa fa-users fa-2x"></i></span>; icon = <span><i className="mention-img fa fa-users fa-2x"></i></span>;
} }

Просмотреть файл

@@ -9,7 +9,12 @@ var Mention = require('./mention.jsx');
var Constants = require('../utils/constants.jsx'); var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes; var ActionTypes = Constants.ActionTypes;
var MAX_HEIGHT_LIST = 292;
var MAX_ITEMS_IN_LIST = 25;
var ITEM_HEIGHT = 36;
module.exports = React.createClass({ module.exports = React.createClass({
displayName: "MentionList",
componentDidMount: function() { componentDidMount: function() {
PostStore.addMentionDataChangeListener(this._onChange); PostStore.addMentionDataChangeListener(this._onChange);
@@ -72,7 +77,7 @@ module.exports = React.createClass({
}, },
render: function() { render: function() {
var mentionText = this.state.mentionText; var mentionText = this.state.mentionText;
if (mentionText === '-1') return (<div/>); if (mentionText === '-1') return null;
var profiles = UserStore.getActiveOnlyProfiles(); var profiles = UserStore.getActiveOnlyProfiles();
var users = []; var users = [];
@@ -100,8 +105,7 @@ module.exports = React.createClass({
var mentions = {}; var mentions = {};
var index = 0; var index = 0;
for (var i = 0; i < users.length; i++) { for (var i = 0; i < users.length && index < MAX_ITEMS_IN_LIST; i++) {
if (Object.keys(mentions).length >= 25) break;
if (this.alreadyMentioned(users[i].username)) continue; if (this.alreadyMentioned(users[i].username)) continue;
var firstName = "", lastName = ""; var firstName = "", lastName = "";
@@ -127,17 +131,20 @@ module.exports = React.createClass({
} }
var numMentions = Object.keys(mentions).length; var numMentions = Object.keys(mentions).length;
if (numMentions < 1) return (<div/>); if (numMentions < 1) return null;
var height = (numMentions*36) + 4; var $mention_tab = $('#'+this.props.id);
var width = $('#'+this.props.id).parent().width(); var maxHeight = Math.min(MAX_HEIGHT_LIST, $mention_tab.offset().top - 10);
var bottom = $(window).height() - $('#'+this.props.id).offset().top; var style = {
var left = $('#'+this.props.id).offset().left; height: Math.min(maxHeight, (numMentions*ITEM_HEIGHT) + 4),
var max_height = $('#'+this.props.id).offset().top - 10; width: $mention_tab.parent().width(),
bottom: $(window).height() - $mention_tab.offset().top,
left: $mention_tab.offset().left
};
return ( return (
<div className="mentions--top" style={{height: height, width: width, bottom: bottom, left: left}}> <div className="mentions--top" style={style}>
<div ref="mentionlist" className="mentions-box" style={{height: height, width: width}}> <div ref="mentionlist" className="mentions-box">
{ mentions } { mentions }
</div> </div>
</div> </div>

Просмотреть файл

@@ -7,6 +7,7 @@ var client = require('../utils/client.jsx');
var asyncClient = require('../utils/async_client.jsx'); var asyncClient = require('../utils/async_client.jsx');
var UserStore = require('../stores/user_store.jsx'); var UserStore = require('../stores/user_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx'); var ChannelStore = require('../stores/channel_store.jsx');
var LoadingScreen = require('./loading_screen.jsx');
function getStateFromStores() { function getStateFromStores() {
return { return {
@@ -16,6 +17,8 @@ function getStateFromStores() {
} }
module.exports = React.createClass({ module.exports = React.createClass({
displayName: "MoreChannelsModal",
componentDidMount: function() { componentDidMount: function() {
ChannelStore.addMoreChangeListener(this._onChange); ChannelStore.addMoreChangeListener(this._onChange);
$(this.refs.modal.getDOMNode()).on('shown.bs.modal', function (e) { $(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-name">{channel.display_name}</p>
<p className="more-channel-description">{channel.description}</p> <p className="more-channel-description">{channel.description}</p>
</td> </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> </tr>
) )
})} })}
@@ -100,15 +103,7 @@ module.exports = React.createClass({
<p className="primary-message">No more channels to join</p> <p className="primary-message">No more channels to join</p>
<p className="secondary-message">Click 'Create New Channel' to make a new one</p> <p className="secondary-message">Click 'Create New Channel' to make a new one</p>
</div>) </div>)
: <div ref="loadingscreen" className="loading-screen loading-screen--channel"> : <LoadingScreen /> }
<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>
}
{ server_error } { server_error }
</div> </div>
<div className="modal-footer"> <div className="modal-footer">

Просмотреть файл

@@ -10,6 +10,7 @@ var UserStore = require('../stores/user_store.jsx');
var ActionTypes = Constants.ActionTypes; var ActionTypes = Constants.ActionTypes;
module.exports = React.createClass({ module.exports = React.createClass({
displayName: "Post",
componentDidMount: function() { componentDidMount: function() {
$('.modal').on('show.bs.modal', function () { $('.modal').on('show.bs.modal', function () {
$('.modal-body').css('overflow-y', 'auto'); $('.modal-body').css('overflow-y', 'auto');
@@ -19,7 +20,7 @@ module.exports = React.createClass({
handleCommentClick: function(e) { handleCommentClick: function(e) {
e.preventDefault(); e.preventDefault();
data = {}; var data = {};
data.order = [this.props.post.id]; data.order = [this.props.post.id];
data.posts = this.props.posts; data.posts = this.props.posts;
@@ -48,7 +49,6 @@ module.exports = React.createClass({
var commentCount = 0; var commentCount = 0;
var commentRootId = parentPost ? post.root_id : post.id; var commentRootId = parentPost ? post.root_id : post.id;
var rootUser = "";
for (var postId in posts) { for (var postId in posts) {
if (posts[postId].root_id == commentRootId) { if (posts[postId].root_id == commentRootId) {
commentCount += 1; 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; 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){ var rootUser = this.props.sameRoot ? "same--root" : "other--root";
rootUser = "same--root";
}
else {
rootUser = "other--root";
}
var postType = ""; var postType = "";
if (type != "Post"){ if (type != "Post"){
@@ -74,14 +69,16 @@ module.exports = React.createClass({
currentUserCss = "current--user"; currentUserCss = "current--user";
} }
var timestamp = UserStore.getCurrentUser().update_at;
return ( return (
<div> <div>
<div id={post.id} className={"post " + this.props.sameUser + " " + rootUser + " " + postType + " " + currentUserCss}> <div id={post.id} className={"post " + this.props.sameUser + " " + rootUser + " " + postType + " " + currentUserCss}>
{ !this.props.hideProfilePic ? { !this.props.hideProfilePic ?
<div className="post-profile-img__container"> <div className="post-profile-img__container">
<img className="post-profile-img" src={"/api/v1/users/" + post.user_id + "/image"} height="36" width="36" /> <img className="post-profile-img" src={"/api/v1/users/" + post.user_id + "/image?time=" + timestamp} height="36" width="36" />
</div> </div>
: "" } : null }
<div className="post__content"> <div className="post__content">
<PostHeader post={post} sameRoot={this.props.sameRoot} commentCount={commentCount} handleCommentClick={this.handleCommentClick} isLastComment={this.props.isLastComment} /> <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} /> <PostBody post={post} sameRoot={this.props.sameRoot} parentPost={parentPost} posts={posts} handleCommentClick={this.handleCommentClick} />

Просмотреть файл

@@ -71,11 +71,22 @@ module.exports = React.createClass({
name = <a className="theme" onClick={function(){ utils.searchForTerm(profile.username); }}>{profile.username}</a>; 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 = ( comment = (
<p className="post-link"> <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> </p>
); );
@@ -120,7 +131,7 @@ module.exports = React.createClass({
return ( return (
<div className="post-body"> <div className="post-body">
{ comment } { 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 ? { filenames && filenames.length > 0 ?
<div className="post-image__columns"> <div className="post-image__columns">
{ postFiles } { postFiles }

Просмотреть файл

@@ -8,6 +8,7 @@ var UserProfile = require( './user_profile.jsx' );
var AsyncClient = require('../utils/async_client.jsx'); var AsyncClient = require('../utils/async_client.jsx');
var CreatePost = require('./create_post.jsx'); var CreatePost = require('./create_post.jsx');
var Post = require('./post.jsx'); var Post = require('./post.jsx');
var LoadingScreen = require('./loading_screen.jsx');
var SocketStore = require('../stores/socket_store.jsx'); var SocketStore = require('../stores/socket_store.jsx');
var utils = require('../utils/utils.jsx'); var utils = require('../utils/utils.jsx');
var Client = require('../utils/client.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({ module.exports = React.createClass({
displayName: "PostList",
scrollPosition: 0, scrollPosition: 0,
preventScrollTrigger: false, preventScrollTrigger: false,
gotMorePosts: 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('a.theme', 'color:'+user.props.theme+'; fill:'+user.props.theme+'!important;');
utils.changeCss('div.theme', 'background-color:'+user.props.theme+';'); utils.changeCss('div.theme', 'background-color:'+user.props.theme+';');
utils.changeCss('.btn.btn-primary', 'background: ' + 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('.modal .modal-header', 'background: ' + user.props.theme+';');
utils.changeCss('.mention', 'background: ' + user.props.theme+';'); utils.changeCss('.mention', 'background: ' + user.props.theme+';');
utils.changeCss('.mention-link', 'color: ' + user.props.theme+';'); utils.changeCss('.mention-link', 'color: ' + user.props.theme+';');
@@ -161,24 +133,20 @@ module.exports = React.createClass({
$('body').off('click.userpopover'); $('body').off('click.userpopover');
}, },
resize: function() { resize: function() {
var post_holder = $(".post-list-holder-by-time")[0];
this.preventScrollTrigger = true;
if (this.gotMorePosts) { if (this.gotMorePosts) {
this.gotMorePosts = false; 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).scrollTop($(post_holder).scrollTop() + (post_holder.scrollHeight-this.oldScrollHeight) );
$(post_holder).perfectScrollbar('update');
} else { } else {
var post_holder = $(".post-list-holder-by-time")[0];
this.preventScrollTrigger = true;
if ($("#new_message")[0] && !this.scrolledToNew) { if ($("#new_message")[0] && !this.scrolledToNew) {
$(post_holder).scrollTop($(post_holder).scrollTop() + $("#new_message").offset().top - 63); $(post_holder).scrollTop($(post_holder).scrollTop() + $("#new_message").offset().top - 63);
$(post_holder).perfectScrollbar('update');
this.scrolledToNew = true; this.scrolledToNew = true;
} else { } else {
$(post_holder).scrollTop(post_holder.scrollHeight); $(post_holder).scrollTop(post_holder.scrollHeight);
$(post_holder).perfectScrollbar('update');
} }
} }
$(post_holder).perfectScrollbar('update');
}, },
_onChange: function() { _onChange: function() {
var newState = getStateFromStores(); var newState = getStateFromStores();
@@ -342,12 +310,15 @@ module.exports = React.createClass({
more_messages = ( more_messages = (
<div className="channel-intro"> <div className="channel-intro">
<div className="post-profile-img__container channel-intro-img"> <div className="post-profile-img__container channel-intro-img">
<img className="post-profile-img" src={"/api/v1/users/" + teammate.id + "/image"} height="50" width="50" /> <img className="post-profile-img" src={"/api/v1/users/" + teammate.id + "/image?time=" + teammate.update_at} height="50" width="50" />
</div> </div>
<div className="channel-intro-profile"> <div className="channel-intro-profile">
<strong><UserProfile userId={teammate.id} /></strong> <strong><UserProfile userId={teammate.id} /></strong>
</div> </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> </div>
); );
} else { } 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." } { channel.type === 'P' ? " Only invited members can see this private group." : " Any member can join and read this channel." }
<br/> <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" 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> </p>
</div> </div>
); );
@@ -420,37 +391,35 @@ module.exports = React.createClass({
var postCtls = []; 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 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--) { for (var i = order.length-1; i >= 0; i--) {
var post = posts[order[i]]; var post = posts[order[i]];
var parentPost; var parentPost = post.parent_id ? posts[post.parent_id] : null;
if (post.parent_id) { var sameUser = '';
parentPost = posts[post.parent_id]; var sameRoot = false;
} else { var hideProfilePic = false;
parentPost = null; var prevPost = (i < order.length - 1) ? posts[order[i + 1]] : null;
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
hideProfilePic = (prevPost.user_id === post.user_id) && !utils.isComment(prevPost) && !utils.isComment(post);
} }
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;
// 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 === '';
// check if it's the last comment in a consecutive string of comments on the same post // check if it's the last comment in a consecutive string of comments on the same post
var isLastComment = false; // it is the last comment if it is last post in the channel or the next post has a different root post
if (utils.isComment(post)) { var isLastComment = utils.isComment(post) && (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
isLastComment = (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} />; 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); 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( postCtls.push(
<div className="date-separator"> <div className="date-separator">
<hr className="separator__hr" /> <hr className="separator__hr" />
@@ -469,20 +438,10 @@ module.exports = React.createClass({
); );
} }
postCtls.push(postCtl); postCtls.push(postCtl);
previousPostDay = utils.getDateForUnixTicks(post.create_at); previousPostDay = currentPostDay;
} }
} } else {
else { postCtls.push(<LoadingScreen position="absolute" />);
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>
);
} }
return ( return (
@@ -497,5 +456,3 @@ module.exports = React.createClass({
); );
} }
}); });

Просмотреть файл

@@ -67,6 +67,7 @@ RootPost = React.createClass({
var message = utils.textToJsx(this.props.post.message); var message = utils.textToJsx(this.props.post.message);
var filenames = this.props.post.filenames; var filenames = this.props.post.filenames;
var isOwner = UserStore.getCurrentId() == this.props.post.user_id; var isOwner = UserStore.getCurrentId() == this.props.post.user_id;
var timestamp = UserStore.getProfile(this.props.post.user_id).update_at;
var type = "Post"; var type = "Post";
if (this.props.post.root_id.length > 0) { if (this.props.post.root_id.length > 0) {
@@ -118,7 +119,7 @@ RootPost = React.createClass({
return ( return (
<div className={"post post--root " + currentUserCss}> <div className={"post post--root " + currentUserCss}>
<div className="post-profile-img__container"> <div className="post-profile-img__container">
<img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image"} height="36" width="36" /> <img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image?time=" + timestamp} height="36" width="36" />
</div> </div>
<div className="post__content"> <div className="post__content">
<ul className="post-header"> <ul className="post-header">
@@ -227,11 +228,12 @@ CommentPost = React.createClass({
} }
var message = utils.textToJsx(this.props.post.message); var message = utils.textToJsx(this.props.post.message);
var timestamp = UserStore.getCurrentUser().update_at;
return ( return (
<div className={commentClass + " " + currentUserCss}> <div className={commentClass + " " + currentUserCss}>
<div className="post-profile-img__container"> <div className="post-profile-img__container">
<img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image"} height="36" width="36" /> <img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image?time=" + timestamp} height="36" width="36" />
</div> </div>
<div className="post__content"> <div className="post__content">
<ul className="post-header"> <ul className="post-header">

Просмотреть файл

@@ -76,6 +76,7 @@ SearchItem = React.createClass({
var message = utils.textToJsx(this.props.post.message, {searchTerm: this.props.term, noMentionHighlight: !this.props.isMentionSearch}); var message = utils.textToJsx(this.props.post.message, {searchTerm: this.props.term, noMentionHighlight: !this.props.isMentionSearch});
var channelName = ""; var channelName = "";
var channel = ChannelStore.get(this.props.post.channel_id) var channel = ChannelStore.get(this.props.post.channel_id)
var timestamp = UserStore.getCurrentUser().update_at;
if (channel) { if (channel) {
if (channel.type === 'D') { if (channel.type === 'D') {
@@ -89,7 +90,7 @@ SearchItem = React.createClass({
<div className="search-item-container post" onClick={this.handleClick}> <div className="search-item-container post" onClick={this.handleClick}>
<div className="search-channel__name">{ channelName }</div> <div className="search-channel__name">{ channelName }</div>
<div className="post-profile-img__container"> <div className="post-profile-img__container">
<img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image"} height="36" width="36" /> <img className="post-profile-img" src={"/api/v1/users/" + this.props.post.user_id + "/image?time=" + timestamp} height="36" width="36" />
</div> </div>
<div className="post__content"> <div className="post__content">
<ul className="post-header"> <ul className="post-header">

Просмотреть файл

@@ -249,11 +249,27 @@ var SidebarLoggedIn = React.createClass({
var repRegex = new RegExp("<br>", "g"); var repRegex = new RegExp("<br>", "g");
var post = JSON.parse(msg.props.post); var post = JSON.parse(msg.props.post);
var msg = post.message.replace(repRegex, "\n").split("\n")[0].replace("<mention>", "").replace("</mention>", ""); var msgProps = msg.props;
var msg = post.message.replace(repRegex, "\n").replace(/\n+/g, " ").replace("<mention>", "").replace("</mention>", "");
if (msg.length > 50) { if (msg.length > 50) {
msg = msg.substring(0,49) + "..."; 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") { if (!user.notify_props || user.notify_props.desktop_sound === "true") {
utils.ding(); utils.ding();
} }

Просмотреть файл

@@ -77,7 +77,7 @@ var NavbarDropdown = React.createClass({
for (var i = 0; i < this.state.teams.length; i++) { for (var i = 0; i < this.state.teams.length; i++) {
var domain = this.state.teams[i]; var domain = this.state.teams[i];
if (domain == utils.getSubDomain()) if (domain == utils.getSubDomain())
continue; continue;
if (teams.length == 0) if (teams.length == 0)
@@ -121,10 +121,15 @@ module.exports = React.createClass({
}, },
render: function() { render: function() {
var teamName = this.props.teamName ? this.props.teamName : config.SiteName; var teamName = this.props.teamName ? this.props.teamName : config.SiteName;
var me = UserStore.getCurrentUser()
return ( return (
<div className="team__header theme"> <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} /> <NavbarDropdown teamType={this.props.teamType} />
</div> </div>
); );

Просмотреть файл

@@ -596,19 +596,14 @@ PasswordPage = React.createClass({
module.exports = React.createClass({ module.exports = React.createClass({
updateParent: function(state, skipSet) { updateParent: function(state, skipSet) {
BrowserStore.setGlobalItem(this.props.hash, JSON.stringify(state)); BrowserStore.setGlobalItem(this.props.hash, state);
if (!skipSet) { if (!skipSet) {
this.setState(state); this.setState(state);
} }
}, },
getInitialState: function() { getInitialState: function() {
var props = null; var props = BrowserStore.getGlobalItem(this.props.hash);
try {
props = JSON.parse(BrowserStore.getGlobalItem(this.props.hash));
}
catch(parse_error) {
}
if (!props) { if (!props) {
props = {}; props = {};
@@ -628,7 +623,7 @@ module.exports = React.createClass({
props.data = this.props.data; props.data = this.props.data;
} }
return props ; return props;
}, },
render: function() { render: function() {
if (this.state.wizard == "welcome") { if (this.state.wizard == "welcome") {

Просмотреть файл

@@ -17,7 +17,7 @@ module.exports = React.createClass({
return; return;
} }
var username_error = utils.isValidUsername(this.state.user.username) var username_error = utils.isValidUsername(this.state.user.username);
if (username_error === "Cannot use a reserved word as a username.") { if (username_error === "Cannot use a reserved word as a username.") {
this.setState({name_error: "This username is reserved, please choose a new one.", email_error: "", password_error: "", server_error: ""}); this.setState({name_error: "This username is reserved, please choose a new one.", email_error: "", password_error: "", server_error: ""});
return; return;
@@ -72,12 +72,7 @@ module.exports = React.createClass({
); );
}, },
getInitialState: function() { getInitialState: function() {
var props = null; var props = BrowserStore.getGlobalItem(this.props.hash);
try {
props = JSON.parse(BrowserStore.getGlobalItem(this.props.hash));
}
catch(parse_error) {
}
if (!props) { if (!props) {
props = {}; props = {};
@@ -90,7 +85,7 @@ module.exports = React.createClass({
props.original_email = this.props.email; props.original_email = this.props.email;
} }
return props ; return props;
}, },
render: function() { render: function() {

Просмотреть файл

@@ -53,7 +53,7 @@ module.exports = React.createClass({
var name = this.props.overwriteName ? this.props.overwriteName : this.state.profile.username; 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' height='128' width='128' />"; 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' />";
if (!config.ShowEmail) { if (!config.ShowEmail) {
data_content += "<div class='text-nowrap'>Email not shared</div>"; data_content += "<div class='text-nowrap'>Email not shared</div>";
} else { } else {

Просмотреть файл

@@ -626,7 +626,7 @@ var SecurityTab = React.createClass({
client.updatePassword(data, client.updatePassword(data,
function(data) { function(data) {
this.updateSection(""); this.props.updateSection("");
AsyncClient.getMe(); AsyncClient.getMe();
this.setState({ current_password: '', new_password: '', confirm_password: '' }); this.setState({ current_password: '', new_password: '', confirm_password: '' });
}.bind(this), }.bind(this),

Просмотреть файл

@@ -1,85 +1,104 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. // Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information. // See License.txt for license information.
var UserStore = require('../stores/user_store.jsx');
// Also change model/utils.go ETAG_ROOT_VERSION var UserStore;
var BROWSER_STORE_VERSION = '.1'; function getPrefix() {
if (!UserStore) UserStore = require('./user_store.jsx');
var _initialized = false; return UserStore.getCurrentId() + '_';
function _initialize() {
var currentVersion = localStorage.getItem("local_storage_version");
if (currentVersion !== BROWSER_STORE_VERSION) {
localStorage.clear();
sessionStorage.clear();
localStorage.setItem("local_storage_version", BROWSER_STORE_VERSION);
}
_initialized = true;
} }
module.exports.setItem = function(name, value) { // Also change model/utils.go ETAG_ROOT_VERSION
if (!_initialized) _initialize(); var BROWSER_STORE_VERSION = '.3';
var user_id = UserStore.getCurrentId();
localStorage.setItem(user_id + "_" + name, value);
};
module.exports.getItem = function(name) { module.exports = {
if (!_initialized) _initialize(); _initialized: false,
var user_id = UserStore.getCurrentId();
return localStorage.getItem(user_id + "_" + name);
};
module.exports.removeItem = function(name) { _initialize: function() {
if (!_initialized) _initialize(); var currentVersion = localStorage.getItem("local_storage_version");
var user_id = UserStore.getCurrentId(); if (currentVersion !== BROWSER_STORE_VERSION) {
localStorage.removeItem(user_id + "_" + name); this.clear();
}; localStorage.setItem("local_storage_version", BROWSER_STORE_VERSION);
}
this._initialized = true;
},
module.exports.setGlobalItem = function(name, value) { getItem: function(name, defaultValue) {
if (!_initialized) _initialize(); return this.getGlobalItem(getPrefix() + name, defaultValue);
localStorage.setItem(name, value); },
};
module.exports.getGlobalItem = function(name) { setItem: function(name, value) {
if (!_initialized) _initialize(); this.setGlobalItem(getPrefix() + name, value);
return localStorage.getItem(name); },
};
module.exports.removeGlobalItem = function(name) { removeItem: function(name) {
if (!_initialized) _initialize(); if (!this._initialized) this._initialize();
localStorage.removeItem(name);
};
module.exports.clear = function() { localStorage.removeItem(getPrefix() + name);
localStorage.clear(); },
sessionStorage.clear();
};
// Preforms the given action on each item that has the given prefix setGlobalItem: function(name, value) {
// Signiture for action is action(key, value) if (!this._initialized) this._initialize();
module.exports.actionOnItemsWithPrefix = function (prefix, action) {
var user_id = UserStore.getCurrentId(); localStorage.setItem(name, JSON.stringify(value));
var id_len = user_id.length; },
var prefix_len = prefix.length;
for (var key in localStorage) { getGlobalItem: function(name, defaultValue) {
if (key.substring(id_len, id_len + prefix_len) === prefix) { if (!this._initialized) this._initialize();
var userkey = key.substring(id_len);
action(userkey, BrowserStore.getItem(key)); var result = null;
try {
result = JSON.parse(localStorage.getItem(name));
} catch (err) {}
if (result === null && typeof defaultValue !== 'undefined') {
result = defaultValue;
}
return result;
},
removeGlobalItem: function(name) {
if (!this._initialized) this._initialize();
localStorage.removeItem(name);
},
clear: function() {
localStorage.clear();
sessionStorage.clear();
},
/**
* Preforms the given action on each item that has the given prefix
* Signiture for action is action(key, value)
*/
actionOnItemsWithPrefix: function (prefix, action) {
if (!this._initialized) this._initialize();
var globalPrefix = getPrefix();
var globalPrefixiLen = globalPrefix.length;
for (var key in localStorage) {
if (key.lastIndexOf(globalPrefix + prefix, 0) === 0) {
var userkey = key.substring(globalPrefixiLen);
action(userkey, this.getGlobalItem(key));
}
}
},
isLocalStorageSupported: function() {
try {
sessionStorage.setItem("testSession", '1');
sessionStorage.removeItem("testSession");
localStorage.setItem("testLocal", '1');
if (localStorage.getItem("testLocal") != '1') {
return false;
}
localStorage.removeItem("testLocal", '1');
return true;
} catch (e) {
return false;
} }
} }
}; };
module.exports.isLocalStorageSupported = function() {
try {
sessionStorage.setItem("testSession", '1');
sessionStorage.removeItem("testSession");
localStorage.setItem("testLocal", '1');
localStorage.removeItem("testLocal", '1');
return true;
}
catch (e) {
return false;
}
};

Просмотреть файл

@@ -16,6 +16,7 @@ var MORE_CHANGE_EVENT = 'change';
var EXTRA_INFO_EVENT = 'extra_info'; var EXTRA_INFO_EVENT = 'extra_info';
var ChannelStore = assign({}, EventEmitter.prototype, { var ChannelStore = assign({}, EventEmitter.prototype, {
_current_id: null,
emitChange: function() { emitChange: function() {
this.emit(CHANGE_EVENT); this.emit(CHANGE_EVENT);
}, },
@@ -88,10 +89,7 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
return this._getMoreChannels(); return this._getMoreChannels();
}, },
setCurrentId: function(id) { setCurrentId: function(id) {
if (id == null) this._current_id = id;
BrowserStore.removeItem("current_channel_id");
else
BrowserStore.setItem("current_channel_id", id);
}, },
setLastVisitedName: function(name) { setLastVisitedName: function(name) {
if (name == null) if (name == null)
@@ -117,10 +115,10 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
this._storeChannelMembers(cm); this._storeChannelMembers(cm);
}, },
getCurrentId: function() { getCurrentId: function() {
return BrowserStore.getItem("current_channel_id"); return this._current_id;
}, },
getCurrent: function() { getCurrent: function() {
var currentId = ChannelStore.getCurrentId(); var currentId = this.getCurrentId();
if (currentId != null) if (currentId != null)
return this.get(currentId); return this.get(currentId);
@@ -165,49 +163,22 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
return extra; return extra;
}, },
_storeChannels: function(channels) { _storeChannels: function(channels) {
BrowserStore.setItem("channels", JSON.stringify(channels)); BrowserStore.setItem("channels", channels);
}, },
_getChannels: function() { _getChannels: function() {
var channels = []; return BrowserStore.getItem("channels", []);
try {
channels = JSON.parse(BrowserStore.getItem("channels"));
}
catch (err) {
}
if (channels == null) {
channels = [];
}
return channels;
}, },
_storeChannelMembers: function(channelMembers) { _storeChannelMembers: function(channelMembers) {
BrowserStore.setItem("channel_members", JSON.stringify(channelMembers)); BrowserStore.setItem("channel_members", channelMembers);
}, },
_getChannelMembers: function() { _getChannelMembers: function() {
var members = {}; return BrowserStore.getItem("channel_members", {});
try {
members = JSON.parse(BrowserStore.getItem("channel_members"));
}
catch (err) {
}
if (members == null) {
members = {};
}
return members;
}, },
_storeMoreChannels: function(channels) { _storeMoreChannels: function(channels) {
BrowserStore.setItem("more_channels", JSON.stringify(channels)); BrowserStore.setItem("more_channels", channels);
}, },
_getMoreChannels: function() { _getMoreChannels: function() {
var channels = null; var channels = BrowserStore.getItem("more_channels");
try {
channels = JSON.parse(BrowserStore.getItem("more_channels"));
}
catch (err) {
}
if (channels == null) { if (channels == null) {
channels = {}; channels = {};
@@ -217,21 +188,10 @@ var ChannelStore = assign({}, EventEmitter.prototype, {
return channels; return channels;
}, },
_storeExtraInfos: function(extraInfos) { _storeExtraInfos: function(extraInfos) {
BrowserStore.setItem("extra_infos", JSON.stringify(extraInfos)); BrowserStore.setItem("extra_infos", extraInfos);
}, },
_getExtraInfos: function() { _getExtraInfos: function() {
var members = {}; return BrowserStore.getItem("extra_infos", {});
try {
members = JSON.parse(BrowserStore.getItem("extra_infos"));
}
catch (err) {
}
if (members == null) {
members = {};
}
return members;
} }
}); });

Просмотреть файл

@@ -29,18 +29,11 @@ var ErrorStore = assign({}, EventEmitter.prototype, {
BrowserStore.removeItem("last_error"); BrowserStore.removeItem("last_error");
}, },
getLastError: function() { getLastError: function() {
var error = null; return BrowserStore.getItem('last_error');
try {
error = JSON.parse(BrowserStore.getItem("last_error"));
}
catch (err) {
}
return error;
}, },
_storeLastError: function(error) { _storeLastError: function(error) {
BrowserStore.setItem("last_error", JSON.stringify(error)); BrowserStore.setItem("last_error", error);
}, },
}); });

Просмотреть файл

@@ -106,55 +106,27 @@ var PostStore = assign({}, EventEmitter.prototype, {
this.emitChange(); this.emitChange();
}, },
_storePosts: function(channelId, posts) { _storePosts: function(channelId, posts) {
BrowserStore.setItem("posts_" + channelId, JSON.stringify(posts)); BrowserStore.setItem("posts_" + channelId, posts);
}, },
getPosts: function(channelId) { getPosts: function(channelId) {
var posts = null; return BrowserStore.getItem("posts_" + channelId);
try {
posts = JSON.parse(BrowserStore.getItem("posts_" + channelId));
}
catch (err) {
}
return posts;
}, },
storeSearchResults: function(results, is_mention_search) { storeSearchResults: function(results, is_mention_search) {
BrowserStore.setItem("search_results", JSON.stringify(results)); BrowserStore.setItem("search_results", results);
is_mention_search = is_mention_search ? true : false; // force to bool is_mention_search = is_mention_search ? true : false; // force to bool
BrowserStore.setItem("is_mention_search", JSON.stringify(is_mention_search)); BrowserStore.setItem("is_mention_search", is_mention_search);
}, },
getSearchResults: function() { getSearchResults: function() {
var results = null; return BrowserStore.getItem("search_results");
try {
results = JSON.parse(BrowserStore.getItem("search_results"));
}
catch (err) {
}
return results;
}, },
getIsMentionSearch: function() { getIsMentionSearch: function() {
var result = false; return BrowserStore.getItem("is_mention_search");
try {
result = JSON.parse(BrowserStore.getItem("is_mention_search"));
}
catch (err) {
}
return result;
}, },
storeSelectedPost: function(post_list) { storeSelectedPost: function(post_list) {
BrowserStore.setItem("select_post", JSON.stringify(post_list)); BrowserStore.setItem("select_post", post_list);
}, },
getSelectedPost: function() { getSelectedPost: function() {
var post_list = null; return BrowserStore.getItem("select_post");
try {
post_list = JSON.parse(BrowserStore.getItem("select_post"));
}
catch (err) {
}
return post_list;
}, },
storeSearchTerm: function(term) { storeSearchTerm: function(term) {
BrowserStore.setItem("search_term", term); BrowserStore.setItem("search_term", term);
@@ -165,25 +137,24 @@ var PostStore = assign({}, EventEmitter.prototype, {
storeCurrentDraft: function(draft) { storeCurrentDraft: function(draft) {
var channel_id = ChannelStore.getCurrentId(); var channel_id = ChannelStore.getCurrentId();
var user_id = UserStore.getCurrentId(); var user_id = UserStore.getCurrentId();
BrowserStore.setItem("draft_" + channel_id + "_" + user_id, JSON.stringify(draft)); BrowserStore.setItem("draft_" + channel_id + "_" + user_id, draft);
}, },
getCurrentDraft: function() { getCurrentDraft: function() {
var channel_id = ChannelStore.getCurrentId(); var channel_id = ChannelStore.getCurrentId();
var user_id = UserStore.getCurrentId(); var user_id = UserStore.getCurrentId();
return JSON.parse(BrowserStore.getItem("draft_" + channel_id + "_" + user_id)); return BrowserStore.getItem("draft_" + channel_id + "_" + user_id);
}, },
storeDraft: function(channel_id, user_id, draft) { storeDraft: function(channel_id, user_id, draft) {
BrowserStore.setItem("draft_" + channel_id + "_" + user_id, JSON.stringify(draft)); BrowserStore.setItem("draft_" + channel_id + "_" + user_id, draft);
}, },
getDraft: function(channel_id, user_id) { getDraft: function(channel_id, user_id) {
return JSON.parse(BrowserStore.getItem("draft_" + channel_id + "_" + user_id)); return BrowserStore.getItem("draft_" + channel_id + "_" + user_id);
}, },
clearDraftUploads: function() { clearDraftUploads: function() {
BrowserStore.actionOnItemsWithPrefix("draft_", function (key, value) { BrowserStore.actionOnItemsWithPrefix("draft_", function (key, value) {
var d = JSON.parse(value); if (value) {
if (d) { value.uploadsInProgress = 0;
d['uploadsInProgress'] = 0; BrowserStore.setItem(key, value);
BrowserStore.setItem(key, JSON.stringify(d));
} }
}); });
} }

Просмотреть файл

@@ -10,8 +10,6 @@ var client = require('../utils/client.jsx');
var Constants = require('../utils/constants.jsx'); var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes; var ActionTypes = Constants.ActionTypes;
var BrowserStore = require('../stores/browser_store.jsx');
var CHANGE_EVENT = 'change'; var CHANGE_EVENT = 'change';
var conn; var conn;

Просмотреть файл

@@ -63,22 +63,10 @@ var TeamStore = assign({}, EventEmitter.prototype, {
this._storeTeams(teams); this._storeTeams(teams);
}, },
_storeTeams: function(teams) { _storeTeams: function(teams) {
BrowserStore.setItem("user_teams", JSON.stringify(teams)); BrowserStore.setItem("user_teams", teams);
}, },
_getTeams: function() { _getTeams: function() {
var teams = {}; return BrowserStore.getItem("user_teams", {});
try {
teams = JSON.parse(BrowserStore.getItem("user_teams"));
}
catch (err) {
}
if (teams == null) {
teams = {};
}
return teams;
} }
}); });

Просмотреть файл

@@ -8,7 +8,7 @@ var client = require('../utils/client.jsx');
var Constants = require('../utils/constants.jsx'); var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes; var ActionTypes = Constants.ActionTypes;
var BrowserStore = require('../stores/browser_store.jsx'); var BrowserStore = require('./browser_store.jsx');
var CHANGE_EVENT = 'change'; var CHANGE_EVENT = 'change';
var CHANGE_EVENT_SESSIONS = 'change_sessions'; var CHANGE_EVENT_SESSIONS = 'change_sessions';
@@ -18,6 +18,8 @@ var CHANGE_EVENT_STATUSES = 'change_statuses';
var UserStore = assign({}, EventEmitter.prototype, { var UserStore = assign({}, EventEmitter.prototype, {
_current_id: null,
emitChange: function(userId) { emitChange: function(userId) {
this.emit(CHANGE_EVENT, userId); this.emit(CHANGE_EVENT, userId);
}, },
@@ -64,13 +66,10 @@ var UserStore = assign({}, EventEmitter.prototype, {
this.removeListener(CHANGE_EVENT_STATUSES, callback); this.removeListener(CHANGE_EVENT_STATUSES, callback);
}, },
setCurrentId: function(id) { setCurrentId: function(id) {
if (id == null) this._current_id = id;
BrowserStore.removeGlobalItem("current_user_id");
else
BrowserStore.setGlobalItem("current_user_id", id);
}, },
getCurrentId: function(skipFetch) { getCurrentId: function(skipFetch) {
var current_id = BrowserStore.getGlobalItem("current_user_id"); var current_id = this._current_id;
// this is a speical case to force fetch the // this is a speical case to force fetch the
// current user if it's missing // current user if it's missing
@@ -97,21 +96,13 @@ var UserStore = assign({}, EventEmitter.prototype, {
this.setCurrentId(user.id); this.setCurrentId(user.id);
}, },
getLastDomain: function() { getLastDomain: function() {
var last_domain = BrowserStore.getItem("last_domain"); return BrowserStore.getItem("last_domain", '');
if (last_domain == null) {
last_domain = "";
}
return last_domain;
}, },
setLastDomain: function(domain) { setLastDomain: function(domain) {
BrowserStore.setItem("last_domain", domain); BrowserStore.setItem("last_domain", domain);
}, },
getLastEmail: function() { getLastEmail: function() {
var last_email = BrowserStore.getItem("last_email"); return BrowserStore.getItem("last_email", '');
if (last_email == null) {
last_email = "";
}
return last_email;
}, },
setLastEmail: function(email) { setLastEmail: function(email) {
BrowserStore.setItem("last_email", email); BrowserStore.setItem("last_email", email);
@@ -153,91 +144,36 @@ var UserStore = assign({}, EventEmitter.prototype, {
this._storeProfiles(ps); this._storeProfiles(ps);
}, },
_storeProfiles: function(profiles) { _storeProfiles: function(profiles) {
BrowserStore.setGlobalItem("profiles", JSON.stringify(profiles)); BrowserStore.setGlobalItem("profiles", profiles);
var profileUsernameMap = {}; var profileUsernameMap = {};
for (var id in profiles) { for (var id in profiles) {
profileUsernameMap[profiles[id].username] = profiles[id]; profileUsernameMap[profiles[id].username] = profiles[id];
} }
BrowserStore.setGlobalItem("profileUsernameMap", JSON.stringify(profileUsernameMap)); BrowserStore.setGlobalItem("profileUsernameMap", profileUsernameMap);
}, },
_getProfiles: function() { _getProfiles: function() {
var profiles = {}; return BrowserStore.getGlobalItem("profiles", {});
try {
profiles = JSON.parse(BrowserStore.getGlobalItem("profiles"));
}
catch (err) {
}
if (profiles == null) {
profiles = {};
}
return profiles;
}, },
_getProfilesUsernameMap: function() { _getProfilesUsernameMap: function() {
var profileUsernameMap = {}; return BrowserStore.getGlobalItem("profileUsernameMap", {});
try {
profileUsernameMap = JSON.parse(BrowserStore.getGlobalItem("profileUsernameMap"));
}
catch (err) {
}
if (profileUsernameMap == null) {
profileUsernameMap = {};
}
return profileUsernameMap;
}, },
setSessions: function(sessions) { setSessions: function(sessions) {
BrowserStore.setItem("sessions", JSON.stringify(sessions)); BrowserStore.setItem("sessions", sessions);
}, },
getSessions: function() { getSessions: function() {
var sessions = []; return BrowserStore.getItem("sessions", []);
try {
sessions = JSON.parse(BrowserStore.getItem("sessions"));
}
catch (err) {
}
if (sessions == null) {
sessions = [];
}
return sessions;
}, },
setAudits: function(audits) { setAudits: function(audits) {
BrowserStore.setItem("audits", JSON.stringify(audits)); BrowserStore.setItem("audits", audits);
}, },
getAudits: function() { getAudits: function() {
var audits = []; return BrowserStore.getItem("audits", []);
try {
audits = JSON.parse(BrowserStore.getItem("audits"));
}
catch (err) {
}
if (audits == null) {
audits = [];
}
return audits;
}, },
setTeams: function(teams) { setTeams: function(teams) {
BrowserStore.setItem("teams", JSON.stringify(teams)); BrowserStore.setItem("teams", teams);
}, },
getTeams: function() { getTeams: function() {
var teams = []; return BrowserStore.getItem("teams", []);
try {
teams = JSON.parse(BrowserStore.getItem("teams"));
}
catch (err) {
}
if (teams == null) {
teams = [];
}
return teams;
}, },
getCurrentMentionKeys: function() { getCurrentMentionKeys: function() {
var user = this.getCurrentUser(); var user = this.getCurrentUser();
@@ -258,11 +194,7 @@ var UserStore = assign({}, EventEmitter.prototype, {
} }
}, },
getLastVersion: function() { getLastVersion: function() {
var last_version = BrowserStore.getItem("last_version"); return BrowserStore.getItem("last_version", '');
if (last_version == null) {
last_version = "";
}
return last_version;
}, },
setLastVersion: function(version) { setLastVersion: function(version) {
BrowserStore.setItem("last_version", version); BrowserStore.setItem("last_version", version);
@@ -272,7 +204,7 @@ var UserStore = assign({}, EventEmitter.prototype, {
this.emitStatusesChange(); this.emitStatusesChange();
}, },
_setStatuses: function(statuses) { _setStatuses: function(statuses) {
BrowserStore.setItem("statuses", JSON.stringify(statuses)); BrowserStore.setItem("statuses", statuses);
}, },
setStatus: function(user_id, status) { setStatus: function(user_id, status) {
var statuses = this.getStatuses(); var statuses = this.getStatuses();
@@ -281,18 +213,7 @@ var UserStore = assign({}, EventEmitter.prototype, {
this.emitStatusesChange(); this.emitStatusesChange();
}, },
getStatuses: function() { getStatuses: function() {
var statuses = {}; return BrowserStore.getItem("statuses", {});
try {
statuses = JSON.parse(BrowserStore.getItem("statuses"));
}
catch (err) {
}
if (statuses == null) {
statuses = {};
}
return statuses;
}, },
getStatus: function(id) { getStatus: function(id) {
return this.getStatuses()[id]; return this.getStatuses()[id];
@@ -341,4 +262,3 @@ UserStore.setMaxListeners(0);
global.window.UserStore = UserStore; global.window.UserStore = UserStore;
module.exports = UserStore; module.exports = UserStore;

Просмотреть файл

@@ -225,7 +225,7 @@ module.exports.extractLinks = function(text) {
} }
return { "links": links, "text": text }; return { "links": links, "text": text };
} }
module.exports.escapeRegExp = function(string) { module.exports.escapeRegExp = function(string) {
return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
@@ -671,13 +671,13 @@ module.exports.isValidUsername = function (name) {
error = "First character must be a letter."; error = "First character must be a letter.";
} }
else else
{ {
var lowerName = name.toLowerCase().trim(); 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."; error = "Cannot use a reserved word as a username.";
break; break;
@@ -782,3 +782,34 @@ module.exports.getHomeLink = function() {
parts[0] = "www"; parts[0] = "www";
return window.location.protocol + "//" + parts.join("."); 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);
};

Просмотреть файл

@@ -137,7 +137,7 @@
border: 1px solid #E2E2E2; border: 1px solid #E2E2E2;
background-color: #FFF; background-color: #FFF;
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: left center; background-position: top left;
} }
a { a {
text-decoration: none; text-decoration: none;

Просмотреть файл

@@ -75,14 +75,16 @@
// Team Header in Sidebar // Team Header in Sidebar
.sidebar--left, .sidebar--menu { .sidebar--left, .sidebar--menu {
.team__header { .team__header {
padding: 0 15px 0 15px; padding: 10px;
@include legacy-pie-clearfix; @include legacy-pie-clearfix;
a { a {
color: #fff; color: #fff;
} }
.navbar-right { .navbar-right {
font-size: 0.85em; font-size: 0.85em;
margin: 16px -5px 0; position: absolute;
top: 20px;
right: 22px;
.dropdown-toggle { .dropdown-toggle {
padding: 0 10px; padding: 0 10px;
} }
@@ -100,17 +102,32 @@
display: inline-block; display: inline-block;
} }
} }
.team__name { .user__picture {
width: 36px;
height: 36px;
float: left; 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-weight: 600;
font-size: 1.2em; font-size: 16px;
max-width: 80%; max-width: 80%;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
text-decoration: none; text-decoration: none;
} }
.user__name {
font-size: 14px;
font-weight: 400;
color: #eee;
}
> .nav { > .nav {
> li { > li {
> a { > a {

Просмотреть файл

@@ -2,13 +2,8 @@
display: table; display: table;
width: 100%; width: 100%;
height: 100%; height: 100%;
position: absolute; padding: 60px;
@include box-sizing(border-box);
text-align: center; text-align: center;
&.loading-screen--channel {
position: relative;
padding: 4em 0 3.5em;
}
.loading__content { .loading__content {
display: table-cell; display: table-cell;
vertical-align: middle; vertical-align: middle;
@@ -19,11 +14,7 @@
margin: 0 0.2em 0; margin: 0 0.2em 0;
display: inline-block; display: inline-block;
} }
}
}
.loading-screen {
.loading__content {
.round { .round {
background-color: #444; background-color: #444;
width: 4px; width: 4px;
@@ -32,43 +23,18 @@
margin: 0 2px; margin: 0 2px;
opacity: 0.1; opacity: 0.1;
@include border-radius(10px); @include border-radius(10px);
-moz-animation: move 0.75s infinite linear; @include animation(move 0.75s infinite linear);
-webkit-animation: move 0.75s infinite linear;
} }
#round_1 { @for $i from 1 through 3 {
-moz-animation-delay: .2s; .round-#{$i} {
-webkit-animation-delay: .2s; @include animation-delay(.2s*$i);
}
#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;
} }
100% {
opacity: 0.1;
};
} }
@-webkit-keyframes move { @include keyframes(move) {
0% { from { opacity: 1; }
opacity: 1; to { opacity: 0.1; }
}
100% {
opacity: 0.1;
};
} }
} }
} }

Просмотреть файл

@@ -11,13 +11,14 @@
position: absolute; position: absolute;
z-index: 1060; z-index: 1060;
.mentions-box { .mentions-box {
max-height: 303px; width: 100%;
position:absolute; height: 100%;
background-color:#fff; position: absolute;
background-color: #fff;
border: $border-gray; border: $border-gray;
overflow-x: hidden; overflow-x: hidden;
overflow-y: scroll; overflow-y: scroll;
bottom:0; bottom: 0;
} }
} }

Просмотреть файл

@@ -119,6 +119,7 @@ body.ios {
table-layout: fixed; table-layout: fixed;
width: 100%; width: 100%;
min-height: 100%; min-height: 100%;
height: 100%;
.post-list__content { .post-list__content {
display: table-cell; display: table-cell;
vertical-align: bottom; vertical-align: bottom;