summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml29
-rw-r--r--Makefile6
-rw-r--r--api/channel.go53
-rw-r--r--api/channel_test.go62
-rw-r--r--docker/dev/Dockerfile7
-rwxr-xr-xdocker/dev/Dockerrun.aws.json2
-rw-r--r--docker/local/Dockerfile84
-rwxr-xr-xdocker/local/Dockerrun.aws.json13
-rw-r--r--docker/local/config_docker.json98
-rwxr-xr-xdocker/local/docker-entry.sh116
-rw-r--r--model/channel_count.go63
-rw-r--r--model/channel_data.go43
-rw-r--r--model/client.go18
-rw-r--r--model/file.go4
-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/edit_channel_modal.jsx2
-rw-r--r--web/react/components/file_upload.jsx33
-rw-r--r--web/react/components/invite_member_modal.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/rename_channel_modal.jsx10
-rw-r--r--web/react/components/sidebar.jsx20
-rw-r--r--web/react/components/view_image.jsx57
-rw-r--r--web/react/stores/channel_store.jsx446
-rw-r--r--web/react/utils/async_client.jsx240
-rw-r--r--web/react/utils/client.jsx37
-rw-r--r--web/react/utils/constants.jsx3
-rw-r--r--web/react/utils/utils.jsx23
30 files changed, 1266 insertions, 423 deletions
diff --git a/.travis.yml b/.travis.yml
index 359de244e..b8a503714 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -30,10 +30,25 @@ addons:
hosts:
- 127.0.0.1 dockerhost
deploy:
- provider: releases
- api_key:
- secure: ma8Y0oimU+LB6LTAh8to2E1/ghaDPhcsAFXBrODsHpd4JgxA6HYoEwSEBCJFHSpu/JteclsxSTfp9hcuzw/IOtlwlSAiVoBZ60s24MRKTIAQNtrJ4QrX5wyfAZi+Bcuk/E8NynmoIW5qpaElSAdjgocyjAJIQ5ChMEztglL0cAEBXQRWbWMqSZ0hVLPrKDCIkWIyv3pFxqdLOxktkzxW07r2dlT0hppXR3dCaPJo0nelArS2H3LdN/3Iv6cAddfS27RaZkqDj/PDh6OZr4EguC99TxlVNChIr7nPr3/OiAssbkvEnhlSLeABFO9+7KfutL2WhAjpFXTjtPVq6Qalc8UW0K0gxq//sVfhb1MzjenmdOf06uB2bilQ8kgwHo7dDdRZBqqAtxQ6Q0Ht3SFMj6v/1zVD3s+YX/kWCEbUTHm6r2G/eF794ozcJyU+6j1L8hm6mvf8Mr9XCqBfgpZy6FCLX+9OKdMvX2jY8reo3Xz1PA9R6yzhN08vjku+jW+fsoYrBLd0fY1UGK2uOuvBByCeJzXupd3YpBMjEyRupVxqEj7K0GWOJeml65mkqKSNsHdDSeSjMpb8mwneZyTbdjsxCFQRLcLgpAajFrkk4G2Yz3KfhXSo29XKEGX+EbY5NuP8KmDsBsguPI0zfwv/co0hAY8PIIcehxcdoR9Vb2c=
- file: dist/mattermost.tar.gz
- skip_cleanup: true
- on:
- tags: true
+ - provider: releases
+ api_key:
+ secure: ma8Y0oimU+LB6LTAh8to2E1/ghaDPhcsAFXBrODsHpd4JgxA6HYoEwSEBCJFHSpu/JteclsxSTfp9hcuzw/IOtlwlSAiVoBZ60s24MRKTIAQNtrJ4QrX5wyfAZi+Bcuk/E8NynmoIW5qpaElSAdjgocyjAJIQ5ChMEztglL0cAEBXQRWbWMqSZ0hVLPrKDCIkWIyv3pFxqdLOxktkzxW07r2dlT0hppXR3dCaPJo0nelArS2H3LdN/3Iv6cAddfS27RaZkqDj/PDh6OZr4EguC99TxlVNChIr7nPr3/OiAssbkvEnhlSLeABFO9+7KfutL2WhAjpFXTjtPVq6Qalc8UW0K0gxq//sVfhb1MzjenmdOf06uB2bilQ8kgwHo7dDdRZBqqAtxQ6Q0Ht3SFMj6v/1zVD3s+YX/kWCEbUTHm6r2G/eF794ozcJyU+6j1L8hm6mvf8Mr9XCqBfgpZy6FCLX+9OKdMvX2jY8reo3Xz1PA9R6yzhN08vjku+jW+fsoYrBLd0fY1UGK2uOuvBByCeJzXupd3YpBMjEyRupVxqEj7K0GWOJeml65mkqKSNsHdDSeSjMpb8mwneZyTbdjsxCFQRLcLgpAajFrkk4G2Yz3KfhXSo29XKEGX+EbY5NuP8KmDsBsguPI0zfwv/co0hAY8PIIcehxcdoR9Vb2c=
+ file: dist/mattermost.tar.gz
+ skip_cleanup: true
+ on:
+ repo: mattermost/platform
+ tags: true
+
+ - provider: s3
+ access_key_id: AKIAJCO3KJYEGWJIKDIQ
+ secret_access_key:
+ secure: p66X2tJBmKgtcVyPtGgkAwW29IiRojqGA39RjCJkIWNTJ0e/9JvBOiMS2c4a7I4aOads38rsthwdaigBWagDWNH7bGsEZN7B0TszZuFAuU+XGjU5A66MIOfFfzbUg8AnByysr+XG5/bknFIrP/XhM2fbRr6gbYrFUK7TNkpgjFs5u3BzUrz2iTAV8uOpSJqKSnaf0pTZk1EywOK/X8W8ViIjc7Di3FzQcqIW9K3D27N+3rVsv8SRT1hWASVlnG6aThqqebiM8FCGCzAYVgQb3h3Wu8JT5fIz7Qo7A6siVRwNBwWwzP8HkGoinEK32Wsj/fDXk27vjpFQO/+9sV0xfcTbIZA6MnuYWF4rHOT59KcshCWCD3V0FopX57p/dtOzM9+6lxIctAT++izxWoZit/5c5A4633iY1d+RMeTko1POix6MSlxPMRHZUFwSXROgFuWWRpyD6TlUTCST9/wTTd0WDPklAAiYcnuEPW3qCnw0r0xkrA4AwWUXqXdAIwDt5bA27KcjRyY4Fofv9NxH09BNuBTXNPrvnYPZMmaKrv+HOX3NFTreuV6+5LJdhYUxYSBvSWo1jeWIQ5Q9RUdTU0PqmKpMhJKbKey/S4gxCXHg2HR8DwLCcbIZcvneF9yPEAT71YA6zpLKoPVSwWwH97huKSzjpic/RUfFXQOcgCQ=
+ bucket: mattermost-travis-master
+ local_dir: dist
+ acl: public_read
+ region: us-east-1
+ skip_cleanup: true
+ detect_encoding: true
+ on:
+ repo: mattermost/platform
+ branch: master
diff --git a/Makefile b/Makefile
index 30babfa39..915eb4cea 100644
--- a/Makefile
+++ b/Makefile
@@ -245,10 +245,8 @@ dist: install
tar -C dist -czf $(DIST_PATH).tar.gz mattermost
-docker-build: stop dist
- cp $(DIST_PATH).tar.gz docker/dev
- cd docker/dev && docker build -t ${DOCKERNAME} .
- rm docker/dev/mattermost.tar.gz
+docker-build: stop
+ docker build -t ${DOCKERNAME} -f docker/local/Dockerfile .
docker-run: docker-build
docker run --name ${DOCKER_CONTAINER_NAME} -d --publish 8065:80 ${DOCKERNAME}
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/docker/dev/Dockerfile b/docker/dev/Dockerfile
index a684ac10d..cdfdc1845 100644
--- a/docker/dev/Dockerfile
+++ b/docker/dev/Dockerfile
@@ -13,8 +13,6 @@ ENV MYSQL_DATABASE=mattermost_test
RUN groupadd -r mysql && useradd -r -g mysql mysql
-RUN apt-get update && apt-get install -y perl --no-install-recommends && rm -rf /var/lib/apt/lists/*
-
RUN apt-key adv --keyserver pool.sks-keyservers.net --recv-keys A4A9406876FCBD3C456770C88C718D3B5072E1F5
ENV MYSQL_MAJOR 5.6
@@ -24,7 +22,7 @@ RUN echo "deb http://repo.mysql.com/apt/debian/ wheezy mysql-${MYSQL_MAJOR}" > /
RUN apt-get update \
&& export DEBIAN_FRONTEND=noninteractive \
- && apt-get -y install mysql-server \
+ && apt-get -y install perl wget mysql-server \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql
@@ -36,7 +34,8 @@ VOLUME /var/lib/mysql
WORKDIR /mattermost
# Copy over files
-ADD mattermost.tar.gz /
+RUN wget --no-check-certificate https://s3.amazonaws.com/mattermost-travis-master/mattermost.tar.gz
+RUN tar -zxvf mattermost.tar.gz --strip-components=1 && rm mattermost.tar.gz
ADD config_docker.json /
ADD docker-entry.sh /
diff --git a/docker/dev/Dockerrun.aws.json b/docker/dev/Dockerrun.aws.json
index 9f69f1fb1..ae847ef80 100755
--- a/docker/dev/Dockerrun.aws.json
+++ b/docker/dev/Dockerrun.aws.json
@@ -1,7 +1,7 @@
{
"AWSEBDockerrunVersion": "1",
"Image": {
- "Name": "YOUR_DEV_INSTANCE_HERE",
+ "Name": "mattermost/platform:dev",
"Update": "true"
},
"Ports": [
diff --git a/docker/local/Dockerfile b/docker/local/Dockerfile
new file mode 100644
index 000000000..55725e293
--- /dev/null
+++ b/docker/local/Dockerfile
@@ -0,0 +1,84 @@
+# Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+# See License.txt for license information.
+FROM ubuntu:14.04
+
+# Install Dependancies
+RUN apt-get update && apt-get install -y build-essential
+RUN apt-get install -y curl
+RUN curl -sL https://deb.nodesource.com/setup | bash -
+RUN apt-get install -y nodejs
+RUN apt-get install -y ruby-full
+RUN gem install compass
+
+#
+# Install GO
+#
+
+RUN apt-get update && apt-get install -y \
+ gcc libc6-dev make git mercurial \
+ --no-install-recommends \
+ && rm -rf /var/lib/apt/lists/*
+
+ENV GOLANG_VERSION 1.4.2
+
+RUN curl -sSL https://golang.org/dl/go$GOLANG_VERSION.src.tar.gz \
+ | tar -v -C /usr/src -xz
+
+RUN cd /usr/src/go/src && ./make.bash --no-clean 2>&1
+
+ENV PATH /usr/src/go/bin:$PATH
+
+RUN mkdir -p /go/src /go/bin && chmod -R 777 /go
+ENV GOPATH /go
+ENV PATH /go/bin:$PATH
+WORKDIR /go
+
+# ---------------------------------------------------------------------------------------------------------------------
+
+#
+# Install SQL
+#
+
+ENV MYSQL_ROOT_PASSWORD=mostest
+ENV MYSQL_USER=mmuser
+ENV MYSQL_PASSWORD=mostest
+ENV MYSQL_DATABASE=mattermost_test
+
+RUN groupadd -r mysql && useradd -r -g mysql mysql
+
+RUN apt-key adv --keyserver pool.sks-keyservers.net --recv-keys A4A9406876FCBD3C456770C88C718D3B5072E1F5
+
+ENV MYSQL_MAJOR 5.6
+ENV MYSQL_VERSION 5.6.25
+
+RUN echo "deb http://repo.mysql.com/apt/debian/ wheezy mysql-${MYSQL_MAJOR}" > /etc/apt/sources.list.d/mysql.list
+
+RUN apt-get update \
+ && export DEBIAN_FRONTEND=noninteractive \
+ && apt-get -y install perl mysql-server \
+ && rm -rf /var/lib/apt/lists/* \
+ && rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql
+
+RUN sed -Ei 's/^(bind-address|log)/#&/' /etc/mysql/my.cnf
+
+VOLUME /var/lib/mysql
+# ---------------------------------------------------------------------------------------------------------------------
+
+# Copy over files
+ADD . /go/src/github.com/mattermost/platform
+
+RUN go get github.com/tools/godep
+RUN cd /go/src/github.com/mattermost/platform/; godep go install
+RUN cd /go/src/github.com/mattermost/platform/web/react; npm install
+
+ADD /docker/local/config_docker.json /
+ADD /docker/local/docker-entry.sh /
+
+RUN chmod +x /docker-entry.sh
+ENTRYPOINT /docker-entry.sh
+
+# Create default storage directory
+RUN mkdir /mattermost-data/
+
+# Ports
+EXPOSE 80
diff --git a/docker/local/Dockerrun.aws.json b/docker/local/Dockerrun.aws.json
new file mode 100755
index 000000000..9f69f1fb1
--- /dev/null
+++ b/docker/local/Dockerrun.aws.json
@@ -0,0 +1,13 @@
+{
+ "AWSEBDockerrunVersion": "1",
+ "Image": {
+ "Name": "YOUR_DEV_INSTANCE_HERE",
+ "Update": "true"
+ },
+ "Ports": [
+ {
+ "ContainerPort": "80"
+ }
+ ],
+ "Logging": "/var/log/"
+}
diff --git a/docker/local/config_docker.json b/docker/local/config_docker.json
new file mode 100644
index 000000000..cd612c7fe
--- /dev/null
+++ b/docker/local/config_docker.json
@@ -0,0 +1,98 @@
+{
+ "LogSettings": {
+ "ConsoleEnable": true,
+ "ConsoleLevel": "INFO",
+ "FileEnable": true,
+ "FileLevel": "INFO",
+ "FileFormat": "",
+ "FileLocation": ""
+ },
+ "ServiceSettings": {
+ "SiteName": "Mattermost",
+ "Mode" : "dev",
+ "AllowTesting" : true,
+ "UseSSL": false,
+ "Port": "80",
+ "Version": "developer",
+ "Shards": {
+ },
+ "InviteSalt": "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6",
+ "PublicLinkSalt": "TO3pTyXIZzwHiwyZgGql7lM7DG3zeId4",
+ "ResetSalt": "IPxFzSfnDFsNsRafZxz8NaYqFKhf9y2t",
+ "AnalyticsUrl": "",
+ "UseLocalStorage": true,
+ "StorageDirectory": "/mattermost/data/",
+ "AllowedLoginAttempts": 10
+ },
+ "SSOSettings": {
+ "gitlab": {
+ "Allow": false,
+ "Secret" : "",
+ "Id": "",
+ "AuthEndpoint": "",
+ "TokenEndpoint": "",
+ "UserApiEndpoint": ""
+ }
+ },
+ "SqlSettings": {
+ "DriverName": "mysql",
+ "DataSource": "mmuser:mostest@tcp(localhost:3306)/mattermost_test?charset=utf8mb4,utf8",
+ "DataSourceReplicas": ["mmuser:mostest@tcp(localhost:3306)/mattermost_test?charset=utf8mb4,utf8"],
+ "MaxIdleConns": 10,
+ "MaxOpenConns": 10,
+ "Trace": false,
+ "AtRestEncryptKey": "Ya0xMrybACJ3sZZVWQC7e31h5nSDWZFS"
+ },
+ "AWSSettings": {
+ "S3AccessKeyId": "",
+ "S3SecretAccessKey": "",
+ "S3Bucket": "",
+ "S3Region": ""
+ },
+ "ImageSettings": {
+ "ThumbnailWidth": 120,
+ "ThumbnailHeight": 100,
+ "PreviewWidth": 1024,
+ "PreviewHeight": 0,
+ "ProfileWidth": 128,
+ "ProfileHeight": 128,
+ "InitialFont": "luximbi.ttf"
+ },
+ "EmailSettings": {
+ "ByPassEmail" : true,
+ "SMTPUsername": "",
+ "SMTPPassword": "",
+ "SMTPServer": "",
+ "UseTLS": false,
+ "FeedbackEmail": "",
+ "FeedbackName": "",
+ "ApplePushServer": "",
+ "ApplePushCertPublic": "",
+ "ApplePushCertPrivate": ""
+ },
+ "RateLimitSettings": {
+ "UseRateLimiter": true,
+ "PerSec": 10,
+ "MemoryStoreSize": 10000,
+ "VaryByRemoteAddr": true,
+ "VaryByHeader": ""
+ },
+ "PrivacySettings": {
+ "ShowEmailAddress": true,
+ "ShowPhoneNumber": true,
+ "ShowSkypeId": true,
+ "ShowFullName": true
+ },
+ "TeamSettings": {
+ "MaxUsersPerTeam": 150,
+ "AllowPublicLink": true,
+ "AllowValetDefault": false,
+ "TermsLink": "/static/help/configure_links.html",
+ "PrivacyLink": "/static/help/configure_links.html",
+ "AboutLink": "/static/help/configure_links.html",
+ "HelpLink": "/static/help/configure_links.html",
+ "ReportProblemLink": "/static/help/configure_links.html",
+ "TourLink": "/static/help/configure_links.html",
+ "DefaultThemeColor": "#2389D7"
+ }
+}
diff --git a/docker/local/docker-entry.sh b/docker/local/docker-entry.sh
new file mode 100755
index 000000000..16583e525
--- /dev/null
+++ b/docker/local/docker-entry.sh
@@ -0,0 +1,116 @@
+#!/bin/bash
+# Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
+# See License.txt for license information.
+
+mkdir -p web/static/js
+
+echo "127.0.0.1 dockerhost" >> /etc/hosts
+/etc/init.d/networking restart
+
+echo configuring mysql
+
+# SQL!!!
+set -e
+
+get_option () {
+ local section=$1
+ local option=$2
+ local default=$3
+ ret=$(my_print_defaults $section | grep '^--'${option}'=' | cut -d= -f2-)
+ [ -z $ret ] && ret=$default
+ echo $ret
+}
+
+
+# Get config
+DATADIR="$("mysqld" --verbose --help 2>/dev/null | awk '$1 == "datadir" { print $2; exit }')"
+SOCKET=$(get_option mysqld socket "$DATADIR/mysql.sock")
+PIDFILE=$(get_option mysqld pid-file "/var/run/mysqld/mysqld.pid")
+
+if [ ! -d "$DATADIR/mysql" ]; then
+ if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" ]; then
+ echo >&2 'error: database is uninitialized and MYSQL_ROOT_PASSWORD not set'
+ echo >&2 ' Did you forget to add -e MYSQL_ROOT_PASSWORD=... ?'
+ exit 1
+ fi
+
+ mkdir -p "$DATADIR"
+ chown -R mysql:mysql "$DATADIR"
+
+ echo 'Running mysql_install_db'
+ mysql_install_db --user=mysql --datadir="$DATADIR" --rpm --keep-my-cnf
+ echo 'Finished mysql_install_db'
+
+ mysqld --user=mysql --datadir="$DATADIR" --skip-networking &
+ for i in $(seq 30 -1 0); do
+ [ -S "$SOCKET" ] && break
+ echo 'MySQL init process in progress...'
+ sleep 1
+ done
+ if [ $i = 0 ]; then
+ echo >&2 'MySQL init process failed.'
+ exit 1
+ fi
+
+ # These statements _must_ be on individual lines, and _must_ end with
+ # semicolons (no line breaks or comments are permitted).
+ # TODO proper SQL escaping on ALL the things D:
+
+ tempSqlFile=$(mktemp /tmp/mysql-first-time.XXXXXX.sql)
+ cat > "$tempSqlFile" <<-EOSQL
+ -- What's done in this file shouldn't be replicated
+ -- or products like mysql-fabric won't work
+ SET @@SESSION.SQL_LOG_BIN=0;
+
+ DELETE FROM mysql.user ;
+ CREATE USER 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;
+ GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION ;
+ DROP DATABASE IF EXISTS test ;
+ EOSQL
+
+ if [ "$MYSQL_DATABASE" ]; then
+ echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" >> "$tempSqlFile"
+ fi
+
+ if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then
+ echo "CREATE USER '"$MYSQL_USER"'@'%' IDENTIFIED BY '"$MYSQL_PASSWORD"' ;" >> "$tempSqlFile"
+
+ if [ "$MYSQL_DATABASE" ]; then
+ echo "GRANT ALL ON \`"$MYSQL_DATABASE"\`.* TO '"$MYSQL_USER"'@'%' ;" >> "$tempSqlFile"
+ fi
+ fi
+
+ echo 'FLUSH PRIVILEGES ;' >> "$tempSqlFile"
+
+ mysql -uroot < "$tempSqlFile"
+
+ rm -f "$tempSqlFile"
+ kill $(cat $PIDFILE)
+ for i in $(seq 30 -1 0); do
+ [ -f "$PIDFILE" ] || break
+ echo 'MySQL init process in progress...'
+ sleep 1
+ done
+ if [ $i = 0 ]; then
+ echo >&2 'MySQL hangs during init process.'
+ exit 1
+ fi
+ echo 'MySQL init process done. Ready for start up.'
+fi
+
+chown -R mysql:mysql "$DATADIR"
+
+mysqld &
+
+sleep 5
+
+# ------------------------
+
+echo starting react processor
+cd /go/src/github.com/mattermost/platform/web/react && npm start &
+
+echo starting go web server
+cd /go/src/github.com/mattermost/platform/; godep go run mattermost.go -config=/config_docker.json &
+
+echo starting compass watch
+cd /go/src/github.com/mattermost/platform/web/sass-files && compass watch
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/model/file.go b/model/file.go
index 3d38ddbd1..85545d718 100644
--- a/model/file.go
+++ b/model/file.go
@@ -13,8 +13,8 @@ const (
)
var (
- IMAGE_EXTENSIONS = [4]string{".jpg", ".gif", ".bmp", ".png"}
- IMAGE_MIME_TYPES = map[string]string{".jpg": "image/jpeg", ".gif": "image/gif", ".bmp": "image/bmp", ".png": "image/png", ".tiff": "image/tiff"}
+ IMAGE_EXTENSIONS = [5]string{".jpg", ".jpeg", ".gif", ".bmp", ".png"}
+ IMAGE_MIME_TYPES = map[string]string{".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".bmp": "image/bmp", ".png": "image/png", ".tiff": "image/tiff"}
)
type FileUploadResponse struct {
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/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_upload.jsx b/web/react/components/file_upload.jsx
index b90fa4fd3..c1fab669c 100644
--- a/web/react/components/file_upload.jsx
+++ b/web/react/components/file_upload.jsx
@@ -7,6 +7,13 @@ var ChannelStore = require('../stores/channel_store.jsx');
var utils = require('../utils/utils.jsx');
module.exports = React.createClass({
+ displayName: 'FileUpload',
+ propTypes: {
+ onUploadError: React.PropTypes.func,
+ getFileCount: React.PropTypes.func,
+ onFileUpload: React.PropTypes.func,
+ onUploadStart: React.PropTypes.func
+ },
getInitialState: function() {
return {requests: {}};
},
@@ -21,7 +28,7 @@ module.exports = React.createClass({
// This looks redundant, but must be done this way due to
// setState being an asynchronous call
var numFiles = 0;
- for(var i = 0; i < files.length; i++) {
+ for (var i = 0; i < files.length; i++) {
if (files[i].size <= Constants.MAX_FILE_SIZE) {
numFiles++;
}
@@ -51,11 +58,11 @@ module.exports = React.createClass({
var request = client.uploadFile(formData,
function(data) {
var parsedData = $.parseJSON(data);
- this.props.onFileUpload(parsedData['filenames'], parsedData['client_ids'], channelId);
+ this.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId);
var requests = this.state.requests;
- for (var i = 0; i < parsedData['client_ids'].length; i++) {
- delete requests[parsedData['client_ids'][i]];
+ for (var i = 0; i < parsedData.client_ids.length; i++) {
+ delete requests[parsedData.client_ids[i]];
}
this.setState({requests: requests});
}.bind(this),
@@ -100,12 +107,9 @@ module.exports = React.createClass({
if (items) {
for (var i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) {
- var ext = items[i].type.split('/')[1].toLowerCase();
- if (ext === 'jpeg') {
- ext = 'jpg';
- }
+ var testExt = items[i].type.split('/')[1].toLowerCase();
- if (Constants.IMAGE_TYPES.indexOf(ext) < 0) {
+ if (Constants.IMAGE_TYPES.indexOf(testExt) < 0) {
continue;
}
@@ -113,7 +117,7 @@ module.exports = React.createClass({
}
}
- var numToUpload = Math.min(Constants.MAX_UPLOAD_FILES - self.props.getFileCount(channelId), numItems);
+ var numToUpload = Math.min(Constants.MAX_UPLOAD_FILES - self.props.getFileCount(ChannelStore.getCurrentId()), numItems);
if (numItems > numToUpload) {
self.props.onUploadError('Uploads limited to ' + Constants.MAX_UPLOAD_FILES + ' files maximum. Please use additional posts for more files.');
@@ -124,9 +128,6 @@ module.exports = React.createClass({
var file = items[i].getAsFile();
var ext = items[i].type.split('/')[1].toLowerCase();
- if (ext === 'jpeg') {
- ext = 'jpg';
- }
if (Constants.IMAGE_TYPES.indexOf(ext) < 0) {
continue;
@@ -161,11 +162,11 @@ module.exports = React.createClass({
var request = client.uploadFile(formData,
function(data) {
var parsedData = $.parseJSON(data);
- self.props.onFileUpload(parsedData['filenames'], parsedData['client_ids'], channelId);
+ self.props.onFileUpload(parsedData.filenames, parsedData.client_ids, channelId);
var requests = self.state.requests;
- for (var i = 0; i < parsedData['client_ids'].length; i++) {
- delete requests[parsedData['client_ids'][i]];
+ for (var i = 0; i < parsedData.client_ids.length; i++) {
+ delete requests[parsedData.client_ids[i]];
}
self.setState({requests: requests});
},
diff --git a/web/react/components/invite_member_modal.jsx b/web/react/components/invite_member_modal.jsx
index ef63465c8..5b6924891 100644
--- a/web/react/components/invite_member_modal.jsx
+++ b/web/react/components/invite_member_modal.jsx
@@ -302,7 +302,7 @@ module.exports = React.createClass({
parent_id='invite_member'
title='Discard Invitations?'
message='You have unsent invitations, are you sure you want to discard them?'
- confirm_button='Yes, Discard/'
+ confirm_button='Yes, Discard'
/>
</div>
);
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/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/sidebar.jsx b/web/react/components/sidebar.jsx
index fe73cbcf7..80e3632c7 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,
@@ -157,9 +157,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 +216,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 +284,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 +296,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';
}
diff --git a/web/react/components/view_image.jsx b/web/react/components/view_image.jsx
index dc85b53e5..2b7f64030 100644
--- a/web/react/components/view_image.jsx
+++ b/web/react/components/view_image.jsx
@@ -6,6 +6,13 @@ var utils = require('../utils/utils.jsx');
module.exports = React.createClass({
displayName: 'ViewImageModal',
+ propTypes: {
+ filenames: React.PropTypes.array,
+ modalId: React.PropTypes.string,
+ channelId: React.PropTypes.string,
+ userId: React.PropTypes.string,
+ startId: React.PropTypes.number
+ },
canSetState: false,
handleNext: function() {
var id = this.state.imgId + 1;
@@ -56,8 +63,8 @@ module.exports = React.createClass({
progress[id] = img.completedPercentage;
self.setState({progress: progress});
});
- img.onload = function(imgid) {
- return function() {
+ img.onload = function onload(imgid) {
+ return function onloadReturn() {
var loaded = self.state.loaded;
loaded[imgid] = true;
self.setState({loaded: loaded});
@@ -83,21 +90,21 @@ module.exports = React.createClass({
},
componentDidMount: function() {
var self = this;
- $('#' + this.props.modalId).on('shown.bs.modal', function() {
+ $('#' + this.props.modalId).on('shown.bs.modal', function onModalShow() {
self.setState({viewed: true});
self.loadImage(self.state.imgId);
});
- $(this.refs.modal.getDOMNode()).click(function(e) {
+ $(this.refs.modal.getDOMNode()).click(function onModalClick(e) {
if (e.target === this || e.target === self.refs.imageBody.getDOMNode()) {
$('.image_modal').modal('hide');
}
});
$(this.refs.imageWrap.getDOMNode()).hover(
- function() {
+ function onModalHover() {
$(self.refs.imageFooter.getDOMNode()).addClass('footer--show');
- }, function() {
+ }, function offModalHover() {
$(self.refs.imageFooter.getDOMNode()).removeClass('footer--show');
}
);
@@ -117,10 +124,14 @@ module.exports = React.createClass({
data.user_id = this.props.userId;
data.filename = this.props.filenames[this.state.imgId];
Client.getPublicLink(data,
- function(serverData) {
- window.open(serverData.public_link);
+ function sucess(serverData) {
+ if (utils.isMobile()) {
+ window.location.href = serverData.public_link;
+ } else {
+ window.open(serverData.public_link);
+ }
},
- function() {
+ function error() {
}
);
},
@@ -145,7 +156,7 @@ module.exports = React.createClass({
getInitialState: function() {
var loaded = [];
var progress = [];
- for (var i = 0; i < this.props.filenames.length; i ++) {
+ for (var i = 0; i < this.props.filenames.length; i++) {
loaded.push(false);
progress.push(0);
}
@@ -198,7 +209,7 @@ module.exports = React.createClass({
if (!(filename in this.state.fileSizes)) {
var self = this;
- utils.getFileSize(utils.getFileUrl(filename), function(fileSize) {
+ utils.getFileSize(utils.getFileUrl(filename), function fileSizeOp(fileSize) {
if (self.canSetState) {
var fileSizes = self.state.fileSizes;
fileSizes[filename] = fileSize;
@@ -210,14 +221,20 @@ module.exports = React.createClass({
} else {
// display a progress indicator when the preview for an image is still loading
var percentage = Math.floor(this.state.progress[this.state.imgId]);
- content = (
- <div>
- <img className='loader-image' src='/static/images/load.gif' />
- { percentage > 0 ?
- <span className='loader-percent' >{'Previewing ' + percentage + '%'}</span>
- : ''}
- </div>
- );
+ if (percentage) {
+ content = (
+ <div>
+ <img className='loader-image' src='/static/images/load.gif' />
+ <span className='loader-percent' >{'Previewing ' + percentage + '%'}</span>
+ </div>
+ );
+ } else {
+ content = (
+ <div>
+ <img className='loader-image' src='/static/images/load.gif' />
+ </div>
+ );
+ }
bgClass = 'black-bg';
}
@@ -256,7 +273,7 @@ module.exports = React.createClass({
<div className='modal-close' data-dismiss='modal'></div>
{content}
<div ref='imageFooter' className='modal-button-bar'>
- <span className='pull-left text'>{'Image ' + (this.state.imgId + 1) + ' of ' + this.props.filenames.length}</span>
+ <span className='pull-left text'>{'File ' + (this.state.imgId + 1) + ' of ' + this.props.filenames.length}</span>
<div className='image-links'>
{publicLink}
<a href={fileUrl} download={name} className='text'>Download</a>
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/utils/async_client.jsx b/web/react/utils/async_client.jsx
index afb9a1d79..0b87bbd7b 100644
--- a/web/react/utils/async_client.jsx
+++ b/web/react/utils/async_client.jsx
@@ -15,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();
+
+ if (channels.length === 0 || force) {
+ if (isCallInProgress('getChannels')) {
+ return;
+ }
-module.exports.getChannels = function(force, updateLastViewed, checkVersion) {
- 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;
@@ -119,8 +190,8 @@ module.exports.getMoreChannels = function(force) {
});
},
function(err) {
- callTracker["getMoreChannels"] = 0;
- dispatchError(err, "getMoreChannels");
+ callTracker['getMoreChannels'] = 0;
+ dispatchError(err, 'getMoreChannels');
}
);
}
@@ -130,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;
@@ -148,8 +219,8 @@ module.exports.getChannelExtraInfo = function(force) {
});
},
function(err) {
- callTracker["getChannelExtraInfo_"+channelId] = 0;
- dispatchError(err, "getChannelExtraInfo");
+ callTracker['getChannelExtraInfo_'+channelId] = 0;
+ dispatchError(err, 'getChannelExtraInfo');
}
);
}
@@ -157,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;
@@ -172,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;
@@ -195,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;
@@ -218,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;
@@ -243,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;
@@ -267,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');
}
);
}
@@ -277,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();
@@ -292,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,
@@ -309,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;
@@ -335,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;
@@ -357,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;
@@ -379,8 +453,8 @@ module.exports.getMyTeam = function() {
});
},
function(err) {
- callTracker["getMyTeam"] = 0;
- dispatchError(err, "getMyTeam");
+ callTracker['getMyTeam'] = 0;
+ dispatchError(err, 'getMyTeam');
}
);
}
diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx
index 8178ab01a..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",
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 36c6ab653..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,
@@ -40,7 +41,7 @@ module.exports = {
}),
SPECIAL_MENTIONS: ['all', 'channel'],
CHARACTER_LIMIT: 4000,
- IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png'],
+ IMAGE_TYPES: ['jpg', 'gif', 'bmp', 'png', 'jpeg'],
AUDIO_TYPES: ['mp3', 'wav', 'wma', 'm4a', 'flac', 'aac'],
VIDEO_TYPES: ['mp4', 'avi', 'webm', 'mkv', 'wmv', 'mpg', 'mov', 'flv'],
SPREADSHEET_TYPES: ['ppt', 'pptx', 'csv'],
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 2214b6239..7591c138f 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -732,20 +732,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 +758,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;