From 628813fa23bb7a0b4162e258d76b5b53fb0d7c50 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Wed, 2 Sep 2015 14:28:22 -0400 Subject: Updating dockerfile --- docker/0.7/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/0.7/Dockerfile b/docker/0.7/Dockerfile index 66aac0fa3..b10756ccd 100644 --- a/docker/0.7/Dockerfile +++ b/docker/0.7/Dockerfile @@ -34,7 +34,7 @@ VOLUME /var/lib/mysql WORKDIR /mattermost # Copy over files -ADD https://github.com/mattermost/platform/releases/download/v0.7.0-rc1/mattermost.tar.gz / +ADD https://github.com/mattermost/platform/releases/download/v0.7.0-rc3/mattermost.tar.gz / RUN tar -zxvf /mattermost.tar.gz --strip-components=1 && rm /mattermost.tar.gz ADD config_docker.json / ADD docker-entry.sh / -- cgit v1.2.3-1-g7c22 From 95ac7490307989213b583d4a3a14522c825bed59 Mon Sep 17 00:00:00 2001 From: it33 Date: Thu, 3 Sep 2015 14:40:22 -0700 Subject: Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fc6698208..69336c9fb 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ There are multiple ways to install Mattermost depending on your needs. - [Developer Machine Setup](doc/install/dev-setup.md) - Setup your local machine development environment using Docker on Mac OSX or Ubuntu. -#### Production Deployment +#### Production Deployment (for Beta2 and later) Prior to production installation, please review [Mattermost system requirements](doc/install/requirements.md). -- cgit v1.2.3-1-g7c22 From c5295218f661dd6e84dd7489f499fdc59f228944 Mon Sep 17 00:00:00 2001 From: it33 Date: Thu, 3 Sep 2015 14:40:56 -0700 Subject: Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 69336c9fb..7d7fd4d70 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Please see the [features pages of the Mattermost website](http://www.mattermost. - Attach sound, video and image files from mobile devices - Define team-specific branding and color themes across your devices -#### Enterprise Focused +#### Enterprise Compatible - Self-host Mattermost entirely within your organization's insfrastructure - GitLab Mattermost omnibus supports install to over 100,000 organizations using GitLab -- cgit v1.2.3-1-g7c22 From 8fd7b2b567a473371b9fda1a5e7a2ce63eee23d5 Mon Sep 17 00:00:00 2001 From: it33 Date: Thu, 3 Sep 2015 22:11:30 -0700 Subject: Adding hardware requirements --- doc/install/requirements.md | 57 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index cc0d1833d..fa54e81ef 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -1,8 +1,8 @@ -# Requirements +## Software Requirements -## Web Client +### Web Client -Supported Operating Systems and Browsers: +Supported Operating Systems and Browsers for the Mattermost Web Client include: - PC: Windows 7, Windows 8 (Chrome 43+, Firefox 38+, Internet Explorer 10+) - Mac: OS 10 (Safari 7, Chrome 43+) @@ -10,9 +10,9 @@ Supported Operating Systems and Browsers: - iPhone 4s and higher (Safari on iOS 8.3+, Chrome 43+) - Android 5 and higher (Chrome 43+) -## Server +### Server -While the pre-released version of Mattermost is not currently supported in production, the intention from the product team is to support: +Supported Operating Systems for the Mattermost Server include: - Ubuntu - Debian @@ -21,3 +21,50 @@ While the pre-released version of Mattermost is not currently supported in produ - Oracle Linux The Mattermost roadmap does not currently include production support for Fedora, FreeBSD or Arch Linux. + +## Hardware Requirements + +Mattermost offers both real-time communication and file sharing. CPU and Memory requirements are typically driven by the number of concurrent users using real-time messaging. Storage requirements are typically driven by number and size of files shared. + +The below guidelines offer estimates based on real world usage of Mattermost across industries. + +### CPU + +- 2 cores is the recommended number of cores and supports up to 250 users +- 4 cores supports up to 1,000 users +- 8 cores supports up to 2,500 users +- 16 cores supports up to 5,000 users +- 32 cores supports up to 10,000 users +- 64 cores supports up to 20,000 users + +### Memory + +- 2GB RAM is the recommended memory size and supports up to 50 users +- 4GB RAM supports up to 500 users +- 8GB RAM supports up to 1,000 users +- 16GB RAM supports up to 2,000 users +- 32GB RAM supports up to 4,000 users +- 64GB RAM supports up to 8,000 users +- 128GB RAM supports up to 16,000 users + +### Storage + +To estimate initial storage requirements begin with a Mattermost server approximately 600 MB to 800 MB in size including operating system and database, then add the multiplied product of: + +- Estimated storage per user per month (see below), multipled by 12 months in a year +- Estimated mean average number of users in a year +- A 1-2x safety factor + +**Estimated storage per user per month** + +File usage per user varies significantly across industries. The below benchmarks are recommended: + +- **Low usage teams** (1-5 MB/user/month) - Primarily use text-messages and links to communicate. Examples would include software development teams that heavily use web-based document creation and management tools, and therefore rarely upload files to the server. + +- **Medium usage teams** (5-25 MB/user/month) - Use a mix of text-messages and shared documents and images to communicate. Examples might include business teams that may commonly drag and drop screenshots, PDFs and Microsoft Office documents into Mattermost for sharing and review. + +- **High usage teams** - (25-100 MB/user/month) - Heaviest utlization comes from teams uploading a high number of large files into Mattermost on a regular basis. Examples include creative teams sharing and storing artwork and media with tags and commentary in a pipeline production process. + +*Example:* A 30 person team with medium usage (5-25 MB/user/month) with a safety factor of 2x would require between 300 MB (30 users * 5 MB * 2x safety factor) and 1500 MB (30 users * 25 MB * 2x safety factor) of free space in the next year. + +It's recommended to review storage utilization at least quarterly to ensure adequate free space is available. -- cgit v1.2.3-1-g7c22 From feb2768edb0dbf2e3f08eb74cc7d1aca3caef3dd Mon Sep 17 00:00:00 2001 From: it33 Date: Thu, 3 Sep 2015 22:33:04 -0700 Subject: Update README.md --- doc/README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/doc/README.md b/doc/README.md index baf499777..4926ba2f9 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,13 +1,18 @@ -# Documentation +# Mattermost Documentation ## Administrator Documentation ### Installation -- [Install Requirements](doc/install/requirements.md) -- [Local Machine Setup ](doc/install/single-container-install.md) -- [AWS Elastic Beanstalk Setup](doc/install/aws-ebs-setup.md) +- [Software and Hardware Requirements](install/requirements.md) +- [Local Machine Setup ](install/single-container-install.md) +- [AWS Elastic Beanstalk Setup](install/aws-ebs-setup.md) ### Configuration -- [GitLab SSO Configuration](integrations/sso/gitlab-sso.md) - Configure OAuth2 Single-Sign-On for GitLab. +- [GitLab SSO Configuration](integrations/sso/gitlab-sso.md) +- [SMTP Email Setup](smtp-email-setup.md) + +## Developer Documentation + +- [Mattermost Style Guide](developer/style-guide.md) -- cgit v1.2.3-1-g7c22 From 9f7084611d8f1147ac19a14a6a61c2d1a8ef0a97 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Fri, 4 Sep 2015 09:26:15 -0400 Subject: Make onHeightChange not required. --- web/react/components/textbox.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/react/components/textbox.jsx b/web/react/components/textbox.jsx index b4518fe80..ea8126bec 100644 --- a/web/react/components/textbox.jsx +++ b/web/react/components/textbox.jsx @@ -242,7 +242,7 @@ export default class Textbox extends React.Component { const e = React.findDOMNode(this.refs.message); const w = React.findDOMNode(this.refs.wrapper); - let prevHeight = $(e).height(); + const prevHeight = $(e).height(); const lht = parseInt($(e).css('lineHeight'), 10); const lines = e.scrollHeight / lht; @@ -260,7 +260,7 @@ export default class Textbox extends React.Component { $(w).css({height: 'auto'}).height(167); } - if (prevHeight !== $(e).height()) { + if (prevHeight !== $(e).height() && this.props.onHeightChange) { this.props.onHeightChange(); } } @@ -320,6 +320,6 @@ Textbox.propTypes = { messageText: React.PropTypes.string.isRequired, onUserInput: React.PropTypes.func.isRequired, onKeyPress: React.PropTypes.func.isRequired, - onHeightChange: React.PropTypes.func.isRequired, + onHeightChange: React.PropTypes.func, createMessage: React.PropTypes.string.isRequired }; -- cgit v1.2.3-1-g7c22 From d2353f107d80a7e805f6855c2a579121e16f2cce Mon Sep 17 00:00:00 2001 From: it33 Date: Fri, 4 Sep 2015 07:07:43 -0700 Subject: Refined email setup instructions --- doc/config/smtp-email-setup.md | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/doc/config/smtp-email-setup.md b/doc/config/smtp-email-setup.md index b90d78919..328786ee8 100644 --- a/doc/config/smtp-email-setup.md +++ b/doc/config/smtp-email-setup.md @@ -1,37 +1,40 @@ ## SMTP Email Setup -The following instructions maybe used when SMTP email is not setup as part of the installation process. +In some product evaluation setups email is intentionally bypassed using a `ByPassEmail=true` option. This option allows account creation and system operation without having to set up an email service (email verification is bypassed). -1. Setup an email sending service. If you already have credentials for a SMTP server you can skip this step. +To enable email, turn this option off by setting `ByPassEmail=false` and configuring an SMTP email service as follows: + +1. **Set up an SMTP email sending service.** (If you already have credentials for a SMTP server you can skip this step.) 1. [Setup Amazon Simple Email Service](https://console.aws.amazon.com/ses) 2. From the `SMTP Settings` menu click `Create My SMTP Credentials` 3. Copy the `Server Name`, `Port`, `SMTP Username`, and `SMTP Password` 4. From the `Domains` menu setup and verify a new domain. It it also a good practice to enable `Generate DKIM Settings` for this domain. 5. Choose an email address like `feedback@example.com` for Mattermost to send emails from. 6. Test sending an email from `feedback@example.com` by clicking the `Send a Test Email` button and verify everything appears to be working correctly. -2. Modify the Mattermost configuration file config.json or config_docker.json with the SMTP information. +2. **Modify the Mattermost configuration file config.json or config_docker.json with the SMTP information.** 1. If you're running Mattermost on Amazon Beanstalk you can shell into the instance with the following commands 2. `ssh ec2-user@[domain for the docker instance]` 3. `sudo gpasswd -a ec2-user docker` 4. Retrieve the name of the container with `sudo docker ps` 5. `sudo docker exec -ti container_name /bin/bash` -3. Edit the config file `vi /config_docker.json` with the settings you captured from the step above. See an example below and notice `ByPassEmail` has been set to `false` -``` bash -"EmailSettings": { - "ByPassEmail" : false, - "SMTPUsername": "AKIADTOVBGERKLCBV", - "SMTPPassword": "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY", - "SMTPServer": "email-smtp.us-east-1.amazonaws.com:465", - "UseTLS": true, - "FeedbackEmail": "feedback@example.com", - "FeedbackName": "Feedback", - "ApplePushServer": "", - "ApplePushCertPublic": "", - "ApplePushCertPrivate": "" -} -``` -4. Restart Mattermost +3. **Edit the config file `vi /config_docker.json` with the settings you captured from the step above.** + 1. See an example below and notice `ByPassEmail` has been set to `false` + ``` bash + "EmailSettings": { + "ByPassEmail" : false, + "SMTPUsername": "AKIADTOVBGERKLCBV", + "SMTPPassword": "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY", + "SMTPServer": "email-smtp.us-east-1.amazonaws.com:465", + "UseTLS": true, + "FeedbackEmail": "feedback@example.com", + "FeedbackName": "Feedback", + "ApplePushServer": "", + "ApplePushCertPublic": "", + "ApplePushCertPrivate": "" + } + ``` +4. **Restart Mattermost** 1. Find the process id with `ps -A` and look for the process named `platform` 2. Kill the process `kill pid` 3. The service should restart automatically. Verify the Mattermost service is running with `ps -A` -- cgit v1.2.3-1-g7c22 From e9d1eb3485bff1a952c811143bafa3b90d6f9319 Mon Sep 17 00:00:00 2001 From: it33 Date: Fri, 4 Sep 2015 07:20:15 -0700 Subject: Clarifying purpose of ByPassEmail option --- doc/config/smtp-email-setup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/config/smtp-email-setup.md b/doc/config/smtp-email-setup.md index 328786ee8..86e2bb20e 100644 --- a/doc/config/smtp-email-setup.md +++ b/doc/config/smtp-email-setup.md @@ -1,7 +1,7 @@ ## SMTP Email Setup -In some product evaluation setups email is intentionally bypassed using a `ByPassEmail=true` option. This option allows account creation and system operation without having to set up an email service (email verification is bypassed). +In some product evaluation setups email is intentionally bypassed using a `ByPassEmail=true` option. This option allows account creation and system operation without having to set up an email service (e.g. no email verification is required for account creation). This also means neither email notifications nor password reset by email are available. To enable email, turn this option off by setting `ByPassEmail=false` and configuring an SMTP email service as follows: -- cgit v1.2.3-1-g7c22 From 6640ee7d42eec0705fc1bfb2608b079ff1e85aea Mon Sep 17 00:00:00 2001 From: it33 Date: Fri, 4 Sep 2015 07:34:18 -0700 Subject: Added developer machine setup link --- doc/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/README.md b/doc/README.md index 4926ba2f9..ac00dc976 100644 --- a/doc/README.md +++ b/doc/README.md @@ -7,6 +7,7 @@ - [Software and Hardware Requirements](install/requirements.md) - [Local Machine Setup ](install/single-container-install.md) - [AWS Elastic Beanstalk Setup](install/aws-ebs-setup.md) +- [Developer Machine Setup](doc/install/dev-setup.md) ### Configuration @@ -15,4 +16,5 @@ ## Developer Documentation +- [Developer Machine Setup](doc/install/dev-setup.md) - [Mattermost Style Guide](developer/style-guide.md) -- cgit v1.2.3-1-g7c22 From 83889dcf7a3b5db6ff7491a3e71ca289cfbec849 Mon Sep 17 00:00:00 2001 From: it33 Date: Fri, 4 Sep 2015 07:47:32 -0700 Subject: Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d7fd4d70..9f6b46d5f 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,6 @@ There are multiple ways to install Mattermost depending on your needs. Prior to production installation, please review [Mattermost system requirements](doc/install/requirements.md). -- [GitLab Mattermost Production Installation](https://about.gitlab.com/downloads/) - Install Mattermost for production environments bundled with GitLab, a leading open source Git repository, using an omnibus package for Ubuntu 12.04, Ubuntu 14.04, Debian 7, Debian 8, and CentOS 6 (and RedHat/Oracle/Scientific Linux 6), CentOS 7 (and RedHat/Oracle/Scientific Linux 7). +- [GitLab Mattermost Production Installation](https://gitlab.com/gitlab-org/gitlab-mattermost) - Install Mattermost for production environments bundled with GitLab, a leading open source Git repository, using an omnibus package for Ubuntu 12.04, Ubuntu 14.04, Debian 7, Debian 8, and CentOS 6 (and RedHat/Oracle/Scientific Linux 6), CentOS 7 (and RedHat/Oracle/Scientific Linux 7). For technical questions and answers, please visit the [Mattermost forum](http://forum.mattermost.org). -- cgit v1.2.3-1-g7c22 From f0fd9a9e8b85544089246bde71b6d61ba90eeb0e Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Wed, 26 Aug 2015 12:49:07 -0400 Subject: Adding ability to export data from mattermost --- api/export.go | 292 +++++++++++++++++++++++++++ api/file.go | 42 ++++ api/team.go | 21 ++ model/channel.go | 3 + model/post.go | 3 + model/team.go | 3 + model/user.go | 10 + store/sql_channel_store.go | 22 ++ store/sql_post_store.go | 24 +++ store/sql_team_store.go | 20 ++ store/sql_user_store.go | 27 +++ store/store.go | 4 + web/react/components/team_export_tab.jsx | 96 +++++++++ web/react/components/team_settings.jsx | 8 + web/react/components/team_settings_modal.jsx | 1 + web/react/utils/client.jsx | 13 ++ 16 files changed, 589 insertions(+) create mode 100644 api/export.go create mode 100644 web/react/components/team_export_tab.jsx diff --git a/api/export.go b/api/export.go new file mode 100644 index 000000000..9345f892f --- /dev/null +++ b/api/export.go @@ -0,0 +1,292 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +package api + +import ( + "archive/zip" + "encoding/json" + "github.com/mattermost/platform/model" + "github.com/mattermost/platform/utils" + "io" + "os" +) + +const ( + EXPORT_PATH = "export/" + EXPORT_FILENAME = "MattermostExport.zip" + EXPORT_OPTIONS_FILE = "options.json" + EXPORT_TEAMS_FOLDER = "teams" + EXPORT_CHANNELS_FOLDER = "channels" + EXPORT_CHANNEL_MEMBERS_FOLDER = "members" + EXPORT_POSTS_FOLDER = "posts" + EXPORT_USERS_FOLDER = "users" + EXPORT_LOCAL_STORAGE_FOLDER = "files" +) + +type ExportWriter interface { + Create(name string) (io.Writer, error) +} + +type ExportOptions struct { + TeamsToExport []string `json:"teams"` + ChannelsToExport []string `json:"channels"` + UsersToExport []string `json:"users"` + ExportLocalStorage bool `json:"export_local_storage"` +} + +func (options *ExportOptions) ToJson() string { + b, err := json.Marshal(options) + if err != nil { + return "" + } else { + return string(b) + } +} + +func ExportOptionsFromJson(data io.Reader) *ExportOptions { + decoder := json.NewDecoder(data) + var o ExportOptions + decoder.Decode(&o) + return &o +} + +func ExportToFile(options *ExportOptions) (link string, err *model.AppError) { + // Open file for export + if file, err := openFileWriteStream(EXPORT_PATH + EXPORT_FILENAME); err != nil { + return "", err + } else { + defer closeFileWriteStream(file) + ExportToWriter(file, options) + } + + return "/api/v1/files/get_export", nil +} + +func ExportToWriter(w io.Writer, options *ExportOptions) *model.AppError { + // Open a writer to write to zip file + zipWriter := zip.NewWriter(w) + defer zipWriter.Close() + + // Write our options to file + if optionsFile, err := zipWriter.Create(EXPORT_OPTIONS_FILE); err != nil { + return model.NewAppError("ExportToWriter", "Unable to create options file", err.Error()) + } else { + if _, err := optionsFile.Write([]byte(options.ToJson())); err != nil { + return model.NewAppError("ExportToWriter", "Unable to write to options file", err.Error()) + } + } + + // Export Teams + ExportTeams(zipWriter, options) + + return nil +} + +func ExportTeams(writer ExportWriter, options *ExportOptions) *model.AppError { + // Get the teams + var teams []*model.Team + if len(options.TeamsToExport) == 0 { + if result := <-Srv.Store.Team().GetForExport(); result.Err != nil { + return result.Err + } else { + teams = result.Data.([]*model.Team) + } + } else { + for _, teamId := range options.TeamsToExport { + if result := <-Srv.Store.Team().Get(teamId); result.Err != nil { + return result.Err + } else { + team := result.Data.(*model.Team) + teams = append(teams, team) + } + } + } + + // Export the teams + for i := range teams { + // Sanitize + teams[i].PreExport() + + if teamFile, err := writer.Create(EXPORT_TEAMS_FOLDER + "/" + teams[i].Name + ".json"); err != nil { + return model.NewAppError("ExportTeams", "Unable to open file for export", err.Error()) + } else { + if _, err := teamFile.Write([]byte(teams[i].ToJson())); err != nil { + return model.NewAppError("ExportTeams", "Unable to write to team export file", err.Error()) + } + } + + } + + // Export the channels, local storage and users + for _, team := range teams { + if err := ExportChannels(writer, options, team.Id); err != nil { + return err + } + if err := ExportUsers(writer, options, team.Id); err != nil { + return err + } + if err := ExportLocalStorage(writer, options, team.Id); err != nil { + return err + } + } + + return nil +} + +func ExportChannels(writer ExportWriter, options *ExportOptions, teamId string) *model.AppError { + // Get the channels + var channels []*model.Channel + if len(options.ChannelsToExport) == 0 { + if result := <-Srv.Store.Channel().GetForExport(teamId); result.Err != nil { + return result.Err + } else { + channels = result.Data.([]*model.Channel) + } + } else { + for _, channelId := range options.ChannelsToExport { + if result := <-Srv.Store.Channel().Get(channelId); result.Err != nil { + return result.Err + } else { + channel := result.Data.(*model.Channel) + channels = append(channels, channel) + } + } + } + + for i := range channels { + // Get members + mchan := Srv.Store.Channel().GetMembers(channels[i].Id) + + // Sanitize + channels[i].PreExport() + + if channelFile, err := writer.Create(EXPORT_CHANNELS_FOLDER + "/" + channels[i].Id + ".json"); err != nil { + return model.NewAppError("ExportChannels", "Unable to open file for export", err.Error()) + } else { + if _, err := channelFile.Write([]byte(channels[i].ToJson())); err != nil { + return model.NewAppError("ExportChannels", "Unable to write to export file", err.Error()) + } + } + + var members []model.ChannelMember + if result := <-mchan; result.Err != nil { + return result.Err + } else { + members = result.Data.([]model.ChannelMember) + } + + if membersFile, err := writer.Create(EXPORT_CHANNELS_FOLDER + "/" + channels[i].Id + "_members.json"); err != nil { + return model.NewAppError("ExportChannels", "Unable to open file for export", err.Error()) + } else { + result, err2 := json.Marshal(members) + if err2 != nil { + return model.NewAppError("ExportChannels", "Unable to convert to json", err.Error()) + } + if _, err3 := membersFile.Write([]byte(result)); err3 != nil { + return model.NewAppError("ExportChannels", "Unable to write to export file", err.Error()) + } + } + } + + for _, channel := range channels { + if err := ExportPosts(writer, options, channel.Id); err != nil { + return err + } + } + + return nil +} + +func ExportPosts(writer ExportWriter, options *ExportOptions, channelId string) *model.AppError { + // Get the posts + var posts []*model.Post + if result := <-Srv.Store.Post().GetForExport(channelId); result.Err != nil { + return result.Err + } else { + posts = result.Data.([]*model.Post) + } + + // Export the posts + if postsFile, err := writer.Create(EXPORT_POSTS_FOLDER + "/" + channelId + "_posts.json"); err != nil { + return model.NewAppError("ExportPosts", "Unable to open file for export", err.Error()) + } else { + result, err2 := json.Marshal(posts) + if err2 != nil { + return model.NewAppError("ExportPosts", "Unable to convert to json", err.Error()) + } + if _, err3 := postsFile.Write([]byte(result)); err3 != nil { + return model.NewAppError("ExportPosts", "Unable to write to export file", err.Error()) + } + } + + return nil +} + +func ExportUsers(writer ExportWriter, options *ExportOptions, teamId string) *model.AppError { + // Get the users + var users []*model.User + if result := <-Srv.Store.User().GetForExport(teamId); result.Err != nil { + return result.Err + } else { + users = result.Data.([]*model.User) + } + + // Write the users + if usersFile, err := writer.Create(EXPORT_USERS_FOLDER + "/" + teamId + "_users.json"); err != nil { + return model.NewAppError("ExportUsers", "Unable to open file for export", err.Error()) + } else { + result, err2 := json.Marshal(users) + if err2 != nil { + return model.NewAppError("ExportUsers", "Unable to convert to json", err.Error()) + } + if _, err3 := usersFile.Write([]byte(result)); err3 != nil { + return model.NewAppError("ExportUsers", "Unable to write to export file", err.Error()) + } + } + return nil +} + +func copyDirToExportWriter(writer ExportWriter, inPath string, outPath string) *model.AppError { + dir, err := os.Open(inPath) + if err != nil { + return model.NewAppError("copyDirToExportWriter", "Unable to open directory", err.Error()) + } + + fileInfoList, err := dir.Readdir(0) + if err != nil { + return model.NewAppError("copyDirToExportWriter", "Unable to read directory", err.Error()) + } + + for _, fileInfo := range fileInfoList { + if fileInfo.IsDir() { + copyDirToExportWriter(writer, inPath+"/"+fileInfo.Name(), outPath+"/"+fileInfo.Name()) + } else { + if toFile, err := writer.Create(outPath + "/" + fileInfo.Name()); err != nil { + return model.NewAppError("copyDirToExportWriter", "Unable to open file for export", err.Error()) + } else { + fromFile, err := os.Open(inPath + "/" + fileInfo.Name()) + if err != nil { + return model.NewAppError("copyDirToExportWriter", "Unable to open file", err.Error()) + } + io.Copy(toFile, fromFile) + } + } + } + + return nil +} + +func ExportLocalStorage(writer ExportWriter, options *ExportOptions, teamId string) *model.AppError { + teamDir := utils.Cfg.ServiceSettings.StorageDirectory + "teams/" + teamId + + if utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage { + return model.NewAppError("ExportLocalStorage", "S3 is not supported for local storage export.", "") + } else if utils.Cfg.ServiceSettings.UseLocalStorage && len(utils.Cfg.ServiceSettings.StorageDirectory) > 0 { + if err := copyDirToExportWriter(writer, teamDir, EXPORT_LOCAL_STORAGE_FOLDER); err != nil { + return err + } + } + + return nil +} diff --git a/api/file.go b/api/file.go index 800c512c5..1d8244fac 100644 --- a/api/file.go +++ b/api/file.go @@ -40,6 +40,7 @@ func InitFile(r *mux.Router) { sr.Handle("/get/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:([A-Za-z0-9]+/)?.+(\\.[A-Za-z0-9]{3,})?}", ApiAppHandler(getFile)).Methods("GET") sr.Handle("/get_info/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:([A-Za-z0-9]+/)?.+(\\.[A-Za-z0-9]{3,})?}", ApiAppHandler(getFileInfo)).Methods("GET") sr.Handle("/get_public_link", ApiUserRequired(getPublicLink)).Methods("POST") + sr.Handle("/get_export", ApiUserRequired(getExport)).Methods("GET") } func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) { @@ -414,6 +415,23 @@ func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.MapToJson(rData))) } +func getExport(c *Context, w http.ResponseWriter, r *http.Request) { + if !c.HasPermissionsToTeam(c.Session.TeamId, "export") || !c.IsTeamAdmin(c.Session.UserId) { + c.Err = model.NewAppError("getExport", "Only a team admin can retrieve exported data.", "userId="+c.Session.UserId) + c.Err.StatusCode = http.StatusForbidden + return + } + data, err := readFile(EXPORT_PATH + EXPORT_FILENAME) + if err != nil { + c.Err = model.NewAppError("getExport", "Unable to retrieve exported file. Please re-export", err.Error()) + return + } + + w.Header().Set("Content-Disposition", "attachment; filename="+EXPORT_FILENAME) + w.Header().Set("Content-Type", "application/octet-stream") + w.Write(data) +} + func writeFile(f []byte, path string) *model.AppError { if utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage { @@ -488,3 +506,27 @@ func readFile(path string) ([]byte, *model.AppError) { return nil, model.NewAppError("readFile", "File storage not configured properly. Please configure for either S3 or local server file storage.", "") } } + +func openFileWriteStream(path string) (io.Writer, *model.AppError) { + if utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage { + return nil, model.NewAppError("openFileWriteStream", "S3 is not supported.", "") + } else if utils.Cfg.ServiceSettings.UseLocalStorage && len(utils.Cfg.ServiceSettings.StorageDirectory) > 0 { + if err := os.MkdirAll(filepath.Dir(utils.Cfg.ServiceSettings.StorageDirectory+path), 0774); err != nil { + return nil, model.NewAppError("openFileWriteStream", "Encountered an error creating the directory for the new file", err.Error()) + } + + if fileHandle, err := os.Create(utils.Cfg.ServiceSettings.StorageDirectory + path); err != nil { + return nil, model.NewAppError("openFileWriteStream", "Encountered an error writing to local server storage", err.Error()) + } else { + fileHandle.Chmod(0644) + return fileHandle, nil + } + + } + + return nil, model.NewAppError("openFileWriteStream", "File storage not configured properly. Please configure for either S3 or local server file storage.", "") +} + +func closeFileWriteStream(file io.Writer) { + file.(*os.File).Close() +} diff --git a/api/team.go b/api/team.go index 8cce384c3..e1b3b274a 100644 --- a/api/team.go +++ b/api/team.go @@ -32,7 +32,9 @@ func InitTeam(r *mux.Router) { sr.Handle("/update_name", ApiUserRequired(updateTeamDisplayName)).Methods("POST") sr.Handle("/update_valet_feature", ApiUserRequired(updateValetFeature)).Methods("POST") sr.Handle("/me", ApiUserRequired(getMyTeam)).Methods("GET") + // These should be moved to the global admain console sr.Handle("/import_team", ApiUserRequired(importTeam)).Methods("POST") + sr.Handle("/export_team", ApiUserRequired(exportTeam)).Methods("GET") } func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) { @@ -675,3 +677,22 @@ func importTeam(c *Context, w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/octet-stream") http.ServeContent(w, r, "MattermostImportLog.txt", time.Now(), bytes.NewReader(log.Bytes())) } + +func exportTeam(c *Context, w http.ResponseWriter, r *http.Request) { + if !c.HasPermissionsToTeam(c.Session.TeamId, "export") || !c.IsTeamAdmin(c.Session.UserId) { + c.Err = model.NewAppError("exportTeam", "Only a team admin can export data.", "userId="+c.Session.UserId) + c.Err.StatusCode = http.StatusForbidden + return + } + + options := ExportOptionsFromJson(r.Body) + + if link, err := ExportToFile(options); err != nil { + c.Err = err + return + } else { + result := map[string]string{} + result["link"] = link + w.Write([]byte(model.MapToJson(result))) + } +} diff --git a/model/channel.go b/model/channel.go index b46f79f75..7d8edeee7 100644 --- a/model/channel.go +++ b/model/channel.go @@ -117,3 +117,6 @@ func (o *Channel) PreUpdate() { func (o *Channel) ExtraUpdated() { o.ExtraUpdateAt = GetMillis() } + +func (o *Channel) PreExport() { +} diff --git a/model/post.go b/model/post.go index 0c035d4e7..e78469940 100644 --- a/model/post.go +++ b/model/post.go @@ -147,3 +147,6 @@ func (o *Post) AddProp(key string, value string) { o.Props[key] = value } + +func (o *Post) PreExport() { +} diff --git a/model/team.go b/model/team.go index 95e2757c8..6006f738c 100644 --- a/model/team.go +++ b/model/team.go @@ -197,3 +197,6 @@ func CleanTeamName(s string) string { return s } + +func (o *Team) PreExport() { +} diff --git a/model/user.go b/model/user.go index d82f96db3..9f90b8204 100644 --- a/model/user.go +++ b/model/user.go @@ -272,6 +272,16 @@ func (u *User) GetDisplayName() string { } } +func (u *User) PreExport() { + u.Password = "" + u.AuthData = "" + u.LastActivityAt = 0 + u.LastPingAt = 0 + u.LastPasswordUpdate = 0 + u.LastPictureUpdate = 0 + u.FailedAttempts = 0 +} + // UserFromJson will decode the input and return a User func UserFromJson(data io.Reader) *User { decoder := json.NewDecoder(data) diff --git a/store/sql_channel_store.go b/store/sql_channel_store.go index d2e3943df..b58166fd6 100644 --- a/store/sql_channel_store.go +++ b/store/sql_channel_store.go @@ -678,3 +678,25 @@ func (s SqlChannelStore) UpdateNotifyLevel(channelId, userId, notifyLevel string return storeChannel } + +func (s SqlChannelStore) GetForExport(teamId string) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + var data []*model.Channel + _, err := s.GetReplica().Select(&data, "SELECT * FROM Channels WHERE TeamId = :TeamId AND DeleteAt = 0 AND Type = 'O'", map[string]interface{}{"TeamId": teamId}) + + if err != nil { + result.Err = model.NewAppError("SqlChannelStore.GetAllChannels", "We couldn't get all the channels", "teamId="+teamId+", err="+err.Error()) + } else { + result.Data = data + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} diff --git a/store/sql_post_store.go b/store/sql_post_store.go index 297d60397..20de23eb7 100644 --- a/store/sql_post_store.go +++ b/store/sql_post_store.go @@ -506,3 +506,27 @@ func (s SqlPostStore) Search(teamId string, userId string, terms string, isHasht return storeChannel } + +func (s SqlPostStore) GetForExport(channelId string) StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + var posts []*model.Post + _, err := s.GetReplica().Select( + &posts, + "SELECT * FROM Posts WHERE ChannelId = :ChannelId AND DeleteAt = 0", + map[string]interface{}{"ChannelId": channelId}) + if err != nil { + result.Err = model.NewAppError("SqlPostStore.GetForExport", "We couldn't get the posts for the channel", "channelId="+channelId+err.Error()) + } else { + result.Data = posts + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} diff --git a/store/sql_team_store.go b/store/sql_team_store.go index 2784f8630..fcbcaab9f 100644 --- a/store/sql_team_store.go +++ b/store/sql_team_store.go @@ -193,3 +193,23 @@ func (s SqlTeamStore) GetTeamsForEmail(email string) StoreChannel { return storeChannel } + +func (s SqlTeamStore) GetForExport() StoreChannel { + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + var data []*model.Team + if _, err := s.GetReplica().Select(&data, "SELECT * FROM Teams"); err != nil { + result.Err = model.NewAppError("SqlTeamStore.GetForExport", "We could not get all teams", err.Error()) + } + + result.Data = data + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} diff --git a/store/sql_user_store.go b/store/sql_user_store.go index 64a18545a..be1d29df0 100644 --- a/store/sql_user_store.go +++ b/store/sql_user_store.go @@ -452,3 +452,30 @@ func (us SqlUserStore) VerifyEmail(userId string) StoreChannel { return storeChannel } + +func (us SqlUserStore) GetForExport(teamId string) StoreChannel { + + storeChannel := make(StoreChannel) + + go func() { + result := StoreResult{} + + var users []*model.User + + if _, err := us.GetReplica().Select(&users, "SELECT * FROM Users WHERE TeamId = :TeamId", map[string]interface{}{"TeamId": teamId}); err != nil { + result.Err = model.NewAppError("SqlUserStore.GetProfiles", "We encounted an error while finding user profiles", err.Error()) + } else { + for _, u := range users { + u.Password = "" + u.AuthData = "" + } + + result.Data = users + } + + storeChannel <- result + close(storeChannel) + }() + + return storeChannel +} diff --git a/store/store.go b/store/store.go index 271caa366..959e93fa4 100644 --- a/store/store.go +++ b/store/store.go @@ -44,6 +44,7 @@ type TeamStore interface { Get(id string) StoreChannel GetByName(name string) StoreChannel GetTeamsForEmail(domain string) StoreChannel + GetForExport() StoreChannel } type ChannelStore interface { @@ -55,6 +56,7 @@ type ChannelStore interface { GetChannels(teamId string, userId string) StoreChannel GetMoreChannels(teamId string, userId string) StoreChannel GetChannelCounts(teamId string, userId string) StoreChannel + GetForExport(teamId string) StoreChannel SaveMember(member *model.ChannelMember) StoreChannel GetMembers(channelId string) StoreChannel @@ -78,6 +80,7 @@ type PostStore interface { GetPostsSince(channelId string, time int64) StoreChannel GetEtag(channelId string) StoreChannel Search(teamId string, userId string, terms string, isHashtagSearch bool) StoreChannel + GetForExport(channelId string) StoreChannel } type UserStore interface { @@ -96,6 +99,7 @@ type UserStore interface { VerifyEmail(userId string) StoreChannel GetEtagForProfiles(teamId string) StoreChannel UpdateFailedPasswordAttempts(userId string, attempts int) StoreChannel + GetForExport(teamId string) StoreChannel } type SessionStore interface { diff --git a/web/react/components/team_export_tab.jsx b/web/react/components/team_export_tab.jsx new file mode 100644 index 000000000..1bc5abdb1 --- /dev/null +++ b/web/react/components/team_export_tab.jsx @@ -0,0 +1,96 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +var Client = require('../utils/client.jsx'); + +export default class TeamExportTab extends React.Component { + constructor(props) { + super(props); + this.state = {status: 'request', link: '', err: ''}; + + this.onExportSuccess = this.onExportSuccess.bind(this); + this.onExportFailure = this.onExportFailure.bind(this); + this.doExport = this.doExport.bind(this); + } + onExportSuccess(data) { + this.setState({status: 'ready', link: data.link, err: ''}); + } + onExportFailure(e) { + this.setState({status: 'failure', link: '', err: e.message}); + } + doExport() { + if (this.state.status === 'in-progress') { + return; + } + this.setState({status: 'in-progress'}); + Client.exportTeam(this.onExportSuccess, this.onExportFailure); + } + render() { + var messageSection = ''; + switch (this.state.status) { + case 'request': + messageSection = ''; + break; + case 'in-progress': + messageSection = ( +

