summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md2
-rw-r--r--api/channel.go8
-rw-r--r--api/channel_test.go3
-rw-r--r--api/post.go13
-rw-r--r--config/config.json4
-rw-r--r--doc/install/Administration.md30
-rw-r--r--doc/install/Production-Ubuntu.md2
-rw-r--r--doc/install/Upgrade-Guide.md8
-rw-r--r--doc/integrations/webhooks/Outgoing-Webhooks.md4
-rw-r--r--docker/1.2/Dockerfile2
-rw-r--r--docker/1.2/config_docker.json4
-rw-r--r--docker/dev/config_docker.json4
-rw-r--r--docker/local/config_docker.json4
-rw-r--r--model/version.go1
-rw-r--r--store/sql_user_store.go2
-rw-r--r--utils/textgeneration.go15
-rw-r--r--web/react/components/channel_header.jsx41
-rw-r--r--web/react/components/navbar.jsx56
-rw-r--r--web/react/components/post_body.jsx38
-rw-r--r--web/react/components/rename_channel_modal.jsx51
-rw-r--r--web/react/components/rhs_thread.jsx2
-rw-r--r--web/react/components/search_results.jsx2
-rw-r--r--web/react/components/sidebar_right_menu.jsx18
-rw-r--r--web/react/components/team_general_tab.jsx3
-rw-r--r--web/react/components/tutorial/tutorial_tip.jsx27
-rw-r--r--web/react/components/user_settings/user_settings_general.jsx164
-rw-r--r--web/react/utils/channel_intro_mssages.jsx1
-rw-r--r--web/react/utils/utils.jsx6
-rw-r--r--web/sass-files/sass/partials/_content.scss1
-rw-r--r--web/sass-files/sass/partials/_files.scss8
-rw-r--r--web/sass-files/sass/partials/_forms.scss4
-rw-r--r--web/sass-files/sass/partials/_headers.scss31
-rw-r--r--web/sass-files/sass/partials/_post.scss15
-rw-r--r--web/sass-files/sass/partials/_post_right.scss17
-rw-r--r--web/sass-files/sass/partials/_responsive.scss36
-rw-r--r--web/sass-files/sass/partials/_search.scss11
-rw-r--r--web/sass-files/sass/partials/_sidebar--left.scss2
-rw-r--r--web/sass-files/sass/partials/_sidebar--menu.scss5
-rw-r--r--web/sass-files/sass/partials/_sidebar--right.scss26
-rw-r--r--web/sass-files/sass/partials/_videos.scss2
-rw-r--r--web/web.go2
41 files changed, 395 insertions, 280 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 74c5b5208..3e423557d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -98,7 +98,7 @@ Multiple settings were added to [`config.json`](./config/config.json). These opt
- Added: `"RestrictTeamNames": true` to control whether team names can contain reserved words like www, admin, support, test, etc.
- Added: `"EnableTeamListing": false` to control whether teams can be listed on the root page of the site
- Under `ServiceSettings` in `config.json`
- - Added: `EnableOutgoingWebhooks": true` to turn on outgoing webhooks
+ - Added: `EnableOutgoingWebhooks": false` to control whether outgoing webhooks are enabled
#### Database Changes from v1.1 to v1.2
diff --git a/api/channel.go b/api/channel.go
index 75ca9680d..99640e71a 100644
--- a/api/channel.go
+++ b/api/channel.go
@@ -205,9 +205,11 @@ func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) {
}
if oldChannel.Name == model.DEFAULT_CHANNEL {
- c.Err = model.NewAppError("updateChannel", "Cannot update the default channel "+model.DEFAULT_CHANNEL, "")
- c.Err.StatusCode = http.StatusForbidden
- return
+ if (len(channel.Name) > 0 && channel.Name != oldChannel.Name) || (len(channel.Type) > 0 && channel.Type != oldChannel.Type) {
+ c.Err = model.NewAppError("updateChannel", "Tried to perform an invalid update of the default channel "+model.DEFAULT_CHANNEL, "")
+ c.Err.StatusCode = http.StatusForbidden
+ return
+ }
}
oldChannel.Header = channel.Header
diff --git a/api/channel_test.go b/api/channel_test.go
index faed387dd..e7e1f4eb0 100644
--- a/api/channel_test.go
+++ b/api/channel_test.go
@@ -215,8 +215,9 @@ func TestUpdateChannel(t *testing.T) {
for _, c := range data.Channels {
if c.Name == model.DEFAULT_CHANNEL {
c.Header = "new header"
+ c.Name = "pseudo-square"
if _, err := Client.UpdateChannel(c); err == nil {
- t.Fatal("should have errored on updating default channel")
+ t.Fatal("should have errored on updating default channel name")
}
break
}
diff --git a/api/post.go b/api/post.go
index 3892d4ee8..0860fd299 100644
--- a/api/post.go
+++ b/api/post.go
@@ -229,6 +229,14 @@ func handlePostEventsAndForget(c *Context, post *model.Post, triggerWebhooks boo
func handleWebhookEventsAndForget(c *Context, post *model.Post, team *model.Team, channel *model.Channel, user *model.User) {
go func() {
+ if !utils.Cfg.ServiceSettings.EnableOutgoingWebhooks {
+ return
+ }
+
+ if channel.Type != model.CHANNEL_OPEN {
+ return
+ }
+
hchan := Srv.Store.Webhook().GetOutgoingByTeam(c.Session.TeamId)
hooks := []*model.OutgoingWebhook{}
@@ -536,11 +544,10 @@ func sendNotificationsAndForget(c *Context, post *model.Post, team *model.Team,
alreadySeen := make(map[string]string)
for _, session := range sessions {
- if len(session.DeviceId) > 0 && alreadySeen[session.DeviceId] == "" {
-
+ if len(session.DeviceId) > 0 && alreadySeen[session.DeviceId] == "" && strings.HasPrefix(session.DeviceId, "apple:") {
alreadySeen[session.DeviceId] = session.DeviceId
- utils.SendAppleNotifyAndForget(session.DeviceId, subjectPage.Render(), 1)
+ utils.SendAppleNotifyAndForget(strings.TrimPrefix(session.DeviceId, "apple:"), subjectPage.Render(), 1)
}
}
}
diff --git a/config/config.json b/config/config.json
index a927620b5..932bed8a2 100644
--- a/config/config.json
+++ b/config/config.json
@@ -5,8 +5,8 @@
"SegmentDeveloperKey": "",
"GoogleDeveloperKey": "",
"EnableOAuthServiceProvider": false,
- "EnableIncomingWebhooks": true,
- "EnableOutgoingWebhooks": true,
+ "EnableIncomingWebhooks": false,
+ "EnableOutgoingWebhooks": false,
"EnablePostUsernameOverride": false,
"EnablePostIconOverride": false,
"EnableTesting": false,
diff --git a/doc/install/Administration.md b/doc/install/Administration.md
index 76ec78abd..15bd07778 100644
--- a/doc/install/Administration.md
+++ b/doc/install/Administration.md
@@ -33,11 +33,35 @@ For help and support around your GitLab Mattermost deployment please see:
- [GitLab Mattermost discussion forum](https://forum.mattermost.org/c/general/gitlab)
- [GitLab Mattermost issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab-mattermost/issues)
-### Setting up realtime notifications from GitLab to Mattermost
+### Connecting Mattermost to integrations with incoming webhooks
-To set up standard notification from GitLab to Mattermost [follow these steps](https://github.com/mattermost/platform/blob/master/doc/integrations/webhooks/Incoming-Webhooks.md#connecting-mattermost-to-gitlab-using-slack-ui).
+#### Connecting Mattermost to GitLab for Slack-equivalent functionality.
-To set up a set of fully customizable realtime notifications from GitLab to Mattermost you can run the [GitLab Integration Service for Mattermost](https://github.com/mattermost/mattermost-integration-gitlab).
+Mattermost is designed to be _Slack-compatible, not Slack-limited_ and supports integration via the Slack UI in GitLab, as well as fully customizable integrations.
+
+To enable this:
+
+1. In Mattermost, from a team site where you have System Administration privileges, from the main menu go to **System Console** > **Serice Settings** > **Enable Incoming Webhooks** and select **true** then click **Save**
+
+2. Follow the step-by-step example of [connecting Mattermost incoming webhooks to GitLab's Slack webhooks UI](https://github.com/mattermost/platform/blob/master/doc/integrations/webhooks/Incoming-Webhooks.md#connecting-mattermost-to-gitlab-using-slack-ui).
+
+#### Connecting Mattermost to GitLab for functionality exceeding Slack integration.
+
+To enable this:
+
+1. In Mattermost, from a team site where you have System Administration privileges, from the main menu go to **System Console** > **Serice Settings** > **Enable Incoming Webhooks** and select **true** then click **Save**
+
+2. Set up the [GitLab Integration Service for Mattermost](https://github.com/mattermost/mattermost-integration-gitlab).
+
+### Connecting Mattermost to integrations with outgoing webhooks
+
+Mattermost offers Slack-compatible outgoing webhooks, that can connect to applications created by the Mattermost community, such as [Hubot](https://www.npmjs.com/package/hubot-mattermost) and [IRC](https://github.com/42wim/matterbridge) support.
+
+To enable this:
+
+1. In Mattermost, from a team site where you have System Administration privileges, from the main menu go to **System Console** > **Serice Settings** > **Enable Outgoing Webhooks** and select **true** then click **Save**
+
+2. Select a [Mattermost community application](http://www.mattermost.org/community-applications/) using outgoing webhooks--or adapt a Slack application using the same outgoing webhook standard--and follow the setup instructions provided.
### Upgrading GitLab Mattermost manually
diff --git a/doc/install/Production-Ubuntu.md b/doc/install/Production-Ubuntu.md
index 098707ada..482c2a0ba 100644
--- a/doc/install/Production-Ubuntu.md
+++ b/doc/install/Production-Ubuntu.md
@@ -38,7 +38,7 @@
## Set up Mattermost Server
1. For the purposes of this guide we will assume this server has an IP address of 10.10.10.2
1. Download the latest Mattermost Server by typing:
- * ``` wget https://github.com/mattermost/platform/releases/download/v1.1.0/mattermost.tar.gz```
+ * ``` wget https://github.com/mattermost/platform/releases/download/v1.2.1/mattermost.tar.gz```
1. Unzip the Mattermost Server by typing:
* ``` tar -xvzf mattermost.tar.gz```
1. For the sake of making this guide simple we located the files at `/home/ubuntu/mattermost`. In the future we will give guidance for storing under `/opt`.
diff --git a/doc/install/Upgrade-Guide.md b/doc/install/Upgrade-Guide.md
index 7f4eeaeb9..aec9aa8ef 100644
--- a/doc/install/Upgrade-Guide.md
+++ b/doc/install/Upgrade-Guide.md
@@ -4,6 +4,8 @@
Each release of Mattermost contains logic to upgrade it from the previously major build version. For example, version 1.2 upgrades the database and configuration data schema for a Mattermost version 1.1 server. The following procedure outlines how to upgrade Mattermost to the next major release version.
+If you're upgrading across multiple major releases, from 1.0.x to 1.2.x for example, please run the following procedure once for each incremental upgrade, in sequential order.
+
1. Download the **next major build release** of your server
1. Determine the current version of your Mattermost server
1. Go to any team site, opening the main menu at the top right of the left-hand sidebar and selecting **About Mattermost**
@@ -11,7 +13,7 @@ Each release of Mattermost contains logic to upgrade it from the previously majo
1. For example, if your current version is 1.1.0, you want to select version 1.2.0.
1. In some cases there will be **minor build releases**, such as 1.2.1 and 1.2.2. The minor build number indicates a bug fix or security issue release. Testing on minor build versions is less extensive than on major build versions and it is recommended that you use the minor build only if you need the specific additions included.
3. Review Release Notes
- 1. Check the release notes for the version of Mattermost you are able to install, and note any setting changes in the **Compatibility** section that apply to your deployment
+ 1. Check the release notes for the version of Mattermost you are able to install, and note any setting changes in the **Compatibility** section that apply to your deployment (Release notes across versions are available from the [CHANGELOG](https://github.com/mattermost/platform/blob/master/CHANGELOG.md)).
4. Download the `mattermost.tar.gz` file with the correct version for your upgrade
1. You can use `wget` to retrieve a specific version. For example, to download v1.1.0 run `wget https://github.com/mattermost/platform/releases/download/v1.1.0/mattermost.tar.gz`
2. Stop the Mattermost Server
@@ -26,8 +28,8 @@ Each release of Mattermost contains logic to upgrade it from the previously majo
5. Restore the state of your server by copying the backed up version of `config.json` in place of the default `config.json`
6. Start your server and address any setting changes relevant in the latest version of Mattermost
1. Run `sudo start mattermost`
- 2. The server will upgrade your database schema to be compatibile with the new release, as well as upgrade your `config.json` file to the latest format, using default values for new settings added
- 3. Go to the System Console to update any settings that have been added or modified based on the **Compatibility** documentation in the release notes
+ 2. Go to the **System Console** to update any settings that have been added or modified based on the **Compatibility** section in the release notes of the version you are installing (Release notes across versions are available from the [CHANGELOG](https://github.com/mattermost/platform/blob/master/CHANGELOG.md)).
+ 1. Opening the System Console and saving a change will upgrade your `config.json` schema to the latest version using default values for new settings added
7. Test the system is working by going to the URL of an existing team
### Upgrading from Mattermost Beta (Version 0.7)
diff --git a/doc/integrations/webhooks/Outgoing-Webhooks.md b/doc/integrations/webhooks/Outgoing-Webhooks.md
index 008245715..5fbbb9d87 100644
--- a/doc/integrations/webhooks/Outgoing-Webhooks.md
+++ b/doc/integrations/webhooks/Outgoing-Webhooks.md
@@ -1,7 +1,5 @@
# Outgoing Webhooks
-#### [To be released in Mattermost v1.2, available now on master]
-
Outgoing webhooks allow external applications, written in the programming language of your choice--to receive HTTP POST requests whenever a user posts to a certain channel, with a trigger word at the beginning of the message, or a combination of both. If the external application responds appropriately to the HTTP request, as response post can be made in the channel where the original post occurred.
A couple key points:
@@ -36,7 +34,7 @@ Which would render in a Mattermost message as follows:
---
### Enabling Outgoing Webhooks
-Outgoing webhooks should be enabled on your Mattermost instance by default, but if they are not you'll need to get your system administrator to enable them. If you are the system administrator you can enable them by doing the following:
+Outgoing webhooks are off by default, and can be enabled by the system administrator. If you are the system administrator you can enable them by doing the following:
1. Login to your Mattermost team account that has the system administrator role.
1. Enable outgoing webhooks from **System Console -> Service Settings**.
diff --git a/docker/1.2/Dockerfile b/docker/1.2/Dockerfile
index cb2b58f53..e00c4e5ca 100644
--- a/docker/1.2/Dockerfile
+++ b/docker/1.2/Dockerfile
@@ -34,7 +34,7 @@ VOLUME /var/lib/mysql
WORKDIR /mattermost
# Copy over files
-ADD https://github.com/mattermost/platform/releases/download/v1.2.0/mattermost.tar.gz /
+ADD https://github.com/mattermost/platform/releases/download/v1.2.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 /
diff --git a/docker/1.2/config_docker.json b/docker/1.2/config_docker.json
index 80e6ab14e..c23a72cd1 100644
--- a/docker/1.2/config_docker.json
+++ b/docker/1.2/config_docker.json
@@ -5,8 +5,8 @@
"SegmentDeveloperKey": "",
"GoogleDeveloperKey": "",
"EnableOAuthServiceProvider": false,
- "EnableIncomingWebhooks": true,
- "EnableOutgoingWebhooks": true,
+ "EnableIncomingWebhooks": false,
+ "EnableOutgoingWebhooks": false,
"EnablePostUsernameOverride": false,
"EnablePostIconOverride": false,
"EnableTesting": false,
diff --git a/docker/dev/config_docker.json b/docker/dev/config_docker.json
index 80e6ab14e..c23a72cd1 100644
--- a/docker/dev/config_docker.json
+++ b/docker/dev/config_docker.json
@@ -5,8 +5,8 @@
"SegmentDeveloperKey": "",
"GoogleDeveloperKey": "",
"EnableOAuthServiceProvider": false,
- "EnableIncomingWebhooks": true,
- "EnableOutgoingWebhooks": true,
+ "EnableIncomingWebhooks": false,
+ "EnableOutgoingWebhooks": false,
"EnablePostUsernameOverride": false,
"EnablePostIconOverride": false,
"EnableTesting": false,
diff --git a/docker/local/config_docker.json b/docker/local/config_docker.json
index 80e6ab14e..c23a72cd1 100644
--- a/docker/local/config_docker.json
+++ b/docker/local/config_docker.json
@@ -5,8 +5,8 @@
"SegmentDeveloperKey": "",
"GoogleDeveloperKey": "",
"EnableOAuthServiceProvider": false,
- "EnableIncomingWebhooks": true,
- "EnableOutgoingWebhooks": true,
+ "EnableIncomingWebhooks": false,
+ "EnableOutgoingWebhooks": false,
"EnablePostUsernameOverride": false,
"EnablePostIconOverride": false,
"EnableTesting": false,
diff --git a/model/version.go b/model/version.go
index 6eae436bc..af99717cd 100644
--- a/model/version.go
+++ b/model/version.go
@@ -12,6 +12,7 @@ import (
// It should be maitained in chronological order with most current
// release at the front of the list.
var versions = []string{
+ "1.2.1",
"1.2.0",
"1.1.0",
"1.0.0",
diff --git a/store/sql_user_store.go b/store/sql_user_store.go
index d38b5c214..77ff5bfab 100644
--- a/store/sql_user_store.go
+++ b/store/sql_user_store.go
@@ -157,7 +157,7 @@ func (us SqlUserStore) Update(user *model.User, allowActiveUpdate bool) StoreCha
if count, err := us.GetMaster().Update(user); err != nil {
if IsUniqueConstraintError(err.Error(), "Email", "users_email_teamid_key") {
- result.Err = model.NewAppError("SqlUserStore.Update", "This email is already taken. Please choose another", "user_id="+user.Id+", "+err.Error())
+ result.Err = model.NewAppError("SqlUserStore.Update", "This email is already taken. Please choose another.", "user_id="+user.Id+", "+err.Error())
} else if IsUniqueConstraintError(err.Error(), "Username", "users_username_teamid_key") {
result.Err = model.NewAppError("SqlUserStore.Update", "This username is already taken. Please choose another.", "user_id="+user.Id+", "+err.Error())
} else {
diff --git a/utils/textgeneration.go b/utils/textgeneration.go
index affd65bc1..31b6517b8 100644
--- a/utils/textgeneration.go
+++ b/utils/textgeneration.go
@@ -126,6 +126,13 @@ http://en.wikipedia.org/wiki/Foo
https://vine.co/v/eDeVgbFrt9L
`,
+ `**[7] [Image Test]**
+
+## this *should* display an image
+
+http://37.media.tumblr.com/tumblr_mavsumGGAd1qboaw8o1_500.jpg
+`,
+
/* `**[2] [Username Linking Test]**
I saw @alice--and I said "Hi @alice!" then "What's up @alice?" and then @alice, was totally @alice; she just "@alice"'d me and walked on by. That's @alice...
@alice‽‽
@@ -134,7 +141,7 @@ https://vine.co/v/eDeVgbFrt9L
`**[3] [Mention Highlighting Test]**
`,*/
- `**[4] [Emoji Display Test 1]**
+ `**[8] [Emoji Display Test 1]**
:+1: :-1: :100: :1234: :8ball: :a: :ab: :abc: :abcd: :accept:
:aerial_tramway: :airplane: :alarm_clock: :ambulance: :anchor: :angel: :anger: :angry: :anguished: :ant:
:apple: :aquarius: :aries: :arrow_backward: :arrow_double_down: :arrow_double_up: :arrow_down: :arrow_down_small: :arrow_forward: :arrow_heading_down:
@@ -169,7 +176,7 @@ https://vine.co/v/eDeVgbFrt9L
:fork_and_knife: :fountain: :four: :four_leaf_clover: :fr: :free: :fried_shrimp: :fries: :frog: :frowning:
:fu: :fuelpump: :full_moon: :full_moon_with_face: :game_die: :gb: :gem: :gemini: :ghost: :gift:`,
- `**[5] [Emoji Display Test 2]**
+ `**[9] [Emoji Display Test 2]**
:gift_heart: :girl: :globe_with_meridians: :goat: :goberserk: :godmode: :golf: :grapes: :green_apple: :green_book:
:green_heart: :grey_exclamation: :grey_question: :grimacing: :grin: :grinning: :guardsman: :guitar: :gun: :haircut:
:hamburger: :hammer: :hamster: :hand: :handbag: :hankey: :hash: :hatched_chick: :hatching_chick: :headphones:
@@ -198,7 +205,7 @@ https://vine.co/v/eDeVgbFrt9L
:person_with_blond_hair: :person_with_pouting_face: :phone: :pig: :pig2: :pig_nose: :pill: :pineapple: :pisces: :pizza:
`,
- `**[6] [Emoji Display Test 3]**
+ `**[10] [Emoji Display Test 3]**
:plus1: :point_down: :point_left: :point_right: :point_up: :point_up_2: :police_car: :poodle: :poop: :post_office:
:postal_horn: :postbox: :potable_water: :pouch: :poultry_leg: :pound: :pouting_cat: :pray: :princess: :punch:
:purple_heart: :purse: :pushpin: :put_litter_in_its_place: :question: :rabbit: :rabbit2: :racehorse: :radio: :radio_button:
@@ -231,7 +238,7 @@ https://vine.co/v/eDeVgbFrt9L
Unnamed: :u5272: :u5408: :u55b6: :u6307: :u6708: :u6709: :u6e80: :u7121: :u7533: :u7981: :u7a7a:
`,
- `**[7] [Auto Linking]**
+ `**[11] [Auto Linking]**
#### should be turned into links:
http://example.com
https://example.com
diff --git a/web/react/components/channel_header.jsx b/web/react/components/channel_header.jsx
index a8d4ec100..79c7f90a9 100644
--- a/web/react/components/channel_header.jsx
+++ b/web/react/components/channel_header.jsx
@@ -276,26 +276,27 @@ export default class ChannelHeader extends React.Component {
</li>
);
- if (!ChannelStore.isDefault(channel)) {
- if (isAdmin) {
- dropdownContents.push(
- <li
- key='rename_channel'
- role='presentation'
+ if (isAdmin) {
+ dropdownContents.push(
+ <li
+ key='rename_channel'
+ role='presentation'
+ >
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#rename_channel'
+ data-display={channel.display_name}
+ data-name={channel.name}
+ data-channelid={channel.id}
>
- <a
- role='menuitem'
- href='#'
- data-toggle='modal'
- data-target='#rename_channel'
- data-display={channel.display_name}
- data-name={channel.name}
- data-channelid={channel.id}
- >
- {'Rename '}{channelTerm}{'...'}
- </a>
- </li>
- );
+ {'Rename '}{channelTerm}{'...'}
+ </a>
+ </li>
+ );
+
+ if (!ChannelStore.isDefault(channel)) {
dropdownContents.push(
<li
key='delete_channel'
@@ -314,7 +315,9 @@ export default class ChannelHeader extends React.Component {
</li>
);
}
+ }
+ if (!ChannelStore.isDefault(channel)) {
dropdownContents.push(
<li
key='leave_channel'
diff --git a/web/react/components/navbar.jsx b/web/react/components/navbar.jsx
index af29f219e..7ad1f9305 100644
--- a/web/react/components/navbar.jsx
+++ b/web/react/components/navbar.jsx
@@ -178,18 +178,35 @@ export default class Navbar extends React.Component {
var manageMembersOption;
var renameChannelOption;
var deleteChannelOption;
- if (!isDirect && isAdmin && !ChannelStore.isDefault(channel)) {
- manageMembersOption = (
- <li role='presentation'>
- <a
- role='menuitem'
- href='#'
- onClick={() => this.setState({showMembersModal: true})}
- >
- {'Manage Members'}
- </a>
- </li>
- );
+ if (!isDirect && isAdmin) {
+ if (!ChannelStore.isDefault(channel)) {
+ manageMembersOption = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ onClick={() => this.setState({showMembersModal: true})}
+ >
+ {'Manage Members'}
+ </a>
+ </li>
+ );
+
+ deleteChannelOption = (
+ <li role='presentation'>
+ <a
+ role='menuitem'
+ href='#'
+ data-toggle='modal'
+ data-target='#delete_channel'
+ data-title={channel.display_name}
+ data-channelid={channel.id}
+ >
+ {'Delete Channel...'}
+ </a>
+ </li>
+ );
+ }
renameChannelOption = (
<li role='presentation'>
@@ -206,21 +223,6 @@ export default class Navbar extends React.Component {
</a>
</li>
);
-
- deleteChannelOption = (
- <li role='presentation'>
- <a
- role='menuitem'
- href='#'
- data-toggle='modal'
- data-target='#delete_channel'
- data-title={channel.display_name}
- data-channelid={channel.id}
- >
- {'Delete Channel...'}
- </a>
- </li>
- );
}
var notificationPreferenceOption;
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index 617b4b36c..975ac64dc 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -16,13 +16,13 @@ export default class PostBody extends React.Component {
super(props);
this.receivedYoutubeData = false;
- this.isGifLoading = false;
+ this.isImgLoading = false;
this.handleUserChange = this.handleUserChange.bind(this);
this.parseEmojis = this.parseEmojis.bind(this);
this.createEmbed = this.createEmbed.bind(this);
- this.createGifEmbed = this.createGifEmbed.bind(this);
- this.loadGif = this.loadGif.bind(this);
+ this.createImageEmbed = this.createImageEmbed.bind(this);
+ this.loadImg = this.loadImg.bind(this);
this.createYoutubeEmbed = this.createYoutubeEmbed.bind(this);
const linkData = Utils.extractLinks(this.props.post.message);
@@ -117,8 +117,12 @@ export default class PostBody extends React.Component {
return embed;
}
- if (link.substring(link.length - 4) === '.gif') {
- return this.createGifEmbed(link, this.state.gifLoaded);
+ for (let i = 0; i < Constants.IMAGE_TYPES.length; i++) {
+ const imageType = Constants.IMAGE_TYPES[i];
+ const suffix = link.substring(link.length - (imageType.length + 1));
+ if (suffix === '.' + imageType || suffix === '=' + imageType) {
+ return this.createImageEmbed(link, this.state.imgLoaded);
+ }
}
return null;
@@ -135,29 +139,29 @@ export default class PostBody extends React.Component {
return false;
}
- loadGif(src) {
- if (this.isGifLoading) {
+ loadImg(src) {
+ if (this.isImgLoading) {
return;
}
- this.isGifLoading = true;
+ this.isImgLoading = true;
- const gif = new Image();
- gif.onload = (
+ const img = new Image();
+ img.onload = (
() => {
- this.embed = this.createGifEmbed(src, true);
- this.setState({gifLoaded: true});
+ this.embed = this.createImageEmbed(src, true);
+ this.setState({imgLoaded: true});
}
);
- gif.src = src;
+ img.src = src;
}
- createGifEmbed(link, isLoaded) {
+ createImageEmbed(link, isLoaded) {
if (!isLoaded) {
- this.loadGif(link);
+ this.loadImg(link);
return (
<img
- className='gif-div placeholder'
+ className='img-div placeholder'
height='500px'
/>
);
@@ -165,7 +169,7 @@ export default class PostBody extends React.Component {
return (
<img
- className='gif-div'
+ className='img-div'
src={link}
/>
);
diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx
index 9fb3af035..f47009cce 100644
--- a/web/react/components/rename_channel_modal.jsx
+++ b/web/react/components/rename_channel_modal.jsx
@@ -5,6 +5,7 @@ const Utils = require('../utils/utils.jsx');
const Client = require('../utils/client.jsx');
const AsyncClient = require('../utils/async_client.jsx');
const ChannelStore = require('../stores/channel_store.jsx');
+const Constants = require('../utils/constants.jsx');
export default class RenameChannelModal extends React.Component {
constructor(props) {
@@ -36,10 +37,10 @@ export default class RenameChannelModal extends React.Component {
return;
}
- let channel = ChannelStore.get(this.state.channelId);
+ const channel = ChannelStore.get(this.state.channelId);
const oldName = channel.name;
const oldDisplayName = channel.displayName;
- let state = {serverError: ''};
+ const state = {serverError: ''};
channel.display_name = this.state.displayName.trim();
if (!channel.display_name) {
@@ -60,7 +61,7 @@ export default class RenameChannelModal extends React.Component {
state.nameError = 'This field must be less than 22 characters';
state.invalid = true;
} else {
- let cleanedName = Utils.cleanUpUrlable(channel.name);
+ const cleanedName = Utils.cleanUpUrlable(channel.name);
if (cleanedName === channel.name) {
state.nameError = '';
} else {
@@ -76,7 +77,7 @@ export default class RenameChannelModal extends React.Component {
}
Client.updateChannel(channel,
- function handleUpdateSuccess() {
+ () => {
$(ReactDOM.findDOMNode(this.refs.modal)).modal('hide');
AsyncClient.getChannel(channel.id);
@@ -84,12 +85,12 @@ export default class RenameChannelModal extends React.Component {
ReactDOM.findDOMNode(this.refs.displayName).value = '';
ReactDOM.findDOMNode(this.refs.channelName).value = '';
- }.bind(this),
- function handleUpdateError(err) {
+ },
+ (err) => {
state.serverError = err.message;
state.invalid = true;
this.setState(state);
- }.bind(this)
+ }
);
}
onNameChange() {
@@ -99,10 +100,12 @@ export default class RenameChannelModal extends React.Component {
this.setState({displayName: ReactDOM.findDOMNode(this.refs.displayName).value});
}
displayNameKeyUp() {
- const displayName = ReactDOM.findDOMNode(this.refs.displayName).value.trim();
- const channelName = Utils.cleanUpUrlable(displayName);
- ReactDOM.findDOMNode(this.refs.channelName).value = channelName;
- this.setState({channelName: channelName});
+ if (this.state.channelName !== Constants.DEFAULT_CHANNEL) {
+ const displayName = ReactDOM.findDOMNode(this.refs.displayName).value.trim();
+ const channelName = Utils.cleanUpUrlable(displayName);
+ ReactDOM.findDOMNode(this.refs.channelName).value = channelName;
+ this.setState({channelName: channelName});
+ }
}
handleClose() {
this.setState({
@@ -150,6 +153,15 @@ export default class RenameChannelModal extends React.Component {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
+ let handleInputLabel = 'Handle';
+ let handleInputClass = 'form-control';
+ let readOnlyHandleInput = false;
+ if (this.state.channelName === Constants.DEFAULT_CHANNEL) {
+ handleInputLabel += ' - Cannot be changed for the default channel';
+ handleInputClass += ' disabled-input';
+ readOnlyHandleInput = true;
+ }
+
return (
<div
className='modal fade'
@@ -167,15 +179,15 @@ export default class RenameChannelModal extends React.Component {
className='close'
data-dismiss='modal'
>
- <span aria-hidden='true'>&times;</span>
- <span className='sr-only'>Close</span>
+ <span aria-hidden='true'>{'×'}</span>
+ <span className='sr-only'>{'Close'}</span>
</button>
- <h4 className='modal-title'>Rename Channel</h4>
+ <h4 className='modal-title'>{'Rename Channel'}</h4>
</div>
<form role='form'>
<div className='modal-body'>
<div className={displayNameClass}>
- <label className='control-label'>Display Name</label>
+ <label className='control-label'>{'Display Name'}</label>
<input
onKeyUp={this.displayNameKeyUp}
onChange={this.onDisplayNameChange}
@@ -190,15 +202,16 @@ export default class RenameChannelModal extends React.Component {
{displayNameError}
</div>
<div className={nameClass}>
- <label className='control-label'>Handle</label>
+ <label className='control-label'>{handleInputLabel}</label>
<input
onChange={this.onNameChange}
type='text'
- className='form-control'
+ className={handleInputClass}
ref='channelName'
placeholder='lowercase alphanumeric&#39;s only'
value={this.state.channelName}
maxLength='64'
+ readOnly={readOnlyHandleInput}
/>
{nameError}
</div>
@@ -210,14 +223,14 @@ export default class RenameChannelModal extends React.Component {
className='btn btn-default'
data-dismiss='modal'
>
- Cancel
+ {'Cancel'}
</button>
<button
onClick={this.handleSubmit}
type='submit'
className='btn btn-primary'
>
- Save
+ {'Save'}
</button>
</div>
</form>
diff --git a/web/react/components/rhs_thread.jsx b/web/react/components/rhs_thread.jsx
index 7c11de7cf..cc062c538 100644
--- a/web/react/components/rhs_thread.jsx
+++ b/web/react/components/rhs_thread.jsx
@@ -117,8 +117,6 @@ export default class RhsThread extends React.Component {
}
}
resize() {
- var height = this.state.windowHeight - $('#error_bar').outerHeight() - 100;
- $('.post-right__scroll').css('height', height + 'px');
$('.post-right__scroll').scrollTop(100000);
if (this.state.windowWidth > 768) {
$('.post-right__scroll').perfectScrollbar();
diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx
index 2f0068908..f4d8647db 100644
--- a/web/react/components/search_results.jsx
+++ b/web/react/components/search_results.jsx
@@ -62,8 +62,6 @@ export default class SearchResults extends React.Component {
}
resize() {
- var height = this.state.windowHeight - $('#error_bar').outerHeight() - 100;
- $('#search-items-container').css('height', height + 'px');
$('#search-items-container').scrollTop(0);
if (this.state.windowWidth > 768) {
$('#search-items-container').perfectScrollbar();
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index 2135e3ef3..6a428e884 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -48,7 +48,7 @@ export default class SidebarRightMenu extends React.Component {
href='#'
onClick={InviteMemberModal.show}
>
- <i className='glyphicon glyphicon-user'></i>Invite New Member
+ <i className='fa fa-user'></i>Invite New Member
</a>
</li>
);
@@ -61,7 +61,7 @@ export default class SidebarRightMenu extends React.Component {
data-target='#get_link'
data-title='Team Invite'
data-value={utils.getWindowLocationOrigin() + '/signup_user_complete/?id=' + TeamStore.getCurrent().invite_id}
- ><i className='glyphicon glyphicon-link'></i>Get Team Invite Link</a>
+ ><i className='fa fa-link'></i>Get Team Invite Link</a>
</li>
);
}
@@ -74,7 +74,7 @@ export default class SidebarRightMenu extends React.Component {
href='#'
data-toggle='modal'
data-target='#team_settings'
- ><i className='glyphicon glyphicon-globe'></i>Team Settings</a>
+ ><i className='fa fa-globe'></i>Team Settings</a>
</li>
);
manageLink = (
@@ -84,7 +84,7 @@ export default class SidebarRightMenu extends React.Component {
data-toggle='modal'
data-target='#team_members'
>
- <i className='glyphicon glyphicon-wrench'></i>Manage Members</a>
+ <i className='fa fa-users'></i>Manage Members</a>
</li>
);
}
@@ -95,7 +95,7 @@ export default class SidebarRightMenu extends React.Component {
<a
href={'/admin_console?' + utils.getSessionIndex()}
>
- <i className='glyphicon glyphicon-wrench'></i>System Console</a>
+ <i className='fa fa-wrench'></i>System Console</a>
</li>
);
}
@@ -125,7 +125,7 @@ export default class SidebarRightMenu extends React.Component {
href='#'
onClick={() => this.setState({showUserSettingsModal: true})}
>
- <i className='glyphicon glyphicon-cog'></i>Account Settings
+ <i className='fa fa-cog'></i>Account Settings
</a>
</li>
{teamSettingsLink}
@@ -137,18 +137,18 @@ export default class SidebarRightMenu extends React.Component {
<a
href='#'
onClick={this.handleLogoutClick}
- ><i className='glyphicon glyphicon-log-out'></i>Logout</a></li>
+ ><i className='fa fa-sign-out'></i>Logout</a></li>
<li className='divider'></li>
<li>
<a
target='_blank'
href='/static/help/configure_links.html'
- ><i className='glyphicon glyphicon-question-sign'></i>Help</a></li>
+ ><i className='fa fa-question'></i>Help</a></li>
<li>
<a
target='_blank'
href='/static/help/configure_links.html'
- ><i className='glyphicon glyphicon-earphone'></i>Report a Problem</a></li>
+ ><i className='fa fa-phone'></i>Report a Problem</a></li>
</ul>
</div>
<UserSettingsModal
diff --git a/web/react/components/team_general_tab.jsx b/web/react/components/team_general_tab.jsx
index 587ef5ec2..a50859489 100644
--- a/web/react/components/team_general_tab.jsx
+++ b/web/react/components/team_general_tab.jsx
@@ -393,7 +393,7 @@ export default class GeneralTab extends React.Component {
</div>
</div>
</div>
- <div className='setting-list__hint'>{'When allowing open invites this code is used as part of the signup process. Changing this code will invalidate the previous open signup link.'}</div>
+ <div className='setting-list__hint'>{'Your Invite Code is used in the URL sent to people to join your team. Regenerating your Invite Code will invalidate the URLs in previous invitations, unless "Allow anyone to sign-up from login page" is enabled.'}</div>
</div>
);
@@ -452,6 +452,7 @@ export default class GeneralTab extends React.Component {
server_error={serverError}
client_error={clientError}
updateSection={this.onUpdateNameSection}
+ extraInfo='Set the name of the team as it appears on your sign-in screen and at the top of the left-hand sidebar.'
/>
);
} else {
diff --git a/web/react/components/tutorial/tutorial_tip.jsx b/web/react/components/tutorial/tutorial_tip.jsx
index 75d73e920..dd231b816 100644
--- a/web/react/components/tutorial/tutorial_tip.jsx
+++ b/web/react/components/tutorial/tutorial_tip.jsx
@@ -51,21 +51,22 @@ export default class TutorialTip extends React.Component {
const dots = [];
if (this.props.screens.length > 1) {
for (let i = 0; i < this.props.screens.length; i++) {
+ let className = 'circle';
if (i === this.state.currentScreen) {
- dots.push(
- <div
- className='circle active'
- key={'dotactive' + i}
- />
- );
- } else {
- dots.push(
- <div
- className='circle'
- key={'dotinactive' + i}
- />
- );
+ className += ' active';
}
+
+ dots.push(
+ <a
+ href='#'
+ key={'dotactive' + i}
+ className={className}
+ onClick={(e) => { //eslint-disable-line no-loop-func
+ e.preventDefault();
+ this.setState({currentScreen: i});
+ }}
+ />
+ );
}
}
diff --git a/web/react/components/user_settings/user_settings_general.jsx b/web/react/components/user_settings/user_settings_general.jsx
index 1bfae6930..b363f0673 100644
--- a/web/react/components/user_settings/user_settings_general.jsx
+++ b/web/react/components/user_settings/user_settings_general.jsx
@@ -1,15 +1,16 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-var UserStore = require('../../stores/user_store.jsx');
-var ErrorStore = require('../../stores/error_store.jsx');
-var SettingItemMin = require('../setting_item_min.jsx');
-var SettingItemMax = require('../setting_item_max.jsx');
-var SettingPicture = require('../setting_picture.jsx');
-var client = require('../../utils/client.jsx');
-var AsyncClient = require('../../utils/async_client.jsx');
-var utils = require('../../utils/utils.jsx');
-var assign = require('object-assign');
+const SettingItemMin = require('../setting_item_min.jsx');
+const SettingItemMax = require('../setting_item_max.jsx');
+const SettingPicture = require('../setting_picture.jsx');
+
+const UserStore = require('../../stores/user_store.jsx');
+const ErrorStore = require('../../stores/error_store.jsx');
+
+const Client = require('../../utils/client.jsx');
+const AsyncClient = require('../../utils/async_client.jsx');
+const Utils = require('../../utils/utils.jsx');
export default class UserSettingsGeneralTab extends React.Component {
constructor(props) {
@@ -32,17 +33,15 @@ export default class UserSettingsGeneralTab extends React.Component {
this.updatePicture = this.updatePicture.bind(this);
this.updateSection = this.updateSection.bind(this);
- this.setupInitialState = this.setupInitialState.bind(this);
-
this.state = this.setupInitialState(props);
}
submitUsername(e) {
e.preventDefault();
- var user = this.props.user;
- var username = this.state.username.trim().toLowerCase();
+ const user = Object.assign({}, this.props.user);
+ const username = this.state.username.trim().toLowerCase();
- var usernameError = utils.isValidUsername(username);
+ const usernameError = Utils.isValidUsername(username);
if (usernameError === 'Cannot use a reserved word as a username.') {
this.setState({clientError: 'This username is reserved, please choose a new one.'});
return;
@@ -52,7 +51,7 @@ export default class UserSettingsGeneralTab extends React.Component {
}
if (user.username === username) {
- this.setState({clientError: 'You must submit a new username'});
+ this.setState({clientError: 'You must submit a new username.', emailError: '', serverError: ''});
return;
}
@@ -63,11 +62,11 @@ export default class UserSettingsGeneralTab extends React.Component {
submitNickname(e) {
e.preventDefault();
- var user = UserStore.getCurrentUser();
- var nickname = this.state.nickname.trim();
+ const user = Object.assign({}, this.props.user);
+ const nickname = this.state.nickname.trim();
if (user.nickname === nickname) {
- this.setState({clientError: 'You must submit a new nickname'});
+ this.setState({clientError: 'You must submit a new nickname.', emailError: '', serverError: ''});
return;
}
@@ -78,12 +77,12 @@ export default class UserSettingsGeneralTab extends React.Component {
submitName(e) {
e.preventDefault();
- var user = UserStore.getCurrentUser();
- var firstName = this.state.firstName.trim();
- var lastName = this.state.lastName.trim();
+ const user = Object.assign({}, this.props.user);
+ const firstName = this.state.firstName.trim();
+ const lastName = this.state.lastName.trim();
if (user.first_name === firstName && user.last_name === lastName) {
- this.setState({clientError: 'You must submit a new first or last name'});
+ this.setState({clientError: 'You must submit a new first or last name.', emailError: '', serverError: ''});
return;
}
@@ -95,21 +94,21 @@ export default class UserSettingsGeneralTab extends React.Component {
submitEmail(e) {
e.preventDefault();
- var user = UserStore.getCurrentUser();
- var email = this.state.email.trim().toLowerCase();
- var confirmEmail = this.state.confirmEmail.trim().toLowerCase();
+ const user = Object.assign({}, this.props.user);
+ const email = this.state.email.trim().toLowerCase();
+ const confirmEmail = this.state.confirmEmail.trim().toLowerCase();
if (user.email === email) {
return;
}
- if (email === '' || !utils.isEmail(email)) {
- this.setState({emailError: 'Please enter a valid email address'});
+ if (email === '' || !Utils.isEmail(email)) {
+ this.setState({emailError: 'Please enter a valid email address.', clientError: '', serverError: ''});
return;
}
if (email !== confirmEmail) {
- this.setState({emailError: 'The new emails you entered do not match'});
+ this.setState({emailError: 'The new emails you entered do not match.', clientError: '', serverError: ''});
return;
}
@@ -117,7 +116,7 @@ export default class UserSettingsGeneralTab extends React.Component {
this.submitUser(user, true);
}
submitUser(user, emailUpdated) {
- client.updateUser(user,
+ Client.updateUser(user,
() => {
this.updateSection('');
AsyncClient.getMe();
@@ -130,13 +129,13 @@ export default class UserSettingsGeneralTab extends React.Component {
}
},
(err) => {
- var state = this.setupInitialState(this.props);
+ let serverError;
if (err.message) {
- state.serverError = err.message;
+ serverError = err.message;
} else {
- state.serverError = err;
+ serverError = err;
}
- this.setState(state);
+ this.setState({serverError, emailError: '', clientError: ''});
}
);
}
@@ -151,10 +150,10 @@ export default class UserSettingsGeneralTab extends React.Component {
return;
}
- var picture = this.state.picture;
+ const picture = this.state.picture;
if (picture.type !== 'image/jpeg' && picture.type !== 'image/png') {
- this.setState({clientError: 'Only JPG or PNG images may be used for profile pictures'});
+ this.setState({clientError: 'Only JPG or PNG images may be used for profile pictures.'});
return;
}
@@ -162,17 +161,17 @@ export default class UserSettingsGeneralTab extends React.Component {
formData.append('image', picture, picture.name);
this.setState({loadingPicture: true});
- client.uploadProfileImage(formData,
- function imageUploadSuccess() {
+ Client.uploadProfileImage(formData,
+ () => {
this.submitActive = false;
AsyncClient.getMe();
window.location.reload();
- }.bind(this),
- function imageUploadFailure(err) {
+ },
+ (err) => {
var state = this.setupInitialState(this.props);
state.serverError = err.message;
this.setState(state);
- }.bind(this)
+ }
);
}
updateUsername(e) {
@@ -205,34 +204,34 @@ export default class UserSettingsGeneralTab extends React.Component {
}
updateSection(section) {
const emailChangeInProgress = this.state.emailChangeInProgress;
- this.setState(assign({}, this.setupInitialState(this.props), {emailChangeInProgress: emailChangeInProgress, clientError: '', serverError: '', emailError: ''}));
+ this.setState(Object.assign({}, this.setupInitialState(this.props), {emailChangeInProgress, clientError: '', serverError: '', emailError: ''}));
this.submitActive = false;
this.props.updateSection(section);
}
setupInitialState(props) {
- var user = props.user;
+ const user = props.user;
return {username: user.username, firstName: user.first_name, lastName: user.last_name, nickname: user.nickname,
email: user.email, confirmEmail: '', picture: null, loadingPicture: false, emailChangeInProgress: false};
}
render() {
- var user = this.props.user;
+ const user = this.props.user;
- var clientError = null;
+ let clientError = null;
if (this.state.clientError) {
clientError = this.state.clientError;
}
- var serverError = null;
+ let serverError = null;
if (this.state.serverError) {
serverError = this.state.serverError;
}
- var emailError = null;
+ let emailError = null;
if (this.state.emailError) {
emailError = this.state.emailError;
}
- var nameSection;
- var inputs = [];
+ let nameSection;
+ const inputs = [];
if (this.props.activeSection === 'name') {
inputs.push(
@@ -298,15 +297,15 @@ export default class UserSettingsGeneralTab extends React.Component {
submit={this.submitName}
server_error={serverError}
client_error={clientError}
- updateSection={function clearSection(e) {
+ updateSection={(e) => {
this.updateSection('');
e.preventDefault();
- }.bind(this)}
+ }}
extraInfo={extraInfo}
/>
);
} else {
- var fullName = '';
+ let fullName = '';
if (user.first_name && user.last_name) {
fullName = user.first_name + ' ' + user.last_name;
@@ -320,17 +319,17 @@ export default class UserSettingsGeneralTab extends React.Component {
<SettingItemMin
title='Full Name'
describe={fullName}
- updateSection={function updateNameSection() {
+ updateSection={() => {
this.updateSection('name');
- }.bind(this)}
+ }}
/>
);
}
- var nicknameSection;
+ let nicknameSection;
if (this.props.activeSection === 'nickname') {
let nicknameLabel = 'Nickname';
- if (utils.isMobile()) {
+ if (Utils.isMobile()) {
nicknameLabel = '';
}
@@ -364,10 +363,10 @@ export default class UserSettingsGeneralTab extends React.Component {
submit={this.submitNickname}
server_error={serverError}
client_error={clientError}
- updateSection={function clearSection(e) {
+ updateSection={(e) => {
this.updateSection('');
e.preventDefault();
- }.bind(this)}
+ }}
extraInfo={extraInfo}
/>
);
@@ -376,17 +375,17 @@ export default class UserSettingsGeneralTab extends React.Component {
<SettingItemMin
title='Nickname'
describe={UserStore.getCurrentUser().nickname}
- updateSection={function updateNicknameSection() {
+ updateSection={() => {
this.updateSection('nickname');
- }.bind(this)}
+ }}
/>
);
}
- var usernameSection;
+ let usernameSection;
if (this.props.activeSection === 'username') {
let usernameLabel = 'Username';
- if (utils.isMobile()) {
+ if (Utils.isMobile()) {
usernameLabel = '';
}
@@ -416,10 +415,10 @@ export default class UserSettingsGeneralTab extends React.Component {
submit={this.submitUsername}
server_error={serverError}
client_error={clientError}
- updateSection={function clearSection(e) {
+ updateSection={(e) => {
this.updateSection('');
e.preventDefault();
- }.bind(this)}
+ }}
extraInfo={extraInfo}
/>
);
@@ -428,13 +427,14 @@ export default class UserSettingsGeneralTab extends React.Component {
<SettingItemMin
title='Username'
describe={UserStore.getCurrentUser().username}
- updateSection={function updateUsernameSection() {
+ updateSection={() => {
this.updateSection('username');
- }.bind(this)}
+ }}
/>
);
}
- var emailSection;
+
+ let emailSection;
if (this.props.activeSection === 'email') {
const emailEnabled = global.window.mm_config.SendEmailNotifications === 'true';
const emailVerificationEnabled = global.window.mm_config.RequireEmailVerification === 'true';
@@ -507,10 +507,10 @@ export default class UserSettingsGeneralTab extends React.Component {
submit={submit}
server_error={serverError}
client_error={emailError}
- updateSection={function clearSection(e) {
+ updateSection={(e) => {
this.updateSection('');
e.preventDefault();
- }.bind(this)}
+ }}
/>
);
} else {
@@ -534,26 +534,26 @@ export default class UserSettingsGeneralTab extends React.Component {
<SettingItemMin
title='Email'
describe={describe}
- updateSection={function updateEmailSection() {
+ updateSection={() => {
this.updateSection('email');
- }.bind(this)}
+ }}
/>
);
}
- var pictureSection;
+ let pictureSection;
if (this.props.activeSection === 'picture') {
pictureSection = (
<SettingPicture
title='Profile Picture'
submit={this.submitPicture}
- src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update + '&' + utils.getSessionIndex()}
+ src={'/api/v1/users/' + user.id + '/image?time=' + user.last_picture_update + '&' + Utils.getSessionIndex()}
server_error={serverError}
client_error={clientError}
- updateSection={function clearSection(e) {
+ updateSection={(e) => {
this.updateSection('');
e.preventDefault();
- }.bind(this)}
+ }}
picture={this.state.picture}
pictureChange={this.updatePicture}
submitActive={this.submitActive}
@@ -561,17 +561,17 @@ export default class UserSettingsGeneralTab extends React.Component {
/>
);
} else {
- var minMessage = 'Click \'Edit\' to upload an image.';
+ let minMessage = 'Click \'Edit\' to upload an image.';
if (user.last_picture_update) {
- minMessage = 'Image last updated ' + utils.displayDate(user.last_picture_update);
+ minMessage = 'Image last updated ' + Utils.displayDate(user.last_picture_update);
}
pictureSection = (
<SettingItemMin
title='Profile Picture'
describe={minMessage}
- updateSection={function updatePictureSection() {
+ updateSection={() => {
this.updateSection('picture');
- }.bind(this)}
+ }}
/>
);
}
@@ -619,10 +619,10 @@ 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,
+ user: React.PropTypes.object.isRequired,
+ updateSection: React.PropTypes.func.isRequired,
+ updateTab: React.PropTypes.func.isRequired,
+ activeSection: React.PropTypes.string.isRequired,
closeModal: React.PropTypes.func.isRequired,
collapseModal: React.PropTypes.func.isRequired
};
diff --git a/web/react/utils/channel_intro_mssages.jsx b/web/react/utils/channel_intro_mssages.jsx
index f27e23a82..11b69e28f 100644
--- a/web/react/utils/channel_intro_mssages.jsx
+++ b/web/react/utils/channel_intro_mssages.jsx
@@ -92,6 +92,7 @@ export function createOffTopicIntroMessage(channel, showInviteModal) {
</a>
<a
href='#'
+ className='intro-links'
onClick={showInviteModal}
>
<i className='fa fa-user-plus'></i>{'Invite others to this channel'}
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 6f3924829..77b3ecb57 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -616,7 +616,7 @@ export function applyTheme(theme) {
if (theme.centerChannelColor) {
changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .command-name, .modal .modal-content, .dropdown-menu, .popover, .mentions-name, .tip-overlay', 'color:' + theme.centerChannelColor, 1);
changeCss('#post-create', 'color:' + theme.centerChannelColor, 2);
- changeCss('.channel-header__links a', 'fill:' + changeOpacity(theme.centerChannelColor, 0.7), 1);
+ changeCss('.channel-header__links a', 'fill:' + changeOpacity(theme.centerChannelColor, 0.9), 1);
changeCss('.channel-header__links a:hover, .channel-header__links a:active', 'fill:' + theme.centerChannelColor, 2);
changeCss('.mentions--top, .command-box', 'box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 3);
changeCss('.mentions--top, .command-box', '-webkit-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 2);
@@ -644,7 +644,7 @@ export function applyTheme(theme) {
changeCss('.search-bar__container .search__form .search-bar, .form-control', 'color:' + theme.centerChannelColor, 2);
changeCss('@media(max-width: 768px){.search-bar__container .search__form .search-bar', 'background:' + changeOpacity(theme.centerChannelColor, 0.2) + '; color: inherit;', 1);
changeCss('.input-group-addon, .search-bar__container .search__form, .form-control', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
- changeCss('.form-control:focus', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
+ changeCss('.form-control:focus, .channel-header__links', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
changeCss('.attachment .attachment__content', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
changeCss('.channel-intro .channel-intro__content, .webhooks__container', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1);
changeCss('.date-separator .separator__text', 'color:' + theme.centerChannelColor, 2);
@@ -656,7 +656,7 @@ export function applyTheme(theme) {
changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
changeCss('@media(max-width: 1440px){.post.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
changeCss('@media(max-width: 1800px){.inner__wrap.move--left .post.post--comment.same--root', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.07), 2);
- changeCss('.post:hover, .modal .more-table tbody>tr:hover td, .sidebar--right .sidebar--right__header, .settings-modal .settings-table .settings-content .section-min:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
+ changeCss('.post:hover, .channel-header__links a:hover, .channel-header__links a:active, .modal .more-table tbody>tr:hover td, .settings-modal .settings-table .settings-content .section-min:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.date-separator.hovered--before:after, .date-separator.hovered--after:before, .new-separator.hovered--after:before, .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.command-name:hover, .mentions-name:hover, .mentions-focus, .dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover, .bot-indicator', 'background:' + changeOpacity(theme.centerChannelColor, 0.15), 1);
changeCss('code', 'background:' + changeOpacity(theme.centerChannelColor, 0.1), 1);
diff --git a/web/sass-files/sass/partials/_content.scss b/web/sass-files/sass/partials/_content.scss
index 6228cc45e..44a959a9b 100644
--- a/web/sass-files/sass/partials/_content.scss
+++ b/web/sass-files/sass/partials/_content.scss
@@ -20,7 +20,6 @@
background: #fff;
@include display-flex;
@include flex-direction(column);
- flex-direction: column;
.channel__wrap & {
padding-top: 0;
}
diff --git a/web/sass-files/sass/partials/_files.scss b/web/sass-files/sass/partials/_files.scss
index 49fb8e847..168634b5e 100644
--- a/web/sass-files/sass/partials/_files.scss
+++ b/web/sass-files/sass/partials/_files.scss
@@ -1,9 +1,9 @@
.preview-container {
position: relative;
- margin-top: 10px;
+ margin-top: 25px;
width: 100%;
- max-height: 110px;
- height: 110px;
+ max-height: 100px;
+ height: 100px;
white-space: nowrap;
overflow-x: auto;
overflow-y: hidden;
@@ -11,7 +11,7 @@
display: inline-block;
width: 120px;
height: 100px;
- margin: 7px 0 0 5px;
+ margin: 0 0 0 5px;
vertical-align: top;
position: relative;
border: 1px solid #DDD;
diff --git a/web/sass-files/sass/partials/_forms.scss b/web/sass-files/sass/partials/_forms.scss
index 2d7b6cd26..685677ad0 100644
--- a/web/sass-files/sass/partials/_forms.scss
+++ b/web/sass-files/sass/partials/_forms.scss
@@ -43,3 +43,7 @@
margin: 10px 0 0;
color: #999;
}
+
+.disabled-input {
+ background-color: #dddddd !important;
+}
diff --git a/web/sass-files/sass/partials/_headers.scss b/web/sass-files/sass/partials/_headers.scss
index 67c938b8c..69bc56841 100644
--- a/web/sass-files/sass/partials/_headers.scss
+++ b/web/sass-files/sass/partials/_headers.scss
@@ -1,8 +1,5 @@
#channel-header {
- padding: 3px 0;
- height: 58px;
- flex: 0 0 58px;
- @include flex(0 0 50px);
+ @include flex(0 0 56px);
}
.row {
&.header {
@@ -45,9 +42,9 @@
text-overflow: ellipsis;
margin-top: 2px;
max-height: 45px;
- .markdown-inline-img {
- max-height: 45px
- }
+ .markdown-inline-img {
+ max-height: 45px
+ }
}
&.popover {
white-space: normal;
@@ -97,7 +94,7 @@
.sidebar--left, .sidebar--menu {
.team__header {
position: relative;
- padding: 10px;
+ padding: 9px 10px;
@include legacy-pie-clearfix;
&:before {
@include single-transition(all, 0.05s, linear);
@@ -125,7 +122,7 @@
.navbar-right {
font-size: 0.85em;
position: absolute;
- top: 11px;
+ top: 10px;
right: 22px;
z-index: 5;
.dropdown-toggle {
@@ -217,7 +214,7 @@
width:100%;
border-left: none;
font-size: 14px;
- line-height: 50px;
+ line-height: 56px;
#member_popover {
margin-right: 5px;
width: 45px;
@@ -296,17 +293,19 @@
.channel-header__links {
height: 32px;
- vertical-align: top;
- display: inline-block;
- width: 15px;
- margin: 9px 6px 3px 0;
- a {
+ width: 32px;
+ @include border-radius(50px);
+ border: 1px solid #ccc;
+ margin-right: 10px;
+ > a {
+ @include border-radius(50px);
height: 100%;
display: block;
+ @include single-transition(all, 0.1s, ease-in);
}
svg {
vertical-align: top;
- margin-top: 8px;
+ margin: 7px 0 0 0px;
fill: inherit;
}
}
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index 7b26581e9..5a7d79afe 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -332,18 +332,19 @@ body.ios {
}
.post-create-footer {
@include clearfix;
- padding: 0;
+ padding: 3px 0 0 0;
+ font-size: 13px;
.has-error {
.control-label {
+ height: 0;
+ display: block;
font-weight: normal;
margin-bottom: 0;
}
}
.msg-typing {
min-height: 25px;
- line-height: 25px;
display: block;
- font-size: 13px;
@include opacity(0.7);
}
}
@@ -425,9 +426,6 @@ body.ios {
}
}
}
- .post-create-footer {
- padding: 0;
- }
p {
margin: 0 0 1em;
line-height: 1.6em;
@@ -553,13 +551,14 @@ body.ios {
}
&.post-info {
.post-profile-time {
- color: #a8adb7;
vertical-align: top;
+ width: 150px;
max-width: 220px;
overflow: hidden;
display: block;
white-space: nowrap;
text-overflow: ellipsis;
+ margin: 0 0 0 50px;
}
}
.post-header-col {
@@ -585,7 +584,7 @@ body.ios {
}
}
.post-profile-time {
- color: #a8adb7;
+ @include opacity(0.5);
}
}
.post-comment {
diff --git a/web/sass-files/sass/partials/_post_right.scss b/web/sass-files/sass/partials/_post_right.scss
index ba41d3b95..54c3bcdf8 100644
--- a/web/sass-files/sass/partials/_post_right.scss
+++ b/web/sass-files/sass/partials/_post_right.scss
@@ -1,4 +1,7 @@
.post-right__container {
+ @include display-flex;
+ @include flex-direction(column);
+ height: 100%;
.post-right-root-message {
padding: 1em 1em 0;
@@ -19,6 +22,15 @@
margin: 1em 0 0 0;
}
}
+ .post-header {
+ .post-profile-time {
+ width: 200px;
+ display: inline-block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ }
}
.post-create__container {
@@ -44,7 +56,7 @@
}
.post-create-footer {
width: 100%;
- padding-top: 5px;
+ padding: 5px 0 10px;
}
.post-right-comments-upload-in-progress {
padding: 6px 0;
@@ -103,8 +115,9 @@
.post-right__scroll {
position: relative;
- overflow: scroll;
+ overflow: auto;
-webkit-overflow-scrolling: touch;
+ @include flex(1 1 auto);
}
.post-right-comment-time {
diff --git a/web/sass-files/sass/partials/_responsive.scss b/web/sass-files/sass/partials/_responsive.scss
index 3877bfaf0..aad991035 100644
--- a/web/sass-files/sass/partials/_responsive.scss
+++ b/web/sass-files/sass/partials/_responsive.scss
@@ -303,12 +303,17 @@
}
}
@media screen and (max-width: 768px) {
+ .textarea-wrapper {
+ .textbox-preview-link {
+ display: none;
+ }
+ }
.form-control {
&.min-height {
min-height: 100px;
}
}
- .gif-div {
+ .img-div {
max-width: 100%;
}
.tip-div {
@@ -535,17 +540,22 @@
.post-create__container {
.post-right__container & {
padding: 0 1em;
+ .msg-typing {
+ display: none;
+ }
}
form {
- padding: 0;
+ padding: 0.5em 0;
}
.post-create-footer {
+ padding: 0 1em;
.msg-typing {
- margin-left: 45px;
- width: 55%;
- white-space: nowrap;
- text-overflow: ellipsis;
- overflow: hidden;
+ display: none;
+ }
+ .has-error {
+ .control-label {
+ height: auto;
+ }
}
}
.post-create-body {
@@ -574,8 +584,8 @@
}
}
.preview-container {
- padding: 0px 10px;
- margin-top: 20px;
+ padding: 0px;
+ margin-top: 5px;
.preview-div {
margin-top: 0;
}
@@ -648,7 +658,7 @@
}
.search-bar__container {
padding: 0;
- height: 45px;
+ @include flex(0 0 44px);
background: $primary-color;
color: #fff;
&.focused {
@@ -777,6 +787,12 @@
.sidebar--right__close {
display: none;
}
+ .sidebar-right__body {
+ height: calc(100% - 44px);
+ }
+ }
+ .search-items-container {
+ height: calc(100% - 44px);
}
.inner__wrap {
&.move--right {
diff --git a/web/sass-files/sass/partials/_search.scss b/web/sass-files/sass/partials/_search.scss
index 73b69c866..bedf35376 100644
--- a/web/sass-files/sass/partials/_search.scss
+++ b/web/sass-files/sass/partials/_search.scss
@@ -1,8 +1,9 @@
#channel-header .search-bar__container {
- padding: 8px 8px 8px 0;
+ padding: 0 8px 0 0;
}
.search-bar__container {
- padding: 12px 8px 12px 0;
+ padding: 12px 8px 0 0;
+ @include flex(0 0 56px);
}
.search__clear {
display: none;
@@ -71,8 +72,10 @@
.search-items-container {
position: relative;
- overflow: scroll;
+ overflow: auto;
-webkit-overflow-scrolling: touch;
+ @include flex(1 1 auto);
+ height: calc(100% - 56px);
}
.search-results-header {
@@ -92,9 +95,11 @@
padding: 10px 1em;
margin: 0;
cursor: pointer;
+
&:first-child {
border: none;
}
+
.search-channel__name {
font-weight: 600;
margin: 0 0 10px 0;
diff --git a/web/sass-files/sass/partials/_sidebar--left.scss b/web/sass-files/sass/partials/_sidebar--left.scss
index ab13d1b42..eb7c1b83f 100644
--- a/web/sass-files/sass/partials/_sidebar--left.scss
+++ b/web/sass-files/sass/partials/_sidebar--left.scss
@@ -47,7 +47,7 @@
.nav-pills__container {
height: 100%;
position: relative;
- overflow: scroll;
+ overflow: auto;
-webkit-overflow-scrolling: touch;
}
diff --git a/web/sass-files/sass/partials/_sidebar--menu.scss b/web/sass-files/sass/partials/_sidebar--menu.scss
index 6f4a0cc38..e34cd72c1 100644
--- a/web/sass-files/sass/partials/_sidebar--menu.scss
+++ b/web/sass-files/sass/partials/_sidebar--menu.scss
@@ -57,8 +57,11 @@
color: inherit;
line-height: 40px;
padding: 0 15px;
- .glyphicon {
+ .fa ,.glyphicon {
width: 25px;
+ text-align: center;
+ left: -5px;
+ position: relative;
}
}
}
diff --git a/web/sass-files/sass/partials/_sidebar--right.scss b/web/sass-files/sass/partials/_sidebar--right.scss
index a4267294c..2527eef28 100644
--- a/web/sass-files/sass/partials/_sidebar--right.scss
+++ b/web/sass-files/sass/partials/_sidebar--right.scss
@@ -3,24 +3,38 @@
width: 400px;
height: 100%;
right: 0px;
- padding: 0 0 2em 0;
+ padding: 0;
background: #fff;
@include single-transition(transform, 0.5s, ease);
right: -320px;
+
&.move--left {
right: 0;
}
+
+ .sidebar--right__content {
+ height: 100%;
+ @include display-flex;
+ @include flex-direction(column);
+ }
+
.sidebar--right__back {
- color: #666;
- width: 20px;
+ color: inherit;
+ @include opacity(0.8);
+ width: 30px;
text-align: center;
- margin: 0 0 0 -6px;
- font-size: 12px;
+ margin: 0 0 0 -14px;
+ font-size: 13px;
display: inline-block;
}
.sidebar-right__body {
+ @include flex(1 1 auto);
border-left: $border-gray;
border-top: $border-gray;
+ @include display-flex;
+ @include flex-direction(column);
+ height: calc(100% - 56px);
+ @include border-radius(2px 0 0 0);
}
.post {
.post-header {
@@ -75,7 +89,7 @@
height: 44px;
padding: 0 1em;
line-height: 44px;
- background: #F5F5F5;
+ @include flex(0 0 44px);
border-bottom: $border-gray;
}
.sidebar--right__subheader {
diff --git a/web/sass-files/sass/partials/_videos.scss b/web/sass-files/sass/partials/_videos.scss
index bb36b6223..3f15f8f1e 100644
--- a/web/sass-files/sass/partials/_videos.scss
+++ b/web/sass-files/sass/partials/_videos.scss
@@ -51,7 +51,7 @@
border-left:60px solid rgba(255,255,255,0.4);
}
-.gif-div {
+.img-div {
-moz-force-broken-image-icon: 1;
position:relative;
max-width: 450px;
diff --git a/web/web.go b/web/web.go
index ffc791cb7..02ceb69ba 100644
--- a/web/web.go
+++ b/web/web.go
@@ -989,7 +989,7 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
}
text := parsedRequest.Text
- if len(text) == 0 {
+ if len(text) == 0 && parsedRequest.Attachments == nil {
c.Err = model.NewAppError("incomingWebhook", "No text specified", "")
return
}