summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--api/api.go1
-rw-r--r--api/channel.go53
-rw-r--r--api/channel_test.go62
-rw-r--r--api/config.go34
-rw-r--r--model/channel_count.go63
-rw-r--r--model/channel_data.go43
-rw-r--r--model/client.go18
-rw-r--r--store/sql_channel_store.go35
-rw-r--r--store/sql_channel_store_test.go47
-rw-r--r--store/store.go1
-rw-r--r--web/react/components/access_history_modal.jsx57
-rw-r--r--web/react/components/activity_log_modal.jsx113
-rw-r--r--web/react/components/edit_channel_modal.jsx2
-rw-r--r--web/react/components/file_attachment.jsx10
-rw-r--r--web/react/components/invite_member_modal.jsx66
-rw-r--r--web/react/components/login.jsx2
-rw-r--r--web/react/components/more_channels.jsx123
-rw-r--r--web/react/components/new_channel.jsx12
-rw-r--r--web/react/components/post.jsx6
-rw-r--r--web/react/components/post_list.jsx7
-rw-r--r--web/react/components/rename_channel_modal.jsx10
-rw-r--r--web/react/components/setting_item_min.jsx19
-rw-r--r--web/react/components/sidebar.jsx31
-rw-r--r--web/react/components/sidebar_header.jsx2
-rw-r--r--web/react/components/sidebar_right_menu.jsx2
-rw-r--r--web/react/components/signup_team_complete.jsx609
-rw-r--r--web/react/components/user_settings.jsx41
-rw-r--r--web/react/components/user_settings_modal.jsx3
-rw-r--r--web/react/pages/channel.jsx21
-rw-r--r--web/react/pages/signup_team.jsx8
-rw-r--r--web/react/stores/channel_store.jsx446
-rw-r--r--web/react/stores/config_store.jsx56
-rw-r--r--web/react/stores/team_store.jsx143
-rw-r--r--web/react/utils/async_client.jsx266
-rw-r--r--web/react/utils/client.jsx52
-rw-r--r--web/react/utils/constants.jsx3
-rw-r--r--web/react/utils/utils.jsx33
-rw-r--r--web/sass-files/sass/partials/_files.scss9
-rw-r--r--web/sass-files/sass/partials/_responsive.scss3
-rw-r--r--web/sass-files/sass/partials/_settings.scss6
41 files changed, 1663 insertions, 857 deletions
diff --git a/README.md b/README.md
index 5af82bb72..361de4739 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ You're installing "Mattermost Alpha", a pre-released version providing an early
That said, any issues at all, please let us know on the Mattermost forum at: http://forum.mattermost.org
Notes:
-- For Alpha, Docker is intentionally setup as a single container, since production deployment not yet recommended.
+- For Alpha, Docker is intentionally setup as a single container, since production deployment is not yet recommended.
Local Machine Setup (Docker)
-----------------------------
diff --git a/api/api.go b/api/api.go
index 2ea27ed9f..9770930f7 100644
--- a/api/api.go
+++ b/api/api.go
@@ -40,6 +40,7 @@ func InitApi() {
InitWebSocket(r)
InitFile(r)
InitCommand(r)
+ InitConfig(r)
templatesDir := utils.FindDir("api/templates")
l4g.Debug("Parsing server templates at %v", templatesDir)
diff --git a/api/channel.go b/api/channel.go
index 803274d32..151627623 100644
--- a/api/channel.go
+++ b/api/channel.go
@@ -18,11 +18,13 @@ func InitChannel(r *mux.Router) {
sr := r.PathPrefix("/channels").Subrouter()
sr.Handle("/", ApiUserRequiredActivity(getChannels, false)).Methods("GET")
sr.Handle("/more", ApiUserRequired(getMoreChannels)).Methods("GET")
+ sr.Handle("/counts", ApiUserRequiredActivity(getChannelCounts, false)).Methods("GET")
sr.Handle("/create", ApiUserRequired(createChannel)).Methods("POST")
sr.Handle("/create_direct", ApiUserRequired(createDirectChannel)).Methods("POST")
sr.Handle("/update", ApiUserRequired(updateChannel)).Methods("POST")
sr.Handle("/update_desc", ApiUserRequired(updateChannelDesc)).Methods("POST")
sr.Handle("/update_notify_level", ApiUserRequired(updateNotifyLevel)).Methods("POST")
+ sr.Handle("/{id:[A-Za-z0-9]+}/", ApiUserRequiredActivity(getChannel, false)).Methods("GET")
sr.Handle("/{id:[A-Za-z0-9]+}/extra_info", ApiUserRequired(getChannelExtraInfo)).Methods("GET")
sr.Handle("/{id:[A-Za-z0-9]+}/join", ApiUserRequired(joinChannel)).Methods("POST")
sr.Handle("/{id:[A-Za-z0-9]+}/leave", ApiUserRequired(leaveChannel)).Methods("POST")
@@ -275,7 +277,7 @@ func updateChannelDesc(c *Context, w http.ResponseWriter, r *http.Request) {
func getChannels(c *Context, w http.ResponseWriter, r *http.Request) {
- // user is already in the newtork
+ // user is already in the team
if result := <-Srv.Store.Channel().GetChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil {
if result.Err.Message == "No channels were found" {
@@ -300,7 +302,7 @@ func getChannels(c *Context, w http.ResponseWriter, r *http.Request) {
func getMoreChannels(c *Context, w http.ResponseWriter, r *http.Request) {
- // user is already in the newtork
+ // user is already in the team
if result := <-Srv.Store.Channel().GetMoreChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil {
c.Err = result.Err
@@ -314,6 +316,22 @@ func getMoreChannels(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
+func getChannelCounts(c *Context, w http.ResponseWriter, r *http.Request) {
+
+ // user is already in the team
+
+ if result := <-Srv.Store.Channel().GetChannelCounts(c.Session.TeamId, c.Session.UserId); result.Err != nil {
+ c.Err = model.NewAppError("getChannelCounts", "Unable to get channel counts from the database", result.Err.Message)
+ return
+ } else if HandleEtag(result.Data.(*model.ChannelCounts).Etag(), w, r) {
+ return
+ } else {
+ data := result.Data.(*model.ChannelCounts)
+ w.Header().Set(model.HEADER_ETAG_SERVER, data.Etag())
+ w.Write([]byte(data.ToJson()))
+ }
+}
+
func joinChannel(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
@@ -548,6 +566,37 @@ func updateLastViewedAt(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.MapToJson(result)))
}
+func getChannel(c *Context, w http.ResponseWriter, r *http.Request) {
+ params := mux.Vars(r)
+ id := params["id"]
+
+ //pchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, id, c.Session.UserId)
+ cchan := Srv.Store.Channel().Get(id)
+ cmchan := Srv.Store.Channel().GetMember(id, c.Session.UserId)
+
+ if cresult := <-cchan; cresult.Err != nil {
+ c.Err = cresult.Err
+ return
+ } else if cmresult := <-cmchan; cmresult.Err != nil {
+ c.Err = cmresult.Err
+ return
+ } else {
+ data := &model.ChannelData{}
+ data.Channel = cresult.Data.(*model.Channel)
+ member := cmresult.Data.(model.ChannelMember)
+ data.Member = &member
+
+ if HandleEtag(data.Etag(), w, r) {
+ return
+ } else {
+ w.Header().Set(model.HEADER_ETAG_SERVER, data.Etag())
+ w.Header().Set("Expires", "-1")
+ w.Write([]byte(data.ToJson()))
+ }
+ }
+
+}
+
func getChannelExtraInfo(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
diff --git a/api/channel_test.go b/api/channel_test.go
index d4fb11bd8..d65aff66c 100644
--- a/api/channel_test.go
+++ b/api/channel_test.go
@@ -320,6 +320,27 @@ func TestGetChannel(t *testing.T) {
if _, err := Client.UpdateLastViewedAt(channel2.Id); err != nil {
t.Fatal(err)
}
+
+ if resp, err := Client.GetChannel(channel1.Id, ""); err != nil {
+ t.Fatal(err)
+ } else {
+ data := resp.Data.(*model.ChannelData)
+ if data.Channel.DisplayName != channel1.DisplayName {
+ t.Fatal("name didn't match")
+ }
+
+ // test etag caching
+ if cache_result, err := Client.GetChannel(channel1.Id, resp.Etag); err != nil {
+ t.Fatal(err)
+ } else if cache_result.Data.(*model.ChannelData) != nil {
+ t.Log(cache_result.Data)
+ t.Fatal("cache should be empty")
+ }
+ }
+
+ if _, err := Client.GetChannel("junk", ""); err == nil {
+ t.Fatal("should have failed - bad channel id")
+ }
}
func TestGetMoreChannel(t *testing.T) {
@@ -366,6 +387,47 @@ func TestGetMoreChannel(t *testing.T) {
}
}
+func TestGetChannelCounts(t *testing.T) {
+ Setup()
+
+ team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
+ team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+
+ user := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
+ store.Must(Srv.Store.User().VerifyEmail(user.Id))
+
+ Client.LoginByEmail(team.Name, user.Email, "pwd")
+
+ channel1 := &model.Channel{DisplayName: "A Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
+ channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+
+ channel2 := &model.Channel{DisplayName: "B Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
+ channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
+
+ if result, err := Client.GetChannelCounts(""); err != nil {
+ t.Fatal(err)
+ } else {
+ counts := result.Data.(*model.ChannelCounts)
+
+ if len(counts.Counts) != 4 {
+ t.Fatal("wrong number of channel counts")
+ }
+
+ if len(counts.UpdateTimes) != 4 {
+ t.Fatal("wrong number of channel update times")
+ }
+
+ if cache_result, err := Client.GetChannelCounts(result.Etag); err != nil {
+ t.Fatal(err)
+ } else if cache_result.Data.(*model.ChannelCounts) != nil {
+ t.Log(cache_result.Data)
+ t.Fatal("result data should be empty")
+ }
+ }
+
+}
+
func TestJoinChannel(t *testing.T) {
Setup()
diff --git a/api/config.go b/api/config.go
new file mode 100644
index 000000000..142d1ca66
--- /dev/null
+++ b/api/config.go
@@ -0,0 +1,34 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api
+
+import (
+ l4g "code.google.com/p/log4go"
+ "encoding/json"
+ "github.com/gorilla/mux"
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+ "net/http"
+ "strconv"
+)
+
+func InitConfig(r *mux.Router) {
+ l4g.Debug("Initializing config api routes")
+
+ sr := r.PathPrefix("/config").Subrouter()
+ sr.Handle("/get_all", ApiAppHandler(getConfig)).Methods("GET")
+}
+
+func getConfig(c *Context, w http.ResponseWriter, r *http.Request) {
+ settings := make(map[string]string)
+
+ settings["ByPassEmail"] = strconv.FormatBool(utils.Cfg.EmailSettings.ByPassEmail)
+
+ if bytes, err := json.Marshal(settings); err != nil {
+ c.Err = model.NewAppError("getConfig", "Unable to marshall configuration data", err.Error())
+ return
+ } else {
+ w.Write(bytes)
+ }
+}
diff --git a/model/channel_count.go b/model/channel_count.go
new file mode 100644
index 000000000..d5daba14e
--- /dev/null
+++ b/model/channel_count.go
@@ -0,0 +1,63 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "crypto/md5"
+ "encoding/json"
+ "fmt"
+ "io"
+ "sort"
+ "strconv"
+)
+
+type ChannelCounts struct {
+ Counts map[string]int64 `json:"counts"`
+ UpdateTimes map[string]int64 `json:"update_times"`
+}
+
+func (o *ChannelCounts) Etag() string {
+
+ ids := []string{}
+ for id, _ := range o.Counts {
+ ids = append(ids, id)
+ }
+ sort.Strings(ids)
+
+ str := ""
+ for _, id := range ids {
+ str += id + strconv.FormatInt(o.Counts[id], 10)
+ }
+
+ md5Counts := fmt.Sprintf("%x", md5.Sum([]byte(str)))
+
+ var update int64 = 0
+ for _, u := range o.UpdateTimes {
+ if u > update {
+ update = u
+ }
+ }
+
+ return Etag(md5Counts, update)
+}
+
+func (o *ChannelCounts) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func ChannelCountsFromJson(data io.Reader) *ChannelCounts {
+ decoder := json.NewDecoder(data)
+ var o ChannelCounts
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
diff --git a/model/channel_data.go b/model/channel_data.go
new file mode 100644
index 000000000..234bdec6e
--- /dev/null
+++ b/model/channel_data.go
@@ -0,0 +1,43 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package model
+
+import (
+ "encoding/json"
+ "io"
+)
+
+type ChannelData struct {
+ Channel *Channel `json:"channel"`
+ Member *ChannelMember `json:"member"`
+}
+
+func (o *ChannelData) Etag() string {
+ var mt int64 = 0
+ if o.Member != nil {
+ mt = o.Member.LastUpdateAt
+ }
+
+ return Etag(o.Channel.Id, o.Channel.UpdateAt, o.Channel.LastPostAt, mt)
+}
+
+func (o *ChannelData) ToJson() string {
+ b, err := json.Marshal(o)
+ if err != nil {
+ return ""
+ } else {
+ return string(b)
+ }
+}
+
+func ChannelDataFromJson(data io.Reader) *ChannelData {
+ decoder := json.NewDecoder(data)
+ var o ChannelData
+ err := decoder.Decode(&o)
+ if err == nil {
+ return &o
+ } else {
+ return nil
+ }
+}
diff --git a/model/client.go b/model/client.go
index a5016fa2c..6fcfa5043 100644
--- a/model/client.go
+++ b/model/client.go
@@ -390,6 +390,15 @@ func (c *Client) GetChannels(etag string) (*Result, *AppError) {
}
}
+func (c *Client) GetChannel(id, etag string) (*Result, *AppError) {
+ if r, err := c.DoGet("/channels/"+id+"/", "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), ChannelDataFromJson(r.Body)}, nil
+ }
+}
+
func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) {
if r, err := c.DoGet("/channels/more", "", etag); err != nil {
return nil, err
@@ -399,6 +408,15 @@ func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) {
}
}
+func (c *Client) GetChannelCounts(etag string) (*Result, *AppError) {
+ if r, err := c.DoGet("/channels/counts", "", etag); err != nil {
+ return nil, err
+ } else {
+ return &Result{r.Header.Get(HEADER_REQUEST_ID),
+ r.Header.Get(HEADER_ETAG_SERVER), ChannelCountsFromJson(r.Body)}, nil
+ }
+}
+
func (c *Client) JoinChannel(id string) (*Result, *AppError) {
if r, err := c.DoPost("/channels/"+id+"/join", ""); err != nil {
return nil, err
diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go
index cac5c681b..b8bf8b5ac 100644
--- a/store/sql_channel_store.go
+++ b/store/sql_channel_store.go
@@ -281,6 +281,41 @@ func (s SqlChannelStore) GetMoreChannels(teamId string, userId string) StoreChan
return storeChannel
}
+type channelIdWithCountAndUpdateAt struct {
+ Id string
+ TotalMsgCount int64
+ UpdateAt int64
+}
+
+func (s SqlChannelStore) GetChannelCounts(teamId string, userId string) StoreChannel {
+ storeChannel := make(StoreChannel)
+
+ go func() {
+ result := StoreResult{}
+
+ var data []channelIdWithCountAndUpdateAt
+ _, err := s.GetReplica().Select(&data, "SELECT Id, TotalMsgCount, UpdateAt FROM Channels WHERE Id IN (SELECT ChannelId FROM ChannelMembers WHERE UserId = :UserId) AND TeamId = :TeamId AND DeleteAt = 0 ORDER BY DisplayName", map[string]interface{}{"TeamId": teamId, "UserId": userId})
+
+ if err != nil {
+ result.Err = model.NewAppError("SqlChannelStore.GetChannelCounts", "We couldn't get the channel counts", "teamId="+teamId+", userId="+userId+", err="+err.Error())
+ } else {
+ counts := &model.ChannelCounts{Counts: make(map[string]int64), UpdateTimes: make(map[string]int64)}
+ for i := range data {
+ v := data[i]
+ counts.Counts[v.Id] = v.TotalMsgCount
+ counts.UpdateTimes[v.Id] = v.UpdateAt
+ }
+
+ result.Data = counts
+ }
+
+ storeChannel <- result
+ close(storeChannel)
+ }()
+
+ return storeChannel
+}
+
func (s SqlChannelStore) GetByName(teamId string, name string) StoreChannel {
storeChannel := make(StoreChannel)
diff --git a/store/sql_channel_store_test.go b/store/sql_channel_store_test.go
index b14883843..dabe39904 100644
--- a/store/sql_channel_store_test.go
+++ b/store/sql_channel_store_test.go
@@ -462,6 +462,53 @@ func TestChannelStoreGetMoreChannels(t *testing.T) {
}
}
+func TestChannelStoreGetChannelCounts(t *testing.T) {
+ Setup()
+
+ o2 := model.Channel{}
+ o2.TeamId = model.NewId()
+ o2.DisplayName = "Channel2"
+ o2.Name = "a" + model.NewId() + "b"
+ o2.Type = model.CHANNEL_OPEN
+ Must(store.Channel().Save(&o2))
+
+ o1 := model.Channel{}
+ o1.TeamId = model.NewId()
+ o1.DisplayName = "Channel1"
+ o1.Name = "a" + model.NewId() + "b"
+ o1.Type = model.CHANNEL_OPEN
+ Must(store.Channel().Save(&o1))
+
+ m1 := model.ChannelMember{}
+ m1.ChannelId = o1.Id
+ m1.UserId = model.NewId()
+ m1.NotifyLevel = model.CHANNEL_NOTIFY_ALL
+ Must(store.Channel().SaveMember(&m1))
+
+ m2 := model.ChannelMember{}
+ m2.ChannelId = o1.Id
+ m2.UserId = model.NewId()
+ m2.NotifyLevel = model.CHANNEL_NOTIFY_ALL
+ Must(store.Channel().SaveMember(&m2))
+
+ m3 := model.ChannelMember{}
+ m3.ChannelId = o2.Id
+ m3.UserId = model.NewId()
+ m3.NotifyLevel = model.CHANNEL_NOTIFY_ALL
+ Must(store.Channel().SaveMember(&m3))
+
+ cresult := <-store.Channel().GetChannelCounts(o1.TeamId, m1.UserId)
+ counts := cresult.Data.(*model.ChannelCounts)
+
+ if len(counts.Counts) != 1 {
+ t.Fatal("wrong number of counts")
+ }
+
+ if len(counts.UpdateTimes) != 1 {
+ t.Fatal("wrong number of update times")
+ }
+}
+
func TestChannelStoreUpdateLastViewedAt(t *testing.T) {
Setup()
diff --git a/store/store.go b/store/store.go
index 0934fe84b..613fe4198 100644
--- a/store/store.go
+++ b/store/store.go
@@ -50,6 +50,7 @@ type ChannelStore interface {
GetByName(team_id string, domain string) StoreChannel
GetChannels(teamId string, userId string) StoreChannel
GetMoreChannels(teamId string, userId string) StoreChannel
+ GetChannelCounts(teamId string, userId string) StoreChannel
SaveMember(member *model.ChannelMember) StoreChannel
GetMembers(channelId string) StoreChannel
diff --git a/web/react/components/access_history_modal.jsx b/web/react/components/access_history_modal.jsx
index 16768a119..a19e5c16e 100644
--- a/web/react/components/access_history_modal.jsx
+++ b/web/react/components/access_history_modal.jsx
@@ -13,21 +13,23 @@ function getStateFromStoresForAudits() {
}
module.exports = React.createClass({
+ displayName: 'AccessHistoryModal',
componentDidMount: function() {
- UserStore.addAuditsChangeListener(this._onChange);
- $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function(e) {
+ UserStore.addAuditsChangeListener(this.onListenerChange);
+ $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function() {
AsyncClient.getAudits();
});
var self = this;
- $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) {
+ $(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function() {
+ $('#user_settings').modal('show');
self.setState({moreInfo: []});
});
},
componentWillUnmount: function() {
- UserStore.removeAuditsChangeListener(this._onChange);
+ UserStore.removeAuditsChangeListener(this.onListenerChange);
},
- _onChange: function() {
+ onListenerChange: function() {
var newState = getStateFromStoresForAudits();
if (!utils.areStatesEqual(newState.audits, this.state.audits)) {
this.setState(newState);
@@ -61,6 +63,21 @@ module.exports = React.createClass({
currentAudit.session_id = 'N/A (Login attempt)';
}
+ var moreInfo = (<a href='#' className='theme' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>);
+ if (this.state.moreInfo[i]) {
+ moreInfo = (
+ <div>
+ <div>{'Session ID: ' + currentAudit.session_id}</div>
+ <div>{'URL: ' + currentAudit.action.replace(/\/api\/v[1-9]/, '')}</div>
+ </div>
+ );
+ }
+
+ var divider = null;
+ if (i < this.state.audits.length - 1) {
+ divider = (<div className='divider-light'></div>)
+ }
+
accessList[i] = (
<div className='access-history__table'>
<div className='access__date'>{newDate}</div>
@@ -68,25 +85,21 @@ module.exports = React.createClass({
<div className='report__time'>{newHistoryDate.toLocaleTimeString(navigator.language, {hour: '2-digit', minute: '2-digit'})}</div>
<div className='report__info'>
<div>{'IP: ' + currentAudit.ip_address}</div>
- {this.state.moreInfo[i] ?
- <div>
- <div>{'Session ID: ' + currentAudit.session_id}</div>
- <div>{'URL: ' + currentAudit.action.replace(/\/api\/v[1-9]/, '')}</div>
- </div>
- :
- <a href='#' className='theme' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>
- }
+ {moreInfo}
</div>
- {i < this.state.audits.length - 1 ?
- <div className='divider-light'/>
- :
- null
- }
+ {divider}
</div>
</div>
);
}
+ var content;
+ if (this.state.audits.loading) {
+ content = (<LoadingScreen />);
+ } else {
+ content = (<form role='form'>{accessList}</form>);
+ }
+
return (
<div>
<div className='modal fade' ref='modal' id='access-history' tabIndex='-1' role='dialog' aria-hidden='true'>
@@ -97,13 +110,7 @@ module.exports = React.createClass({
<h4 className='modal-title' id='myModalLabel'>Access History</h4>
</div>
<div ref='modalBody' className='modal-body'>
- {!this.state.audits.loading ?
- <form role='form'>
- {accessList}
- </form>
- :
- <LoadingScreen />
- }
+ {content}
</div>
</div>
</div>
diff --git a/web/react/components/activity_log_modal.jsx b/web/react/components/activity_log_modal.jsx
index f28f0d5f1..1192a72bc 100644
--- a/web/react/components/activity_log_modal.jsx
+++ b/web/react/components/activity_log_modal.jsx
@@ -10,40 +10,41 @@ var utils = require('../utils/utils.jsx');
function getStateFromStoresForSessions() {
return {
sessions: UserStore.getSessions(),
- server_error: null,
- client_error: null
+ serverError: null,
+ clientError: null
};
}
module.exports = React.createClass({
+ displayName: 'ActivityLogModal',
submitRevoke: function(altId) {
- var self = this;
Client.revokeSession(altId,
function(data) {
AsyncClient.getSessions();
}.bind(this),
function(err) {
- state = getStateFromStoresForSessions();
- state.server_error = err;
+ var state = getStateFromStoresForSessions();
+ state.serverError = err;
this.setState(state);
}.bind(this)
);
},
componentDidMount: function() {
- UserStore.addSessionsChangeListener(this._onChange);
+ UserStore.addSessionsChangeListener(this.onListenerChange);
$(this.refs.modal.getDOMNode()).on('shown.bs.modal', function (e) {
AsyncClient.getSessions();
});
var self = this;
$(this.refs.modal.getDOMNode()).on('hidden.bs.modal', function(e) {
- self.setState({ moreInfo: [] });
+ $('#user_settings').modal('show');
+ self.setState({moreInfo: []});
});
},
componentWillUnmount: function() {
- UserStore.removeSessionsChangeListener(this._onChange);
+ UserStore.removeSessionsChangeListener(this.onListenerChange);
},
- _onChange: function() {
+ onListenerChange: function() {
var newState = getStateFromStoresForSessions();
if (!utils.areStatesEqual(newState.sessions, this.state.sessions)) {
this.setState(newState);
@@ -52,7 +53,7 @@ module.exports = React.createClass({
handleMoreInfo: function(index) {
var newMoreInfo = this.state.moreInfo;
newMoreInfo[index] = true;
- this.setState({ moreInfo: newMoreInfo });
+ this.setState({moreInfo: newMoreInfo});
},
getInitialState: function() {
var initialState = getStateFromStoresForSessions();
@@ -61,68 +62,76 @@ module.exports = React.createClass({
},
render: function() {
var activityList = [];
- var server_error = this.state.server_error ? this.state.server_error : null;
+ var serverError = this.state.serverError;
+
+ // Squash any false-y value for server error into null
+ if (!serverError) {
+ serverError = null;
+ }
for (var i = 0; i < this.state.sessions.length; i++) {
var currentSession = this.state.sessions[i];
var lastAccessTime = new Date(currentSession.last_activity_at);
var firstAccessTime = new Date(currentSession.create_at);
- var devicePicture = "";
+ var devicePicture = '';
- if (currentSession.props.platform === "Windows") {
- devicePicture = "fa fa-windows";
+ if (currentSession.props.platform === 'Windows') {
+ devicePicture = 'fa fa-windows';
}
- else if (currentSession.props.platform === "Macintosh" || currentSession.props.platform === "iPhone") {
- devicePicture = "fa fa-apple";
+ else if (currentSession.props.platform === 'Macintosh' || currentSession.props.platform === 'iPhone') {
+ devicePicture = 'fa fa-apple';
}
- else if (currentSession.props.platform === "Linux") {
- devicePicture = "fa fa-linux";
+ else if (currentSession.props.platform === 'Linux') {
+ devicePicture = 'fa fa-linux';
+ }
+
+ var moreInfo;
+ if (this.state.moreInfo[i]) {
+ moreInfo = (
+ <div>
+ <div>{'First time active: ' + firstAccessTime.toDateString() + ', ' + lastAccessTime.toLocaleTimeString()}</div>
+ <div>{'OS: ' + currentSession.props.os}</div>
+ <div>{'Browser: ' + currentSession.props.browser}</div>
+ <div>{'Session ID: ' + currentSession.alt_id}</div>
+ </div>
+ );
+ } else {
+ moreInfo = (<a className='theme' href='#' onClick={this.handleMoreInfo.bind(this, i)}>More info</a>);
}
activityList[i] = (
- <div className="activity-log__table">
- <div className="activity-log__report">
- <div className="report__platform"><i className={devicePicture} />{currentSession.props.platform}</div>
- <div className="report__info">
- <div>{"Last activity: " + lastAccessTime.toDateString() + ", " + lastAccessTime.toLocaleTimeString()}</div>
- { this.state.moreInfo[i] ?
- <div>
- <div>{"First time active: " + firstAccessTime.toDateString() + ", " + lastAccessTime.toLocaleTimeString()}</div>
- <div>{"OS: " + currentSession.props.os}</div>
- <div>{"Browser: " + currentSession.props.browser}</div>
- <div>{"Session ID: " + currentSession.alt_id}</div>
- </div>
- :
- <a className="theme" href="#" onClick={this.handleMoreInfo.bind(this, i)}>More info</a>
- }
+ <div className='activity-log__table'>
+ <div className='activity-log__report'>
+ <div className='report__platform'><i className={devicePicture} />{currentSession.props.platform}</div>
+ <div className='report__info'>
+ <div>{'Last activity: ' + lastAccessTime.toDateString() + ', ' + lastAccessTime.toLocaleTimeString()}</div>
+ {moreInfo}
</div>
</div>
- <div className="activity-log__action"><button onClick={this.submitRevoke.bind(this, currentSession.alt_id)} className="btn btn-primary">Logout</button></div>
+ <div className='activity-log__action'><button onClick={this.submitRevoke.bind(this, currentSession.alt_id)} className='btn btn-primary'>Logout</button></div>
</div>
);
}
+ var content;
+ if (this.state.sessions.loading) {
+ content = (<LoadingScreen />);
+ } else {
+ content = (<form role='form'>{activityList}</form>);
+ }
+
return (
<div>
- <div className="modal fade" ref="modal" id="activity-log" tabIndex="-1" role="dialog" aria-hidden="true">
- <div className="modal-dialog modal-lg">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
- <h4 className="modal-title" id="myModalLabel">Active Sessions</h4>
+ <div className='modal fade' ref='modal' id='activity-log' tabIndex='-1' role='dialog' aria-hidden='true'>
+ <div className='modal-dialog modal-lg'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button type='button' className='close' data-dismiss='modal' aria-label='Close'><span aria-hidden='true'>&times;</span></button>
+ <h4 className='modal-title' id='myModalLabel'>Active Sessions</h4>
</div>
- <p className="session-help-text">Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the "Logout" button below to end a session.</p>
- <div ref="modalBody" className="modal-body">
- { !this.state.sessions.loading ?
- <div>
- <form role="form">
- { activityList }
- </form>
- { server_error }
- </div>
- :
- <LoadingScreen />
- }
+ <p className='session-help-text'>Sessions are created when you log in with your email and password to a new browser on a device. Sessions let you use Mattermost for up to 30 days without having to log in again. If you want to log out sooner, use the 'Logout' button below to end a session.</p>
+ <div ref='modalBody' className='modal-body'>
+ {content}
</div>
</div>
</div>
diff --git a/web/react/components/edit_channel_modal.jsx b/web/react/components/edit_channel_modal.jsx
index 06d7fc3e8..dcff5b89d 100644
--- a/web/react/components/edit_channel_modal.jsx
+++ b/web/react/components/edit_channel_modal.jsx
@@ -14,7 +14,7 @@ module.exports = React.createClass({
Client.updateChannelDesc(data,
function(data) {
this.setState({ server_error: "" });
- AsyncClient.getChannels(true);
+ AsyncClient.getChannel(this.state.channel_id);
$(this.refs.modal.getDOMNode()).modal('hide');
}.bind(this),
function(err) {
diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx
index b7ea5734f..c36c908d2 100644
--- a/web/react/components/file_attachment.jsx
+++ b/web/react/components/file_attachment.jsx
@@ -113,6 +113,14 @@ module.exports = React.createClass({
fileSizeString = utils.fileSizeToString(this.state.fileSize);
}
+ var filenameString = decodeURIComponent(utils.getFileName(filename));
+ var trimmedFilename;
+ if (filenameString.length > 35) {
+ trimmedFilename = filenameString.substring(0, Math.min(35, filenameString.length)) + "...";
+ } else {
+ trimmedFilename = filenameString;
+ }
+
return (
<div className="post-image__column" key={filename}>
<a className="post-image__thumbnail" href="#" onClick={this.props.handleImageClick}
@@ -120,7 +128,7 @@ module.exports = React.createClass({
{thumbnail}
</a>
<div className="post-image__details">
- <div className="post-image__name">{decodeURIComponent(utils.getFileName(filename))}</div>
+ <div data-toggle="tooltip" title={filenameString} className="post-image__name">{trimmedFilename}</div>
<div>
<span className="post-image__type">{fileInfo.ext.toUpperCase()}</span>
<span className="post-image__size">{fileSizeString}</span>
diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx
index 75538c8fe..5b6924891 100644
--- a/web/react/components/invite_member_modal.jsx
+++ b/web/react/components/invite_member_modal.jsx
@@ -2,6 +2,7 @@
// See License.txt for license information.
var utils = require('../utils/utils.jsx');
+var ConfigStore = require('../stores/config_store.jsx');
var Client = require('../utils/client.jsx');
var UserStore = require('../stores/user_store.jsx');
var ConfirmModal = require('./confirm_modal.jsx');
@@ -35,6 +36,10 @@ module.exports = React.createClass({
});
},
handleSubmit: function(e) {
+ if (!this.state.emailEnabled) {
+ return;
+ }
+
var inviteIds = this.state.inviteIds;
var count = inviteIds.length;
var invites = [];
@@ -147,12 +152,18 @@ module.exports = React.createClass({
idCount: 0,
emailErrors: {},
firstNameErrors: {},
- lastNameErrors: {}
+ lastNameErrors: {},
+ emailEnabled: !ConfigStore.getSettingAsBoolean('ByPassEmail', false)
};
},
render: function() {
var currentUser = UserStore.getCurrentUser();
+ var inputDisabled = '';
+ if (!this.state.emailEnabled) {
+ inputDisabled = 'disabled';
+ }
+
if (currentUser != null) {
var inviteSections = [];
var inviteIds = this.state.inviteIds;
@@ -195,13 +206,13 @@ module.exports = React.createClass({
nameFields = (<div className='row--invite'>
<div className='col-sm-6'>
<div className={firstNameClass}>
- <input type='text' className='form-control' ref={'first_name' + index} placeholder='First name' maxLength='64' />
+ <input type='text' className='form-control' ref={'first_name' + index} placeholder='First name' maxLength='64' disabled={!this.state.emailEnabled}/>
{firstNameError}
</div>
</div>
<div className='col-sm-6'>
<div className={lastNameClass}>
- <input type='text' className='form-control' ref={'last_name' + index} placeholder='Last name' maxLength='64' />
+ <input type='text' className='form-control' ref={'last_name' + index} placeholder='Last name' maxLength='64' disabled={!this.state.emailEnabled}/>
{lastNameError}
</div>
</div>
@@ -212,7 +223,7 @@ module.exports = React.createClass({
<div key={'key' + index}>
{removeButton}
<div className={emailClass}>
- <input onKeyUp={this.displayNameKeyUp} type='text' ref={'email' + index} className='form-control' placeholder='email@domain.com' maxLength='64' />
+ <input onKeyUp={this.displayNameKeyUp} type='text' ref={'email' + index} className='form-control' placeholder='email@domain.com' maxLength='64' disabled={!this.state.emailEnabled}/>
{emailError}
</div>
{nameFields}
@@ -225,6 +236,45 @@ module.exports = React.createClass({
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
+ var content = null;
+ var sendButton = null;
+ if (this.state.emailEnabled) {
+ content = (
+ <div>
+ {serverError}
+ <button type='button' className='btn btn-default' onClick={this.addInviteFields}>Add another</button>
+ <br/>
+ <br/>
+ <span>People invited automatically join Town Square channel.</span>
+ </div>
+ );
+
+ sendButton = <button onClick={this.handleSubmit} type='button' className='btn btn-primary'>Send Invitations</button>
+ } else {
+ var teamInviteLink = null;
+ if (currentUser && this.props.teamType === 'O') {
+ var linkUrl = utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + currentUser.team_id;
+ var link = <a href='#' data-toggle='modal' data-target='#get_link' data-title='Team Invite' data-value={linkUrl} onClick={
+ function() {
+ $('#invite_member').modal('hide');
+ }
+ }>Team Invite Link</a>;
+
+ teamInviteLink = (
+ <p>
+ You can also invite people using the {link}.
+ </p>
+ );
+ }
+
+ content = (
+ <div>
+ <p>Email is currently disabled for your team, and email invitations cannot be sent. Contact your system administrator to enable email and email invitations.</p>
+ {teamInviteLink}
+ </div>
+ );
+ }
+
return (
<div>
<div className='modal fade' ref='modal' id='invite_member' tabIndex='-1' role='dialog' aria-hidden='true'>
@@ -238,15 +288,11 @@ module.exports = React.createClass({
<form role='form'>
{inviteSections}
</form>
- {serverError}
- <button type='button' className='btn btn-default' onClick={this.addInviteFields}>Add another</button>
- <br/>
- <br/>
- <span>People invited automatically join Town Square channel.</span>
+ {content}
</div>
<div className='modal-footer'>
<button type='button' className='btn btn-default' data-dismiss='modal'>Cancel</button>
- <button onClick={this.handleSubmit} type='button' className='btn btn-primary'>Send Invitations</button>
+ {sendButton}
</div>
</div>
</div>
diff --git a/web/react/components/login.jsx b/web/react/components/login.jsx
index fe0a47777..eba4f06f4 100644
--- a/web/react/components/login.jsx
+++ b/web/react/components/login.jsx
@@ -50,7 +50,7 @@ module.exports = React.createClass({
var redirect = utils.getUrlParameter("redirect");
if (redirect) {
- window.location.pathname = decodeURI(redirect);
+ window.location.pathname = decodeURIComponent(redirect);
} else {
window.location.pathname = '/' + name + '/channels/town-square';
}
diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx
index 007476f9b..5261ed6a7 100644
--- a/web/react/components/more_channels.jsx
+++ b/web/react/components/more_channels.jsx
@@ -1,34 +1,32 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-
var utils = require('../utils/utils.jsx');
var client = require('../utils/client.jsx');
var asyncClient = require('../utils/async_client.jsx');
-var UserStore = require('../stores/user_store.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
var LoadingScreen = require('./loading_screen.jsx');
function getStateFromStores() {
- return {
- channels: ChannelStore.getMoreAll(),
- server_error: null
- };
+ return {
+ channels: ChannelStore.getMoreAll(),
+ serverError: null
+ };
}
module.exports = React.createClass({
- displayName: "MoreChannelsModal",
+ displayName: 'MoreChannelsModal',
componentDidMount: function() {
ChannelStore.addMoreChangeListener(this._onChange);
- $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function (e) {
+ $(this.refs.modal.getDOMNode()).on('shown.bs.modal', function shown() {
asyncClient.getMoreChannels(true);
});
var self = this;
- $(this.refs.modal.getDOMNode()).on('show.bs.modal', function(e) {
+ $(this.refs.modal.getDOMNode()).on('show.bs.modal', function show(e) {
var button = e.relatedTarget;
- self.setState({ channel_type: $(button).attr('data-channeltype') });
+ self.setState({channelType: $(button).attr('data-channeltype')});
});
},
componentWillUnmount: function() {
@@ -42,18 +40,17 @@ module.exports = React.createClass({
},
getInitialState: function() {
var initState = getStateFromStores();
- initState.channel_type = "";
+ initState.channelType = '';
return initState;
},
- handleJoin: function(e) {
- var self = this;
- client.joinChannel(e,
- function(data) {
- $(self.refs.modal.getDOMNode()).modal('hide');
- asyncClient.getChannels(true);
+ handleJoin: function(id) {
+ client.joinChannel(id,
+ function() {
+ $(this.refs.modal.getDOMNode()).modal('hide');
+ asyncClient.getChannel(id);
}.bind(this),
function(err) {
- this.state.server_error = err.message;
+ this.state.serverError = err.message;
this.setState(this.state);
}.bind(this)
);
@@ -62,52 +59,66 @@ module.exports = React.createClass({
$(this.refs.modal.getDOMNode()).modal('hide');
},
render: function() {
- var server_error = this.state.server_error ? <div className='form-group has-error'><label className='control-label'>{ this.state.server_error }</label></div> : null;
+ var serverError;
+ if (this.state.serverError) {
+ serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
+ }
+
var outter = this;
var moreChannels;
- if (this.state.channels != null)
- moreChannels = this.state.channels;
+ if (this.state.channels != null) {
+ var channels = this.state.channels;
+ if (!channels.loading) {
+ if (channels.length) {
+ moreChannels = (
+ <table className='more-channel-table table'>
+ <tbody>
+ {channels.map(function cMap(channel) {
+ return (
+ <tr key={channel.id}>
+ <td>
+ <p className='more-channel-name'>{channel.display_name}</p>
+ <p className='more-channel-description'>{channel.description}</p>
+ </td>
+ <td className='td--action'><button onClick={outter.handleJoin.bind(outter, channel.id)} className='btn btn-primary'>Join</button></td>
+ </tr>
+ );
+ })}
+ </tbody>
+ </table>
+ );
+ } else {
+ moreChannels = (
+ <div className='no-channel-message'>
+ <p className='primary-message'>No more channels to join</p>
+ <p className='secondary-message'>Click 'Create New Channel' to make a new one</p>
+ </div>
+ );
+ }
+ } else {
+ moreChannels = <LoadingScreen />;
+ }
+ }
return (
- <div className="modal fade" id="more_channels" ref="modal" tabIndex="-1" role="dialog" aria-hidden="true">
- <div className="modal-dialog">
- <div className="modal-content">
- <div className="modal-header">
- <button type="button" className="close" data-dismiss="modal">
- <span aria-hidden="true">&times;</span>
- <span className="sr-only">Close</span>
+ <div className='modal fade' id='more_channels' ref='modal' tabIndex='-1' role='dialog' aria-hidden='true'>
+ <div className='modal-dialog'>
+ <div className='modal-content'>
+ <div className='modal-header'>
+ <button type='button' className='close' data-dismiss='modal'>
+ <span aria-hidden='true'>&times;</span>
+ <span className='sr-only'>Close</span>
</button>
- <h4 className="modal-title">More Channels</h4>
- <button data-toggle="modal" data-target="#new_channel" data-channeltype={this.state.channel_type} type="button" className="btn btn-primary channel-create-btn" onClick={this.handleNewChannel}>Create New Channel</button>
+ <h4 className='modal-title'>More Channels</h4>
+ <button data-toggle='modal' data-target='#new_channel' data-channeltype={this.state.channelType} type='button' className='btn btn-primary channel-create-btn' onClick={this.handleNewChannel}>Create New Channel</button>
</div>
- <div className="modal-body">
- {!moreChannels.loading ?
- (moreChannels.length ?
- <table className="more-channel-table table">
- <tbody>
- {moreChannels.map(function(channel) {
- return (
- <tr key={channel.id}>
- <td>
- <p className="more-channel-name">{channel.display_name}</p>
- <p className="more-channel-description">{channel.description}</p>
- </td>
- <td className="td--action"><button onClick={outter.handleJoin.bind(outter, channel.id)} className="btn btn-primary">Join</button></td>
- </tr>
- )
- })}
- </tbody>
- </table>
- : <div className="no-channel-message">
- <p className="primary-message">No more channels to join</p>
- <p className="secondary-message">Click 'Create New Channel' to make a new one</p>
- </div>)
- : <LoadingScreen /> }
- { server_error }
+ <div className='modal-body'>
+ {moreChannels}
+ {serverError}
</div>
- <div className="modal-footer">
- <button type="button" className="btn btn-default" data-dismiss="modal">Close</button>
+ <div className='modal-footer'>
+ <button type='button' className='btn btn-default' data-dismiss='modal'>Close</button>
</div>
</div>
</div>
diff --git a/web/react/components/new_channel.jsx b/web/react/components/new_channel.jsx
index c22147022..b00376758 100644
--- a/web/react/components/new_channel.jsx
+++ b/web/react/components/new_channel.jsx
@@ -55,16 +55,16 @@ module.exports = React.createClass({
channel.description = this.refs.channel_desc.getDOMNode().value.trim();
channel.type = this.state.channelType;
- var self = this;
client.createChannel(channel,
- function() {
+ function(data) {
+ $(this.refs.modal.getDOMNode()).modal('hide');
+
+ asyncClient.getChannel(data.id);
+ utils.switchChannel(data);
+
this.refs.display_name.getDOMNode().value = '';
this.refs.channel_name.getDOMNode().value = '';
this.refs.channel_desc.getDOMNode().value = '';
-
- $(self.refs.modal.getDOMNode()).modal('hide');
- window.location = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
- asyncClient.getChannels(true);
}.bind(this),
function(err) {
state.serverError = err.message;
diff --git a/web/react/components/post.jsx b/web/react/components/post.jsx
index e72a2d001..f099c67ab 100644
--- a/web/react/components/post.jsx
+++ b/web/react/components/post.jsx
@@ -11,12 +11,6 @@ var ActionTypes = Constants.ActionTypes;
module.exports = React.createClass({
displayName: "Post",
- componentDidMount: function() {
- $('.modal').on('show.bs.modal', function () {
- $('.modal-body').css('overflow-y', 'auto');
- $('.modal-body').css('max-height', $(window).height() * 0.7);
- });
- },
handleCommentClick: function(e) {
e.preventDefault();
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 83f806b79..fa74d4295 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -69,6 +69,12 @@ module.exports = React.createClass({
this.oldScrollHeight = post_holder.scrollHeight;
this.oldZoom = (window.outerWidth - 8) / window.innerWidth;
+ $('.modal').on('show.bs.modal', function () {
+ $('.modal-body').css('overflow-y', 'auto');
+ $('.modal-body').css('max-height', $(window).height() * 0.7);
+ });
+
+ // Timeout exists for the DOM to fully render before making changes
var self = this;
$(window).resize(function(){
$(post_holder).perfectScrollbar('update');
@@ -140,6 +146,7 @@ module.exports = React.createClass({
UserStore.removeStatusesChangeListener(this._onTimeChange);
SocketStore.removeChangeListener(this._onSocketChange);
$('body').off('click.userpopover');
+ $('.modal').off('show.bs.modal')
},
resize: function() {
var post_holder = $(".post-list-holder-by-time")[0];
diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx
index 26593b7fa..93cb6ef21 100644
--- a/web/react/components/rename_channel_modal.jsx
+++ b/web/react/components/rename_channel_modal.jsx
@@ -63,12 +63,14 @@ module.exports = React.createClass({
Client.updateChannel(channel,
function(data, text, req) {
+ $(this.refs.modal.getDOMNode()).modal('hide');
+
+ AsyncClient.getChannel(channel.id);
+ utils.updateTabTitle(channel.display_name);
+ utils.updateAddressBar(channel.name);
+
this.refs.display_name.getDOMNode().value = "";
this.refs.channel_name.getDOMNode().value = "";
-
- $('#' + this.props.modalId).modal('hide');
- window.location.href = TeamStore.getCurrentTeamUrl() + '/channels/' + this.state.channel_name;
- AsyncClient.getChannels(true);
}.bind(this),
function(err) {
state.server_error = err.message;
diff --git a/web/react/components/setting_item_min.jsx b/web/react/components/setting_item_min.jsx
index 2209c74d1..3c87e416e 100644
--- a/web/react/components/setting_item_min.jsx
+++ b/web/react/components/setting_item_min.jsx
@@ -2,12 +2,23 @@
// See License.txt for license information.
module.exports = React.createClass({
+ displayName: 'SettingsItemMin',
+ propTypes: {
+ title: React.PropTypes.string,
+ disableOpen: React.PropTypes.bool,
+ updateSection: React.PropTypes.func,
+ describe: React.PropTypes.string
+ },
render: function() {
+ var editButton = '';
+ if (!this.props.disableOpen) {
+ editButton = <li className='col-sm-2 section-edit'><a className='section-edit theme' href='#' onClick={this.props.updateSection}>Edit</a></li>;
+ }
return (
- <ul className="section-min">
- <li className="col-sm-10 section-title">{this.props.title}</li>
- <li className="col-sm-2 section-edit"><a className="section-edit theme" href="#" onClick={this.props.updateSection}>Edit</a></li>
- <li className="col-sm-7 section-describe">{this.props.describe}</li>
+ <ul className='section-min'>
+ <li className='col-sm-10 section-title'>{this.props.title}</li>
+ {editButton}
+ <li className='col-sm-7 section-describe'>{this.props.describe}</li>
</ul>
);
}
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index fe73cbcf7..a8496b385 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -102,7 +102,7 @@ function getStateFromStores() {
}
readDirectChannels = readDirectChannels.slice(index);
- showDirectChannels.sort(function(a, b) {
+ showDirectChannels.sort(function directSort(a, b) {
if (a.display_name < b.display_name) {
return -1;
}
@@ -114,7 +114,7 @@ function getStateFromStores() {
}
return {
- active_id: currentId,
+ activeId: currentId,
channels: ChannelStore.getAll(),
members: members,
showDirectChannels: showDirectChannels,
@@ -128,6 +128,7 @@ module.exports = React.createClass({
ChannelStore.addChangeListener(this.onChange);
UserStore.addChangeListener(this.onChange);
UserStore.addStatusesChangeListener(this.onChange);
+ TeamStore.addChangeListener(this.onChange);
SocketStore.addChangeListener(this.onSocketChange);
$('.nav-pills__container').perfectScrollbar();
@@ -146,6 +147,7 @@ module.exports = React.createClass({
ChannelStore.removeChangeListener(this.onChange);
UserStore.removeChangeListener(this.onChange);
UserStore.removeStatusesChangeListener(this.onChange);
+ TeamStore.removeChangeListener(this.onChange);
SocketStore.removeChangeListener(this.onSocketChange);
},
onChange: function() {
@@ -157,9 +159,11 @@ module.exports = React.createClass({
onSocketChange: function(msg) {
if (msg.action === 'posted') {
if (ChannelStore.getCurrentId() === msg.channel_id) {
- AsyncClient.getChannels(true, window.isActive);
+ if (window.isActive) {
+ AsyncClient.updateLastViewedAt();
+ }
} else {
- AsyncClient.getChannels(true);
+ AsyncClient.getChannels();
}
if (UserStore.getCurrentId() !== msg.user_id) {
@@ -214,12 +218,12 @@ module.exports = React.createClass({
}
}
} else if (msg.action === 'viewed') {
- if (ChannelStore.getCurrentId() != msg.channel_id) {
- AsyncClient.getChannels(true);
+ if (ChannelStore.getCurrentId() !== msg.channel_id && UserStore.getCurrentId() === msg.user_id) {
+ AsyncClient.getChannel(msg.channel_id);
}
} else if (msg.action === 'user_added') {
if (UserStore.getCurrentId() === msg.user_id) {
- AsyncClient.getChannels(true);
+ AsyncClient.getChannel(msg.channel_id);
}
} else if (msg.action === 'user_removed') {
if (msg.user_id === UserStore.getCurrentId()) {
@@ -282,7 +286,7 @@ module.exports = React.createClass({
},
render: function() {
var members = this.state.members;
- var activeId = this.state.active_id;
+ var activeId = this.state.activeId;
var badgesActive = false;
// keep track of the first and last unread channels so we can use them to set the unread indicators
@@ -294,7 +298,7 @@ module.exports = React.createClass({
var channelMember = members[channel.id];
var linkClass = '';
- if (channel.id === self.state.active_id) {
+ if (channel.id === activeId) {
linkClass = 'active';
}
@@ -346,15 +350,16 @@ module.exports = React.createClass({
// set up click handler to switch channels (or create a new channel for non-existant ones)
var clickHandler = null;
- var href;
+ var href = '#';
+ var teamURL = TeamStore.getCurrentTeamUrl();
if (!channel.fake) {
clickHandler = function(e) {
e.preventDefault();
utils.switchChannel(channel);
};
- href = '#';
- } else {
- href = TeamStore.getCurrentTeamUrl() + '/channels/' + channel.name;
+ }
+ if (channel.fake && teamURL){
+ href = teamURL + '/channels/' + channel.name;
}
return (
diff --git a/web/react/components/sidebar_header.jsx b/web/react/components/sidebar_header.jsx
index cc3f255ee..761c06e74 100644
--- a/web/react/components/sidebar_header.jsx
+++ b/web/react/components/sidebar_header.jsx
@@ -96,7 +96,7 @@ var NavbarDropdown = React.createClass({
<span className='dropdown__icon' dangerouslySetInnerHTML={{__html: Constants.MENU_ICON}} />
</a>
<ul className='dropdown-menu' role='menu'>
- <li><a href='#' data-toggle='modal' data-target='#user_settings1'>Account Settings</a></li>
+ <li><a href='#' data-toggle='modal' data-target='#user_settings'>Account Settings</a></li>
{teamSettings}
{inviteLink}
{teamLink}
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index 2439719a1..d221ca840 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -72,7 +72,7 @@ module.exports = React.createClass({
<div className='nav-pills__container'>
<ul className='nav nav-pills nav-stacked'>
- <li><a href='#' data-toggle='modal' data-target='#user_settings1'><i className='glyphicon glyphicon-cog'></i>Account Settings</a></li>
+ <li><a href='#' data-toggle='modal' data-target='#user_settings'><i className='glyphicon glyphicon-cog'></i>Account Settings</a></li>
{teamSettingsLink}
{inviteLink}
{teamLink}
diff --git a/web/react/components/signup_team_complete.jsx b/web/react/components/signup_team_complete.jsx
index 3f35a5912..e27fcd19d 100644
--- a/web/react/components/signup_team_complete.jsx
+++ b/web/react/components/signup_team_complete.jsx
@@ -1,8 +1,8 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-
var utils = require('../utils/utils.jsx');
+var ConfigStore = require('../stores/config_store.jsx');
var client = require('../utils/client.jsx');
var UserStore = require('../stores/user_store.jsx');
var BrowserStore = require('../stores/browser_store.jsx');
@@ -11,111 +11,132 @@ var constants = require('../utils/constants.jsx');
WelcomePage = React.createClass({
submitNext: function (e) {
if (!BrowserStore.isLocalStorageSupported()) {
- this.setState({ storage_error: "This service requires local storage to be enabled. Please enable it or exit private browsing."} );
+ this.setState({storageError: 'This service requires local storage to be enabled. Please enable it or exit private browsing.'});
return;
}
e.preventDefault();
- this.props.state.wizard = "team_display_name";
+ this.props.state.wizard = 'team_display_name';
this.props.updateParent(this.props.state);
},
handleDiffEmail: function (e) {
e.preventDefault();
- this.setState({ use_diff: true });
+ this.setState({useDiff: true});
},
handleDiffSubmit: function (e) {
e.preventDefault();
- var state = { use_diff: true, server_error: "" };
+ var state = {useDiff: true, serverError: ''};
var email = this.refs.email.getDOMNode().value.trim().toLowerCase();
if (!email || !utils.isEmail(email)) {
- state.email_error = "Please enter a valid email address";
+ state.emailError = 'Please enter a valid email address';
this.setState(state);
return;
- }
- else if (!BrowserStore.isLocalStorageSupported()) {
- state.email_error = "This service requires local storage to be enabled. Please enable it or exit private browsing.";
+ } else if (!BrowserStore.isLocalStorageSupported()) {
+ state.emailError = 'This service requires local storage to be enabled. Please enable it or exit private browsing.';
this.setState(state);
return;
- }
- else {
- state.email_error = "";
+ } else {
+ state.emailError = '';
}
client.signupTeam(email,
function(data) {
- if (data["follow_link"]) {
- window.location.href = data["follow_link"];
+ if (data['follow_link']) {
+ window.location.href = data['follow_link'];
} else {
- this.props.state.wizard = "finished";
+ this.props.state.wizard = 'finished';
this.props.updateParent(this.props.state);
- window.location.href = "/signup_team_confirm/?email=" + encodeURIComponent(team.email);
+ window.location.href = '/signup_team_confirm/?email=' + encodeURIComponent(team.email);
}
}.bind(this),
function(err) {
- this.state.server_error = err.message;
+ this.state.serverError = err.message;
this.setState(this.state);
}.bind(this)
);
},
getInitialState: function() {
- return { use_diff: false };
+ return {useDiff: false};
},
handleKeyPress: function(event) {
- if (event.keyCode == 13) {
+ if (event.keyCode === 13) {
this.submitNext(event);
}
},
componentWillMount: function() {
- document.addEventListener("keyup", this.handleKeyPress, false);
+ document.addEventListener('keyup', this.handleKeyPress, false);
},
componentWillUnmount: function() {
- document.removeEventListener("keyup", this.handleKeyPress, false);
+ document.removeEventListener('keyup', this.handleKeyPress, false);
},
render: function() {
-
client.track('signup', 'signup_team_01_welcome');
- var storage_error = this.state.storage_error ? <label className="control-label">{ this.state.storage_error }</label> : null;
- var email_error = this.state.email_error ? <label className="control-label">{ this.state.email_error }</label> : null;
- var server_error = this.state.server_error ? <div className={ "form-group has-error" }><label className="control-label">{ this.state.server_error }</label></div> : null;
+ var storageError = null;
+ if (this.state.storageError) {
+ storageError = <label className='control-label'>{this.state.storageError}</label>;
+ }
+
+ var emailError = null;
+ var emailDivClass = 'form-group';
+ if (this.state.emailError) {
+ emailError = <label className='control-label'>{this.state.emailError}</label>;
+ emailDivClass += ' has-error';
+ }
+
+ var serverError = null;
+ if (this.state.serverError) {
+ serverError = (
+ <div className='form-group has-error'>
+ <label className='control-label'>{this.state.serverError}</label>
+ </div>
+ );
+ }
+
+ var differentEmailLinkClass = '';
+ var emailDivContainerClass = 'hidden';
+ if (this.state.useDiff) {
+ differentEmailLinkClass = 'hidden';
+ emailDivContainerClass = '';
+ }
return (
<div>
<p>
- <img className="signup-team-logo" src="/static/images/logo.png" />
- <h3 className="sub-heading">Welcome to:</h3>
- <h1 className="margin--top-none">{config.SiteName}</h1>
+ <img className='signup-team-logo' src='/static/images/logo.png' />
+ <h3 className='sub-heading'>Welcome to:</h3>
+ <h1 className='margin--top-none'>{config.SiteName}</h1>
</p>
- <p className="margin--less">Let's set up your new team</p>
+ <p className='margin--less'>Let's set up your new team</p>
<p>
Please confirm your email address:<br />
- <div className="inner__content">
- <div className="block--gray">{ this.props.state.team.email }</div>
+ <div className='inner__content'>
+ <div className='block--gray'>{this.props.state.team.email}</div>
</div>
</p>
- <p className="margin--extra color--light">
+ <p className='margin--extra color--light'>
Your account will administer the new team site. <br />
You can add other administrators later.
</p>
- <div className="form-group">
- <button className="btn-primary btn form-group" type="submit" onClick={this.submitNext}><i className="glyphicon glyphicon-ok"></i>Yes, this address is correct</button>
- { storage_error }
+ <div className='form-group'>
+ <button className='btn-primary btn form-group' type='submit' onClick={this.submitNext}><i className='glyphicon glyphicon-ok'></i>Yes, this address is correct</button>
+ {storageError}
</div>
<hr />
- <div className={ this.state.use_diff ? "" : "hidden" }>
- <div className={ email_error ? "form-group has-error" : "form-group" }>
- <div className="row">
- <div className="col-sm-9">
- <input type="email" ref="email" className="form-control" placeholder="Email Address" maxLength="128" />
+ <div className={emailDivContainerClass}>
+ <div className={emailDivClass}>
+ <div className='row'>
+ <div className='col-sm-9'>
+ <input type='email' ref='email' className='form-control' placeholder='Email Address' maxLength='128' />
</div>
</div>
- { email_error }
+ {emailError}
</div>
- { server_error }
- <button className="btn btn-md btn-primary" type="button" onClick={this.handleDiffSubmit} type="submit">Use this instead</button>
+ {serverError}
+ <button className='btn btn-md btn-primary' type='button' onClick={this.handleDiffSubmit} type='submit'>Use this instead</button>
</div>
- <a href="#" onClick={this.handleDiffEmail} className={ this.state.use_diff ? "hidden" : "" }>Use a different email</a>
+ <a href='#' onClick={this.handleDiffEmail} className={differentEmailLinkClass}>Use a different email</a>
</div>
);
}
@@ -124,7 +145,7 @@ WelcomePage = React.createClass({
TeamDisplayNamePage = React.createClass({
submitBack: function (e) {
e.preventDefault();
- this.props.state.wizard = "welcome";
+ this.props.state.wizard = 'welcome';
this.props.updateParent(this.props.state);
},
submitNext: function (e) {
@@ -132,17 +153,17 @@ TeamDisplayNamePage = React.createClass({
var display_name = this.refs.name.getDOMNode().value.trim();
if (!display_name) {
- this.setState({name_error: "This field is required"});
+ this.setState({nameError: 'This field is required'});
return;
}
- this.props.state.wizard = "team_url";
+ this.props.state.wizard = 'team_url';
this.props.state.team.display_name = display_name;
this.props.state.team.name = utils.cleanUpUrlable(display_name);
this.props.updateParent(this.props.state);
},
getInitialState: function() {
- return { };
+ return {};
},
handleFocus: function(e) {
e.preventDefault();
@@ -150,31 +171,35 @@ TeamDisplayNamePage = React.createClass({
e.currentTarget.select();
},
render: function() {
-
client.track('signup', 'signup_team_02_name');
- var name_error = this.state.name_error ? <label className="control-label">{ this.state.name_error }</label> : null;
+ var nameError = null;
+ var nameDivClass = 'form-group';
+ if (this.state.nameError) {
+ nameError = <label className='control-label'>{this.state.nameError}</label>;
+ nameDivClass += ' has-error';
+ }
return (
<div>
<form>
- <img className="signup-team-logo" src="/static/images/logo.png" />
+ <img className='signup-team-logo' src='/static/images/logo.png' />
- <h2>{utils.toTitleCase(strings.Team) + " Name"}</h2>
- <div className={ name_error ? "form-group has-error" : "form-group" }>
- <div className="row">
- <div className="col-sm-9">
- <input type="text" ref="name" className="form-control" placeholder="" maxLength="128" defaultValue={this.props.state.team.display_name} autoFocus={true} onFocus={this.handleFocus} />
+ <h2>{utils.toTitleCase(strings.Team) + ' Name'}</h2>
+ <div className={nameDivClass}>
+ <div className='row'>
+ <div className='col-sm-9'>
+ <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' defaultValue={this.props.state.team.display_name} autoFocus={true} onFocus={this.handleFocus} />
+ </div>
+ </div>
+ {nameError}
</div>
- </div>
- { name_error }
- </div>
- <div>{"Name your " + strings.Team + " in any language. Your " + strings.Team + " name shows in menus and headings."}</div>
- <button type="submit" className="btn btn-primary margin--extra" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button>
- <div className="margin--extra">
- <a href="#" onClick={this.submitBack}>Back to previous step</a>
- </div>
- </form>
+ <div>{'Name your ' + strings.Team + ' in any language. Your ' + strings.Team + ' name shows in menus and headings.'}</div>
+ <button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button>
+ <div className='margin--extra'>
+ <a href='#' onClick={this.submitBack}>Back to previous step</a>
+ </div>
+ </form>
</div>
);
}
@@ -183,7 +208,7 @@ TeamDisplayNamePage = React.createClass({
TeamURLPage = React.createClass({
submitBack: function (e) {
e.preventDefault();
- this.props.state.wizard = "team_display_name";
+ this.props.state.wizard = 'team_display_name';
this.props.updateParent(this.props.state);
},
submitNext: function (e) {
@@ -191,25 +216,24 @@ TeamURLPage = React.createClass({
var name = this.refs.name.getDOMNode().value.trim();
if (!name) {
- this.setState({name_error: "This field is required"});
+ this.setState({nameError: 'This field is required'});
return;
}
- var cleaned_name = utils.cleanUpUrlable(name);
+ var cleanedName = utils.cleanUpUrlable(name);
var urlRegex = /^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$/g;
- if (cleaned_name != name || !urlRegex.test(name)) {
- this.setState({name_error: "Must be lowercase alphanumeric characters"});
+ if (cleanedName !== name || !urlRegex.test(name)) {
+ this.setState({nameError: 'Must be lowercase alphanumeric characters'});
return;
- }
- else if (cleaned_name.length <= 3 || cleaned_name.length > 15) {
- this.setState({name_error: "Name must be 4 or more characters up to a maximum of 15"})
+ } else if (cleanedName.length <= 3 || cleanedName.length > 15) {
+ this.setState({nameError: 'Name must be 4 or more characters up to a maximum of 15'});
return;
}
for (var index = 0; index < constants.RESERVED_TEAM_NAMES.length; index++) {
- if (cleaned_name.indexOf(constants.RESERVED_TEAM_NAMES[index]) == 0) {
- this.setState({name_error: "This team name is unavailable"})
+ if (cleanedName.indexOf(constants.RESERVED_TEAM_NAMES[index]) === 0) {
+ this.setState({nameError: 'This team name is unavailable'});
return;
}
}
@@ -218,28 +242,27 @@ TeamURLPage = React.createClass({
function(data) {
if (!data) {
if (config.AllowSignupDomainsWizard) {
- this.props.state.wizard = "allowed_domains";
+ this.props.state.wizard = 'allowed_domains';
} else {
- this.props.state.wizard = "send_invites";
+ this.props.state.wizard = 'send_invites';
this.props.state.team.type = 'O';
}
this.props.state.team.name = name;
this.props.updateParent(this.props.state);
- }
- else {
- this.state.name_error = "This URL is unavailable. Please try another.";
+ } else {
+ this.state.nameError = 'This URL is unavailable. Please try another.';
this.setState(this.state);
}
}.bind(this),
function(err) {
- this.state.name_error = err.message;
+ this.state.nameError = err.message;
this.setState(this.state);
}.bind(this)
);
},
getInitialState: function() {
- return { };
+ return {};
},
handleFocus: function(e) {
e.preventDefault();
@@ -247,40 +270,44 @@ TeamURLPage = React.createClass({
e.currentTarget.select();
},
render: function() {
-
$('body').tooltip( {selector: '[data-toggle=tooltip]', trigger: 'hover click'} );
client.track('signup', 'signup_team_03_url');
- var name_error = this.state.name_error ? <label className="control-label">{ this.state.name_error }</label> : null;
+ var nameError = null;
+ var nameDivClass = 'form-group';
+ if (this.state.nameError) {
+ nameError = <label className='control-label'>{this.state.nameError}</label>;
+ nameDivClass += ' has-error';
+ }
return (
<div>
<form>
- <img className="signup-team-logo" src="/static/images/logo.png" />
- <h2>{utils.toTitleCase(strings.Team) + " URL"}</h2>
- <div className={ name_error ? "form-group has-error" : "form-group" }>
- <div className="row">
- <div className="col-sm-11">
- <div className="input-group input-group--limit">
- <span data-toggle="tooltip" title={ utils.getWindowLocationOrigin() + "/" } className="input-group-addon">{ utils.getWindowLocationOrigin() + "/" }</span>
- <input type="text" ref="name" className="form-control" placeholder="" maxLength="128" defaultValue={this.props.state.team.name} autoFocus={true} onFocus={this.handleFocus}/>
+ <img className='signup-team-logo' src='/static/images/logo.png' />
+ <h2>{utils.toTitleCase(strings.Team) + ' URL'}</h2>
+ <div className={nameDivClass}>
+ <div className='row'>
+ <div className='col-sm-11'>
+ <div className='input-group input-group--limit'>
+ <span data-toggle='tooltip' title={utils.getWindowLocationOrigin() + '/'} className='input-group-addon'>{utils.getWindowLocationOrigin() + '/'}</span>
+ <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' defaultValue={this.props.state.team.name} autoFocus={true} onFocus={this.handleFocus}/>
+ </div>
+ </div>
</div>
+ {nameError}
</div>
- </div>
- { name_error }
- </div>
- <p>{"Choose the web address of your new " + strings.Team + ":"}</p>
- <ul className="color--light">
- <li>Short and memorable is best</li>
- <li>Use lowercase letters, numbers and dashes</li>
- <li>Must start with a letter and can't end in a dash</li>
- </ul>
- <button type="submit" className="btn btn-primary margin--extra" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button>
- <div className="margin--extra">
- <a href="#" onClick={this.submitBack}>Back to previous step</a>
- </div>
- </form>
+ <p>{'Choose the web address of your new ' + strings.Team + ':'}</p>
+ <ul className='color--light'>
+ <li>Short and memorable is best</li>
+ <li>Use lowercase letters, numbers and dashes</li>
+ <li>Must start with a letter and can't end in a dash</li>
+ </ul>
+ <button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button>
+ <div className='margin--extra'>
+ <a href='#' onClick={this.submitBack}>Back to previous step</a>
+ </div>
+ </form>
</div>
);
}
@@ -289,14 +316,14 @@ TeamURLPage = React.createClass({
AllowedDomainsPage = React.createClass({
submitBack: function (e) {
e.preventDefault();
- this.props.state.wizard = "team_url";
+ this.props.state.wizard = 'team_url';
this.props.updateParent(this.props.state);
},
submitNext: function (e) {
e.preventDefault();
if (this.refs.open_network.getDOMNode().checked) {
- this.props.state.wizard = "send_invites";
+ this.props.state.wizard = 'send_invites';
this.props.state.team.type = 'O';
this.props.updateParent(this.props.state);
return;
@@ -304,65 +331,72 @@ AllowedDomainsPage = React.createClass({
if (this.refs.allow.getDOMNode().checked) {
var name = this.refs.name.getDOMNode().value.trim();
- var domainRegex = /^\w+\.\w+$/
+ var domainRegex = /^\w+\.\w+$/;
if (!name) {
- this.setState({name_error: "This field is required"});
+ this.setState({nameError: 'This field is required'});
return;
}
- if(!name.trim().match(domainRegex)) {
- this.setState({name_error: "The domain doesn't appear valid"});
+ if (!name.trim().match(domainRegex)) {
+ this.setState({nameError: 'The domain doesn\'t appear valid'});
return;
}
- this.props.state.wizard = "send_invites";
+ this.props.state.wizard = 'send_invites';
this.props.state.team.allowed_domains = name;
this.props.state.team.type = 'I';
this.props.updateParent(this.props.state);
- }
- else {
- this.props.state.wizard = "send_invites";
+ } else {
+ this.props.state.wizard = 'send_invites';
this.props.state.team.type = 'I';
this.props.updateParent(this.props.state);
}
},
getInitialState: function() {
- return { };
+ return {};
},
render: function() {
-
client.track('signup', 'signup_team_04_allow_domains');
- var name_error = this.state.name_error ? <label className="control-label">{ this.state.name_error }</label> : null;
+ var nameError = null;
+ var nameDivClass = 'form-group';
+ if (this.state.nameError) {
+ nameError = <label className='control-label'>{this.state.nameError}</label>;
+ nameDivClass += ' has-error';
+ }
return (
<div>
<form>
- <img className="signup-team-logo" src="/static/images/logo.png" />
- <h2>Email Domain</h2>
- <p>
- <div className="checkbox"><label><input type="checkbox" ref="allow" defaultChecked />{" Allow sign up and " + strings.Team + " discovery with a " + strings.Company + " email address."}</label></div>
- </p>
- <p>{"Check this box to allow your " + strings.Team + " members to sign up using their " + strings.Company + " email addresses if you share the same domain--otherwise, you need to invite everyone yourself."}</p>
- <h4>{"Your " + strings.Team + "'s domain for emails"}</h4>
- <div className={ name_error ? "form-group has-error" : "form-group" }>
- <div className="row">
- <div className="col-sm-9">
- <div className="input-group">
- <span className="input-group-addon">@</span>
- <input type="text" ref="name" className="form-control" placeholder="" maxLength="128" defaultValue={this.props.state.team.allowed_domains} autoFocus={true} onFocus={this.handleFocus}/>
+ <img className='signup-team-logo' src='/static/images/logo.png' />
+ <h2>Email Domain</h2>
+ <p>
+ <div className='checkbox'>
+ <label><input type='checkbox' ref='allow' defaultChecked />{' Allow sign up and ' + strings.Team + ' discovery with a ' + strings.Company + ' email address.'}</label>
+ </div>
+ </p>
+ <p>{'Check this box to allow your ' + strings.Team + ' members to sign up using their ' + strings.Company + ' email addresses if you share the same domain--otherwise, you need to invite everyone yourself.'}</p>
+ <h4>{'Your ' + strings.Team + '\'s domain for emails'}</h4>
+ <div className={nameDivClass}>
+ <div className='row'>
+ <div className='col-sm-9'>
+ <div className='input-group'>
+ <span className='input-group-addon'>@</span>
+ <input type='text' ref='name' className='form-control' placeholder='' maxLength='128' defaultValue={this.props.state.team.allowed_domains} autoFocus={true} onFocus={this.handleFocus}/>
+ </div>
+ </div>
</div>
+ {nameError}
</div>
- </div>
- { name_error }
- </div>
- <p>To allow signups from multiple domains, separate each with a comma.</p>
- <p>
- <div className="checkbox"><label><input type="checkbox" ref="open_network" defaultChecked={this.props.state.team.type == 'O'} /> Allow anyone to signup to this domain without an invitation.</label></div>
- </p>
- <button type="button" className="btn btn-default" onClick={this.submitBack}><i className="glyphicon glyphicon-chevron-left"></i> Back</button>&nbsp;
- <button type="submit" className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button>
- </form>
+ <p>To allow signups from multiple domains, separate each with a comma.</p>
+ <p>
+ <div className='checkbox'>
+ <label><input type='checkbox' ref='open_network' defaultChecked={this.props.state.team.type === 'O'} /> Allow anyone to signup to this domain without an invitation.</label>
+ </div>
+ </p>
+ <button type='button' className='btn btn-default' onClick={this.submitBack}><i className='glyphicon glyphicon-chevron-left'></i> Back</button>&nbsp;
+ <button type='submit' className='btn-primary btn' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button>
+ </form>
</div>
);
}
@@ -370,10 +404,10 @@ AllowedDomainsPage = React.createClass({
EmailItem = React.createClass({
getInitialState: function() {
- return { };
+ return {};
},
getValue: function() {
- return this.refs.email.getDOMNode().value.trim()
+ return this.refs.email.getDOMNode().value.trim();
},
validate: function(teamEmail) {
var email = this.refs.email.getDOMNode().value.trim().toLowerCase();
@@ -383,43 +417,44 @@ EmailItem = React.createClass({
}
if (!utils.isEmail(email)) {
- this.state.email_error = "Please enter a valid email address";
+ this.state.emailError = 'Please enter a valid email address';
this.setState(this.state);
return false;
- }
- else if (email === teamEmail) {
- this.state.email_error = "Please use a different email than the one used at signup";
+ } else if (email === teamEmail) {
+ this.state.emailError = 'Please use a different email than the one used at signup';
this.setState(this.state);
return false;
- }
- else {
- this.state.email_error = "";
+ } else {
+ this.state.emailError = '';
this.setState(this.state);
return true;
}
},
render: function() {
-
- var email_error = this.state.email_error ? <label className="control-label">{ this.state.email_error }</label> : null;
+ var emailError = null;
+ var emailDivClass = 'form-group';
+ if (this.state.emailError) {
+ emailError = <label className='control-label'>{ this.state.emailError }</label>;
+ emailDivClass += ' has-error';
+ }
return (
- <div className={ email_error ? "form-group has-error" : "form-group" }>
- <input autoFocus={this.props.focus} type="email" ref="email" className="form-control" placeholder="Email Address" defaultValue={this.props.email} maxLength="128" />
- { email_error }
+ <div className={emailDivClass}>
+ <input autoFocus={this.props.focus} type='email' ref='email' className='form-control' placeholder='Email Address' defaultValue={this.props.email} maxLength='128' />
+ {emailError}
</div>
);
}
});
-
SendInivtesPage = React.createClass({
submitBack: function (e) {
e.preventDefault();
if (config.AllowSignupDomainsWizard) {
- this.props.state.wizard = "allowed_domains";
+ this.props.state.wizard = 'allowed_domains';
} else {
- this.props.state.wizard = "team_url";
+ this.props.state.wizard = 'team_url';
}
this.props.updateParent(this.props.state);
@@ -428,69 +463,93 @@ SendInivtesPage = React.createClass({
e.preventDefault();
var valid = true;
- var emails = [];
- for (var i = 0; i < this.props.state.invites.length; i++) {
- if (!this.refs['email_' + i].validate(this.props.state.team.email)) {
- valid = false;
- } else {
- emails.push(this.refs['email_' + i].getValue());
+ if (this.state.emailEnabled) {
+ var emails = [];
+
+ for (var i = 0; i < this.props.state.invites.length; i++) {
+ if (!this.refs['email_' + i].validate(this.props.state.team.email)) {
+ valid = false;
+ } else {
+ emails.push(this.refs['email_' + i].getValue());
+ }
}
- }
- if (!valid) {
- return;
+ if (valid) {
+ this.props.state.invites = emails;
+ }
}
- this.props.state.wizard = "username";
- this.props.state.invites = emails;
- this.props.updateParent(this.props.state);
+ if (valid) {
+ this.props.state.wizard = 'username';
+ this.props.updateParent(this.props.state);
+ }
},
submitAddInvite: function (e) {
e.preventDefault();
- this.props.state.wizard = "send_invites";
- if (this.props.state.invites == null || this.props.state.invites.length == 0) {
+ this.props.state.wizard = 'send_invites';
+ if (!this.props.state.invites) {
this.props.state.invites = [];
}
- this.props.state.invites.push("");
+ this.props.state.invites.push('');
this.props.updateParent(this.props.state);
},
submitSkip: function (e) {
e.preventDefault();
- this.props.state.wizard = "username";
+ this.props.state.wizard = 'username';
this.props.updateParent(this.props.state);
},
getInitialState: function() {
- return { };
+ return {
+ emailEnabled: !ConfigStore.getSettingAsBoolean('ByPassEmail', false)
+ };
},
render: function() {
-
client.track('signup', 'signup_team_05_send_invites');
- var name_error = this.state.name_error ? <label className="control-label">{ this.state.name_error }</label> : null;
+ var content = null;
+ var bottomContent = null;
- var emails = [];
+ if (this.state.emailEnabled) {
+ var emails = [];
- for (var i = 0; i < this.props.state.invites.length; i++) {
- if (i == 0) {
- emails.push(<EmailItem focus={true} key={i} ref={'email_' + i} email={this.props.state.invites[i]} />);
- } else {
- emails.push(<EmailItem focus={false} key={i} ref={'email_' + i} email={this.props.state.invites[i]} />);
+ for (var i = 0; i < this.props.state.invites.length; i++) {
+ if (i === 0) {
+ emails.push(<EmailItem focus={true} key={i} ref={'email_' + i} email={this.props.state.invites[i]} />);
+ } else {
+ emails.push(<EmailItem focus={false} key={i} ref={'email_' + i} email={this.props.state.invites[i]} />);
+ }
}
+
+ content = (
+ <div>
+ {emails}
+ <div className='form-group text-right'><a href='#' onClick={this.submitAddInvite}>Add Invitation</a></div>
+ </div>
+ );
+
+ bottomContent = (
+ <p className='color--light'>{'if you prefer, you can invite ' + strings.Team + ' members later'}<br /> and <a href='#' onClick={this.submitSkip}>skip this step</a> for now.</p>
+ );
+ } else {
+ content = (
+ <div className='form-group color--light'>Email is currently disabled for your team, and emails cannot be sent. Contact your system administrator to enable email and email invitations.</div>
+ );
}
return (
<div>
<form>
- <img className="signup-team-logo" src="/static/images/logo.png" />
- <h2>{"Invite " + utils.toTitleCase(strings.Team) + " Members"}</h2>
- { emails }
- <div className="form-group text-right"><a href="#" onClick={this.submitAddInvite}>Add Invitation</a></div>
- <div className="form-group"><button type="submit" className="btn-primary btn" onClick={this.submitNext}>Next<i className="glyphicon glyphicon-chevron-right"></i></button></div>
+ <img className='signup-team-logo' src='/static/images/logo.png' />
+ <h2>{'Invite ' + utils.toTitleCase(strings.Team) + ' Members'}</h2>
+ {content}
+ <div className='form-group'>
+ <button type='submit' className='btn-primary btn' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button>
+ </div>
</form>
- <p className="color--light">{"if you prefer, you can invite " + strings.Team + " members later"}<br /> and <a href="#" onClick={this.submitSkip}>skip this step</a> for now.</p>
- <div className="margin--extra">
- <a href="#" onClick={this.submitBack}>Back to previous step</a>
+ {bottomContent}
+ <div className='margin--extra'>
+ <a href='#' onClick={this.submitBack}>Back to previous step</a>
</div>
</div>
);
@@ -508,12 +567,12 @@ UsernamePage = React.createClass({
var name = this.refs.name.getDOMNode().value.trim();
- var username_error = utils.isValidUsername(name);
- if (username_error === 'Cannot use a reserved word as a username.') {
- this.setState({name_error: 'This username is reserved, please choose a new one.'});
+ var usernameError = utils.isValidUsername(name);
+ if (usernameError === 'Cannot use a reserved word as a username.') {
+ this.setState({nameError: 'This username is reserved, please choose a new one.'});
return;
- } else if (username_error) {
- this.setState({name_error: "Username must begin with a letter, and contain 3 to 15 characters in total, which may be numbers, lowercase letters, or any of the symbols '.', '-', or '_'"});
+ } else if (usernameError) {
+ this.setState({nameError: 'Username must begin with a letter, and contain 3 to 15 characters in total, which may be numbers, lowercase letters, or any of the symbols \'.\', \'-\', or \'_\''});
return;
}
@@ -527,31 +586,36 @@ UsernamePage = React.createClass({
render: function() {
client.track('signup', 'signup_team_06_username');
- var name_error = this.state.name_error ? <label className='control-label'>{this.state.name_error}</label> : null;
+ var nameError = null;
+ var nameDivClass = 'form-group';
+ if (this.state.nameError) {
+ nameError = <label className='control-label'>{this.state.nameError}</label>;
+ nameDivClass += ' has-error';
+ }
return (
<div>
<form>
- <img className='signup-team-logo' src='/static/images/logo.png' />
- <h2 className='margin--less'>Your username</h2>
- <h5 className='color--light'>{'Select a memorable username that makes it easy for ' + strings.Team + 'mates to identify you:'}</h5>
- <div className='inner__content margin--extra'>
- <div className={name_error ? 'form-group has-error' : 'form-group'}>
- <div className='row'>
- <div className='col-sm-11'>
- <h5><strong>Choose your username</strong></h5>
- <input autoFocus={true} type='text' ref='name' className='form-control' placeholder='' defaultValue={this.props.state.user.username} maxLength='128' />
- <div className='color--light form__hint'>Usernames must begin with a letter and contain 3 to 15 characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'</div>
+ <img className='signup-team-logo' src='/static/images/logo.png' />
+ <h2 className='margin--less'>Your username</h2>
+ <h5 className='color--light'>{'Select a memorable username that makes it easy for ' + strings.Team + 'mates to identify you:'}</h5>
+ <div className='inner__content margin--extra'>
+ <div className={nameDivClass}>
+ <div className='row'>
+ <div className='col-sm-11'>
+ <h5><strong>Choose your username</strong></h5>
+ <input autoFocus={true} type='text' ref='name' className='form-control' placeholder='' defaultValue={this.props.state.user.username} maxLength='128' />
+ <div className='color--light form__hint'>Usernames must begin with a letter and contain 3 to 15 characters made up of lowercase letters, numbers, and the symbols '.', '-' and '_'</div>
+ </div>
+ </div>
+ {nameError}
</div>
</div>
- {name_error}
+ <button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button>
+ <div className='margin--extra'>
+ <a href='#' onClick={this.submitBack}>Back to previous step</a>
</div>
- </div>
- <button type='submit' className='btn btn-primary margin--extra' onClick={this.submitNext}>Next<i className='glyphicon glyphicon-chevron-right'></i></button>
- <div className='margin--extra'>
- <a href='#' onClick={this.submitBack}>Back to previous step</a>
- </div>
- </form>
+ </form>
</div>
);
}
@@ -560,7 +624,7 @@ UsernamePage = React.createClass({
PasswordPage = React.createClass({
submitBack: function (e) {
e.preventDefault();
- this.props.state.wizard = "username";
+ this.props.state.wizard = 'username';
this.props.updateParent(this.props.state);
},
submitNext: function (e) {
@@ -568,11 +632,11 @@ PasswordPage = React.createClass({
var password = this.refs.password.getDOMNode().value.trim();
if (!password || password.length < 5) {
- this.setState({password_error: "Please enter at least 5 characters"});
+ this.setState({passwordError: 'Please enter at least 5 characters'});
return;
}
- this.setState({password_error: null, server_error: null});
+ this.setState({passwordError: null, serverError: null});
$('#finish-button').button('loading');
var teamSignup = JSON.parse(JSON.stringify(this.props.state));
teamSignup.user.password = password;
@@ -582,13 +646,12 @@ PasswordPage = React.createClass({
client.createTeamFromSignup(teamSignup,
function(data) {
-
client.track('signup', 'signup_team_08_complete');
var props = this.props;
$('#sign-up-button').button('reset');
- props.state.wizard = "finished";
+ props.state.wizard = 'finished';
props.updateParent(props.state, true);
window.location.href = utils.getWindowLocationOrigin() + '/' + props.state.team.name + '/login?email=' + encodeURIComponent(teamSignup.team.email);
@@ -601,55 +664,63 @@ PasswordPage = React.createClass({
// window.location.href = '/channels/town-square';
// }.bind(ctl),
// function(err) {
- // this.setState({name_error: err.message});
+ // this.setState({nameError: err.message});
// }.bind(ctl)
// );
}.bind(this),
function(err) {
- this.setState({server_error: err.message});
+ this.setState({serverError: err.message});
$('#sign-up-button').button('reset');
}.bind(this)
);
},
getInitialState: function() {
- return { };
+ return {};
},
render: function() {
-
client.track('signup', 'signup_team_07_password');
- var password_error = this.state.password_error ? <div className="form-group has-error"><label className="control-label">{ this.state.password_error }</label></div> : null;
- var server_error = this.state.server_error ? <div className="form-group has-error"><label className="control-label">{ this.state.server_error }</label></div> : null;
+ var passwordError = null;
+ var passwordDivStyle = 'form-group';
+ if (this.state.passwordError) {
+ passwordError = <div className='form-group has-error'><label className='control-label'>{this.state.passwordError}</label></div>;
+ passwordDivStyle = ' has-error';
+ }
+
+ var serverError = null;
+ if (this.state.serverError) {
+ serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
+ }
return (
<div>
<form>
- <img className="signup-team-logo" src="/static/images/logo.png" />
- <h2 className="margin--less">Your password</h2>
- <h5 className="color--light">Select a password that you'll use to login with your email address:</h5>
- <div className="inner__content margin--extra">
- <h5><strong>Email</strong></h5>
- <div className="block--gray form-group">{this.props.state.team.email}</div>
- <div className={ password_error ? "form-group has-error" : "form-group" }>
- <div className="row">
- <div className="col-sm-11">
- <h5><strong>Choose your password</strong></h5>
- <input autoFocus={true} type="password" ref="password" className="form-control" placeholder="" maxLength="128" />
- <div className="color--light form__hint">Passwords must contain 5 to 50 characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.</div>
+ <img className='signup-team-logo' src='/static/images/logo.png' />
+ <h2 className='margin--less'>Your password</h2>
+ <h5 className='color--light'>Select a password that you'll use to login with your email address:</h5>
+ <div className='inner__content margin--extra'>
+ <h5><strong>Email</strong></h5>
+ <div className='block--gray form-group'>{this.props.state.team.email}</div>
+ <div className={passwordDivStyle}>
+ <div className='row'>
+ <div className='col-sm-11'>
+ <h5><strong>Choose your password</strong></h5>
+ <input autoFocus={true} type='password' ref='password' className='form-control' placeholder='' maxLength='128' />
+ <div className='color--light form__hint'>Passwords must contain 5 to 50 characters. Your password will be strongest if it contains a mix of symbols, numbers, and upper and lowercase characters.</div>
+ </div>
+ </div>
+ {passwordError}
+ {serverError}
</div>
</div>
- { password_error }
- { server_error }
+ <div className='form-group'>
+ <button type='submit' className='btn btn-primary margin--extra' id='finish-button' data-loading-text={'<span class=\'glyphicon glyphicon-refresh glyphicon-refresh-animate\'></span> Creating ' + strings.Team + '...'} onClick={this.submitNext}>Finish</button>
</div>
- </div>
- <div className="form-group">
- <button type="submit" className="btn btn-primary margin--extra" id="finish-button" data-loading-text={"<span class='glyphicon glyphicon-refresh glyphicon-refresh-animate'></span> Creating "+strings.Team+"..."} onClick={this.submitNext}>Finish</button>
- </div>
- <p>By proceeding to create your account and use { config.SiteName }, you agree to our <a href={ config.TermsLink }>Terms of Service</a> and <a href={ config.PrivacyLink }>Privacy Policy</a>. If you do not agree, you cannot use {config.SiteName}.</p>
- <div className="margin--extra">
- <a href="#" onClick={this.submitBack}>Back to previous step</a>
- </div>
- </form>
+ <p>By proceeding to create your account and use {config.SiteName}, you agree to our <a href={config.TermsLink}>Terms of Service</a> and <a href={config.PrivacyLink}>Privacy Policy</a>. If you do not agree, you cannot use {config.SiteName}.</p>
+ <div className='margin--extra'>
+ <a href='#' onClick={this.submitBack}>Back to previous step</a>
+ </div>
+ </form>
</div>
);
}
@@ -668,14 +739,14 @@ module.exports = React.createClass({
if (!props) {
props = {};
- props.wizard = "welcome";
+ props.wizard = 'welcome';
props.team = {};
props.team.email = this.props.email;
- props.team.allowed_domains = "";
+ props.team.allowed_domains = '';
props.invites = [];
- props.invites.push("");
- props.invites.push("");
- props.invites.push("");
+ props.invites.push('');
+ props.invites.push('');
+ props.invites.push('');
props.user = {};
props.hash = this.props.hash;
props.data = this.props.data;
@@ -684,36 +755,34 @@ module.exports = React.createClass({
return props;
},
render: function() {
- if (this.state.wizard == "welcome") {
- return <WelcomePage state={this.state} updateParent={this.updateParent} />
+ if (this.state.wizard === 'welcome') {
+ return <WelcomePage state={this.state} updateParent={this.updateParent} />;
}
- if (this.state.wizard == "team_display_name") {
- return <TeamDisplayNamePage state={this.state} updateParent={this.updateParent} />
+ if (this.state.wizard === 'team_display_name') {
+ return <TeamDisplayNamePage state={this.state} updateParent={this.updateParent} />;
}
- if (this.state.wizard == "team_url") {
- return <TeamURLPage state={this.state} updateParent={this.updateParent} />
+ if (this.state.wizard === 'team_url') {
+ return <TeamURLPage state={this.state} updateParent={this.updateParent} />;
}
- if (this.state.wizard == "allowed_domains") {
- return <AllowedDomainsPage state={this.state} updateParent={this.updateParent} />
+ if (this.state.wizard === 'allowed_domains') {
+ return <AllowedDomainsPage state={this.state} updateParent={this.updateParent} />;
}
- if (this.state.wizard == "send_invites") {
- return <SendInivtesPage state={this.state} updateParent={this.updateParent} />
+ if (this.state.wizard === 'send_invites') {
+ return <SendInivtesPage state={this.state} updateParent={this.updateParent} />;
}
- if (this.state.wizard == "username") {
- return <UsernamePage state={this.state} updateParent={this.updateParent} />
+ if (this.state.wizard === 'username') {
+ return <UsernamePage state={this.state} updateParent={this.updateParent} />;
}
- if (this.state.wizard == "password") {
- return <PasswordPage state={this.state} updateParent={this.updateParent} />
+ if (this.state.wizard === 'password') {
+ return <PasswordPage state={this.state} updateParent={this.updateParent} />;
}
return (<div>You've already completed the signup process for this invitation or this invitation has expired.</div>);
}
});
-
-
diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx
index 1a0c313d3..8f29bbe57 100644
--- a/web/react/components/user_settings.jsx
+++ b/web/react/components/user_settings.jsx
@@ -13,6 +13,7 @@ var assign = require('object-assign');
function getNotificationsStateFromStores() {
var user = UserStore.getCurrentUser();
+ var soundNeeded = !utils.isBrowserFirefox();
var sound = (!user.notify_props || user.notify_props.desktop_sound == undefined) ? "true" : user.notify_props.desktop_sound;
var desktop = (!user.notify_props || user.notify_props.desktop == undefined) ? "all" : user.notify_props.desktop;
var email = (!user.notify_props || user.notify_props.email == undefined) ? "true" : user.notify_props.email;
@@ -58,7 +59,7 @@ function getNotificationsStateFromStores() {
}
}
- return { notify_level: desktop, enable_email: email, enable_sound: sound, username_key: username_key, mention_key: mention_key, custom_keys: custom_keys, custom_keys_checked: custom_keys.length > 0, first_name_key: first_name_key, all_key: all_key, channel_key: channel_key };
+ return { notify_level: desktop, enable_email: email, soundNeeded: soundNeeded, enable_sound: sound, username_key: username_key, mention_key: mention_key, custom_keys: custom_keys, custom_keys_checked: custom_keys.length > 0, first_name_key: first_name_key, all_key: all_key, channel_key: channel_key };
}
@@ -105,11 +106,11 @@ var NotificationsTab = React.createClass({
},
componentDidMount: function() {
UserStore.addChangeListener(this._onChange);
- $('#user_settings1').on('hidden.bs.modal', this.handleClose);
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
},
componentWillUnmount: function() {
UserStore.removeChangeListener(this._onChange);
- $('#user_settings1').off('hidden.bs.modal', this.handleClose);
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
this.props.updateSection('');
},
_onChange: function() {
@@ -235,7 +236,7 @@ var NotificationsTab = React.createClass({
}
var soundSection;
- if (this.props.activeSection === 'sound') {
+ if (this.props.activeSection === 'sound' && this.state.soundNeeded) {
var soundActive = ["",""];
if (this.state.enable_sound === "false") {
soundActive[1] = "active";
@@ -265,7 +266,9 @@ var NotificationsTab = React.createClass({
);
} else {
var describe = "";
- if (this.state.enable_sound === "false") {
+ if (!this.state.soundNeeded) {
+ describe = "Please configure notification sounds in your browser settings"
+ } else if (this.state.enable_sound === "false") {
describe = "Off";
} else {
describe = "On";
@@ -276,6 +279,7 @@ var NotificationsTab = React.createClass({
title="Desktop notification sounds"
describe={describe}
updateSection={function(){self.props.updateSection("sound");}}
+ disableOpen = {!this.state.soundNeeded}
/>
);
}
@@ -513,27 +517,34 @@ var SecurityTab = React.createClass({
this.setState({confirmPassword: e.target.value});
},
handleHistoryOpen: function() {
- $('#user_settings1').modal('hide');
+ this.setState({willReturn: true});
+ $("#user_settings").modal('hide');
},
handleDevicesOpen: function() {
- $('#user_settings1').modal('hide');
+ this.setState({willReturn: true});
+ $("#user_settings").modal('hide');
},
handleClose: function() {
$(this.getDOMNode()).find('.form-control').each(function() {
this.value = '';
});
this.setState({currentPassword: '', newPassword: '', confirmPassword: '', serverError: null, passwordError: null});
- this.props.updateTab('general');
+
+ if (!this.state.willReturn) {
+ this.props.updateTab('general');
+ } else {
+ this.setState({willReturn: false});
+ }
},
componentDidMount: function() {
- $('#user_settings1').on('hidden.bs.modal', this.handleClose);
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
},
componentWillUnmount: function() {
- $('#user_settings1').off('hidden.bs.modal', this.handleClose);
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
this.props.updateSection('');
},
getInitialState: function() {
- return {currentPassword: '', newPassword: '', confirmPassword: ''};
+ return {currentPassword: '', newPassword: '', confirmPassword: '', willReturn: false};
},
render: function() {
var serverError = this.state.serverError ? this.state.serverError : null;
@@ -811,10 +822,10 @@ var GeneralTab = React.createClass({
this.props.updateSection('');
},
componentDidMount: function() {
- $('#user_settings1').on('hidden.bs.modal', this.handleClose);
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
},
componentWillUnmount: function() {
- $('#user_settings1').off('hidden.bs.modal', this.handleClose);
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
},
getInitialState: function() {
var user = this.props.user;
@@ -1093,7 +1104,7 @@ var AppearanceTab = React.createClass({
if (this.props.activeSection === "theme") {
$(this.refs[this.state.theme].getDOMNode()).addClass('active-border');
}
- $('#user_settings1').on('hidden.bs.modal', this.handleClose);
+ $('#user_settings').on('hidden.bs.modal', this.handleClose);
},
componentDidUpdate: function() {
if (this.props.activeSection === "theme") {
@@ -1102,7 +1113,7 @@ var AppearanceTab = React.createClass({
}
},
componentWillUnmount: function() {
- $('#user_settings1').off('hidden.bs.modal', this.handleClose);
+ $('#user_settings').off('hidden.bs.modal', this.handleClose);
this.props.updateSection('');
},
getInitialState: function() {
diff --git a/web/react/components/user_settings_modal.jsx b/web/react/components/user_settings_modal.jsx
index 702e7ad7a..7181c4020 100644
--- a/web/react/components/user_settings_modal.jsx
+++ b/web/react/components/user_settings_modal.jsx
@@ -32,7 +32,7 @@ module.exports = React.createClass({
tabs.push({name: "appearance", ui_name: "Appearance", icon: "glyphicon glyphicon-wrench"});
return (
- <div className="modal fade" ref="modal" id="user_settings1" role="dialog" tabIndex="-1" aria-hidden="true">
+ <div className="modal fade" ref="modal" id="user_settings" role="dialog" tabIndex="-1" aria-hidden="true">
<div className="modal-dialog settings-modal">
<div className="modal-content">
<div className="modal-header">
@@ -64,4 +64,3 @@ module.exports = React.createClass({
);
}
});
-
diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx
index 1f07cb519..6e4baa582 100644
--- a/web/react/pages/channel.jsx
+++ b/web/react/pages/channel.jsx
@@ -1,7 +1,6 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var Navbar = require('../components/navbar.jsx');
var Sidebar = require('../components/sidebar.jsx');
@@ -37,21 +36,23 @@ var ActivityLogModal = require('../components/activity_log_modal.jsx');
var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx')
var FileUploadOverlay = require('../components/file_upload_overlay.jsx');
+var AsyncClient = require('../utils/async_client.jsx');
var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes;
global.window.setup_channel_page = function(team_name, team_type, team_id, channel_name, channel_id) {
+ AsyncClient.getConfig();
AppDispatcher.handleViewAction({
- type: ActionTypes.CLICK_CHANNEL,
- name: channel_name,
- id: channel_id
+ type: ActionTypes.CLICK_CHANNEL,
+ name: channel_name,
+ id: channel_id
});
AppDispatcher.handleViewAction({
- type: ActionTypes.CLICK_TEAM,
- id: team_id
+ type: ActionTypes.CLICK_TEAM,
+ id: team_id
});
React.render(
@@ -100,7 +101,7 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann
);
React.render(
- <MemberInviteModal />,
+ <MemberInviteModal teamType={team_type} />,
document.getElementById('invite_member_modal')
);
@@ -195,17 +196,17 @@ global.window.setup_channel_page = function(team_name, team_type, team_id, chann
);
React.render(
- <MentionList id="post_textbox" />,
+ <MentionList id='post_textbox' />,
document.getElementById('post_mention_tab')
);
React.render(
- <MentionList id="reply_textbox" />,
+ <MentionList id='reply_textbox' />,
document.getElementById('reply_mention_tab')
);
React.render(
- <MentionList id="edit_textbox" />,
+ <MentionList id='edit_textbox' />,
document.getElementById('edit_mention_tab')
);
diff --git a/web/react/pages/signup_team.jsx b/web/react/pages/signup_team.jsx
index e982f5a79..37c441d4f 100644
--- a/web/react/pages/signup_team.jsx
+++ b/web/react/pages/signup_team.jsx
@@ -1,11 +1,15 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
-var SignupTeam =require('../components/signup_team.jsx');
+var SignupTeam = require('../components/signup_team.jsx');
+
+var AsyncClient = require('../utils/async_client.jsx');
global.window.setup_signup_team_page = function() {
+ AsyncClient.getConfig();
+
React.render(
<SignupTeam />,
document.getElementById('signup-team')
);
-}; \ No newline at end of file
+};
diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx
index a97f13391..f7c23841c 100644
--- a/web/react/stores/channel_store.jsx
+++ b/web/react/stores/channel_store.jsx
@@ -10,212 +10,264 @@ var ActionTypes = Constants.ActionTypes;
var BrowserStore = require('../stores/browser_store.jsx');
-
var CHANGE_EVENT = 'change';
var MORE_CHANGE_EVENT = 'change';
var EXTRA_INFO_EVENT = 'extra_info';
var ChannelStore = assign({}, EventEmitter.prototype, {
- _current_id: null,
- emitChange: function() {
- this.emit(CHANGE_EVENT);
- },
- addChangeListener: function(callback) {
- this.on(CHANGE_EVENT, callback);
- },
- removeChangeListener: function(callback) {
- this.removeListener(CHANGE_EVENT, callback);
- },
- emitMoreChange: function() {
- this.emit(MORE_CHANGE_EVENT);
- },
- addMoreChangeListener: function(callback) {
- this.on(MORE_CHANGE_EVENT, callback);
- },
- removeMoreChangeListener: function(callback) {
- this.removeListener(MORE_CHANGE_EVENT, callback);
- },
- emitExtraInfoChange: function() {
- this.emit(EXTRA_INFO_EVENT);
- },
- addExtraInfoChangeListener: function(callback) {
- this.on(EXTRA_INFO_EVENT, callback);
- },
- removeExtraInfoChangeListener: function(callback) {
- this.removeListener(EXTRA_INFO_EVENT, callback);
- },
- findFirstBy: function(field, value) {
- var channels = this._getChannels();
- for (var i = 0; i < channels.length; i++) {
- if (channels[i][field] == value) {
- return channels[i];
- }
- }
+ currentId: null,
+ emitChange: function() {
+ this.emit(CHANGE_EVENT);
+ },
+ addChangeListener: function(callback) {
+ this.on(CHANGE_EVENT, callback);
+ },
+ removeChangeListener: function(callback) {
+ this.removeListener(CHANGE_EVENT, callback);
+ },
+ emitMoreChange: function() {
+ this.emit(MORE_CHANGE_EVENT);
+ },
+ addMoreChangeListener: function(callback) {
+ this.on(MORE_CHANGE_EVENT, callback);
+ },
+ removeMoreChangeListener: function(callback) {
+ this.removeListener(MORE_CHANGE_EVENT, callback);
+ },
+ emitExtraInfoChange: function() {
+ this.emit(EXTRA_INFO_EVENT);
+ },
+ addExtraInfoChangeListener: function(callback) {
+ this.on(EXTRA_INFO_EVENT, callback);
+ },
+ removeExtraInfoChangeListener: function(callback) {
+ this.removeListener(EXTRA_INFO_EVENT, callback);
+ },
+ findFirstBy: function(field, value) {
+ var channels = this.pGetChannels();
+ for (var i = 0; i < channels.length; i++) {
+ if (channels[i][field] === value) {
+ return channels[i];
+ }
+ }
- return null;
- },
- get: function(id) {
- return this.findFirstBy('id', id);
- },
- getMember: function(id) {
- return this.getAllMembers()[id];
- },
- getByName: function(name) {
- return this.findFirstBy('name', name);
- },
- getAll: function() {
- return this._getChannels();
- },
- getAllMembers: function() {
- return this._getChannelMembers();
- },
- getMoreAll: function() {
- return this._getMoreChannels();
- },
- setCurrentId: function(id) {
- this._current_id = id;
- },
- setLastVisitedName: function(name) {
- if (name == null)
- BrowserStore.removeItem("last_visited_name");
- else
- BrowserStore.setItem("last_visited_name", name);
- },
- getLastVisitedName: function() {
- return BrowserStore.getItem("last_visited_name");
- },
- resetCounts: function(id) {
- var cm = this._getChannelMembers();
- for (var cmid in cm) {
- if (cm[cmid].channel_id == id) {
- var c = this.get(id);
- if (c) {
- cm[cmid].msg_count = this.get(id).total_msg_count;
- cm[cmid].mention_count = 0;
- }
- break;
- }
- }
- this._storeChannelMembers(cm);
- },
- getCurrentId: function() {
- return this._current_id;
- },
- getCurrent: function() {
- var currentId = this.getCurrentId();
-
- if (currentId)
- return this.get(currentId);
- else
- return null;
- },
- getCurrentMember: function() {
- var currentId = ChannelStore.getCurrentId();
-
- if (currentId)
- return this.getAllMembers()[currentId];
- else
- return null;
- },
- setChannelMember: function(member) {
- var members = this._getChannelMembers();
- members[member.channel_id] = member;
- this._storeChannelMembers(members);
- this.emitChange();
- },
- getCurrentExtraInfo: function() {
- var currentId = ChannelStore.getCurrentId();
- var extra = null;
-
- if (currentId)
- extra = this._getExtraInfos()[currentId];
-
- if (extra == null)
- extra = {members: []};
-
- return extra;
- },
- getExtraInfo: function(channel_id) {
- var extra = null;
-
- if (channel_id)
- extra = this._getExtraInfos()[channel_id];
-
- if (extra == null)
- extra = {members: []};
-
- return extra;
- },
- _storeChannels: function(channels) {
- BrowserStore.setItem("channels", channels);
- },
- _getChannels: function() {
- return BrowserStore.getItem("channels", []);
- },
- _storeChannelMembers: function(channelMembers) {
- BrowserStore.setItem("channel_members", channelMembers);
- },
- _getChannelMembers: function() {
- return BrowserStore.getItem("channel_members", {});
- },
- _storeMoreChannels: function(channels) {
- BrowserStore.setItem("more_channels", channels);
- },
- _getMoreChannels: function() {
- var channels = BrowserStore.getItem("more_channels");
-
- if (channels == null) {
- channels = {};
- channels.loading = true;
- }
+ return null;
+ },
+ get: function(id) {
+ return this.findFirstBy('id', id);
+ },
+ getMember: function(id) {
+ return this.getAllMembers()[id];
+ },
+ getByName: function(name) {
+ return this.findFirstBy('name', name);
+ },
+ getAll: function() {
+ return this.pGetChannels();
+ },
+ getAllMembers: function() {
+ return this.pGetChannelMembers();
+ },
+ getMoreAll: function() {
+ return this.pGetMoreChannels();
+ },
+ setCurrentId: function(id) {
+ this.currentId = id;
+ },
+ setLastVisitedName: function(name) {
+ if (name == null) {
+ BrowserStore.removeItem('last_visited_name');
+ } else {
+ BrowserStore.setItem('last_visited_name', name);
+ }
+ },
+ getLastVisitedName: function() {
+ return BrowserStore.getItem('last_visited_name');
+ },
+ resetCounts: function(id) {
+ var cm = this.pGetChannelMembers();
+ for (var cmid in cm) {
+ if (cm[cmid].channel_id === id) {
+ var c = this.get(id);
+ if (c) {
+ cm[cmid].msg_count = this.get(id).total_msg_count;
+ cm[cmid].mention_count = 0;
+ }
+ break;
+ }
+ }
+ this.pStoreChannelMembers(cm);
+ },
+ getCurrentId: function() {
+ return this.currentId;
+ },
+ getCurrent: function() {
+ var currentId = this.getCurrentId();
+
+ if (currentId) {
+ return this.get(currentId);
+ } else {
+ return null;
+ }
+ },
+ getCurrentMember: function() {
+ var currentId = ChannelStore.getCurrentId();
+
+ if (currentId) {
+ return this.getAllMembers()[currentId];
+ } else {
+ return null;
+ }
+ },
+ setChannelMember: function(member) {
+ var members = this.pGetChannelMembers();
+ members[member.channel_id] = member;
+ this.pStoreChannelMembers(members);
+ this.emitChange();
+ },
+ getCurrentExtraInfo: function() {
+ var currentId = ChannelStore.getCurrentId();
+ var extra = null;
+
+ if (currentId) {
+ extra = this.pGetExtraInfos()[currentId];
+ }
+
+ if (extra == null) {
+ extra = {members: []};
+ }
+
+ return extra;
+ },
+ getExtraInfo: function(channelId) {
+ var extra = null;
+
+ if (channelId) {
+ extra = this.pGetExtraInfos()[channelId];
+ }
+
+ if (extra == null) {
+ extra = {members: []};
+ }
+
+ return extra;
+ },
+ pStoreChannel: function(channel) {
+ var channels = this.pGetChannels();
+ var found;
+
+ for (var i = 0; i < channels.length; i++) {
+ if (channels[i].id === channel.id) {
+ channels[i] = channel;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ channels.push(channel);
+ }
- return channels;
- },
- _storeExtraInfos: function(extraInfos) {
- BrowserStore.setItem("extra_infos", extraInfos);
- },
- _getExtraInfos: function() {
- return BrowserStore.getItem("extra_infos", {});
- },
- isDefault: function(channel) {
- return channel.name == Constants.DEFAULT_CHANNEL;
- }
+ channels.sort(function chanSort(a, b) {
+ if (a.display_name.toLowerCase() < b.display_name.toLowerCase()) {
+ return -1;
+ }
+ if (a.display_name.toLowerCase() > b.display_name.toLowerCase()) {
+ return 1;
+ }
+ return 0;
+ });
+
+ this.pStoreChannels(channels);
+ },
+ pStoreChannels: function(channels) {
+ BrowserStore.setItem('channels', channels);
+ },
+ pGetChannels: function() {
+ return BrowserStore.getItem('channels', []);
+ },
+ pStoreChannelMember: function(channelMember) {
+ var members = this.pGetChannelMembers();
+ members[channelMember.channel_id] = channelMember;
+ this.pStoreChannelMembers(members);
+ },
+ pStoreChannelMembers: function(channelMembers) {
+ BrowserStore.setItem('channel_members', channelMembers);
+ },
+ pGetChannelMembers: function() {
+ return BrowserStore.getItem('channel_members', {});
+ },
+ pStoreMoreChannels: function(channels) {
+ BrowserStore.setItem('more_channels', channels);
+ },
+ pGetMoreChannels: function() {
+ var channels = BrowserStore.getItem('more_channels');
+
+ if (channels == null) {
+ channels = {};
+ channels.loading = true;
+ }
+
+ return channels;
+ },
+ pStoreExtraInfos: function(extraInfos) {
+ BrowserStore.setItem('extra_infos', extraInfos);
+ },
+ pGetExtraInfos: function() {
+ return BrowserStore.getItem('extra_infos', {});
+ },
+ isDefault: function(channel) {
+ return channel.name === Constants.DEFAULT_CHANNEL;
+ }
});
ChannelStore.dispatchToken = AppDispatcher.register(function(payload) {
- var action = payload.action;
-
- switch(action.type) {
-
- case ActionTypes.CLICK_CHANNEL:
- ChannelStore.setCurrentId(action.id);
- ChannelStore.setLastVisitedName(action.name);
- ChannelStore.resetCounts(action.id);
- ChannelStore.emitChange();
- break;
-
- case ActionTypes.RECIEVED_CHANNELS:
- ChannelStore._storeChannels(action.channels);
- ChannelStore._storeChannelMembers(action.members);
- var currentId = ChannelStore.getCurrentId();
- if (currentId) ChannelStore.resetCounts(currentId);
- ChannelStore.emitChange();
- break;
-
- case ActionTypes.RECIEVED_MORE_CHANNELS:
- ChannelStore._storeMoreChannels(action.channels);
- ChannelStore.emitMoreChange();
- break;
-
- case ActionTypes.RECIEVED_CHANNEL_EXTRA_INFO:
- var extra_infos = ChannelStore._getExtraInfos();
- extra_infos[action.extra_info.id] = action.extra_info;
- ChannelStore._storeExtraInfos(extra_infos);
- ChannelStore.emitExtraInfoChange();
- break;
-
- default:
- }
+ var action = payload.action;
+ var currentId;
+
+ switch(action.type) {
+
+ case ActionTypes.CLICK_CHANNEL:
+ ChannelStore.setCurrentId(action.id);
+ ChannelStore.setLastVisitedName(action.name);
+ ChannelStore.resetCounts(action.id);
+ ChannelStore.emitChange();
+ break;
+
+ case ActionTypes.RECIEVED_CHANNELS:
+ ChannelStore.pStoreChannels(action.channels);
+ ChannelStore.pStoreChannelMembers(action.members);
+ currentId = ChannelStore.getCurrentId();
+ if (currentId) {
+ ChannelStore.resetCounts(currentId);
+ }
+ ChannelStore.emitChange();
+ break;
+
+ case ActionTypes.RECIEVED_CHANNEL:
+ ChannelStore.pStoreChannel(action.channel);
+ ChannelStore.pStoreChannelMember(action.member);
+ currentId = ChannelStore.getCurrentId();
+ if (currentId) {
+ ChannelStore.resetCounts(currentId);
+ }
+ ChannelStore.emitChange();
+ break;
+
+ case ActionTypes.RECIEVED_MORE_CHANNELS:
+ ChannelStore.pStoreMoreChannels(action.channels);
+ ChannelStore.emitMoreChange();
+ break;
+
+ case ActionTypes.RECIEVED_CHANNEL_EXTRA_INFO:
+ var extraInfos = ChannelStore.pGetExtraInfos();
+ extraInfos[action.extra_info.id] = action.extra_info;
+ ChannelStore.pStoreExtraInfos(extraInfos);
+ ChannelStore.emitExtraInfoChange();
+ break;
+
+ default:
+ }
});
-module.exports = ChannelStore; \ No newline at end of file
+module.exports = ChannelStore;
diff --git a/web/react/stores/config_store.jsx b/web/react/stores/config_store.jsx
new file mode 100644
index 000000000..7ff177b35
--- /dev/null
+++ b/web/react/stores/config_store.jsx
@@ -0,0 +1,56 @@
+// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
+var EventEmitter = require('events').EventEmitter;
+var assign = require('object-assign');
+
+var BrowserStore = require('../stores/browser_store.jsx');
+
+var Constants = require('../utils/constants.jsx');
+var ActionTypes = Constants.ActionTypes;
+
+var CHANGE_EVENT = 'change';
+
+var ConfigStore = assign({}, EventEmitter.prototype, {
+ emitChange: function emitChange() {
+ this.emit(CHANGE_EVENT);
+ },
+ addChangeListener: function addChangeListener(callback) {
+ this.on(CHANGE_EVENT, callback);
+ },
+ removeChangeListener: function removeChangeListener(callback) {
+ this.removeListener(CHANGE_EVENT, callback);
+ },
+ getSetting: function getSetting(key, defaultValue) {
+ return BrowserStore.getItem('config_' + key, defaultValue);
+ },
+ getSettingAsBoolean: function getSettingAsNumber(key, defaultValue) {
+ var value = ConfigStore.getSetting(key, defaultValue);
+
+ if (typeof value !== 'string') {
+ return !!value;
+ } else {
+ return value === 'true';
+ }
+ },
+ updateStoredSettings: function updateStoredSettings(settings) {
+ for (var key in settings) {
+ BrowserStore.setItem('config_' + key, settings[key]);
+ }
+ }
+});
+
+ConfigStore.dispatchToken = AppDispatcher.register(function registry(payload) {
+ var action = payload.action;
+
+ switch (action.type) {
+ case ActionTypes.RECIEVED_CONFIG:
+ ConfigStore.updateStoredSettings(action.settings);
+ ConfigStore.emitChange();
+ break;
+ default:
+ }
+});
+
+module.exports = ConfigStore;
diff --git a/web/react/stores/team_store.jsx b/web/react/stores/team_store.jsx
index e6380d19e..3f2248c44 100644
--- a/web/react/stores/team_store.jsx
+++ b/web/react/stores/team_store.jsx
@@ -13,89 +13,94 @@ var CHANGE_EVENT = 'change';
var utils;
function getWindowLocationOrigin() {
- if (!utils) utils = require('../utils/utils.jsx');
+ if (!utils) {
+ utils = require('../utils/utils.jsx');
+ }
return utils.getWindowLocationOrigin();
}
var TeamStore = assign({}, EventEmitter.prototype, {
- emitChange: function() {
- this.emit(CHANGE_EVENT);
- },
- addChangeListener: function(callback) {
- this.on(CHANGE_EVENT, callback);
- },
- removeChangeListener: function(callback) {
- this.removeListener(CHANGE_EVENT, callback);
- },
- get: function(id) {
- var c = this._getTeams();
- return c[id];
- },
- getByName: function(name) {
- var current = null;
- var t = this._getTeams();
+ emitChange: function() {
+ this.emit(CHANGE_EVENT);
+ },
+ addChangeListener: function(callback) {
+ this.on(CHANGE_EVENT, callback);
+ },
+ removeChangeListener: function(callback) {
+ this.removeListener(CHANGE_EVENT, callback);
+ },
+ get: function(id) {
+ var c = this.pGetTeams();
+ return c[id];
+ },
+ getByName: function(name) {
+ var t = this.pGetTeams();
- for (id in t) {
- if (t[id].name == name) {
- return t[id];
+ for (var id in t) {
+ if (t[id].name === name) {
+ return t[id];
+ }
}
- }
- return null;
- },
- getAll: function() {
- return this._getTeams();
- },
- setCurrentId: function(id) {
- if (id == null)
- BrowserStore.removeItem("current_team_id");
- else
- BrowserStore.setItem("current_team_id", id);
- },
- getCurrentId: function() {
- return BrowserStore.getItem("current_team_id");
- },
- getCurrent: function() {
- var currentId = TeamStore.getCurrentId();
+ return null;
+ },
+ getAll: function() {
+ return this.pGetTeams();
+ },
+ setCurrentId: function(id) {
+ if (id === null) {
+ BrowserStore.removeItem('current_team_id');
+ } else {
+ BrowserStore.setItem('current_team_id', id);
+ }
+ },
+ getCurrentId: function() {
+ return BrowserStore.getItem('current_team_id');
+ },
+ getCurrent: function() {
+ var currentId = TeamStore.getCurrentId();
- if (currentId != null)
- return this.get(currentId);
- else
- return null;
- },
- getCurrentTeamUrl: function() {
- return getWindowLocationOrigin() + "/" + this.getCurrent().name;
- },
- storeTeam: function(team) {
- var teams = this._getTeams();
- teams[team.id] = team;
- this._storeTeams(teams);
- },
- _storeTeams: function(teams) {
- BrowserStore.setItem("user_teams", teams);
- },
- _getTeams: function() {
- return BrowserStore.getItem("user_teams", {});
- }
+ if (currentId !== null) {
+ return this.get(currentId);
+ }
+ return null;
+ },
+ getCurrentTeamUrl: function() {
+ if (this.getCurrent()) {
+ return getWindowLocationOrigin() + '/' + this.getCurrent().name;
+ }
+ return null;
+ },
+ storeTeam: function(team) {
+ var teams = this.pGetTeams();
+ teams[team.id] = team;
+ this.pStoreTeams(teams);
+ },
+ pStoreTeams: function(teams) {
+ BrowserStore.setItem('user_teams', teams);
+ },
+ pGetTeams: function() {
+ return BrowserStore.getItem('user_teams', {});
+ }
});
-TeamStore.dispatchToken = AppDispatcher.register(function(payload) {
- var action = payload.action;
+TeamStore.dispatchToken = AppDispatcher.register(function registry(payload) {
+ var action = payload.action;
- switch(action.type) {
+ switch (action.type) {
- case ActionTypes.CLICK_TEAM:
- TeamStore.setCurrentId(action.id);
- TeamStore.emitChange();
- break;
+ case ActionTypes.CLICK_TEAM:
+ TeamStore.setCurrentId(action.id);
+ TeamStore.emitChange();
+ break;
- case ActionTypes.RECIEVED_TEAM:
- TeamStore.storeTeam(action.team);
- TeamStore.emitChange();
- break;
+ case ActionTypes.RECIEVED_TEAM:
+ TeamStore.storeTeam(action.team);
+ TeamStore.emitChange();
+ break;
- default:
- }
+ default:
+ }
});
module.exports = TeamStore;
diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx
index f35b0f6cc..0b87bbd7b 100644
--- a/web/react/utils/async_client.jsx
+++ b/web/react/utils/async_client.jsx
@@ -4,6 +4,7 @@
var client = require('./client.jsx');
var AppDispatcher = require('../dispatcher/app_dispatcher.jsx');
var ChannelStore = require('../stores/channel_store.jsx');
+var ConfigStore = require('../stores/config_store.jsx');
var PostStore = require('../stores/post_store.jsx');
var UserStore = require('../stores/user_store.jsx');
var utils = require('./utils.jsx');
@@ -14,100 +15,171 @@ var ActionTypes = Constants.ActionTypes;
// Used to track in progress async calls
var callTracker = {};
-var dispatchError = function(err, method) {
+function dispatchError(err, method) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_ERROR,
err: err,
method: method
});
-};
+}
+module.exports.dispatchError = dispatchError;
-var isCallInProgress = function(callName) {
- if (!(callName in callTracker)) return false;
+function isCallInProgress(callName) {
+ if (!(callName in callTracker)) {
+ return false;
+ }
- if (callTracker[callName] === 0) return false;
+ if (callTracker[callName] === 0) {
+ return false;
+ }
if (utils.getTimestamp() - callTracker[callName] > 5000) {
- console.log("AsyncClient call " + callName + " expired after more than 5 seconds");
+ console.log('AsyncClient call ' + callName + ' expired after more than 5 seconds');
return false;
}
return true;
-};
+}
-module.exports.dispatchError = dispatchError;
+function getChannels(force, updateLastViewed, checkVersion) {
+ var channels = ChannelStore.getAll();
-module.exports.getChannels = function(force, updateLastViewed, checkVersion) {
- if (isCallInProgress("getChannels")) return;
+ if (channels.length === 0 || force) {
+ if (isCallInProgress('getChannels')) {
+ return;
+ }
+
+ callTracker.getChannels = utils.getTimestamp();
- if (ChannelStore.getAll().length == 0 || force) {
- callTracker["getChannels"] = utils.getTimestamp();
client.getChannels(
function(data, textStatus, xhr) {
- callTracker["getChannels"] = 0;
-
- if (updateLastViewed && ChannelStore.getCurrentId() != null) {
- module.exports.updateLastViewedAt();
- }
+ callTracker.getChannels = 0;
if (checkVersion) {
- var serverVersion = xhr.getResponseHeader("X-Version-ID");
+ var serverVersion = xhr.getResponseHeader('X-Version-ID');
if (!UserStore.getLastVersion()) {
UserStore.setLastVersion(serverVersion);
}
- if (serverVersion != UserStore.getLastVersion()) {
+ if (serverVersion !== UserStore.getLastVersion()) {
UserStore.setLastVersion(serverVersion);
window.location.href = window.location.href;
- console.log("Detected version update refreshing the page");
+ console.log('Detected version update refreshing the page');
}
}
- if (xhr.status === 304 || !data) return;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
AppDispatcher.handleServerAction({
type: ActionTypes.RECIEVED_CHANNELS,
channels: data.channels,
members: data.members
});
+ },
+ function(err) {
+ callTracker.getChannels = 0;
+ dispatchError(err, 'getChannels');
+ }
+ );
+ } else {
+ if (isCallInProgress('getChannelCounts')) {
+ return;
+ }
+
+ callTracker.getChannelCounts = utils.getTimestamp();
+
+ client.getChannelCounts(
+ function(data, textStatus, xhr) {
+ callTracker.getChannelCounts = 0;
+ if (xhr.status === 304 || !data) {
+ return;
+ }
+
+ var countMap = data.counts;
+ var updateAtMap = data.update_times;
+
+ for (var id in countMap) {
+ var c = ChannelStore.get(id);
+ var count = countMap[id];
+ var updateAt = updateAtMap[id];
+ if (!c || c.total_msg_count !== count || updateAt > c.update_at) {
+ getChannel(id);
+ }
+ }
},
function(err) {
- callTracker["getChannels"] = 0;
- dispatchError(err, "getChannels");
+ callTracker.getChannelCounts = 0;
+ dispatchError(err, 'getChannelCounts');
}
);
}
+
+ if (updateLastViewed && ChannelStore.getCurrentId() != null) {
+ module.exports.updateLastViewedAt();
+ }
}
+module.exports.getChannels = getChannels;
+
+function getChannel(id) {
+ if (isCallInProgress('getChannel' + id)) {
+ return;
+ }
+
+ callTracker['getChannel' + id] = utils.getTimestamp();
+
+ client.getChannel(id,
+ function(data, textStatus, xhr) {
+ callTracker['getChannel' + id] = 0;
+
+ if (xhr.status === 304 || !data) {
+ return;
+ }
+
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_CHANNEL,
+ channel: data.channel,
+ member: data.member
+ });
+ },
+ function(err) {
+ callTracker['getChannel' + id] = 0;
+ dispatchError(err, 'getChannel');
+ }
+ );
+}
+module.exports.getChannel = getChannel;
module.exports.updateLastViewedAt = function() {
- if (isCallInProgress("updateLastViewed")) return;
+ if (isCallInProgress('updateLastViewed')) return;
if (ChannelStore.getCurrentId() == null) return;
- callTracker["updateLastViewed"] = utils.getTimestamp();
+ callTracker['updateLastViewed'] = utils.getTimestamp();
client.updateLastViewedAt(
ChannelStore.getCurrentId(),
function(data) {
- callTracker["updateLastViewed"] = 0;
+ callTracker['updateLastViewed'] = 0;
},
function(err) {
- callTracker["updateLastViewed"] = 0;
- dispatchError(err, "updateLastViewedAt");
+ callTracker['updateLastViewed'] = 0;
+ dispatchError(err, 'updateLastViewedAt');
}
);
}
module.exports.getMoreChannels = function(force) {
- if (isCallInProgress("getMoreChannels")) return;
+ if (isCallInProgress('getMoreChannels')) return;
if (ChannelStore.getMoreAll().loading || force) {
- callTracker["getMoreChannels"] = utils.getTimestamp();
+ callTracker['getMoreChannels'] = utils.getTimestamp();
client.getMoreChannels(
function(data, textStatus, xhr) {
- callTracker["getMoreChannels"] = 0;
+ callTracker['getMoreChannels'] = 0;
if (xhr.status === 304 || !data) return;
@@ -118,8 +190,8 @@ module.exports.getMoreChannels = function(force) {
});
},
function(err) {
- callTracker["getMoreChannels"] = 0;
- dispatchError(err, "getMoreChannels");
+ callTracker['getMoreChannels'] = 0;
+ dispatchError(err, 'getMoreChannels');
}
);
}
@@ -129,15 +201,15 @@ module.exports.getChannelExtraInfo = function(force) {
var channelId = ChannelStore.getCurrentId();
if (channelId != null) {
- if (isCallInProgress("getChannelExtraInfo_"+channelId)) return;
+ if (isCallInProgress('getChannelExtraInfo_'+channelId)) return;
var minMembers = ChannelStore.getCurrent() && ChannelStore.getCurrent().type === 'D' ? 1 : 0;
if (ChannelStore.getCurrentExtraInfo().members.length <= minMembers || force) {
- callTracker["getChannelExtraInfo_"+channelId] = utils.getTimestamp();
+ callTracker['getChannelExtraInfo_'+channelId] = utils.getTimestamp();
client.getChannelExtraInfo(
channelId,
function(data, textStatus, xhr) {
- callTracker["getChannelExtraInfo_"+channelId] = 0;
+ callTracker['getChannelExtraInfo_'+channelId] = 0;
if (xhr.status === 304 || !data) return;
@@ -147,8 +219,8 @@ module.exports.getChannelExtraInfo = function(force) {
});
},
function(err) {
- callTracker["getChannelExtraInfo_"+channelId] = 0;
- dispatchError(err, "getChannelExtraInfo");
+ callTracker['getChannelExtraInfo_'+channelId] = 0;
+ dispatchError(err, 'getChannelExtraInfo');
}
);
}
@@ -156,12 +228,12 @@ module.exports.getChannelExtraInfo = function(force) {
}
module.exports.getProfiles = function() {
- if (isCallInProgress("getProfiles")) return;
+ if (isCallInProgress('getProfiles')) return;
- callTracker["getProfiles"] = utils.getTimestamp();
+ callTracker['getProfiles'] = utils.getTimestamp();
client.getProfiles(
function(data, textStatus, xhr) {
- callTracker["getProfiles"] = 0;
+ callTracker['getProfiles'] = 0;
if (xhr.status === 304 || !data) return;
@@ -171,20 +243,20 @@ module.exports.getProfiles = function() {
});
},
function(err) {
- callTracker["getProfiles"] = 0;
- dispatchError(err, "getProfiles");
+ callTracker['getProfiles'] = 0;
+ dispatchError(err, 'getProfiles');
}
);
}
module.exports.getSessions = function() {
- if (isCallInProgress("getSessions")) return;
+ if (isCallInProgress('getSessions')) return;
- callTracker["getSessions"] = utils.getTimestamp();
+ callTracker['getSessions'] = utils.getTimestamp();
client.getSessions(
UserStore.getCurrentId(),
function(data, textStatus, xhr) {
- callTracker["getSessions"] = 0;
+ callTracker['getSessions'] = 0;
if (xhr.status === 304 || !data) return;
@@ -194,20 +266,20 @@ module.exports.getSessions = function() {
});
},
function(err) {
- callTracker["getSessions"] = 0;
- dispatchError(err, "getSessions");
+ callTracker['getSessions'] = 0;
+ dispatchError(err, 'getSessions');
}
);
}
module.exports.getAudits = function() {
- if (isCallInProgress("getAudits")) return;
+ if (isCallInProgress('getAudits')) return;
- callTracker["getAudits"] = utils.getTimestamp();
+ callTracker['getAudits'] = utils.getTimestamp();
client.getAudits(
UserStore.getCurrentId(),
function(data, textStatus, xhr) {
- callTracker["getAudits"] = 0;
+ callTracker['getAudits'] = 0;
if (xhr.status === 304 || !data) return;
@@ -217,22 +289,22 @@ module.exports.getAudits = function() {
});
},
function(err) {
- callTracker["getAudits"] = 0;
- dispatchError(err, "getAudits");
+ callTracker['getAudits'] = 0;
+ dispatchError(err, 'getAudits');
}
);
}
module.exports.findTeams = function(email) {
- if (isCallInProgress("findTeams_"+email)) return;
+ if (isCallInProgress('findTeams_'+email)) return;
var user = UserStore.getCurrentUser();
if (user) {
- callTracker["findTeams_"+email] = utils.getTimestamp();
+ callTracker['findTeams_'+email] = utils.getTimestamp();
client.findTeams(
user.email,
function(data, textStatus, xhr) {
- callTracker["findTeams_"+email] = 0;
+ callTracker['findTeams_'+email] = 0;
if (xhr.status === 304 || !data) return;
@@ -242,21 +314,21 @@ module.exports.findTeams = function(email) {
});
},
function(err) {
- callTracker["findTeams_"+email] = 0;
- dispatchError(err, "findTeams");
+ callTracker['findTeams_'+email] = 0;
+ dispatchError(err, 'findTeams');
}
);
}
}
module.exports.search = function(terms) {
- if (isCallInProgress("search_"+String(terms))) return;
+ if (isCallInProgress('search_'+String(terms))) return;
- callTracker["search_"+String(terms)] = utils.getTimestamp();
+ callTracker['search_'+String(terms)] = utils.getTimestamp();
client.search(
terms,
function(data, textStatus, xhr) {
- callTracker["search_"+String(terms)] = 0;
+ callTracker['search_'+String(terms)] = 0;
if (xhr.status === 304 || !data) return;
@@ -266,8 +338,8 @@ module.exports.search = function(terms) {
});
},
function(err) {
- callTracker["search_"+String(terms)] = 0;
- dispatchError(err, "search");
+ callTracker['search_'+String(terms)] = 0;
+ dispatchError(err, 'search');
}
);
}
@@ -276,7 +348,7 @@ module.exports.getPosts = function(force, id, maxPosts) {
if (PostStore.getCurrentPosts() == null || force) {
var channelId = id ? id : ChannelStore.getCurrentId();
- if (isCallInProgress("getPosts_"+channelId)) return;
+ if (isCallInProgress('getPosts_'+channelId)) return;
var post_list = PostStore.getCurrentPosts();
@@ -291,7 +363,7 @@ module.exports.getPosts = function(force, id, maxPosts) {
}
if (channelId != null) {
- callTracker["getPosts_"+channelId] = utils.getTimestamp();
+ callTracker['getPosts_'+channelId] = utils.getTimestamp();
client.getPosts(
channelId,
0,
@@ -308,23 +380,25 @@ module.exports.getPosts = function(force, id, maxPosts) {
module.exports.getProfiles();
},
function(err) {
- dispatchError(err, "getPosts");
+ dispatchError(err, 'getPosts');
},
function() {
- callTracker["getPosts_"+channelId] = 0;
+ callTracker['getPosts_'+channelId] = 0;
}
);
}
}
}
-module.exports.getMe = function() {
- if (isCallInProgress("getMe")) return;
+function getMe() {
+ if (isCallInProgress('getMe')) {
+ return;
+ }
- callTracker["getMe"] = utils.getTimestamp();
+ callTracker.getMe = utils.getTimestamp();
client.getMe(
function(data, textStatus, xhr) {
- callTracker["getMe"] = 0;
+ callTracker.getMe = 0;
if (xhr.status === 304 || !data) return;
@@ -334,19 +408,20 @@ module.exports.getMe = function() {
});
},
function(err) {
- callTracker["getMe"] = 0;
- dispatchError(err, "getMe");
+ callTracker.getMe = 0;
+ dispatchError(err, 'getMe');
}
);
}
+module.exports.getMe = getMe;
module.exports.getStatuses = function() {
- if (isCallInProgress("getStatuses")) return;
+ if (isCallInProgress('getStatuses')) return;
- callTracker["getStatuses"] = utils.getTimestamp();
+ callTracker['getStatuses'] = utils.getTimestamp();
client.getStatuses(
function(data, textStatus, xhr) {
- callTracker["getStatuses"] = 0;
+ callTracker['getStatuses'] = 0;
if (xhr.status === 304 || !data) return;
@@ -356,19 +431,19 @@ module.exports.getStatuses = function() {
});
},
function(err) {
- callTracker["getStatuses"] = 0;
- dispatchError(err, "getStatuses");
+ callTracker['getStatuses'] = 0;
+ dispatchError(err, 'getStatuses');
}
);
}
module.exports.getMyTeam = function() {
- if (isCallInProgress("getMyTeam")) return;
+ if (isCallInProgress('getMyTeam')) return;
- callTracker["getMyTeam"] = utils.getTimestamp();
+ callTracker['getMyTeam'] = utils.getTimestamp();
client.getMyTeam(
function(data, textStatus, xhr) {
- callTracker["getMyTeam"] = 0;
+ callTracker['getMyTeam'] = 0;
if (xhr.status === 304 || !data) return;
@@ -378,8 +453,33 @@ module.exports.getMyTeam = function() {
});
},
function(err) {
- callTracker["getMyTeam"] = 0;
- dispatchError(err, "getMyTeam");
+ callTracker['getMyTeam'] = 0;
+ dispatchError(err, 'getMyTeam');
+ }
+ );
+}
+
+function getConfig() {
+ if (isCallInProgress('getConfig')) {
+ return;
+ }
+
+ callTracker['getConfig'] = utils.getTimestamp();
+ client.getConfig(
+ function(data, textStatus, xhr) {
+ callTracker['getConfig'] = 0;
+
+ if (data && xhr.status !== 304) {
+ AppDispatcher.handleServerAction({
+ type: ActionTypes.RECIEVED_CONFIG,
+ settings: data
+ });
+ }
+ },
+ function(err) {
+ callTracker['getConfig'] = 0;
+ dispatchError(err, 'getConfig');
}
);
}
+module.exports.getConfig = getConfig;
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 6a1f7c820..5aab80d01 100644
--- a/web/react/utils/client.jsx
+++ b/web/react/utils/client.jsx
@@ -540,18 +540,34 @@ module.exports.updateLastViewedAt = function(channelId, success, error) {
});
};
-module.exports.getChannels = function(success, error) {
+function getChannels(success, error) {
$.ajax({
- url: "/api/v1/channels/",
+ url: '/api/v1/channels/',
dataType: 'json',
type: 'GET',
success: success,
ifModified: true,
error: function(xhr, status, err) {
- e = handleError("getChannels", xhr, status, err);
+ var e = handleError('getChannels', xhr, status, err);
error(e);
}
});
+}
+module.exports.getChannels = getChannels;
+
+module.exports.getChannel = function(id, success, error) {
+ $.ajax({
+ url: "/api/v1/channels/" + id + "/",
+ dataType: 'json',
+ type: 'GET',
+ success: success,
+ error: function(xhr, status, err) {
+ e = handleError("getChannel", xhr, status, err);
+ error(e);
+ }
+ });
+
+ module.exports.track('api', 'api_channel_get');
};
module.exports.getMoreChannels = function(success, error) {
@@ -568,6 +584,21 @@ module.exports.getMoreChannels = function(success, error) {
});
};
+function getChannelCounts(success, error) {
+ $.ajax({
+ url: '/api/v1/channels/counts',
+ dataType: 'json',
+ type: 'GET',
+ success: success,
+ ifModified: true,
+ error: function(xhr, status, err) {
+ var e = handleError('getChannelCounts', xhr, status, err);
+ error(e);
+ }
+ });
+}
+module.exports.getChannelCounts = getChannelCounts;
+
module.exports.getChannelExtraInfo = function(id, success, error) {
$.ajax({
url: "/api/v1/channels/" + id + "/extra_info",
@@ -849,3 +880,18 @@ module.exports.updateValetFeature = function(data, success, error) {
module.exports.track('api', 'api_teams_update_valet_feature');
};
+
+function getConfig(success, error) {
+ $.ajax({
+ url: '/api/v1/config/get_all',
+ dataType: 'json',
+ type: 'GET',
+ ifModified: true,
+ success: success,
+ error: function(xhr, status, err) {
+ var e = handleError('getConfig', xhr, status, err);
+ error(e);
+ }
+ });
+};
+module.exports.getConfig = getConfig;
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 505f7fa17..508de9185 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -10,6 +10,7 @@ module.exports = {
CLICK_CHANNEL: null,
CREATE_CHANNEL: null,
RECIEVED_CHANNELS: null,
+ RECIEVED_CHANNEL: null,
RECIEVED_MORE_CHANNELS: null,
RECIEVED_CHANNEL_EXTRA_INFO: null,
@@ -30,6 +31,8 @@ module.exports = {
CLICK_TEAM: null,
RECIEVED_TEAM: null,
+
+ RECIEVED_CONFIG: null
}),
PayloadSources: keyMirror({
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 2214b6239..2312fe225 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -124,8 +124,10 @@ module.exports.notifyMe = function(title, body, channel) {
}
module.exports.ding = function() {
- var audio = new Audio('/static/images/ding.mp3');
- audio.play();
+ if (!module.exports.isBrowserFirefox()) {
+ var audio = new Audio('/static/images/ding.mp3');
+ audio.play();
+ }
}
module.exports.getUrlParameter = function(sParam) {
@@ -732,20 +734,19 @@ module.exports.isValidUsername = function (name) {
return error;
}
-module.exports.switchChannel = function(channel, teammate_name) {
+function switchChannel(channel, teammateName) {
AppDispatcher.handleViewAction({
type: ActionTypes.CLICK_CHANNEL,
name: channel.name,
id: channel.id
});
- var teamURL = window.location.href.split('/channels')[0];
- history.replaceState('data', '', teamURL + '/channels/' + channel.name);
+ updateAddressBar(channel.name);
- if (channel.type === 'D' && teammate_name) {
- document.title = teammate_name + " " + document.title.substring(document.title.lastIndexOf("-"));
+ if (channel.type === 'D' && teammateName) {
+ updateTabTitle(teammateName);
} else {
- document.title = channel.display_name + " " + document.title.substring(document.title.lastIndexOf("-"));
+ updateTabTitle(channel.display_name);
}
AsyncClient.getChannels(true, true, true);
@@ -759,6 +760,18 @@ module.exports.switchChannel = function(channel, teammate_name) {
return false;
}
+module.exports.switchChannel = switchChannel;
+
+function updateTabTitle(name) {
+ document.title = name + ' ' + document.title.substring(document.title.lastIndexOf('-'));
+}
+module.exports.updateTabTitle = updateTabTitle;
+
+function updateAddressBar(channelName) {
+ var teamURL = window.location.href.split('/channels')[0];
+ history.replaceState('data', '', teamURL + '/channels/' + channelName);
+}
+module.exports.updateAddressBar = updateAddressBar;
module.exports.isMobile = function() {
return screen.width <= 768;
@@ -934,3 +947,7 @@ module.exports.generateId = function() {
return id;
};
+
+module.exports.isBrowserFirefox = function() {
+ return navigator && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
+}
diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss
index 9bf6fd83a..1375a10e7 100644
--- a/web/sass-files/sass/partials/_files.scss
+++ b/web/sass-files/sass/partials/_files.scss
@@ -115,7 +115,6 @@
height: 100px;
float: left;
margin: 5px 10px 5px 0;
- display: table;
border: 1px solid lightgrey;
.post__load {
height: 100%;
@@ -137,16 +136,16 @@
}
}
.post-image__thumbnail {
- display: table-cell;
- vertical-align: top;
+ float: left;
width: 50%;
height: 100%;
cursor: zoom-in;
cursor: -webkit-zoom-in;
}
.post-image__details {
- display: table-cell;
- vertical-align: top;
+ float: left;
+ @include clearfix;
+ word-break: break-word;
width: 50%;
height: 100%;
background: white;
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index 52bb5eae6..733d81c2b 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -437,9 +437,6 @@
}
}
}
- #user_settings {
- border-right: none;
- }
body {
&.white {
.inner__wrap {
diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss
index 1fb078bb9..0262ef60c 100644
--- a/web/sass-files/sass/partials/_settings.scss
+++ b/web/sass-files/sass/partials/_settings.scss
@@ -143,12 +143,6 @@
}
}
-#user_settings {
- padding: 0 0.5em;
- border-right: 1px solid #ddd;
- max-width: 800px;
-}
-
.channel-settings {
padding: 0 10px;
}