+ + {' Exporting...'} +

+ ); + break; + case 'ready': + messageSection = ( +

+ + {' Ready for '} + + {'download'} + +

+ ); + break; + case 'failure': + messageSection = ( +

+ + {' Unable to export: ' + this.state.err} +

+ ); + break; + } + + return ( +
+

{'Export'}

+
+ +
+ {messageSection} +
+ ); + } +} diff --git a/web/react/components/team_settings.jsx b/web/react/components/team_settings.jsx index 53855fe1c..396521af9 100644 --- a/web/react/components/team_settings.jsx +++ b/web/react/components/team_settings.jsx @@ -3,6 +3,7 @@ var TeamStore = require('../stores/team_store.jsx'); var ImportTab = require('./team_import_tab.jsx'); +var ExportTab = require('./team_export_tab.jsx'); var FeatureTab = require('./team_feature_tab.jsx'); var GeneralTab = require('./team_general_tab.jsx'); var Utils = require('../utils/utils.jsx'); @@ -64,6 +65,13 @@ export default class TeamSettings extends React.Component {
); break; + case 'export': + result = ( +
+ +
+ ); + break; default: result = (
diff --git a/web/react/components/team_settings_modal.jsx b/web/react/components/team_settings_modal.jsx index 668bf76cf..0513c811f 100644 --- a/web/react/components/team_settings_modal.jsx +++ b/web/react/components/team_settings_modal.jsx @@ -36,6 +36,7 @@ export default class TeamSettingsModal extends React.Component { let tabs = []; tabs.push({name: 'general', uiName: 'General', icon: 'glyphicon glyphicon-cog'}); tabs.push({name: 'import', uiName: 'Import', icon: 'glyphicon glyphicon-upload'}); + tabs.push({name: 'export', uiName: 'Export', icon: 'glyphicon glyphicon-download'}); tabs.push({name: 'feature', uiName: 'Advanced', icon: 'glyphicon glyphicon-wrench'}); return ( diff --git a/web/react/utils/client.jsx b/web/react/utils/client.jsx index 10f9c0b37..51fd16474 100644 --- a/web/react/utils/client.jsx +++ b/web/react/utils/client.jsx @@ -919,6 +919,19 @@ export function importSlack(fileData, success, error) { }); } +export function exportTeam(success, error) { + $.ajax({ + url: '/api/v1/teams/export_team', + type: 'GET', + dataType: 'json', + success: success, + error: function onError(xhr, status, err) { + var e = handleError('exportTeam', xhr, status, err); + error(e); + } + }); +} + export function getStatuses(success, error) { $.ajax({ url: '/api/v1/users/status', -- cgit v1.2.3-1-g7c22 From 228ee859ffacc73835c92ea51432beca856bb21e Mon Sep 17 00:00:00 2001 From: nickago Date: Thu, 3 Sep 2015 12:53:16 -0700 Subject: Added help text to username, nickname, and fullname --- web/react/components/user_settings.jsx | 3 ++- web/react/components/user_settings_general.jsx | 34 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/web/react/components/user_settings.jsx b/web/react/components/user_settings.jsx index 282fb7681..2a607b3e0 100644 --- a/web/react/components/user_settings.jsx +++ b/web/react/components/user_settings.jsx @@ -40,6 +40,7 @@ export default class UserSettings extends React.Component { user={this.state.user} activeSection={this.props.activeSection} updateSection={this.props.updateSection} + updateTab={this.props.updateTab} />
); @@ -86,4 +87,4 @@ UserSettings.propTypes = { activeSection: React.PropTypes.string, updateSection: React.PropTypes.func, updateTab: React.PropTypes.func -}; \ No newline at end of file +}; diff --git a/web/react/components/user_settings_general.jsx b/web/react/components/user_settings_general.jsx index ead7ac1d5..f43889f7a 100644 --- a/web/react/components/user_settings_general.jsx +++ b/web/react/components/user_settings_general.jsx @@ -267,6 +267,27 @@ export default class UserSettingsGeneralTab extends React.Component {
); + function notifClick(e) { + e.preventDefault(); + this.updateSection(''); + this.props.updateTab('notifications'); + } + + let notifLink = ( + + Notifications + + ); + + let extraInfo = ( + + By default, you will receive mention notifications when someone types your first name. + Go to {notifLink} settings to change this default. + + ); + nameSection = ( ); } else { @@ -326,6 +348,13 @@ export default class UserSettingsGeneralTab extends React.Component { ); + let extraInfo = ( + + Use Nickname for a name you might be called that is different from your first name and user name. + This is most often used when two or more people have similar sounding names and usernames. + + ); + nicknameSection = ( ); } else { @@ -375,6 +405,8 @@ export default class UserSettingsGeneralTab extends React.Component { ); + let extraInfo = (Pick something easy for teammates to recognize and recall.); + usernameSection = ( ); } else { @@ -524,5 +557,6 @@ export default class UserSettingsGeneralTab extends React.Component { UserSettingsGeneralTab.propTypes = { user: React.PropTypes.object, updateSection: React.PropTypes.func, + updateTab: React.PropTypes.func, activeSection: React.PropTypes.string }; -- cgit v1.2.3-1-g7c22 From 18807dac42badce57db4a13be5ace2ba2093e8aa Mon Sep 17 00:00:00 2001 From: nickago Date: Fri, 4 Sep 2015 08:36:50 -0700 Subject: Added refactoring to pass style check --- web/react/components/user_settings_general.jsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/web/react/components/user_settings_general.jsx b/web/react/components/user_settings_general.jsx index f43889f7a..f2127ce0c 100644 --- a/web/react/components/user_settings_general.jsx +++ b/web/react/components/user_settings_general.jsx @@ -273,15 +273,16 @@ export default class UserSettingsGeneralTab extends React.Component { this.props.updateTab('notifications'); } - let notifLink = ( + const notifLink = ( + onClick={notifClick.bind(this)} + > Notifications ); - let extraInfo = ( + const extraInfo = ( By default, you will receive mention notifications when someone types your first name. Go to {notifLink} settings to change this default. @@ -348,7 +349,7 @@ export default class UserSettingsGeneralTab extends React.Component { ); - let extraInfo = ( + const extraInfo = ( Use Nickname for a name you might be called that is different from your first name and user name. This is most often used when two or more people have similar sounding names and usernames. @@ -405,7 +406,7 @@ export default class UserSettingsGeneralTab extends React.Component { ); - let extraInfo = (Pick something easy for teammates to recognize and recall.); + const extraInfo = (Pick something easy for teammates to recognize and recall.); usernameSection = ( Date: Fri, 4 Sep 2015 12:34:55 -0400 Subject: Fixed @mentions containing uppercase characters --- web/react/utils/utils.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx index ea42256aa..71cd1d344 100644 --- a/web/react/utils/utils.jsx +++ b/web/react/utils/utils.jsx @@ -498,7 +498,7 @@ export function textToJsx(textin, options) { // do both a non-case sensitive and case senstive check let mClass = ''; - if (('@' + name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@' + name) !== -1) { + if (implicitKeywords.indexOf('@' + name.toLowerCase()) !== -1 || implicitKeywords.indexOf('@' + name) !== -1) { mClass = mentionClass; } -- cgit v1.2.3-1-g7c22 From 842cdf0296f6e0610c45467e8d83ddaa51ef23d5 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Fri, 4 Sep 2015 14:33:18 -0400 Subject: Updating dockerfile for release v0.7.0 --- docker/0.7/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/0.7/Dockerfile b/docker/0.7/Dockerfile index b10756ccd..4a8623d33 100644 --- a/docker/0.7/Dockerfile +++ b/docker/0.7/Dockerfile @@ -34,7 +34,7 @@ VOLUME /var/lib/mysql WORKDIR /mattermost # Copy over files -ADD https://github.com/mattermost/platform/releases/download/v0.7.0-rc3/mattermost.tar.gz / +ADD https://github.com/mattermost/platform/releases/download/v0.7.0/mattermost.tar.gz / RUN tar -zxvf /mattermost.tar.gz --strip-components=1 && rm /mattermost.tar.gz ADD config_docker.json / ADD docker-entry.sh / -- cgit v1.2.3-1-g7c22 From 82e04bf2950636cababc5e396024285bae4d6e21 Mon Sep 17 00:00:00 2001 From: it33 Date: Sat, 5 Sep 2015 11:53:41 -0700 Subject: Create CHANGELOG.md --- CHANGELOG.md | 181 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..be021be37 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,181 @@ +# Mattermost Changelog + +## Release v0.7.0 (Beta1) + +Released 2015-09-05 + +### Release highlights + +#### Improved GitLab Mattermost support + +Following the release of Mattermost v0.6.0 Alpha, GitLab 7.14 offered an automated install of Mattermost with GitLab Single-Sign-On (co-branded as "GitLab Mattermost") in its omnibus installer. + +New features, improvements, and bug fixes recommended by the GitLab community were incorporated into Mattermost v0.7.0 Beta1--in particular, extending support of GitLab SSO to team creation, and restricting team creation to users with verified emails from a configurable list of domains. + +#### Slack Import (Preview) + +Preview of Slack import functionality supports the processing of an "Export" file from Slack containing account information and public channel archives from a Slack team. +- In the feature preview, emails and usernames from Slack are used to create new Mattermost accounts, which users can activate by going to the Password Reset screen in Mattermost to set new credentials. Once logged in, users will have access to previous Slack messages shared in public channels, now imported to Mattermost. + +Limitations: +- Slack does not currently export any files or images that your team has stored in the Slack database. Mattermost will provide links to the location of your assets in Slack's web UI. +- Slack does not currently export any content from your private groups or direct messages that your team has stored in the Slack database. +- The Preview release of Slack Import does not offer pre-checks or roll-back and will not import Slack accounts with username or email address collisions with existing Mattermost accounts. Also, Slack channel names with underscores will not import. These issues are being addressed in Mattermost v0.8.0. + +### New Features + +GitLab Mattermost +- Ability to create teams using GitLab SSO (previously GitLab SSO only supported account creation and sign-in) +- Ability to restrict team creation to GitLab SSO and/or users with email verified from a specific list of domains. + +File and Image Sharing +- New drag-and-drop file sharing to messages and comments +- Ability to paste images from clipboard to messages and comments + +Messaging, Comments and Notifications +- Send messages faster with from optimistic posting and retry on failure + +Documentation +- New style guidelines for Go, React and Javascript + +### Improvements + +Messaging, Comments and Notifications +- Performance improvements to channel rendering +- Added "Unread posts" in left hand sidebar when notification indicator is off-screen + +Documentation +- Install documentation improved based on early adopter feedback + +### Bug Fixes + +- Fixed multiple issues with GitLab SSO, installation and on-boarding +- Fixed multiple IE 10 issues +- Fixed broken link in verify email function +- Fixed public links not working on mobile + +### Contributors + +Many thanks to our external contributors, in no particular order: + +- [CtrlZvi](https://github.com/CtrlZvi) +- [BastienDurel](https://github.com/BastienDurel) +- [manusajith](https://github.com/manusajith) +- [doosp](https://github.com/doosp) +- [zackify](https://github.com/zackify) +- [willstacey](https://github.com/willstacey) + +Special thanks to GitLab community for issue reports and feedback, in no particular order: + +- [Chryb](https://gitlab.com/u/Chryb) +- [cookacounty](https://gitlab.com/u/cookacounty) +- [bweston92](https://gitlab.com/u/bweston92) +- [mablae](https://gitlab.com/u/mablae) +- [picharmer](https://gitlab.com/u/picharmer) +- [cmtonkinson](https://gitlab.com/u/cmtonkinson) +- [cmthomps](https://gitlab.com/u/cmthomps) +- [m.gamperl](https://gitlab.com/u/m.gamperl) +- [StanMarsh](https://gitlab.com/u/StanMarsh) +- [alx1](https://gitlab.com/u/alx1) +- [jeanmarc-leroux](https://gitlab.com/u/jeanmarc-leroux) +- [dnoe](https://gitlab.com/u/dnoe) +- [dblessing](https://gitlab.com/u/dblessing) +- [mechanicjay](https://gitlab.com/u/mechanicjay) +- [larsemil](https://gitlab.com/u/larsemil) +- [vga](https://gitlab.com/u/vga) +- [stanhu](https://gitlab.com/u/stanhu) +- [kohenkatz](https://gitlab.com/u/kohenkatz) +- [RavenB1](https://gitlab.com/u/RavenB1) + +Extra special thanks to GitLab community leaders for successful release of GitLab Mattermost Alpha: +- [marin](https://gitlab.com/u/marin) +- [sytse](https://gitlab.com/u/sytse) + + +## Release v0.6.0 (Alpha) + +Released 2015-08-07 + +### Release highlights + +- Simplified on-prem install +- Support for GitLab Mattermost (GitLab SSO, Postgres support, IE 10+ support) + +### Compatibility + +*Note: While use of Mattermost Preview (v0.5.0) and Mattermost Alpha (v0.6.0) in production is not recommended, we document compatibility considerations for a small number of organizations running Mattermost in productions, supported directly by Mattermost product team.* + +- Switched Team URLs from team.domain.com to domain.com/team + +### New Features + +GitLab Mattermost +- OAuth2 support for GitLab Single-Sign-On +- PostgreSQL support for GitLab Mattermost users +- Support for Internet Explorer 10+ for GitLab Mattermost users + +File and Image Sharing +- New thumbnails and formatting for files and images + +Messaging, Comments and Notifications +- Users now see posts they sent highlighted in a different color +- Mentions can now also trigger on user-defined words + +Security and Administration + +- Enable users to view and log out of active sessions +- Team Admin can now delete posts from any user + +On-boarding + +- “Off-Topic” now available as default channel, in addition to “Town Square” + +### Improvements + +Installation + +- New "ByPassEmail" setting enables Mattermost to operate without having to set up email +- New option to use local storage instead of S3 +- Removed use of Redis to simplify on-premise installation + +On-boarding + +- Team setup wizard updated with usability improvements + +Documentation + +- Install documentation improved based on early adopter feedback + +### Contributors + +Many thanks to our external contributors, in no particular order: + +- [ralder](https://github.com/ralder) +- [jedisct1](https://github.com/jedisct1) +- [faebser](https://github.com/faebser) +- [firstrow](https://github.com/firstrow) +- [haikoschol](https://github.com/haikoschol) +- [adamenger](https://github.com/adamenger) + +## Release v0.5.0 (Preview) + +Released 2015-06-24 + +### Release highlights + +- First release of Mattermost as a team communication service for sharing messagse and files across PCs and phones, with archiving and instant search. + +### New Features + +- Sharing Messaging and Files + - Send messages, comments, files and images across public, private and 1-1 channels + - Personalize notifications for unreads and mentions by channel and keyword + - Use #hashtags to tag and find messages, discussions and files + +- Archiving and Search + - Search public and private channels for historical messages and comments + - View recent mentions of your name, username, nickname, and custom search terms + +- Anywhere Access + - Use Mattermost from web-enabled PCs and phones + - Define team-specific branding and color themes across your devices -- cgit v1.2.3-1-g7c22 From e2d5cc0794e26f209a54dfee81c57546c9d9c03e Mon Sep 17 00:00:00 2001 From: it33 Date: Sat, 5 Sep 2015 12:08:15 -0700 Subject: Minor grammar changes --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be021be37..bfef7bd5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,7 +56,7 @@ Documentation ### Contributors -Many thanks to our external contributors, in no particular order: +Many thanks to our external contributors. In no particular order: - [CtrlZvi](https://github.com/CtrlZvi) - [BastienDurel](https://github.com/BastienDurel) @@ -65,7 +65,7 @@ Many thanks to our external contributors, in no particular order: - [zackify](https://github.com/zackify) - [willstacey](https://github.com/willstacey) -Special thanks to GitLab community for issue reports and feedback, in no particular order: +Special thanks to GitLab Mattermost early adopter community for issue reports and feedback. In no particular order: - [Chryb](https://gitlab.com/u/Chryb) - [cookacounty](https://gitlab.com/u/cookacounty) @@ -148,7 +148,7 @@ Documentation ### Contributors -Many thanks to our external contributors, in no particular order: +Many thanks to our external contributors. In no particular order: - [ralder](https://github.com/ralder) - [jedisct1](https://github.com/jedisct1) -- cgit v1.2.3-1-g7c22 From 554474051c149c8df8e39112fa02d044c28b2234 Mon Sep 17 00:00:00 2001 From: it33 Date: Sat, 5 Sep 2015 12:24:02 -0700 Subject: Adding mentions of contributors to CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bfef7bd5b..960a4efd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,8 @@ Documentation Many thanks to our external contributors. In no particular order: +- [asubset](https://github.com/asubset) +- [felixbuenemann](https://github.com/felixbuenemann) - [CtrlZvi](https://github.com/CtrlZvi) - [BastienDurel](https://github.com/BastienDurel) - [manusajith](https://github.com/manusajith) -- cgit v1.2.3-1-g7c22 From 7f8dfb49bfcb21dc0f5e8de8fab403658d39005e Mon Sep 17 00:00:00 2001 From: it33 Date: Sat, 5 Sep 2015 12:58:44 -0700 Subject: Minor corrections to CHANGELOG --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 960a4efd9..55fdb7143 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -105,7 +105,7 @@ Released 2015-08-07 ### Compatibility -*Note: While use of Mattermost Preview (v0.5.0) and Mattermost Alpha (v0.6.0) in production is not recommended, we document compatibility considerations for a small number of organizations running Mattermost in productions, supported directly by Mattermost product team.* +*Note: While use of Mattermost Preview (v0.5.0) and Mattermost Alpha (v0.6.0) in production is not recommended, we document compatibility considerations for a small number of organizations running Mattermost in production, supported directly by Mattermost product team.* - Switched Team URLs from team.domain.com to domain.com/team @@ -171,7 +171,7 @@ Released 2015-06-24 - Sharing Messaging and Files - Send messages, comments, files and images across public, private and 1-1 channels - - Personalize notifications for unreads and mentions by channel and keyword + - Personalize notifications for unreads and mentions by channel - Use #hashtags to tag and find messages, discussions and files - Archiving and Search -- cgit v1.2.3-1-g7c22 From bb3b97499cf217a1216c4b06144b8df8d2e3fab9 Mon Sep 17 00:00:00 2001 From: it33 Date: Sat, 5 Sep 2015 13:20:57 -0700 Subject: Minor corrections to CHANGELOG --- CHANGELOG.md | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55fdb7143..a497e224a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ Released 2015-09-05 -### Release highlights +### Release Highlights #### Improved GitLab Mattermost support @@ -15,36 +15,45 @@ New features, improvements, and bug fixes recommended by the GitLab community we #### Slack Import (Preview) Preview of Slack import functionality supports the processing of an "Export" file from Slack containing account information and public channel archives from a Slack team. -- In the feature preview, emails and usernames from Slack are used to create new Mattermost accounts, which users can activate by going to the Password Reset screen in Mattermost to set new credentials. Once logged in, users will have access to previous Slack messages shared in public channels, now imported to Mattermost. + +- In the feature preview, emails and usernames from Slack are used to create new Mattermost accounts, which users can activate by going to the Password Reset screen in Mattermost to set new credentials. +- Once logged in, users will have access to previous Slack messages shared in public channels, now imported to Mattermost. Limitations: -- Slack does not currently export any files or images that your team has stored in the Slack database. Mattermost will provide links to the location of your assets in Slack's web UI. -- Slack does not currently export any content from your private groups or direct messages that your team has stored in the Slack database. + +- Slack does not currently export any files or images that your team has stored in Slack's database. Mattermost will provide links to the location of your assets in Slack's web UI. +- Slack does not currently export any content from your private groups or direct messages that your team has stored in Slack's database. - The Preview release of Slack Import does not offer pre-checks or roll-back and will not import Slack accounts with username or email address collisions with existing Mattermost accounts. Also, Slack channel names with underscores will not import. These issues are being addressed in Mattermost v0.8.0. ### New Features GitLab Mattermost + - Ability to create teams using GitLab SSO (previously GitLab SSO only supported account creation and sign-in) - Ability to restrict team creation to GitLab SSO and/or users with email verified from a specific list of domains. File and Image Sharing + - New drag-and-drop file sharing to messages and comments - Ability to paste images from clipboard to messages and comments Messaging, Comments and Notifications + - Send messages faster with from optimistic posting and retry on failure Documentation + - New style guidelines for Go, React and Javascript ### Improvements Messaging, Comments and Notifications + - Performance improvements to channel rendering - Added "Unread posts" in left hand sidebar when notification indicator is off-screen Documentation + - Install documentation improved based on early adopter feedback ### Bug Fixes @@ -90,6 +99,7 @@ Special thanks to GitLab Mattermost early adopter community for issue reports an - [RavenB1](https://gitlab.com/u/RavenB1) Extra special thanks to GitLab community leaders for successful release of GitLab Mattermost Alpha: + - [marin](https://gitlab.com/u/marin) - [sytse](https://gitlab.com/u/sytse) @@ -98,7 +108,7 @@ Extra special thanks to GitLab community leaders for successful release of GitLa Released 2015-08-07 -### Release highlights +### Release Highlights - Simplified on-prem install - Support for GitLab Mattermost (GitLab SSO, Postgres support, IE 10+ support) @@ -112,14 +122,17 @@ Released 2015-08-07 ### New Features GitLab Mattermost + - OAuth2 support for GitLab Single-Sign-On - PostgreSQL support for GitLab Mattermost users - Support for Internet Explorer 10+ for GitLab Mattermost users File and Image Sharing + - New thumbnails and formatting for files and images Messaging, Comments and Notifications + - Users now see posts they sent highlighted in a different color - Mentions can now also trigger on user-defined words @@ -163,21 +176,24 @@ Many thanks to our external contributors. In no particular order: Released 2015-06-24 -### Release highlights +### Release Highlights - First release of Mattermost as a team communication service for sharing messagse and files across PCs and phones, with archiving and instant search. ### New Features -- Sharing Messaging and Files - - Send messages, comments, files and images across public, private and 1-1 channels - - Personalize notifications for unreads and mentions by channel - - Use #hashtags to tag and find messages, discussions and files +Messaging and File Sharing + +- Send messages, comments, files and images across public, private and 1-1 channels +- Personalize notifications for unreads and mentions by channel +- Use #hashtags to tag and find messages, discussions and files + +Archiving and Search + +- Search public and private channels for historical messages and comments +- View recent mentions of your name, username, nickname, and custom search terms -- Archiving and Search - - Search public and private channels for historical messages and comments - - View recent mentions of your name, username, nickname, and custom search terms +Anywhere Access -- Anywhere Access - - Use Mattermost from web-enabled PCs and phones - - Define team-specific branding and color themes across your devices +- Use Mattermost from web-enabled PCs and phones +- Define team-specific branding and color themes across your devices -- cgit v1.2.3-1-g7c22 From 5f613ca9a2a45aed76601a623b3d13010bd33201 Mon Sep 17 00:00:00 2001 From: it33 Date: Sat, 5 Sep 2015 14:05:32 -0700 Subject: Added v0.8.0 "UNDER DEVELOPMENT" to changelog --- CHANGELOG.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a497e224a..2ea6a51b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,45 @@ # Mattermost Changelog +## UNDER DEVELOPMENT - Release v0.8.0 (Beta2) + +The "UNDER DEVELOPMENT" section of the Mattermost changelog appears in the product's `master` branch to note key changes that have been committed to master and are on their way to the next stable release. + +When a stable release is pushed the "UNDER DEVELOPMENT" heading is removed from the final changelog of the release. + +### Release Highlights + +- TBA + +### New Features + +- TBA + +### Improvements + +Documentation + +- Updated software and hardware requirements documentation +- Re-organized install instructions out of README and into separate files +- Consolidated licensing information into LICENSE.txt and NOTICE.txt + +Performance + +- Enabled Javascript optimizations + +Code Quality + +- Reformatted Javascript per Mattermost Style Guide + +### Bug fixes + +- Fixed performance issue with slow typing on iOS + +### Contributors + +Many thanks to our external contributors. In no particular order: + +- TBA + ## Release v0.7.0 (Beta1) Released 2015-09-05 -- cgit v1.2.3-1-g7c22 From 95542317209e6b02d905ae61357ac5d9f58d36f8 Mon Sep 17 00:00:00 2001 From: it33 Date: Sat, 5 Sep 2015 16:50:59 -0700 Subject: Adding special thanks to early adopters --- CHANGELOG.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ea6a51b3..0adc0dc9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -116,8 +116,9 @@ Many thanks to our external contributors. In no particular order: - [zackify](https://github.com/zackify) - [willstacey](https://github.com/willstacey) -Special thanks to GitLab Mattermost early adopter community for issue reports and feedback. In no particular order: +Special thanks to the GitLab Mattermost Alpha early adopter community who have influenced this release, and who play a pivotal role in bringing Mattermost to over 100,000 organizations using GitLab today. In no particular order: +- [cifvts](http://forum.mattermost.org/users/cifvts/activity) - [Chryb](https://gitlab.com/u/Chryb) - [cookacounty](https://gitlab.com/u/cookacounty) - [bweston92](https://gitlab.com/u/bweston92) @@ -137,6 +138,11 @@ Special thanks to GitLab Mattermost early adopter community for issue reports an - [stanhu](https://gitlab.com/u/stanhu) - [kohenkatz](https://gitlab.com/u/kohenkatz) - [RavenB1](https://gitlab.com/u/RavenB1) +- [booksprint](http://forum.mattermost.org/users/booksprint/activity) +- [scottcorscadden](http://forum.mattermost.org/users/scottcorscadden/activity) +- [sskmani](http://forum.mattermost.org/users/sskmani/activity) +- [gosure](http://forum.mattermost.org/users/gosure/activity) +- [jigarshah](http://forum.mattermost.org/users/jigarshah/activity) Extra special thanks to GitLab community leaders for successful release of GitLab Mattermost Alpha: -- cgit v1.2.3-1-g7c22 From 558a127f34d8cc1b5c2920d37ecb1cff2f73e3c8 Mon Sep 17 00:00:00 2001 From: it33 Date: Sat, 5 Sep 2015 17:03:04 -0700 Subject: Updating to match directory changes --- LICENSE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE.txt b/LICENSE.txt index aba905e79..f7e182eac 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -7,7 +7,7 @@ Mattermost server is made available under two separate licensing options: - Free Software Foundation’s GNU AGPL v.3.0, subject to the exceptions outlined in this policy; or - Commercial licenses available from SpinPunch, Inc. by contacting commercial@mattermost.com -Admin Tools and Configuration Files (model/, web/static/, web/templates/, web/react/utils/, api/templates/ and all +Admin Tools and Configuration Files (api/templates/, config/, model/, web/react/utils/, web/static/, web/templates/ and all subdirectories thereof) are made available under: - Apache License v2.0 -- cgit v1.2.3-1-g7c22 From 78588c067b8bdd220ff866e89d0abe3b85ee1b17 Mon Sep 17 00:00:00 2001 From: it33 Date: Sat, 5 Sep 2015 19:44:59 -0700 Subject: Added anticipated RC and final release dates --- CHANGELOG.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0adc0dc9e..bac990fb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,17 +2,18 @@ ## UNDER DEVELOPMENT - Release v0.8.0 (Beta2) -The "UNDER DEVELOPMENT" section of the Mattermost changelog appears in the product's `master` branch to note key changes that have been committed to master and are on their way to the next stable release. +The "UNDER DEVELOPMENT" section of the Mattermost changelog appears in the product's `master` branch to note key changes committed to master and are on their way to the next stable release. When a stable release is pushed the "UNDER DEVELOPMENT" heading is removed from the final changelog of the release. -When a stable release is pushed the "UNDER DEVELOPMENT" heading is removed from the final changelog of the release. +- **Release candidate anticipated:** September 28, 2015 +- **Final release anticipated:** October 2, 2015 ### Release Highlights -- TBA +- [See Product Roadmap for anticipated features](https://mattermost.atlassian.net/issues/?filter=10002) ### New Features -- TBA +- [See Product Roadmap for anticipated features](https://mattermost.atlassian.net/issues/?filter=10002) ### Improvements @@ -30,7 +31,7 @@ Code Quality - Reformatted Javascript per Mattermost Style Guide -### Bug fixes +### Bug Fixes - Fixed performance issue with slow typing on iOS -- cgit v1.2.3-1-g7c22 From c74dfeb484028c88c14a66142205ea4d6961dd8f Mon Sep 17 00:00:00 2001 From: it33 Date: Sat, 5 Sep 2015 21:41:50 -0700 Subject: Documenting release numbering scheme --- doc/install/release-numbering.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 doc/install/release-numbering.md diff --git a/doc/install/release-numbering.md b/doc/install/release-numbering.md new file mode 100644 index 000000000..71374f7ef --- /dev/null +++ b/doc/install/release-numbering.md @@ -0,0 +1,17 @@ +### Mattermost Release Numbering + +Mattermost numbers its stable releases based on the following format: + + `[Version Number].[Major Build Number].[Minor Build Number]` + +Version Number: + +- Indicates a major system release (e.g. 1.x.x, 2.x.x) + +Major Build Number: + +- Indicates significant new functionality, (e.g. 0.5.x, 0.6.x, 0.7.x) + +Minor Build Number: + +- Indicates bugfix/security releases (e.g. 1.2.5, 1.2.6) -- cgit v1.2.3-1-g7c22 From bb9d44829e64b0382657cc45519201cf7acd97a3 Mon Sep 17 00:00:00 2001 From: it33 Date: Sat, 5 Sep 2015 21:44:37 -0700 Subject: Adding link to release numbering scheme --- doc/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/README.md b/doc/README.md index ac00dc976..36d16b744 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,5 +1,9 @@ # Mattermost Documentation +## General Information + +- [Mattermost Release Numbering Scheme](install/release-numbering.md) + ## Administrator Documentation ### Installation -- cgit v1.2.3-1-g7c22 From 77d73baf132eeb04467eb0355ed1b2aee13df284 Mon Sep 17 00:00:00 2001 From: it33 Date: Sat, 5 Sep 2015 23:04:02 -0700 Subject: Renaming to Local Machine Setup Also fixing broken link --- doc/install/single-container-install.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/doc/install/single-container-install.md b/doc/install/single-container-install.md index 3e307ca74..772f3becf 100644 --- a/doc/install/single-container-install.md +++ b/doc/install/single-container-install.md @@ -1,10 +1,7 @@ -# Single Container Installation and Upgrade +# Local Machine Setup and Upgrade The following install instructions are for single-container installs of Mattermost using Docker for exploring product functionality and upgrading to newer versions. -Local Machine Setup (Docker) ------------------------------ - ### Mac OSX ### 1. Install Boot2Docker using instructions at: http://docs.docker.com/installation/mac/ @@ -89,7 +86,7 @@ There are a few configuration settings you might want to adjust when setting up The default single-container Docker instance for Mattermost is designed for product evaluation, and sets `ByPassEmail=true` so the product can run without enabling email, when doing so maybe difficult. -To see the product's full functionality, [enabling SMTP email is recommended](doc/config/smtp-email-setup.md). +To see the product's full functionality, [enabling SMTP email is recommended](/doc/config/smtp-email-setup.md). ## Upgrading Mattermost -- cgit v1.2.3-1-g7c22 From aee65d0552b3e5faa3fe2c28327e431367e80204 Mon Sep 17 00:00:00 2001 From: it33 Date: Sat, 5 Sep 2015 23:11:41 -0700 Subject: Minor wording tweaks --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bac990fb9..ff25abeb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -117,7 +117,7 @@ Many thanks to our external contributors. In no particular order: - [zackify](https://github.com/zackify) - [willstacey](https://github.com/willstacey) -Special thanks to the GitLab Mattermost Alpha early adopter community who have influenced this release, and who play a pivotal role in bringing Mattermost to over 100,000 organizations using GitLab today. In no particular order: +Special thanks to the GitLab Mattermost early adopter community who influenced this release, and who play a pivotal role in bringing Mattermost to over 100,000 organizations using GitLab today. In no particular order: - [cifvts](http://forum.mattermost.org/users/cifvts/activity) - [Chryb](https://gitlab.com/u/Chryb) -- cgit v1.2.3-1-g7c22 From 1c394f32a1c1b85e194c99bc77b31e36e7567994 Mon Sep 17 00:00:00 2001 From: it33 Date: Sun, 6 Sep 2015 08:24:05 -0700 Subject: Adding link to migration support epic --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ff25abeb4..5876dc894 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,10 +62,10 @@ Preview of Slack import functionality supports the processing of an "Export" fil Limitations: -- Slack does not currently export any files or images that your team has stored in Slack's database. Mattermost will provide links to the location of your assets in Slack's web UI. -- Slack does not currently export any content from your private groups or direct messages that your team has stored in Slack's database. -- The Preview release of Slack Import does not offer pre-checks or roll-back and will not import Slack accounts with username or email address collisions with existing Mattermost accounts. Also, Slack channel names with underscores will not import. These issues are being addressed in Mattermost v0.8.0. - +- Slack does not export files or images your team has stored in Slack's database. Mattermost will provide links to the location of your assets in Slack's web UI. +- Slack does not export any content from private groups or direct messages that your team has stored in Slack's database. +- The Preview release of Slack Import does not offer pre-checks or roll-back and will not import Slack accounts with username or email address collisions with existing Mattermost accounts. Also, Slack channel names with underscores will not import. Also, mentions do not yet resolve as Mattermost usernames (still show Slack ID). These issues are being addressed in [Mattermost v0.8.0 Migration Support](https://mattermost.atlassian.net/browse/PLT-22?filter=10002). + ### New Features GitLab Mattermost -- cgit v1.2.3-1-g7c22 From c4fac97242fbbeebbcda0e5330e72a31506c2c55 Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Mon, 7 Sep 2015 23:08:55 +0500 Subject: MM-2123 and MM-2124 - UI changes --- web/sass-files/sass/partials/_responsive.scss | 33 ++++++++++++++++++++------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss index 682809f02..c56625b88 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -243,6 +243,23 @@ } } @media screen and (max-width: 768px) { + .file-details__container { + display: block; + .file-details__preview { + display: block; + width: 100%; + height: 150px; + border-right: none; + border-bottom: 1px solid #ddd; + img { + width: 64px; + height: 64px; + } + } + .file-details { + height: auto; + } + } .center-file-overlay { font-size: 1.3em; } @@ -405,23 +422,23 @@ margin-top: 0; } .remove-preview { - width: 50px; - height: 50px; - left: 50%; - top: 50%; + width: 28px; + height: 28px; + left: auto; + right: 0; + top: 0; background: #444; - margin: -25px 0 0 -25px; - @include border-radius(50px); + background: rgba(#000, 0.5); text-align: center; &:after { display: none; } i { - line-height: 50px; + line-height: 29px; top: auto; right: auto; position: relative; - font-size: 28px; + font-size: 16px; } } } -- cgit v1.2.3-1-g7c22 From 8eb891aa95a224ffac108742865ffa7212bf6f9e Mon Sep 17 00:00:00 2001 From: Andreas Litt Date: Mon, 7 Sep 2015 22:13:29 +0200 Subject: Fix deadlink to AWS file --- doc/install/aws-ebs-setup.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/install/aws-ebs-setup.md b/doc/install/aws-ebs-setup.md index 452cfcb4f..e186fa9c1 100644 --- a/doc/install/aws-ebs-setup.md +++ b/doc/install/aws-ebs-setup.md @@ -1,14 +1,14 @@ ## AWS Elastic Beanstalk Setup (Docker) -1. Create a new Elastic Beanstalk Docker application using the [Dockerrun.aws.zip](docker/0.6/Dockerrun.aws//Dockerrun.aws.zip) file provided. +1. Create a new Elastic Beanstalk Docker application using the [Dockerrun.aws.zip](/docker/0.7/Dockerrun.aws.zip) file provided. 1. From the AWS console select Elastic Beanstalk. 2. Select "Create New Application" from the top right. 3. Name the application and press next. 4. Select "Create a web server" environment. 5. If asked, select create an IAM role and instance profile and press next. 6. For predefined configuration select under Generic: Docker. For environment type select single instance. - 7. For application source, select upload your own and upload Dockerrun.aws.zip from [Dockerrun.aws.zip](docker/0.6/Dockerrun.aws//Dockerrun.aws.zip). Everything else may be left at default. + 7. For application source, select upload your own and upload Dockerrun.aws.zip from [Dockerrun.aws.zip](/docker/0.7/Dockerrun.aws.zip). Everything else may be left at default. 8. Select an environment name, this is how you will refer to your environment. Make sure the URL is available then press next. 9. The options on the additional resources page may be left at default unless you wish to change them. Press Next. 10. On the configuration details place. Select an instance type of t2.small or larger. -- cgit v1.2.3-1-g7c22 From 5c0680397de9d3cfd0018743e0b1d58dfbdca25d Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Tue, 8 Sep 2015 10:11:57 -0400 Subject: Add post list container to hold mounted post lists for faster rendering/channel switching. --- web/react/components/channel_header.jsx | 7 +- web/react/components/channel_loader.jsx | 60 +++++++- web/react/components/post_list.jsx | 202 ++++++++++++++------------- web/react/components/post_list_container.jsx | 62 ++++++++ web/react/pages/channel.jsx | 4 +- web/react/stores/channel_store.jsx | 14 ++ web/react/utils/constants.jsx | 1 + web/sass-files/sass/partials/_post.scss | 6 + 8 files changed, 250 insertions(+), 106 deletions(-) create mode 100644 web/react/components/post_list_container.jsx diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx index 87b9cab04..db23a5831 100644 --- a/web/react/components/channel_header.jsx +++ b/web/react/components/channel_header.jsx @@ -64,9 +64,14 @@ export default class ChannelHeader extends React.Component { handleLeave() { Client.leaveChannel(this.state.channel.id, function handleLeaveSuccess() { + AppDispatcher.handleViewAction({ + type: ActionTypes.LEAVE_CHANNEL, + id: this.state.channel.id + }); + const townsquare = ChannelStore.getByName('town-square'); Utils.switchChannel(townsquare); - }, + }.bind(this), function handleLeaveError(err) { AsyncClient.dispatchError(err, 'handleLeave'); } diff --git a/web/react/components/channel_loader.jsx b/web/react/components/channel_loader.jsx index 8e8ed3f73..ce6f60f87 100644 --- a/web/react/components/channel_loader.jsx +++ b/web/react/components/channel_loader.jsx @@ -17,6 +17,8 @@ export default class ChannelLoader extends React.Component { constructor(props) { super(props); + this.intervalId = null; + this.onSocketChange = this.onSocketChange.bind(this); this.state = {}; @@ -35,10 +37,12 @@ export default class ChannelLoader extends React.Component { PostStore.clearPendingPosts(); /* Set up interval functions */ - setInterval( + this.intervalId = setInterval( function pollStatuses() { AsyncClient.getStatuses(); - }, 30000); + }, + 30000 + ); /* Device tracking setup */ var iOS = (/(iPad|iPhone|iPod)/g).test(navigator.userAgent); @@ -49,12 +53,12 @@ export default class ChannelLoader extends React.Component { /* Set up tracking for whether the window is active */ window.isActive = true; - $(window).focus(function windowFocus() { + $(window).on('focus', function windowFocus() { AsyncClient.updateLastViewedAt(); window.isActive = true; }); - $(window).blur(function windowBlur() { + $(window).on('blur', function windowBlur() { window.isActive = false; }); @@ -84,6 +88,54 @@ export default class ChannelLoader extends React.Component { Utils.changeCss('.btn.btn-primary:hover, .btn.btn-primary:active, .btn.btn-primary:focus', 'background: ' + Utils.changeColor(user.props.theme, +10) + ';'); $('.team__header').addClass('theme--gray'); } + + /* Setup global mouse events */ + $('body').on('click.userpopover', function popOver(e) { + if ($(e.target).attr('data-toggle') !== 'popover' && + $(e.target).parents('.popover.in').length === 0) { + $('.user-popover').popover('hide'); + } + }); + + $('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) { + if (ev.type === 'mouseenter') { + $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after'); + $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--before'); + } else { + $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--after'); + $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--before'); + } + }); + + $('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function mouseOver(ev) { + if (ev.type === 'mouseenter') { + $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment'); + $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--comment'); + } else { + $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--comment'); + $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--comment'); + } + }); + + /* Setup modal events */ + $('.modal').on('show.bs.modal', function onShow() { + $('.modal-body').css('overflow-y', 'auto'); + $('.modal-body').css('max-height', $(window).height() * 0.7); + }); + } + componentWillUnmount() { + clearInterval(this.intervalId); + + $(window).off('focus'); + $(window).off('blur'); + + SocketStore.removeChangeListener(this.onSocketChange); + + $('body').off('click.userpopover'); + $('body').off('mouseenter mouseleave', '.post'); + $('body').off('mouseenter mouseleave', '.post.post--comment.same--root'); + + $('.modal').off('show.bs.modal'); } onSocketChange(msg) { if (msg && msg.user_id && msg.user_id !== UserStore.getCurrentId()) { diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index 6fa87ca4a..d7c0d4862 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -18,8 +18,8 @@ var ActionTypes = Constants.ActionTypes; import {strings} from '../utils/config.js'; export default class PostList extends React.Component { - constructor() { - super(); + constructor(props) { + super(props); this.gotMorePosts = false; this.scrolled = false; @@ -27,6 +27,7 @@ export default class PostList extends React.Component { this.seenNewMessages = false; this.isUserScroll = true; this.userHasSeenNew = false; + this.loadInProgress = false; this.onChange = this.onChange.bind(this); this.onTimeChange = this.onTimeChange.bind(this); @@ -34,22 +35,19 @@ export default class PostList extends React.Component { this.createChannelIntroMessage = this.createChannelIntroMessage.bind(this); this.loadMorePosts = this.loadMorePosts.bind(this); this.loadFirstPosts = this.loadFirstPosts.bind(this); + this.activate = this.activate.bind(this); + this.deactivate = this.deactivate.bind(this); + this.resize = this.resize.bind(this); - this.state = this.getStateFromStores(); + this.state = this.getStateFromStores(props.channelId); this.state.numToDisplay = Constants.POST_CHUNK_SIZE; this.state.isFirstLoadComplete = false; } - getStateFromStores() { - var channel = ChannelStore.getCurrent(); - - if (channel == null) { - channel = {}; - } - - var postList = PostStore.getCurrentPosts(); + getStateFromStores(id) { + var postList = PostStore.getPosts(id); if (postList != null) { - var deletedPosts = PostStore.getUnseenDeletedPosts(channel.id); + var deletedPosts = PostStore.getUnseenDeletedPosts(id); if (deletedPosts && Object.keys(deletedPosts).length > 0) { for (var pid in deletedPosts) { @@ -70,7 +68,7 @@ export default class PostList extends React.Component { }); } - var pendingPostList = PostStore.getPendingPosts(channel.id); + var pendingPostList = PostStore.getPendingPosts(id); if (pendingPostList) { postList.order = pendingPostList.order.concat(postList.order); @@ -82,43 +80,42 @@ export default class PostList extends React.Component { } } - var lastViewed = Number.MAX_VALUE; - - if (ChannelStore.getCurrentMember() != null) { - lastViewed = ChannelStore.getCurrentMember().last_viewed_at; - } - return { - postList: postList, - channel: channel, - lastViewed: lastViewed + postList: postList }; } componentDidMount() { + if (this.props.isActive) { + this.activate(); + this.loadFirstPosts(this.props.channelId); + } + } + componentWillUnmount() { + this.deactivate(); + } + activate() { + this.gotMorePosts = false; + this.scrolled = false; + this.prevScrollTop = 0; + this.seenNewMessages = false; + this.isUserScroll = true; + this.userHasSeenNew = false; + + PostStore.clearUnseenDeletedPosts(this.props.channelId); PostStore.addChangeListener(this.onChange); - ChannelStore.addChangeListener(this.onChange); UserStore.addStatusesChangeListener(this.onTimeChange); SocketStore.addChangeListener(this.onSocketChange); - var postHolder = $('.post-list-holder-by-time'); - - $('.modal').on('show.bs.modal', function onShow() { - $('.modal-body').css('overflow-y', 'auto'); - $('.modal-body').css('max-height', $(window).height() * 0.7); - }); - - $(window).resize(function resize() { - if ($('#create_post').length > 0) { - var height = $(window).height() - $('#create_post').height() - $('#error_bar').outerHeight() - 50; - postHolder.css('height', height + 'px'); - } + var postHolder = $(React.findDOMNode(this.refs.postlist)); + $(window).on('resize.' + this.props.channelId, function resize() { + this.resize(); if (!this.scrolled) { this.scrollToBottom(); } }.bind(this)); - postHolder.scroll(function scroll() { + postHolder.on('scroll', function scroll() { var position = postHolder.scrollTop() + postHolder.height() + 14; var bottom = postHolder[0].scrollHeight; @@ -134,41 +131,25 @@ export default class PostList extends React.Component { this.isUserScroll = true; }.bind(this)); - $('body').on('click.userpopover', function popOver(e) { - if ($(e.target).attr('data-toggle') !== 'popover' && - $(e.target).parents('.popover.in').length === 0) { - $('.user-popover').popover('hide'); - } - }); - $('.post-list__content div .post').removeClass('post--last'); $('.post-list__content div:last-child .post').addClass('post--last'); - $('body').on('mouseenter mouseleave', '.post', function mouseOver(ev) { - if (ev.type === 'mouseenter') { - $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--after'); - $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--before'); - } else { - $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--after'); - $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--before'); - } - }); - - $('body').on('mouseenter mouseleave', '.post.post--comment.same--root', function mouseOver(ev) { - if (ev.type === 'mouseenter') { - $(this).parent('div').prev('.date-separator, .new-separator').addClass('hovered--comment'); - $(this).parent('div').next('.date-separator, .new-separator').addClass('hovered--comment'); - } else { - $(this).parent('div').prev('.date-separator, .new-separator').removeClass('hovered--comment'); - $(this).parent('div').next('.date-separator, .new-separator').removeClass('hovered--comment'); - } - }); + if (!this.state.isFirstLoadComplete) { + this.loadFirstPosts(this.props.channelId); + } + this.resize(); + this.onChange(); this.scrollToBottom(); - - if (this.state.channel.id != null) { - this.loadFirstPosts(this.state.channel.id); - } + } + deactivate() { + PostStore.removeChangeListener(this.onChange); + UserStore.removeStatusesChangeListener(this.onTimeChange); + SocketStore.removeChangeListener(this.onSocketChange); + $('body').off('click.userpopover'); + $(window).off('resize.' + this.props.channelId); + var postHolder = $(React.findDOMNode(this.refs.postlist)); + postHolder.off('scroll'); } componentDidUpdate(prevProps, prevState) { $('.post-list__content div .post').removeClass('post--last'); @@ -187,7 +168,7 @@ export default class PostList extends React.Component { var firstPost = posts[order[0]] || {}; var isNewPost = oldOrder.indexOf(order[0]) === -1; - if (this.state.channel.id !== prevState.channel.id) { + if (this.props.isActive && !prevProps.isActive) { this.scrollToBottom(); } else if (oldOrder.length === 0) { this.scrollToBottom(); @@ -201,37 +182,43 @@ export default class PostList extends React.Component { } else if (isNewPost && userId === firstPost.user_id && !utils.isComment(firstPost)) { - this.state.lastViewed = utils.getTimestamp(); this.scrollToBottom(true); // the user clicked 'load more messages' } else if (this.gotMorePosts) { var lastPost = oldPosts[oldOrder[prevState.numToDisplay]]; $('#' + lastPost.id)[0].scrollIntoView(); + this.gotMorePosts = false; } else { this.scrollTo(this.prevScrollTop); } } componentWillUpdate() { - var postHolder = $('.post-list-holder-by-time'); + var postHolder = $(React.findDOMNode(this.refs.postlist)); this.prevScrollTop = postHolder.scrollTop(); } - componentWillUnmount() { - PostStore.removeChangeListener(this.onChange); - ChannelStore.removeChangeListener(this.onChange); - UserStore.removeStatusesChangeListener(this.onTimeChange); - SocketStore.removeChangeListener(this.onSocketChange); - $('body').off('click.userpopover'); - $('.modal').off('show.bs.modal'); + componentWillReceiveProps(nextProps) { + if (nextProps.isActive === true && this.props.isActive === false) { + this.activate(); + } else if (nextProps.isActive === false && this.props.isActive === true) { + this.deactivate(); + } + } + resize() { + const postHolder = $(React.findDOMNode(this.refs.postlist)); + if ($('#create_post').length > 0) { + const height = $(window).height() - $('#create_post').height() - $('#error_bar').outerHeight() - 50; + postHolder.css('height', height + 'px'); + } } scrollTo(val) { this.isUserScroll = false; - var postHolder = $('.post-list-holder-by-time'); + var postHolder = $(React.findDOMNode(this.refs.postlist)); postHolder[0].scrollTop = val; } scrollToBottom(force) { this.isUserScroll = false; - var postHolder = $('.post-list-holder-by-time'); + var postHolder = $(React.findDOMNode(this.refs.postlist)); if ($('#new_message')[0] && !this.userHasSeenNew && !force) { $('#new_message')[0].scrollIntoView(); } else { @@ -241,34 +228,32 @@ export default class PostList extends React.Component { } } loadFirstPosts(id) { + if (this.loadInProgress) { + return; + } + + if (this.props.channelId == null) { + return; + } + + this.loadInProgress = true; Client.getPosts( id, PostStore.getLatestUpdate(id), function success() { + this.loadInProgress = false; this.setState({isFirstLoadComplete: true}); }.bind(this), function fail() { + this.loadInProgress = false; this.setState({isFirstLoadComplete: true}); }.bind(this) ); } onChange() { - var newState = this.getStateFromStores(); - - // Special case where the channel wasn't yet set in componentDidMount - if (!this.state.isFirstLoadComplete && this.state.channel.id == null && newState.channel.id != null) { - this.loadFirstPosts(newState.channel.id); - } - - if (!utils.areStatesEqual(newState, this.state)) { - if (this.state.channel.id !== newState.channel.id) { - PostStore.clearUnseenDeletedPosts(this.state.channel.id); - this.userHasSeenNew = false; - newState.numToDisplay = Constants.POST_CHUNK_SIZE; - } else { - newState.lastViewed = this.state.lastViewed; - } + var newState = this.getStateFromStores(this.props.channelId); + if (!utils.areStatesEqual(newState.postList, this.state.postList)) { this.setState(newState); } } @@ -424,7 +409,7 @@ export default class PostList extends React.Component { } } - var members = ChannelStore.getCurrentExtraInfo().members; + var members = ChannelStore.getExtraInfo(channel.id).members; for (var i = 0; i < members.length; i++) { if (members[i].roles.indexOf('admin') > -1) { return members[i].username; @@ -488,6 +473,11 @@ export default class PostList extends React.Component { var userId = UserStore.getCurrentId(); var renderedLastViewed = false; + var lastViewed = Number.MAX_VALUE; + + if (ChannelStore.getMember(this.props.channelId) != null) { + lastViewed = ChannelStore.getMember(this.props.channelId).last_viewed_at; + } var numToDisplay = this.state.numToDisplay; if (order.length - 1 < numToDisplay) { @@ -543,7 +533,7 @@ export default class PostList extends React.Component { ); } - if (post.user_id !== userId && post.create_at > this.state.lastViewed && !renderedLastViewed) { + if (post.user_id !== userId && post.create_at > lastViewed && !renderedLastViewed) { renderedLastViewed = true; // Temporary fix to solve ie10/11 rendering issue @@ -577,7 +567,7 @@ export default class PostList extends React.Component { var posts = this.state.postList.posts; var order = this.state.postList.order; - var channelId = this.state.channel.id; + var channelId = this.props.channelId; $(React.findDOMNode(this.refs.loadmore)).text('Retrieving more messages...'); @@ -619,7 +609,7 @@ export default class PostList extends React.Component { render() { var order = []; var posts; - var channel = this.state.channel; + var channel = ChannelStore.get(this.props.channelId); if (this.state.postList != null) { posts = this.state.postList.posts; @@ -628,7 +618,7 @@ export default class PostList extends React.Component { var moreMessages =

Beginning of Channel

; if (channel != null) { - if (order.length > this.state.numToDisplay) { + if (order.length >= this.state.numToDisplay) { moreMessages = ( ); } + var activeClass = ''; + if (!this.props.isActive) { + activeClass = 'inactive'; + } + return (
@@ -670,3 +665,12 @@ export default class PostList extends React.Component { ); } } + +PostList.defaultProps = { + isActive: false, + channelId: null +}; +PostList.propTypes = { + isActive: React.PropTypes.bool, + channelId: React.PropTypes.string +}; diff --git a/web/react/components/post_list_container.jsx b/web/react/components/post_list_container.jsx new file mode 100644 index 000000000..0815ac883 --- /dev/null +++ b/web/react/components/post_list_container.jsx @@ -0,0 +1,62 @@ +// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved. +// See License.txt for license information. + +const PostList = require('./post_list.jsx'); +const ChannelStore = require('../stores/channel_store.jsx'); + +export default class PostListContainer extends React.Component { + constructor() { + super(); + + this.onChange = this.onChange.bind(this); + this.onLeave = this.onLeave.bind(this); + + let currentChannelId = ChannelStore.getCurrentId(); + if (currentChannelId) { + this.state = {currentChannelId: currentChannelId, postLists: [currentChannelId]}; + } else { + this.state = {currentChannelId: null, postLists: []}; + } + } + componentDidMount() { + ChannelStore.addChangeListener(this.onChange); + ChannelStore.addLeaveListener(this.onLeave); + } + onChange() { + let channelId = ChannelStore.getCurrentId(); + if (channelId === this.state.currentChannelId) { + return; + } + + let postLists = this.state.postLists; + if (postLists.indexOf(channelId) === -1) { + postLists.push(channelId); + } + this.setState({currentChannelId: channelId, postLists: postLists}); + } + onLeave(id) { + let postLists = this.state.postLists; + var index = postLists.indexOf(id); + if (index !== -1) { + postLists.splice(index, 1); + } + } + render() { + let postLists = this.state.postLists; + let channelId = this.state.currentChannelId; + + let postListCtls = []; + for (let i = 0; i <= this.state.postLists.length - 1; i++) { + postListCtls.push( + + ); + } + + return ( +
{postListCtls}
+ ); + } +} diff --git a/web/react/pages/channel.jsx b/web/react/pages/channel.jsx index 71a03cde0..182721bef 100644 --- a/web/react/pages/channel.jsx +++ b/web/react/pages/channel.jsx @@ -5,7 +5,7 @@ var AppDispatcher = require('../dispatcher/app_dispatcher.jsx'); var Navbar = require('../components/navbar.jsx'); var Sidebar = require('../components/sidebar.jsx'); var ChannelHeader = require('../components/channel_header.jsx'); -var PostList = require('../components/post_list.jsx'); +var PostListContainer = require('../components/post_list_container.jsx'); var CreatePost = require('../components/create_post.jsx'); var SidebarRight = require('../components/sidebar_right.jsx'); var SidebarRightMenu = require('../components/sidebar_right_menu.jsx'); @@ -159,7 +159,7 @@ function setupChannelPage(teamName, teamType, teamId, channelName, channelId) { ); React.render( - , + , document.getElementById('post-list') ); diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx index bd655b767..b9ba37c27 100644 --- a/web/react/stores/channel_store.jsx +++ b/web/react/stores/channel_store.jsx @@ -10,6 +10,7 @@ var ActionTypes = Constants.ActionTypes; var BrowserStore = require('../stores/browser_store.jsx'); var CHANGE_EVENT = 'change'; +var LEAVE_EVENT = 'leave'; var MORE_CHANGE_EVENT = 'change'; var EXTRA_INFO_EVENT = 'extra_info'; @@ -48,6 +49,15 @@ class ChannelStoreClass extends EventEmitter { removeExtraInfoChangeListener(callback) { this.removeListener(EXTRA_INFO_EVENT, callback); } + emitLeave(id) { + this.emit(LEAVE_EVENT, id); + } + addLeaveListener(callback) { + this.on(LEAVE_EVENT, callback); + } + removeLeaveListener(callback) { + this.removeListener(LEAVE_EVENT, callback); + } findFirstBy(field, value) { var channels = this.pGetChannels(); for (var i = 0; i < channels.length; i++) { @@ -272,6 +282,10 @@ ChannelStore.dispatchToken = AppDispatcher.register(function handleAction(payloa ChannelStore.emitExtraInfoChange(); break; + case ActionTypes.LEAVE_CHANNEL: + ChannelStore.emitLeave(action.id); + break; + default: break; } diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx index 18b7ff59c..7ead079d7 100644 --- a/web/react/utils/constants.jsx +++ b/web/react/utils/constants.jsx @@ -9,6 +9,7 @@ module.exports = { CLICK_CHANNEL: null, CREATE_CHANNEL: null, + LEAVE_CHANNEL: null, RECIEVED_CHANNELS: null, RECIEVED_CHANNEL: null, RECIEVED_MORE_CHANNELS: null, diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index 231e6fe3f..a8e77f7d5 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -143,6 +143,12 @@ body.ios { &.hide-scroll::-webkit-scrollbar { width: 0px !important; } + &.inactive { + display: none; + } + &.active { + display: inline; + } } .post-list__table { display: table; -- cgit v1.2.3-1-g7c22 From 09ac6aa46d7112df581eade2777efd3b4ff8371f Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Tue, 8 Sep 2015 11:54:01 -0400 Subject: Resize create post control when file previews are added. --- web/react/components/create_post.jsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 871b72a43..50aa0850d 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -55,6 +55,11 @@ export default class CreatePost extends React.Component { initialText: messageText }; } + componentDidUpdate(prevProps, prevState) { + if (prevState.previews.length !== this.state.previews.length) { + this.resizePostHolder(); + } + } handleSubmit(e) { e.preventDefault(); -- cgit v1.2.3-1-g7c22 From 62940fb72f8ba89b97bbbd2bdc484fd42f5e09ce Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Tue, 8 Sep 2015 21:12:36 +0500 Subject: UI changes for various tickets --- web/react/components/setting_upload.jsx | 6 +++--- web/react/components/team_import_tab.jsx | 6 +----- web/sass-files/sass/partials/_modal.scss | 4 ++-- web/sass-files/sass/partials/_post.scss | 2 +- web/sass-files/sass/partials/_responsive.scss | 9 +++++++++ web/sass-files/sass/partials/_settings.scss | 16 +++++++++------- web/sass-files/sass/partials/_sidebar--left.scss | 4 ++++ 7 files changed, 29 insertions(+), 18 deletions(-) diff --git a/web/react/components/setting_upload.jsx b/web/react/components/setting_upload.jsx index 5979091c4..fad27b355 100644 --- a/web/react/components/setting_upload.jsx +++ b/web/react/components/setting_upload.jsx @@ -64,9 +64,9 @@ export default class SettingsUpload extends React.Component { } return (
    -
  • {this.props.title}
  • -
  • {this.props.helpText}
  • -
  • +
  • {this.props.title}
  • +
  • {this.props.helpText}
  • +
    • diff --git a/web/react/components/team_import_tab.jsx b/web/react/components/team_import_tab.jsx index 1ab348465..ce0e81299 100644 --- a/web/react/components/team_import_tab.jsx +++ b/web/react/components/team_import_tab.jsx @@ -34,11 +34,7 @@ export default class TeamImportTab extends React.Component { render() { var uploadHelpText = (
      -
      - Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team's public channels. -

      - The Slack import to Mattermost is in "Preview". Slack bot posts and channels with underscores do not yet import. -

      +

      Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team's public channels.

      The Slack import to Mattermost is in "Preview". Slack bot posts and channels with underscores do not yet import.

      ); var uploadSection = ( diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss index dec08b567..29e05f6bf 100644 --- a/web/sass-files/sass/partials/_modal.scss +++ b/web/sass-files/sass/partials/_modal.scss @@ -63,7 +63,6 @@ margin: 0; } button.close { - margin: -2px -2px 0 0; color: #fff; @include opacity(1); z-index: 5; @@ -71,7 +70,8 @@ height: 30px; line-height: 30px; @include single-transition(all, 0.25s, ease-in); - position: relative; + position: absolute; + right: 10px; &:hover { background: rgba(0, 0, 0, 0.1); } diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index 231e6fe3f..9a6e6e4c7 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -140,7 +140,7 @@ body.ios { padding: 1em 0 0; position: relative; -webkit-overflow-scrolling: touch; - &.hide-scroll::-webkit-scrollbar { + &::-webkit-scrollbar { width: 0px !important; } } diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss index c56625b88..a850d5a7f 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -365,6 +365,9 @@ width: 100%; z-index: 5; } + .modal-title { + padding-left: 15px; + } .user-settings { .tab-header { display: none; @@ -513,6 +516,12 @@ height: 45px; position: relative; @include single-transition(all, 0.2s, linear); + .glyphicon-refresh-animate { + right: 33px; + top: 15px; + color: #fff; + color: rgba(255,255,255,0.5); + } .form-control { border: none; padding: 0 10px 0 31px; diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss index 8d743dfe2..25c093957 100644 --- a/web/sass-files/sass/partials/_settings.scss +++ b/web/sass-files/sass/partials/_settings.scss @@ -14,13 +14,15 @@ width:800px; max-width: 100%; .modal-back { - width: 8px; - height: 13px; - background: url("../images/arrow-left.png"); - @include background-size(100% 100%); - margin-right: 10px; - display: inline-block; + width: 40px; + height: 56px; + background: url("../images/arrow-left.png") no-repeat; + @include background-size(8px 13px); + background-position: center; + top: 0; + left: 0; cursor: pointer; + position: absolute; } .modal-body { padding: 0; @@ -59,7 +61,7 @@ .section-max { background: #f2f2f2; - padding: 1em 0; + padding: 1em 0 1.3em; margin-bottom: 0; @include clearfix; .section-title { diff --git a/web/sass-files/sass/partials/_sidebar--left.scss b/web/sass-files/sass/partials/_sidebar--left.scss index 432b8eb46..933be96ff 100644 --- a/web/sass-files/sass/partials/_sidebar--left.scss +++ b/web/sass-files/sass/partials/_sidebar--left.scss @@ -10,6 +10,10 @@ &.sidebar--padded { padding-top: 44px; } + .dropdown-menu { + max-height: 300px; + overflow: auto; + } .search__form { margin: 0; padding: 1em 1em 0; -- cgit v1.2.3-1-g7c22 From 8c9c6de97041f8b2d646a7b4f03852c74e8e8fab Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Tue, 8 Sep 2015 21:22:52 +0500 Subject: Improving formatting in team_import_tab.jsx --- web/react/components/team_import_tab.jsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/web/react/components/team_import_tab.jsx b/web/react/components/team_import_tab.jsx index ce0e81299..07dc3dc6d 100644 --- a/web/react/components/team_import_tab.jsx +++ b/web/react/components/team_import_tab.jsx @@ -34,9 +34,11 @@ export default class TeamImportTab extends React.Component { render() { var uploadHelpText = (
      -

      Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team's public channels.

      The Slack import to Mattermost is in "Preview". Slack bot posts and channels with underscores do not yet import.

      +

      {'Slack does not allow you to export files, images, private groups or direct messages stored in Slack. Therefore, Slack import to Mattermost only supports importing of text messages in your Slack team\'\s public channels.'}

      +

      {'The Slack import to Mattermost is in "Preview". Slack bot posts and channels with underscores do not yet import.'}

      ); + var uploadSection = ( Importing...

      +

      {' Importing...'}

      ); break; case 'done': @@ -95,18 +97,18 @@ export default class TeamImportTab extends React.Component { data-dismiss='modal' aria-label='Close' > - +

      Import

      + >{'Import'}
-

Import

+

{'Import'}

{uploadSection}
-- cgit v1.2.3-1-g7c22 From c6b1368cc7b011ac522ada7e3c927fa1ec57c9e0 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Tue, 8 Sep 2015 15:47:15 -0400 Subject: Added send icon button to create post control for mobile view. --- web/react/components/create_post.jsx | 46 ++++++++++++++++----------- web/sass-files/sass/partials/_post.scss | 19 +++++++++++ web/sass-files/sass/partials/_responsive.scss | 15 ++++++++- 3 files changed, 60 insertions(+), 20 deletions(-) diff --git a/web/react/components/create_post.jsx b/web/react/components/create_post.jsx index 871b72a43..80a5441ae 100644 --- a/web/react/components/create_post.jsx +++ b/web/react/components/create_post.jsx @@ -310,25 +310,33 @@ export default class CreatePost extends React.Component { >
{postError} diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index 231e6fe3f..5b209f41b 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -182,6 +182,25 @@ body.ios { max-width: 850px; padding: 0 0 2px; position: relative; + .post-body__cell { + vertical-align: top; + position: relative; + } + .send-button { + display: none; + cursor: pointer; + padding-right: 4px; + width: 45px; + height: 37px; + font-size: 18px; + line-height: 37px; + vertical-align: top; + text-align: center; + @include single-transition(all, 0.15s); + &:active { + @include opacity(0.75); + } + } .custom-textarea { padding-top: 8px; padding-right: 28px; diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss index c56625b88..71e9d0f96 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -409,9 +409,22 @@ } } } - #post-create { + .post-create__container { + form { + padding: 0; + } .post-create-body { padding-bottom: 10px; + display: table; + width: 100%; + table-layout: fixed; + .post-body__cell { + display: table-cell; + padding-left: 1em; + } + .send-button { + display: table-cell; + } } .post-create-footer .msg-typing { display: none; -- cgit v1.2.3-1-g7c22 From d0224d03798f1b6d392e39a0cc0e3d3a9fcd898b Mon Sep 17 00:00:00 2001 From: JessBot Date: Tue, 8 Sep 2015 22:57:29 -0400 Subject: Punctuation and flow Added some punctuation and changes to some wording to make it flow better. --- doc/install/requirements.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index fa54e81ef..1e0a12fb9 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -49,7 +49,7 @@ The below guidelines offer estimates based on real world usage of Mattermost acr ### Storage -To estimate initial storage requirements begin with a Mattermost server approximately 600 MB to 800 MB in size including operating system and database, then add the multiplied product of: +To estimate initial storage requirements, begin with a Mattermost server approximately 600 MB to 800 MB in size including operating system and database, then add the multiplied product of: - Estimated storage per user per month (see below), multipled by 12 months in a year - Estimated mean average number of users in a year @@ -61,10 +61,10 @@ File usage per user varies significantly across industries. The below benchmarks - **Low usage teams** (1-5 MB/user/month) - Primarily use text-messages and links to communicate. Examples would include software development teams that heavily use web-based document creation and management tools, and therefore rarely upload files to the server. -- **Medium usage teams** (5-25 MB/user/month) - Use a mix of text-messages and shared documents and images to communicate. Examples might include business teams that may commonly drag and drop screenshots, PDFs and Microsoft Office documents into Mattermost for sharing and review. +- **Medium usage teams** (5-25 MB/user/month) - Use a mix of text-messages as well as shared documents and images to communicate. Examples might include business teams that may commonly drag and drop screenshots, PDFs and Microsoft Office documents into Mattermost for sharing and review. -- **High usage teams** - (25-100 MB/user/month) - Heaviest utlization comes from teams uploading a high number of large files into Mattermost on a regular basis. Examples include creative teams sharing and storing artwork and media with tags and commentary in a pipeline production process. +- **High usage teams** - (25-100 MB/user/month) - Heaviest utlization comes from teams uploading a high number of large files into Mattermost on a regular basis. Examples include creative teams who share and store artwork and media with tags and commentary in a pipeline production process. -*Example:* A 30 person team with medium usage (5-25 MB/user/month) with a safety factor of 2x would require between 300 MB (30 users * 5 MB * 2x safety factor) and 1500 MB (30 users * 25 MB * 2x safety factor) of free space in the next year. +*Example:* A 30-person team with medium usage (5-25 MB/user/month) with a safety factor of 2x would require between 300 MB (30 users * 5 MB * 2x safety factor) and 1500 MB (30 users * 25 MB * 2x safety factor) of free space in the next year. It's recommended to review storage utilization at least quarterly to ensure adequate free space is available. -- cgit v1.2.3-1-g7c22 From 2a876f33006497f35dff18272088d373bdd07805 Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Wed, 9 Sep 2015 11:02:46 +0500 Subject: Removing jsx container from × in team_import_tab.jsx --- web/react/components/team_import_tab.jsx | 2 +- web/sass-files/sass/partials/_responsive.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/react/components/team_import_tab.jsx b/web/react/components/team_import_tab.jsx index 07dc3dc6d..63ba7278e 100644 --- a/web/react/components/team_import_tab.jsx +++ b/web/react/components/team_import_tab.jsx @@ -97,7 +97,7 @@ export default class TeamImportTab extends React.Component { data-dismiss='modal' aria-label='Close' > - +

Date: Wed, 9 Sep 2015 18:59:25 +0500 Subject: Adding symbol instead of × for close icon --- web/react/components/team_import_tab.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/react/components/team_import_tab.jsx b/web/react/components/team_import_tab.jsx index 63ba7278e..031abc36a 100644 --- a/web/react/components/team_import_tab.jsx +++ b/web/react/components/team_import_tab.jsx @@ -97,7 +97,7 @@ export default class TeamImportTab extends React.Component { data-dismiss='modal' aria-label='Close' > - +

Date: Wed, 9 Sep 2015 19:42:03 +0500 Subject: MM-1866 - Adding download icon and adding download link for both file title and download icon --- web/react/components/file_attachment.jsx | 16 ++++++++++++++-- web/sass-files/sass/partials/_files.scss | 12 ++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/web/react/components/file_attachment.jsx b/web/react/components/file_attachment.jsx index 78693df98..c9aa06a97 100644 --- a/web/react/components/file_attachment.jsx +++ b/web/react/components/file_attachment.jsx @@ -97,6 +97,7 @@ export default class FileAttachment extends React.Component { var filename = this.props.filename; var fileInfo = utils.splitFileLocation(filename); + var fileUrl = utils.getFileUrl(filename); var type = utils.getFileType(fileInfo.ext); var thumbnail; @@ -150,14 +151,25 @@ export default class FileAttachment extends React.Component { {thumbnail}
-
{trimmedFilename} -
+
+ + + {fileInfo.ext.toUpperCase()} {fileSizeString}
diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss index 70f440989..405265f92 100644 --- a/web/sass-files/sass/partials/_files.scss +++ b/web/sass-files/sass/partials/_files.scss @@ -61,8 +61,8 @@ cursor: pointer; z-index: 5; opacity: inherit; - text-shadow: 0 0px 3px #444; - text-shadow: 0 0px 3px rgba(0, 0, 0, 0.7); + text-shadow: 0 0px 3px #444; + text-shadow: 0 0px 3px rgba(0, 0, 0, 0.7); } } } @@ -159,6 +159,14 @@ padding: 7px; .post-image__name { margin-bottom: 3px; + display: block; + color: #333; + } + .post-image__download { + display: inline-block; + padding-right: 7px; + cursor: pointer; + color: #555; } .post-image__type { color: grey; -- cgit v1.2.3-1-g7c22 From d30426ce17a6aa611c8712d3315d5249cbbeee92 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Wed, 9 Sep 2015 11:28:53 -0400 Subject: Fix race condition where switching between two channels that both had new messages sometimes caused improper scrolling. --- web/react/components/post_list.jsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx index d7c0d4862..9d95887d9 100644 --- a/web/react/components/post_list.jsx +++ b/web/react/components/post_list.jsx @@ -152,6 +152,10 @@ export default class PostList extends React.Component { postHolder.off('scroll'); } componentDidUpdate(prevProps, prevState) { + if (!this.props.isActive) { + return; + } + $('.post-list__content div .post').removeClass('post--last'); $('.post-list__content div:last-child .post').addClass('post--last'); @@ -219,8 +223,8 @@ export default class PostList extends React.Component { scrollToBottom(force) { this.isUserScroll = false; var postHolder = $(React.findDOMNode(this.refs.postlist)); - if ($('#new_message')[0] && !this.userHasSeenNew && !force) { - $('#new_message')[0].scrollIntoView(); + if ($('#new_message_' + this.props.channelId)[0] && !this.userHasSeenNew && !force) { + $('#new_message_' + this.props.channelId)[0].scrollIntoView(); } else { postHolder.addClass('hide-scroll'); postHolder[0].scrollTop = postHolder[0].scrollHeight; @@ -539,7 +543,7 @@ export default class PostList extends React.Component { // Temporary fix to solve ie10/11 rendering issue let newSeparatorId = ''; if (!utils.isBrowserIE()) { - newSeparatorId = 'new_message'; + newSeparatorId = 'new_message_' + this.props.channelId; } postCtls.push(
Date: Wed, 9 Sep 2015 21:26:15 +0500 Subject: MM-1825 - Improving UI for settings --- web/react/components/setting_item_min.jsx | 5 ++++- web/react/components/team_export_tab.jsx | 16 +++++++--------- web/react/components/user_settings_general.jsx | 26 +++++++++++++------------- web/sass-files/sass/partials/_settings.scss | 5 ++++- 4 files changed, 28 insertions(+), 24 deletions(-) diff --git a/web/react/components/setting_item_min.jsx b/web/react/components/setting_item_min.jsx index 098729a4f..d6ccc106c 100644 --- a/web/react/components/setting_item_min.jsx +++ b/web/react/components/setting_item_min.jsx @@ -19,7 +19,10 @@ export default class SettingItemMin extends React.Component { } return ( -
    +
    • {this.props.title}
    • {editButton}
    • {this.props.describe}
    • diff --git a/web/react/components/team_export_tab.jsx b/web/react/components/team_export_tab.jsx index 1bc5abdb1..2914904ad 100644 --- a/web/react/components/team_export_tab.jsx +++ b/web/react/components/team_export_tab.jsx @@ -75,15 +75,13 @@ export default class TeamExportTab extends React.Component {
    • diff --git a/web/react/components/user_settings_general.jsx b/web/react/components/user_settings_general.jsx index f2127ce0c..184534a9a 100644 --- a/web/react/components/user_settings_general.jsx +++ b/web/react/components/user_settings_general.jsx @@ -238,7 +238,7 @@ export default class UserSettingsGeneralTab extends React.Component { key='firstNameSetting' className='form-group' > - +
      - +
      - Notifications + {'Notifications'} ); const extraInfo = ( - By default, you will receive mention notifications when someone types your first name. - Go to {notifLink} settings to change this default. + {'By default, you will receive mention notifications when someone types your first name. '} + {'Go to '} {notifLink} {'settings to change this default.'} ); @@ -351,8 +351,8 @@ export default class UserSettingsGeneralTab extends React.Component { const extraInfo = ( - Use Nickname for a name you might be called that is different from your first name and user name. - This is most often used when two or more people have similar sounding names and usernames. + {'Use Nickname for a name you might be called that is different from your first name and user name.'} + {'This is most often used when two or more people have similar sounding names and usernames.'} ); @@ -406,7 +406,7 @@ export default class UserSettingsGeneralTab extends React.Component {
      ); - const extraInfo = (Pick something easy for teammates to recognize and recall.); + const extraInfo = ({'Pick something easy for teammates to recognize and recall.'}); usernameSection = ( Email is used for notifications, and requires verification if changed.
      ; if (!this.state.emailEnabled) { - helpText =

      Email has been disabled by your system administrator. No notification emails will be sent until it is enabled.
      ; + helpText =
      {'Email has been disabled by your system administrator. No notification emails will be sent until it is enabled.'}
      ; } inputs.push(
      - +
      - +

      - General Settings + {'General Settings'}

      -

      General Settings

      +

      {'General Settings'}

      {nameSection}
      diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss index 25c093957..468880724 100644 --- a/web/sass-files/sass/partials/_settings.scss +++ b/web/sass-files/sass/partials/_settings.scss @@ -56,7 +56,11 @@ .section-min { padding: 1em 0; margin-bottom: 0; + cursor: pointer; @include clearfix; + &:hover { + background: #f9f9f9; + } } .section-max { @@ -97,7 +101,6 @@ } .setting-list__hint { - color: #555; margin-top: 20px; } -- cgit v1.2.3-1-g7c22 From 7fba0adc805f3b633bf5ef23e0c8143c525a23e6 Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Wed, 9 Sep 2015 22:25:01 +0500 Subject: PLT-188 - Moving attachment button to the left and fixing buttons in modal header --- web/sass-files/sass/partials/_modal.scss | 2 +- web/sass-files/sass/partials/_post.scss | 2 +- web/sass-files/sass/partials/_responsive.scss | 10 +++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss index 29e05f6bf..b94ed2113 100644 --- a/web/sass-files/sass/partials/_modal.scss +++ b/web/sass-files/sass/partials/_modal.scss @@ -82,7 +82,7 @@ .btn { &.btn-primary { float: right; - margin-top: -4px; + margin: -4px 25px 0 0; position: relative; i { margin-right: 5px; diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss index 5ae9e656d..6940cf2fb 100644 --- a/web/sass-files/sass/partials/_post.scss +++ b/web/sass-files/sass/partials/_post.scss @@ -200,7 +200,7 @@ body.ios { height: 37px; font-size: 18px; line-height: 37px; - vertical-align: top; + vertical-align: bottom; text-align: center; @include single-transition(all, 0.15s); &:active { diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss index 24c79e6c2..65619f25a 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -423,7 +423,15 @@ table-layout: fixed; .post-body__cell { display: table-cell; - padding-left: 1em; + padding-left: 45px; + } + .btn-file { + width: 45px; + padding: 0; + line-height: 36px; + bottom: -2px; + left: 0; + top: auto; } .send-button { display: table-cell; -- cgit v1.2.3-1-g7c22 From 1f5e43b38817b8c6a3b121d6bd4447411e135f5d Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Wed, 9 Sep 2015 22:32:21 +0500 Subject: Adding minor hover changes for settings --- web/react/components/setting_item_min.jsx | 3 ++- web/sass-files/sass/partials/_settings.scss | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/web/react/components/setting_item_min.jsx b/web/react/components/setting_item_min.jsx index d6ccc106c..2c0fdf2f4 100644 --- a/web/react/components/setting_item_min.jsx +++ b/web/react/components/setting_item_min.jsx @@ -12,7 +12,8 @@ export default class SettingItemMin extends React.Component { href='#' onClick={this.props.updateSection} > - Edit + + {'Edit'} ); diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss index 468880724..2b59a943b 100644 --- a/web/sass-files/sass/partials/_settings.scss +++ b/web/sass-files/sass/partials/_settings.scss @@ -61,6 +61,12 @@ &:hover { background: #f9f9f9; } + &:hover .fa { + display: inline-block; + } + &:hover .section-edit { + text-decoration: underline; + } } .section-max { @@ -81,6 +87,12 @@ .section-edit { text-align: right; margin-bottom: 5px; + .fa { + margin-right: 7px; + font-size: 12px; + color: #aaa; + display: none; + } } .section-describe { -- cgit v1.2.3-1-g7c22 From e210bb34f3215e524d68fd5bc2c7d68d46324af5 Mon Sep 17 00:00:00 2001 From: it33 Date: Wed, 9 Sep 2015 11:50:17 -0700 Subject: Cosmetic adjustment to title of README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9f6b46d5f..de0f4b079 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# Mattermost is an open source, on-prem Slack-alternative +# Mattermost -Mattermost modernizes team communication without locking in your data to a single provider. +Mattermost is an open source, on-prem Slack-alternative. -Offer your end users messaging and file sharing across PCs and phones with archiving and instant search--without losing control of your data. +It modernizes team communication without locking in your data to a single provider. Offer your end users messaging and file sharing across PCs and phones with archiving and instant search--without losing control of your data. ## All team communication in one place, searchable and accessible anywhere -- cgit v1.2.3-1-g7c22 From 2e5a58a11eed385092129775f1a0dd5a57e0c2ce Mon Sep 17 00:00:00 2001 From: hmhealey Date: Wed, 9 Sep 2015 15:21:44 -0400 Subject: PLT-159 Changed unread posts indicator to not be shown for the current channel --- web/react/components/sidebar.jsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx index 983260187..ad934d271 100644 --- a/web/react/components/sidebar.jsx +++ b/web/react/components/sidebar.jsx @@ -315,10 +315,12 @@ export default class Sidebar extends React.Component { if (unread) { titleClass = 'unread-title'; - if (!this.firstUnreadChannel) { - this.firstUnreadChannel = channel.name; + if (channel.id !== activeId) { + if (!this.firstUnreadChannel) { + this.firstUnreadChannel = channel.name; + } + this.lastUnreadChannel = channel.name; } - this.lastUnreadChannel = channel.name; } var badge = null; -- cgit v1.2.3-1-g7c22 From 7a9e709bd25407c1915e97e37bbf424ad5dc3ef5 Mon Sep 17 00:00:00 2001 From: hmhealey Date: Wed, 9 Sep 2015 18:42:28 -0400 Subject: Removed pointless attempt to verify a new user's username --- api/user.go | 5 +---- model/user.go | 5 ----- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/api/user.go b/api/user.go index d69244fad..727accd1f 100644 --- a/api/user.go +++ b/api/user.go @@ -71,10 +71,7 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) { return } - if !model.IsUsernameValid(user.Username) { - c.Err = model.NewAppError("createUser", "That username is invalid", "might be using a resrved username") - return - } + // the user's username is checked to be valid when they are saved to the database user.EmailVerified = false diff --git a/model/user.go b/model/user.go index 9f90b8204..05fc96953 100644 --- a/model/user.go +++ b/model/user.go @@ -335,11 +335,6 @@ func ComparePassword(hash string, password string) bool { return err == nil } -func IsUsernameValid(username string) bool { - - return true -} - var validUsernameChars = regexp.MustCompile(`^[a-z0-9\.\-_]+$`) var restrictedUsernames = []string{ -- cgit v1.2.3-1-g7c22 From 0eab8b88561638d02715ca3eca25c48439757bcc Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Thu, 10 Sep 2015 18:15:39 +0500 Subject: Fixing attachment button in RHS --- web/sass-files/sass/partials/_responsive.scss | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss index 65619f25a..32d65b86b 100644 --- a/web/sass-files/sass/partials/_responsive.scss +++ b/web/sass-files/sass/partials/_responsive.scss @@ -413,6 +413,9 @@ } } .post-create__container { + .post-right__container & { + padding: 0 1em; + } form { padding: 0; } @@ -425,13 +428,15 @@ display: table-cell; padding-left: 45px; } - .btn-file { - width: 45px; - padding: 0; - line-height: 36px; - bottom: -2px; - left: 0; - top: auto; + .app__content & { + .btn-file { + width: 45px; + padding: 0; + line-height: 36px; + bottom: -2px; + left: 0; + top: auto; + } } .send-button { display: table-cell; -- cgit v1.2.3-1-g7c22 From 9fd1f500147928ca0006de284c5bd38c40a82365 Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Thu, 10 Sep 2015 09:32:29 -0400 Subject: Update SSO documentation with new config file setting. --- doc/integrations/sso/gitlab-sso.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/integrations/sso/gitlab-sso.md b/doc/integrations/sso/gitlab-sso.md index c0cd6cb45..6110db504 100644 --- a/doc/integrations/sso/gitlab-sso.md +++ b/doc/integrations/sso/gitlab-sso.md @@ -14,6 +14,6 @@ The following steps can be used to configure Mattermost to use GitLab as a singl * _TokenEndpoint_: `/oauth/token` * _UserApiEndpoint_: `/api/v3/user` -6. (Optional) If you would like to force all users to sign-up with GitLab only, in the _ServiceSettings_ section of config/config.json please set _AllowEmailSignUp_ to `false`. +6. (Optional) If you would like to force all users to sign-up with GitLab only, in the _ServiceSettings_ section of config/config.json please set _DisableEmailSignUp_ to `true`. 7. Restart your Mattermost server to see the changes take effect. -- cgit v1.2.3-1-g7c22 From edcc8109f6c1d9e25b84667b73bfe162deb0591f Mon Sep 17 00:00:00 2001 From: JoramWilander Date: Thu, 10 Sep 2015 10:43:14 -0400 Subject: Set EmailVerified to true for users signing up with SSO. --- web/web.go | 1 + 1 file changed, 1 insertion(+) diff --git a/web/web.go b/web/web.go index c79815a35..1709e1eec 100644 --- a/web/web.go +++ b/web/web.go @@ -561,6 +561,7 @@ func signupCompleteOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) } user.TeamId = team.Id + user.EmailVerified = true ruser := api.CreateUser(c, team, user) if c.Err != nil { -- cgit v1.2.3-1-g7c22 From 985426b9e73e43789d91d3f81465b681d30c9549 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Thu, 10 Sep 2015 14:28:36 -0400 Subject: Updating dockerfile for release v0.7.1 --- docker/0.7/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/0.7/Dockerfile b/docker/0.7/Dockerfile index 4a8623d33..6e82fd809 100644 --- a/docker/0.7/Dockerfile +++ b/docker/0.7/Dockerfile @@ -34,7 +34,7 @@ VOLUME /var/lib/mysql WORKDIR /mattermost # Copy over files -ADD https://github.com/mattermost/platform/releases/download/v0.7.0/mattermost.tar.gz / +ADD https://github.com/mattermost/platform/releases/download/v0.7.1/mattermost.tar.gz / RUN tar -zxvf /mattermost.tar.gz --strip-components=1 && rm /mattermost.tar.gz ADD config_docker.json / ADD docker-entry.sh / -- cgit v1.2.3-1-g7c22 From 1440c953817aac3a5ebc6762f6c3e2668213fd70 Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Thu, 10 Sep 2015 23:34:30 +0500 Subject: PM-183 and PM-162 - Updating UI for more modal and unread notification bar --- web/sass-files/sass/partials/_modal.scss | 13 +++++++------ web/sass-files/sass/partials/_sidebar--left.scss | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss index b94ed2113..d5db5fc46 100644 --- a/web/sass-files/sass/partials/_modal.scss +++ b/web/sass-files/sass/partials/_modal.scss @@ -118,12 +118,11 @@ } &#more_channels { .modal-body { - padding: 5px 10px; + padding: 0; } } .more-channel-table { margin: 0; - table-layout: fixed; p { font-size: 0.9em; overflow: hidden; @@ -138,16 +137,18 @@ } tbody { > tr { - &:first-child { - td { - border: none; - } + &:hover td { + background: #f7f7f7; } td { + width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + padding: 8px 8px 8px 15px; &.td--action { + text-align: right; + padding: 8px 15px 8px 8px; width: 70px; vertical-align: middle; } diff --git a/web/sass-files/sass/partials/_sidebar--left.scss b/web/sass-files/sass/partials/_sidebar--left.scss index 933be96ff..0a504b765 100644 --- a/web/sass-files/sass/partials/_sidebar--left.scss +++ b/web/sass-files/sass/partials/_sidebar--left.scss @@ -50,7 +50,7 @@ right: 0; width: 72%; color: #777; - background: #DCF0FF; + background: #2389D7; @include border-radius(50px); margin: 0 auto; padding: 3px 0 4px; -- cgit v1.2.3-1-g7c22 From 72b9c3d7f6b24f74e145b0be9d9e733f0364ccb6 Mon Sep 17 00:00:00 2001 From: Asaad Mahmood Date: Thu, 10 Sep 2015 23:36:10 +0500 Subject: Removing border from first tr tds in more modal --- web/sass-files/sass/partials/_modal.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss index d5db5fc46..d32306cbc 100644 --- a/web/sass-files/sass/partials/_modal.scss +++ b/web/sass-files/sass/partials/_modal.scss @@ -140,6 +140,11 @@ &:hover td { background: #f7f7f7; } + &:first-child { + td { + border: none; + } + } td { width: 100%; white-space: nowrap; -- cgit v1.2.3-1-g7c22