summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml11
-rw-r--r--CHANGELOG.md181
-rw-r--r--NOTICE.txt68
-rw-r--r--api/user.go2
-rw-r--r--i18n/en.json2
-rw-r--r--i18n/es.json2
-rw-r--r--web/react/components/admin_console/image_settings.jsx5
-rw-r--r--web/react/components/admin_console/user_item.jsx4
-rw-r--r--web/react/components/delete_post_modal.jsx2
-rw-r--r--web/react/components/member_list_item.jsx4
-rw-r--r--web/react/components/member_list_team_item.jsx4
-rw-r--r--web/react/components/more_channels.jsx2
-rw-r--r--web/react/components/posts_view.jsx6
-rw-r--r--web/react/components/search_results.jsx30
-rw-r--r--web/react/components/search_results_item.jsx6
-rw-r--r--web/react/components/signup_user_complete.jsx11
-rw-r--r--web/react/components/user_settings/manage_command_hooks.jsx272
-rw-r--r--web/react/components/user_settings/user_settings_integrations.jsx4
-rw-r--r--web/react/components/user_settings/user_settings_modal.jsx15
-rw-r--r--web/react/stores/channel_store.jsx4
-rw-r--r--web/react/stores/socket_store.jsx4
-rw-r--r--web/react/utils/async_client.jsx8
-rw-r--r--web/react/utils/constants.jsx1
-rw-r--r--web/react/utils/markdown.jsx4
-rw-r--r--web/react/utils/utils.jsx23
-rw-r--r--web/sass-files/sass/partials/_markdown.scss8
-rw-r--r--web/sass-files/sass/partials/_mentions.scss16
-rw-r--r--web/sass-files/sass/partials/_modal.scss16
-rw-r--r--web/sass-files/sass/partials/_popover.scss3
-rw-r--r--web/sass-files/sass/partials/_post.scss19
-rw-r--r--web/sass-files/sass/partials/_settings.scss17
-rw-r--r--web/static/i18n/en.json51
-rw-r--r--web/static/i18n/es.json24
-rw-r--r--web/static/images/postArrows.pngbin5684 -> 0 bytes
34 files changed, 586 insertions, 243 deletions
diff --git a/.travis.yml b/.travis.yml
index 9f25bed88..71dcbefbc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -16,11 +16,16 @@ addons:
hosts:
- 127.0.0.1 dockerhost
after_success:
- - cd dist && curl -F "pr_number=$TRAVIS_PULL_REQUEST" -F "file=@mattermost.tar.gz" mattermod.mattermost.com:8087/upload_pr_build && cd ..
+ - sudo chown -R `whoami` dist
+ - sudo chmod -R 777 dist
+ - cd dist && curl -F "pr_number=$TRAVIS_PULL_REQUEST" -F "file=@mattermost.tar.gz" mattermod.mattermost.com:8087/upload_pr_build
+ - md5sum mattermost.tar.gz > mm.md5
+ - sha1sum mattermost.tar.gz > mm.sha1
+ - sha256sum mattermost.tar.gz > mm.sha256
+ - cat mm.md5 mm.sha1 mm.sha256
+ - cd ..
before_deploy:
- sudo rm -rf dist/mattermost
- - sudo chown `whoami` dist/mattermost.tar.gz
- - sudo chmod 777 dist/mattermost.tar.gz
- rvm 1.9.3 do gem install mime-types -v 2.6.2
deploy:
# Github releases, builds only on tags
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5849b30b9..c27041165 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,186 @@
# Mattermost Changelog
+## Release v2.0.0
+
+Expected Release date: 2016-02-16
+
+### Highlights
+
+#### Incremented Version Number: Mattermost "2.0"
+
+- Version number incremented from "1.x" to "2.x" indicating major product changes, including:
+
+##### Localization
+
+- Addition of localization support to entire user interface plus error and log messages
+- Added Spanish language translation (Beta quality) available from **Account Settings** > **Display**
+
+##### Enhanced Support for Mobile Devices
+
+- BREAKING CHANGE to APIs: New Android and updated iOS apps require `platform` 2.0 and higher
+- iOS added app support for GitLab single-sign-on
+- iOS added app support for LDAP/AD single-sign-on (Enterprise Edition only)
+
+##### Upgrade and Deployment Improvements
+- Mattermost v2.0 now upgrades from up to two previous major builds (e.g. v1.4.x and v1.3.x)
+- Added option to allow use of insecure TLS outbound connections to allow use of self-signed certificates
+
+### New Features
+
+Localization
+
+- Addition of localization support to entire user interface plus error and log messages
+- Added Spanish language translation (Beta quality) available from **Account Settings** > **Display**
+
+Slash Commands
+
+- Added [Slack-compatible slash commands](http://docs.mattermost.com/developer/slash-commands.html) to integrate with external systems
+
+iOS
+
+- [iOS app](https://github.com/mattermost/ios) added support for GitLab single-sign-on
+- [iOS app](https://github.com/mattermost/ios) added support for LDAP/AD single-sign-on (Enterprise Edition only)
+
+Android
+
+- New open source Android application compatible with Mattermost 2.0 and higher
+
+System Console
+
+- Added **Site Reports** to view system statistics on posts, channels and users.
+
+### Improvements
+
+Upgrading
+
+- Mattermost v2.0 now upgrades from up to two previous major builds (e.g. v1.4.x and v1.3.x).
+
+Files and Images
+
+- Public links to images and files created by users no longer expire
+- OGG attachments now play in preview window on Chrome and Firefox
+
+Onboarding
+
+- “Get Team Invite Link” option is disabled from the main menu if user creation is disabled for the team
+- Tutorial colors improved to provide higher contrast with new default theme
+
+Authentication
+
+- Added ability to sign in with username as an alternative to email address
+- Switching from email to SSO for sign in now updates email address to use the SSO email
+
+System Console
+
+- Added option to allow use of insecure TLS outbound connections to allow use of self-signed certificates
+- Removed unused "Disable File Storage" option from **System Console** > **File Storage**
+- Added warning if a user demotes their account from System Administrator
+
+Search
+
+- Hashtag search is no longer case sensitive
+- System messages no longer appear in search results
+- Date separator added to search results
+- Moved the recent mentions icon to the right of the search bar
+
+Messaging
+- Changed the comment bubble to a reply arrow to make post replies and the RHS more discoverable
+- Time stamp next to sequential posts made by users now shows HH:MM instead of on-hover timestamp
+- Code blocks now support horizontal scrolling if content exceeds the max width
+
+User Interface
+
+- Away status added to note users who have been idle for more than 5 minutes.
+- Long usernames are now truncated in the center channel and RHS
+- Added more favicon sizes for home screen icons on mobile devices
+
+#### Bug Fixes
+
+- Incorrect “Mattermost unreachable” error on iOS no longer appears
+- Dialog to confirm deletion of a post now supports hitting “ENTER” to confirm deletion.
+- Keyboard focus on the New Channel modal on IE11 is now contained within the text box.
+- LHS indicator for “Unread Posts Above/Below” now displays on IE11
+- Unresponsive UI when viewing a permalink is fixed if a user clicks outside the text on the "Click here to jump to recent messages" bar.
+- Dismissed blue bar error messages no longer re-appear on page refresh.
+- Console error is no longer thrown on first page load in Firefox and Edge.
+- Console error and missing notification is fixed for the first direct message received from any user.
+- Comment bubble in Firefox no longer appears with a box around it on-hover.
+- Home screen icons on Android and iOS devices now appear with the Mattermost logo.
+- Switching channels now clears the “user is typing” message below the text input box.
+- iOS devices are no longer detected as “unknown” devices in the session history.
+
+### Compatibility
+Changes from v1.4 to v2.0:
+
+**iOS**
+Mattermost iOS app v2.0 requires Mattermost platform v2.0 and higher.
+
+**config.json**
+Multiple setting options were added to `config.json`. Below is a list of the additions and their default values on install. The settings can be modified in `config.json` or the System Console.
+
+- Under `ServiceSettings` in `config.json`:
+ - `"EnableCommands": false` to set whether users can create slash commands from **Account Settings** > **Integrations** > **Commands**
+ - `"EnableOnlyAdminIntegrations": true` to restrict integrations to being created by admins only.
+ - `"EnableInsecureOutgoingConnections": false` sets whether outgoing HTTPS requests can accept unverified, self-signed certificates.
+ - Optional: `"WebsocketSecurePort" : 443` sets the port on which the secured WebSocket will listen using the `wss` protocol. If this setting is not present in `config.json`, it defaults to `443`.
+ - Optional: `"WebsocketPort": 80` sets the port on which the unsecured WebSocket will listen using the `ws` protocol. If this setting is not present in `config.json`, it defaults to `80`.
+
+- Under `EmailSettings` in `config.json`:
+ - `"EnableSignInWithEmail": true` allows users to sign in using their email.
+ - `"EnableSignInWithUsername": false` sets whether users can sign in with their username. Typically only used when email verification is disabled.
+
+**Localization**
+There are two new directories for i18n localization JSON files:
+- platform/i18n for server-side localization files
+- platform/web/static/i18n for client-side localization files
+
+#### Database Changes from v1.4 to v2.0
+
+The following is for informational purposes only, no action needed. Mattermost automatically upgrades database tables from the previous version's schema using only additions.
+
+##### Users Table
+1. Added `Locale` column
+
+##### Licenses Table
+1. Added `Licenses` Table
+
+#### Known Issues
+
+- Navigating to a page with new messages containing inline images added via markdown causes the channel to scroll up and down while loading the inline images.
+- Microsoft Edge does not yet support drag and drop for file attachments.
+- Scroll bar does not appear in the center channel.
+- Unable to paste images into the text box on Firefox, Safari, and IE11.
+- Importing from Slack fails to load messages in certain cases and breaks @mentions.
+- System Console > TEAMS > Statistics > Newly Created Users shows all users as created "just now".
+- Favicon does not turn red when @mentions and direct messages are received in an inactive browser tab.
+- Searching for a phrase in quotations returns more than just the phrase on installations with a Postgres database.
+- Archived channels are not removed from the "More" menu for the person that archived the channel until after refresh.
+- Search results don't highlight searches for @username, non-latin characters, or terms inside Markdown code blocks.
+- Searching for a username or hashtag containing a dot returns a search where the dot is replaced with the "or" operator.
+- Hashtags less than three characters long are not searchable.
+- Users remains in the channel counter after being deactivated.
+- Messages with symbols (<,>,-,+,=,%,^,#,*,|) directly before or after a hashtag are not searchable.
+- Permalinks for the second message or later consecutively sent in a group by the same author displaces the copy link popover or causes an error
+- Emoji smileys ending with a letter at the end of a message do not auto-complete as expected
+
+#### Contributors
+
+Special thanks to [enahum](https://github.com/enahum) for creating the Spanish localization!
+
+Many thanks to all our external contributors. In no particular order:
+
+- [enahum](https://github.com/enahum)
+- [trashcan](https://github.com/trashcan)
+- [khoa-le](https://github.com/khoa-le)
+- [alanmoo](https://github.com/alanmoo)
+- [fallenby](https://github.com/fallenby)
+- [loafoe](https://github.com/loafoe)
+- [gramakri](https://github.com/gramakri)
+- [pawelad](https://github.com/pawelad)
+- [cifvts](https://github.com/cifvts)
+- [rosskusler](https://github.com/rosskusler)
+- [apskim](https://github.com/apskim)
+
## Release v1.4.0
Expected Release date: 2016-01-16
diff --git a/NOTICE.txt b/NOTICE.txt
index 9e6e4d09c..b39ee7cc9 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -1570,3 +1570,71 @@ Creative Commons is not a party to this License, and makes no warranty whatsoeve
Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of this License.
Creative Commons may be contacted at https://creativecommons.org/.
+
+---
+
+This product contains a modified portion of 'go-i18n', a Go package and a command that translates Go programs into multiple languages
+by Nick Snyder.
+
+* HOMEPAGE:
+ * https://github.com/nicksnyder/go-i18n
+
+* LICENSE:
+
+Copyright (c) 2014 Nick Snyder https://github.com/nicksnyder
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+---
+
+This product contains a modified portion of 'react-intl', a library that provides React components and an API to format dates, numbers, and string messages including pluralization.
+by Yahoo Inc.
+
+* HOMEPAGE:
+ * https://github.com/yahoo/react-intl
+
+* LICENSE:
+
+Copyright 2014 Yahoo Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ * Neither the name of the Yahoo Inc. nor the
+ names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL YAHOO! INC. BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/api/user.go b/api/user.go
index f814a93fe..8f381aeda 100644
--- a/api/user.go
+++ b/api/user.go
@@ -796,7 +796,7 @@ func attachDeviceId(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- w.Write([]byte(deviceId))
+ w.Write([]byte(model.MapToJson(props)))
}
func RevokeSessionById(c *Context, sessionId string) {
diff --git a/i18n/en.json b/i18n/en.json
index aeeaf576b..3b204f250 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -685,7 +685,7 @@
},
{
"id": "api.post.check_for_out_of_channel_mentions.message.one",
- "translation": "{{.Username}} was mentioned, but they do not belong to this channel."
+ "translation": "{{.Username}} was mentioned, but does not belong to this channel."
},
{
"id": "api.post.create_post.bad_filename.error",
diff --git a/i18n/es.json b/i18n/es.json
index 6f9c7499b..d22c4564a 100644
--- a/i18n/es.json
+++ b/i18n/es.json
@@ -681,7 +681,7 @@
},
{
"id": "api.post.check_for_out_of_channel_mentions.message.multiple",
- "translation": "{{.Usernames}} y {{.LastUsername}} fueron mencionados, pero ellos no pertenecen a este canal."
+ "translation": "{{.Usernames}} y {{.LastUsername}} fueron mencionados, pero no pertenecen a este canal."
},
{
"id": "api.post.check_for_out_of_channel_mentions.message.one",
diff --git a/web/react/components/admin_console/image_settings.jsx b/web/react/components/admin_console/image_settings.jsx
index 12bf554ea..86f78e093 100644
--- a/web/react/components/admin_console/image_settings.jsx
+++ b/web/react/components/admin_console/image_settings.jsx
@@ -8,10 +8,6 @@ import crypto from 'crypto';
import {injectIntl, intlShape, defineMessages, FormattedMessage} from 'mm-intl';
const holders = defineMessages({
- storeDisabled: {
- id: 'admin.image.storeDisabled',
- defaultMessage: 'Disable File Storage'
- },
storeLocal: {
id: 'admin.image.storeLocal',
defaultMessage: 'Local File System'
@@ -242,7 +238,6 @@ class FileSettings extends React.Component {
defaultValue={this.props.config.FileSettings.DriverName}
onChange={this.handleChange.bind(this, 'DriverName')}
>
- <option value=''>{formatMessage(holders.storeDisabled)}</option>
<option value='local'>{formatMessage(holders.storeLocal)}</option>
<option value='amazons3'>{formatMessage(holders.storeAmazonS3)}</option>
</select>
diff --git a/web/react/components/admin_console/user_item.jsx b/web/react/components/admin_console/user_item.jsx
index 02b01b090..0c1a55cc1 100644
--- a/web/react/components/admin_console/user_item.jsx
+++ b/web/react/components/admin_console/user_item.jsx
@@ -360,8 +360,8 @@ export default class UserItem extends React.Component {
height='36'
width='36'
/>
- <span className='member-name'>{Utils.getDisplayName(user)}</span>
- <span className='member-email'>{email}</span>
+ <span className='more-name'>{Utils.getDisplayName(user)}</span>
+ <span className='more-description'>{email}</span>
<div className='dropdown member-drop'>
<a
href='#'
diff --git a/web/react/components/delete_post_modal.jsx b/web/react/components/delete_post_modal.jsx
index 65ffa96a1..95b2e58a8 100644
--- a/web/react/components/delete_post_modal.jsx
+++ b/web/react/components/delete_post_modal.jsx
@@ -173,7 +173,7 @@ export default class DeletePostModal extends React.Component {
<Modal.Body>
<FormattedMessage
id='delete_post.question'
- defaultMessage='Are you sure you want to delete this ${term}?'
+ defaultMessage='Are you sure you want to delete this {term}?'
values={{
term: (postTerm)
}}
diff --git a/web/react/components/member_list_item.jsx b/web/react/components/member_list_item.jsx
index c50ee5c96..41ea58eeb 100644
--- a/web/react/components/member_list_item.jsx
+++ b/web/react/components/member_list_item.jsx
@@ -124,8 +124,8 @@ export default class MemberListItem extends React.Component {
height='36'
width='36'
/>
- <div className='member-name'>{Utils.displayUsername(member.id)}</div>
- <div className='member-description'>{member.email}</div>
+ <div className='more-name'>{Utils.displayUsername(member.id)}</div>
+ <div className='more-description'>{member.email}</div>
</td>
<td className='td--action lg'>{invite}</td>
</tr>
diff --git a/web/react/components/member_list_team_item.jsx b/web/react/components/member_list_team_item.jsx
index 6e1006911..30086d1b2 100644
--- a/web/react/components/member_list_team_item.jsx
+++ b/web/react/components/member_list_team_item.jsx
@@ -208,8 +208,8 @@ export default class MemberListTeamItem extends React.Component {
height='36'
width='36'
/>
- <span className='member-name'>{Utils.displayUsername(user.id)}</span>
- <span className='member-email'>{email}</span>
+ <span className='more-name'>{Utils.displayUsername(user.id)}</span>
+ <span className='more-description'>{email}</span>
<div className='dropdown member-drop'>
<a
href='#'
diff --git a/web/react/components/more_channels.jsx b/web/react/components/more_channels.jsx
index d12ea4703..d800f93d8 100644
--- a/web/react/components/more_channels.jsx
+++ b/web/react/components/more_channels.jsx
@@ -114,7 +114,7 @@ export default class MoreChannels extends React.Component {
<tr key={channel.id}>
<td>
<p className='more-name'>{channel.display_name}</p>
- <p className='more-purpose'>{channel.purpose}</p>
+ <p className='more-description'>{channel.purpose}</p>
</td>
<td className='td--action'>
{joinButton}
diff --git a/web/react/components/posts_view.jsx b/web/react/components/posts_view.jsx
index f108ace2e..ebe19abad 100644
--- a/web/react/components/posts_view.jsx
+++ b/web/react/components/posts_view.jsx
@@ -94,7 +94,7 @@ export default class PostsView extends React.Component {
});
}
- this.scrollStopAction.fireAfter(1000);
+ this.scrollStopAction.fireAfter(2000);
}
handleScrollStop() {
this.setState({
@@ -564,6 +564,8 @@ function ScrollToBottomArrows({isScrolling, atBottom, onClick}) {
<div
className={className}
onClick={onClick}
- />
+ >
+ <span dangerouslySetInnerHTML={{__html: Constants.SCROLL_BOTTOM_ICON}} />
+ </div>
);
}
diff --git a/web/react/components/search_results.jsx b/web/react/components/search_results.jsx
index 9dcc99061..4adc3afe0 100644
--- a/web/react/components/search_results.jsx
+++ b/web/react/components/search_results.jsx
@@ -1,6 +1,7 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
+import ChannelStore from '../stores/channel_store.jsx';
import SearchStore from '../stores/search_store.jsx';
import UserStore from '../stores/user_store.jsx';
import SearchBox from './search_bar.jsx';
@@ -11,7 +12,22 @@ import SearchResultsItem from './search_results_item.jsx';
import {FormattedMessage, FormattedHTMLMessage} from 'mm-intl';
function getStateFromStores() {
- return {results: SearchStore.getSearchResults()};
+ const results = SearchStore.getSearchResults();
+
+ const channels = new Map();
+ const channelIds = results.order.map((postId) => results.posts[postId].channel_id);
+ for (const id of channelIds) {
+ if (channels.has(id)) {
+ continue;
+ }
+
+ channels.set(id, ChannelStore.get(id));
+ }
+
+ return {
+ results,
+ channels
+ };
}
export default class SearchResults extends React.Component {
@@ -33,16 +49,22 @@ export default class SearchResults extends React.Component {
componentDidMount() {
this.mounted = true;
SearchStore.addSearchChangeListener(this.onChange);
+ ChannelStore.addChangeListener(this.onChange);
this.resize();
window.addEventListener('resize', this.handleResize);
}
+ shouldComponentUpdate(nextProps, nextState) {
+ return !Utils.areObjectsEqual(this.props, nextProps) || !Utils.areObjectsEqual(this.state, nextState);
+ }
+
componentDidUpdate() {
this.resize();
}
componentWillUnmount() {
SearchStore.removeSearchChangeListener(this.onChange);
+ ChannelStore.removeChangeListener(this.onChange);
this.mounted = false;
window.removeEventListener('resize', this.handleResize);
}
@@ -56,10 +78,7 @@ export default class SearchResults extends React.Component {
onChange() {
if (this.mounted) {
- var newState = getStateFromStores();
- if (!Utils.areObjectsEqual(newState, this.state)) {
- this.setState(newState);
- }
+ this.setState(getStateFromStores());
}
}
@@ -116,6 +135,7 @@ export default class SearchResults extends React.Component {
return (
<SearchResultsItem
key={post.id}
+ channel={this.state.channels.get(post.channel_id)}
post={post}
term={searchTerm}
isMentionSearch={this.props.isMentionSearch}
diff --git a/web/react/components/search_results_item.jsx b/web/react/components/search_results_item.jsx
index 544ba920a..d3533037f 100644
--- a/web/react/components/search_results_item.jsx
+++ b/web/react/components/search_results_item.jsx
@@ -1,7 +1,6 @@
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
-import ChannelStore from '../stores/channel_store.jsx';
import UserStore from '../stores/user_store.jsx';
import UserProfile from './user_profile.jsx';
import * as EventHelpers from '../dispatcher/event_helpers.jsx';
@@ -37,8 +36,8 @@ export default class SearchResultsItem extends React.Component {
}
render() {
- var channelName = '';
- var channel = ChannelStore.get(this.props.post.channel_id);
+ var channelName = null;
+ const channel = this.props.channel;
var timestamp = UserStore.getCurrentUser().update_at;
if (channel) {
@@ -136,6 +135,7 @@ export default class SearchResultsItem extends React.Component {
SearchResultsItem.propTypes = {
post: React.PropTypes.object,
+ channel: React.PropTypes.object,
isMentionSearch: React.PropTypes.bool,
term: React.PropTypes.string
};
diff --git a/web/react/components/signup_user_complete.jsx b/web/react/components/signup_user_complete.jsx
index 672213d1a..b770a2a2c 100644
--- a/web/react/components/signup_user_complete.jsx
+++ b/web/react/components/signup_user_complete.jsx
@@ -362,6 +362,17 @@ class SignupUserComplete extends React.Component {
);
}
+ if (signupMessage.length === 0 && !emailSignup) {
+ emailSignup = (
+ <div>
+ <FormattedMessage
+ id='signup_user_completed.none'
+ defaultMessage='No user creation method has been enabled. Please contact an administrator for access.'
+ />
+ </div>
+ );
+ }
+
return (
<div>
<form>
diff --git a/web/react/components/user_settings/manage_command_hooks.jsx b/web/react/components/user_settings/manage_command_hooks.jsx
index d23d2957e..f4009aeaa 100644
--- a/web/react/components/user_settings/manage_command_hooks.jsx
+++ b/web/react/components/user_settings/manage_command_hooks.jsx
@@ -18,7 +18,7 @@ const holders = defineMessages({
},
addDisplayNamePlaceholder: {
id: 'user.settings.cmds.add_display_name.placeholder',
- defaultMessage: 'Display Name'
+ defaultMessage: 'Example: "Search patient records"'
},
addUsernamePlaceholder: {
id: 'user.settings.cmds.add_username.placeholder',
@@ -30,11 +30,11 @@ const holders = defineMessages({
},
addAutoCompleteDescPlaceholder: {
id: 'user.settings.cmds.auto_complete_desc.placeholder',
- defaultMessage: 'A short description of what this commands does.'
+ defaultMessage: 'Example: "Returns search results for patient records"'
},
addAutoCompleteHintPlaceholder: {
id: 'user.settings.cmds.auto_complete_hint.placeholder',
- defaultMessage: '[zipcode]'
+ defaultMessage: 'Example: [Patient Name]'
},
adUrlPlaceholder: {
id: 'user.settings.cmds.url.placeholder',
@@ -261,7 +261,7 @@ export default class ManageCommandCmds extends React.Component {
<strong>
<FormattedMessage
id='user.settings.cmds.trigger'
- defaultMessage='Trigger: '
+ defaultMessage='Command Trigger Word: '
/>
</strong>{cmd.trigger}
</div>
@@ -271,21 +271,43 @@ export default class ManageCommandCmds extends React.Component {
cmds.push(
<div
key={cmd.id}
- className='webcmd__item'
+ className='webhook__item webcmd__item'
>
+ {triggerDiv}
+ <div className='padding-top x2 webcmd__url'>
+ <strong>
+ <FormattedMessage
+ id='user.settings.cmds.url'
+ defaultMessage='Request URL: '
+ />
+ </strong><span className='word-break--all'>{cmd.url}</span>
+ </div>
<div className='padding-top x2'>
<strong>
<FormattedMessage
- id='user.settings.cmds.display_name'
- defaultMessage='Display Name: '
+ id='user.settings.cmds.request_type'
+ defaultMessage='Request Method: '
/>
- </strong><span className='word-break--all'>{cmd.display_name}</span>
+ </strong>
+ <span className='word-break--all'>
+ {
+ cmd.method === 'P' ?
+ <FormattedMessage
+ id='user.settings.cmds.request_type_post'
+ defaultMessage='POST'
+ /> :
+ <FormattedMessage
+ id='user.settings.cmds.request_type_get'
+ defaultMessage='GET'
+ />
+ }
+ </span>
</div>
<div className='padding-top x2'>
<strong>
<FormattedMessage
id='user.settings.cmds.username'
- defaultMessage='Username: '
+ defaultMessage='Response Username: '
/>
</strong><span className='word-break--all'>{cmd.username}</span>
</div>
@@ -293,7 +315,7 @@ export default class ManageCommandCmds extends React.Component {
<strong>
<FormattedMessage
id='user.settings.cmds.icon_url'
- defaultMessage='Icon URL: '
+ defaultMessage='Response Icon: '
/>
</strong><span className='word-break--all'>{cmd.icon_url}</span>
</div>
@@ -301,56 +323,34 @@ export default class ManageCommandCmds extends React.Component {
<strong>
<FormattedMessage
id='user.settings.cmds.auto_complete'
- defaultMessage='Auto Complete: '
+ defaultMessage='Autocomplete: '
/>
</strong><span className='word-break--all'>{cmd.auto_complete ? this.props.intl.formatMessage(holders.autocompleteYes) : this.props.intl.formatMessage(holders.autocompleteNo)}</span>
</div>
<div className='padding-top x2'>
<strong>
<FormattedMessage
- id='user.settings.cmds.auto_complete_desc'
- defaultMessage='Auto Complete Description: '
- />
- </strong><span className='word-break--all'>{cmd.auto_complete_desc}</span>
- </div>
- <div className='padding-top x2'>
- <strong>
- <FormattedMessage
id='user.settings.cmds.auto_complete_hint'
- defaultMessage='Auto Complete Hint: '
+ defaultMessage='Autocomplete Hint: '
/>
</strong><span className='word-break--all'>{cmd.auto_complete_hint}</span>
</div>
<div className='padding-top x2'>
<strong>
<FormattedMessage
- id='user.settings.cmds.request_type'
- defaultMessage='Request Type: '
+ id='user.settings.cmds.auto_complete_desc'
+ defaultMessage='Autocomplete Description: '
/>
- </strong>
- <span className='word-break--all'>
- {
- cmd.method === 'P' ?
- <FormattedMessage
- id='user.settings.cmds.request_type_post'
- defaultMessage='POST'
- /> :
- <FormattedMessage
- id='user.settings.cmds.request_type_get'
- defaultMessage='GET'
- />
- }
- </span>
+ </strong><span className='word-break--all'>{cmd.auto_complete_desc}</span>
</div>
- <div className='padding-top x2 webcmd__url'>
+ <div className='padding-top x2'>
<strong>
<FormattedMessage
- id='user.settings.cmds.url'
- defaultMessage='URL: '
+ id='user.settings.cmds.display_name'
+ defaultMessage='Descriptive Label: '
/>
- </strong><span className='word-break--all'>{cmd.url}</span>
+ </strong><span className='word-break--all'>{cmd.display_name}</span>
</div>
- {triggerDiv}
<div className='padding-top'>
<strong>
<FormattedMessage
@@ -400,7 +400,7 @@ export default class ManageCommandCmds extends React.Component {
}
const existingCmds = (
- <div className='webcmds__container'>
+ <div className='webhooks__container webcmds__container'>
<label className='control-label padding-top x2'>
<FormattedMessage
id='user.settings.cmds.existing'
@@ -408,7 +408,7 @@ export default class ManageCommandCmds extends React.Component {
/>
</label>
<div className='padding-top divider-light'></div>
- <div className='webcmds__list'>
+ <div className='webhooks__list webcmds__list'>
{displayCmds}
</div>
</div>
@@ -420,7 +420,7 @@ export default class ManageCommandCmds extends React.Component {
<div key='addCommandCmd'>
<FormattedHTMLMessage
id='user.settings.cmds.add_desc'
- defaultMessage='Create commands to send message events to an external integration. Please see <a href="http://mattermost.org/commands">http://mattermost.org/commands</a> to learn more.'
+ defaultMessage='Create slash commands to send events to external integrations and receive a response. For example typing `/patient Joe Smith` could bring back search results from your internal health records management system for the name “Joe Smith”. Please see <a href="http://docs.mattermost.com/developer/slash-commands.html">Slash commands documentation</a> for detailed instructions.'
/>
<div><label className='control-label padding-top x2'>
<FormattedMessage
@@ -430,103 +430,139 @@ export default class ManageCommandCmds extends React.Component {
</label></div>
<div className='padding-top divider-light'></div>
<div className='padding-top'>
+
<div className='padding-top x2'>
<label className='control-label'>
<FormattedMessage
- id='user.settings.cmds.display_name'
- defaultMessage='Display Name: '
+ id='user.settings.cmds.trigger'
+ defaultMessage='Command Trigger Word: '
/>
</label>
<div className='padding-top'>
<input
- ref='displayName'
+ ref='trigger'
className='form-control'
- value={this.state.cmd.display_name}
- onChange={this.updateDisplayName}
- placeholder={this.props.intl.formatMessage(holders.addDisplayNamePlaceholder)}
+ value={this.state.cmd.trigger}
+ onChange={this.updateTrigger}
+ placeholder={this.props.intl.formatMessage(holders.addTriggerPlaceholder)}
/>
</div>
<div className='padding-top'>
<FormattedMessage
- id='user.settings.cmds.cmd_display_name'
- defaultMessage='Command display name.'
+ id='user.settings.cmds.trigger_desc'
+ defaultMessage='Examples: /patient, /client, /employee Reserved: /echo, /join, /logout, /me, /shrug'
/>
</div>
</div>
+
<div className='padding-top x2'>
<label className='control-label'>
<FormattedMessage
- id='user.settings.cmds.username'
- defaultMessage='Username: '
+ id='user.settings.cmds.url'
+ defaultMessage='Request URL: '
/>
</label>
<div className='padding-top'>
- <input
- ref='username'
- className='form-control'
- value={this.state.cmd.username}
- onChange={this.updateUsername}
- placeholder={this.props.intl.formatMessage(holders.addUsernamePlaceholder)}
+ <input
+ ref='URL'
+ className='form-control'
+ value={this.state.cmd.url}
+ rows={1}
+ onChange={this.updateURL}
+ placeholder={this.props.intl.formatMessage(holders.adUrlPlaceholder)}
+ />
+ </div>
+ <div className='padding-top'>
+ <FormattedMessage
+ id='user.settings.cmds.url_desc'
+ defaultMessage='The callback URL to receive the HTTP POST or GET event request when the slash command is run.'
/>
</div>
+ </div>
+
+ <div className='padding-top x2'>
+ <label className='control-label'>
+ <FormattedMessage
+ id='user.settings.cmds.request_type'
+ defaultMessage='Request Method: '
+ />
+ </label>
+ <div className='padding-top'>
+ <select
+ ref='method'
+ className='form-control'
+ value={this.state.cmd.method}
+ onChange={this.updateMethod}
+ >
+ <option value='P'>
+ {this.props.intl.formatMessage(holders.requestTypePost)}
+ </option>
+ <option value='G'>
+ {this.props.intl.formatMessage(holders.requestTypeGet)}
+ </option>
+ </select>
+ </div>
<div className='padding-top'>
<FormattedMessage
- id='user.settings.cmds.username_desc'
- defaultMessage='The username to use when overriding the post.'
+ id='user.settings.cmds.request_type_desc'
+ defaultMessage='The type of command request issued to the Request URL.'
/>
</div>
</div>
+
<div className='padding-top x2'>
<label className='control-label'>
<FormattedMessage
- id='user.settings.cmds.icon_url'
- defaultMessage='Icon URL: '
+ id='user.settings.cmds.username'
+ defaultMessage='Response Username: '
/>
</label>
<div className='padding-top'>
<input
- ref='iconURL'
+ ref='username'
className='form-control'
- value={this.state.cmd.icon_url}
- onChange={this.updateIconURL}
- placeholder='https://www.example.com/myicon.png'
+ value={this.state.cmd.username}
+ onChange={this.updateUsername}
+ placeholder={this.props.intl.formatMessage(holders.addUsernamePlaceholder)}
/>
</div>
<div className='padding-top'>
<FormattedMessage
- id='user.settings.cmds.icon_url_desc'
- defaultMessage='URL to an icon'
+ id='user.settings.cmds.username_desc'
+ defaultMessage='Choose a username override for responses for this slash command. Usernames can consist of up to 22 characters consisting of lowercase letters, numbers and they symbols "-", "_", and "." .'
/>
</div>
</div>
+
<div className='padding-top x2'>
<label className='control-label'>
<FormattedMessage
- id='user.settings.cmds.trigger'
- defaultMessage='Trigger: '
+ id='user.settings.cmds.icon_url'
+ defaultMessage='Response Icon: '
/>
</label>
<div className='padding-top'>
<input
- ref='trigger'
+ ref='iconURL'
className='form-control'
- value={this.state.cmd.trigger}
- onChange={this.updateTrigger}
- placeholder={this.props.intl.formatMessage(holders.addTriggerPlaceholder)}
+ value={this.state.cmd.icon_url}
+ onChange={this.updateIconURL}
+ placeholder='https://www.example.com/myicon.png'
/>
</div>
<div className='padding-top'>
<FormattedMessage
- id='user.settings.cmds.trigger_desc'
- defaultMessage='Word to trigger on'
+ id='user.settings.cmds.icon_url_desc'
+ defaultMessage='Choose a profile picture override for the post responses to this slash command. Enter the URL of a .png or .jpg file at least 128 pixels by 128 pixels.'
/>
- {''}</div>
+ </div>
</div>
+
<div className='padding-top x2'>
<label className='control-label'>
<FormattedMessage
id='user.settings.cmds.auto_complete'
- defaultMessage='Auto Complete: '
+ defaultMessage='Autocomplete: '
/>
</label>
<div className='padding-top'>
@@ -539,34 +575,18 @@ export default class ManageCommandCmds extends React.Component {
/>
<FormattedMessage
id='user.settings.cmds.auto_complete_help'
- defaultMessage=' Show this command in autocomplete list'
+ defaultMessage=' Show this command in the autocomplete list.'
/>
</label>
</div>
</div>
</div>
- <div className='padding-top x2'>
- <label className='control-label'>
- <FormattedMessage
- id='user.settings.cmds.auto_complete_desc'
- defaultMessage='Auto Complete Description: '
- />
- </label>
- <div className='padding-top'>
- <input
- ref='autoCompleteDesc'
- className='form-control'
- value={this.state.cmd.auto_complete_desc}
- onChange={this.updateAutoCompleteDesc}
- placeholder={this.props.intl.formatMessage(holders.addAutoCompleteDescPlaceholder)}
- />
- </div>
- </div>
+
<div className='padding-top x2'>
<label className='control-label'>
<FormattedMessage
id='user.settings.cmds.auto_complete_hint'
- defaultMessage='Auto Complete Hint: '
+ defaultMessage='Autocomplete Hint: '
/>
</label>
<div className='padding-top'>
@@ -581,64 +601,60 @@ export default class ManageCommandCmds extends React.Component {
<div className='padding-top'>
<FormattedMessage
id='user.settings.cmds.auto_complete_hint_desc'
- defaultMessage='List parameters to be passed to the command.'
+ defaultMessage='Optional hint in the autocomplete list about parameters needed for command.'
/>
</div>
</div>
+
<div className='padding-top x2'>
<label className='control-label'>
<FormattedMessage
- id='user.settings.cmds.request_type'
- defaultMessage='Request Type: '
+ id='user.settings.cmds.auto_complete_desc'
+ defaultMessage='Autocomplete Description: '
/>
</label>
<div className='padding-top'>
- <select
- ref='method'
+ <input
+ ref='autoCompleteDesc'
className='form-control'
- value={this.state.cmd.method}
- onChange={this.updateMethod}
- >
- <option value='P'>
- {this.props.intl.formatMessage(holders.requestTypePost)}
- </option>
- <option value='G'>
- {this.props.intl.formatMessage(holders.requestTypeGet)}
- </option>
- </select>
+ value={this.state.cmd.auto_complete_desc}
+ onChange={this.updateAutoCompleteDesc}
+ placeholder={this.props.intl.formatMessage(holders.addAutoCompleteDescPlaceholder)}
+ />
</div>
<div className='padding-top'>
<FormattedMessage
- id='user.settings.cmds.request_type_desc'
- defaultMessage='Command request type issued to the callback URL.'
+ id='user.settings.cmds.auto_complete_desc_desc'
+ defaultMessage='Optional short description of slash command for the autocomplete list.'
/>
</div>
</div>
+
<div className='padding-top x2'>
<label className='control-label'>
<FormattedMessage
- id='user.settings.cmds.url'
- defaultMessage='URL: '
+ id='user.settings.cmds.display_name'
+ defaultMessage='Descriptive Label: '
/>
</label>
<div className='padding-top'>
- <input
- ref='URL'
- className='form-control'
- value={this.state.cmd.url}
- rows={1}
- onChange={this.updateURL}
- placeholder={this.props.intl.formatMessage(holders.adUrlPlaceholder)}
- />
+ <input
+ ref='displayName'
+ className='form-control'
+ value={this.state.cmd.display_name}
+ onChange={this.updateDisplayName}
+ placeholder={this.props.intl.formatMessage(holders.addDisplayNamePlaceholder)}
+ />
</div>
<div className='padding-top'>
<FormattedMessage
- id='user.settings.cmds.url_desc'
- defaultMessage='URL that will receive the HTTP POST or GET event'
+ id='user.settings.cmds.cmd_display_name'
+ defaultMessage='Brief description of slash command to show in listings.'
/>
</div>
{addError}
</div>
+
<div className='padding-top x2 padding-bottom'>
<a
className={'btn btn-sm btn-primary'}
diff --git a/web/react/components/user_settings/user_settings_integrations.jsx b/web/react/components/user_settings/user_settings_integrations.jsx
index 1a9edab03..07d5230d1 100644
--- a/web/react/components/user_settings/user_settings_integrations.jsx
+++ b/web/react/components/user_settings/user_settings_integrations.jsx
@@ -28,11 +28,11 @@ const holders = defineMessages({
},
cmdName: {
id: 'user.settings.integrations.commands',
- defaultMessage: 'Commands'
+ defaultMessage: 'Slash Commands'
},
cmdDesc: {
id: 'user.settings.integrations.commandsDescription',
- defaultMessage: 'Manage your commands'
+ defaultMessage: 'Manage your slash commands'
}
});
diff --git a/web/react/components/user_settings/user_settings_modal.jsx b/web/react/components/user_settings/user_settings_modal.jsx
index e0b72157b..a7541073e 100644
--- a/web/react/components/user_settings/user_settings_modal.jsx
+++ b/web/react/components/user_settings/user_settings_modal.jsx
@@ -113,6 +113,7 @@ class UserSettingsModal extends React.Component {
return false;
}
+ this.resetTheme();
this.deactivateTab();
this.props.onModalDismissed();
}
@@ -215,15 +216,19 @@ class UserSettingsModal extends React.Component {
this.showConfirmModal(() => this.updateSection(section, true));
} else {
if (this.state.active_section === 'theme' && section !== 'theme') {
- const user = UserStore.getCurrentUser();
- if (user.theme_props != null) {
- Utils.applyTheme(user.theme_props);
- }
+ this.resetTheme();
}
this.setState({active_section: section});
}
}
+ resetTheme() {
+ const user = UserStore.getCurrentUser();
+ if (user.theme_props != null) {
+ Utils.applyTheme(user.theme_props);
+ }
+ }
+
render() {
const {formatMessage} = this.props.intl;
var tabs = [];
@@ -234,7 +239,7 @@ class UserSettingsModal extends React.Component {
tabs.push({name: 'developer', uiName: formatMessage(holders.developer), icon: 'glyphicon glyphicon-th'});
}
- if (global.window.mm_config.EnableIncomingWebhooks === 'true' || global.window.mm_config.EnableOutgoingWebhooks === 'true') {
+ if (global.window.mm_config.EnableIncomingWebhooks === 'true' || global.window.mm_config.EnableOutgoingWebhooks === 'true' || global.window.mm_config.EnableCommands === 'true') {
tabs.push({name: 'integrations', uiName: formatMessage(holders.integrations), icon: 'glyphicon glyphicon-transfer'});
}
tabs.push({name: 'display', uiName: formatMessage(holders.display), icon: 'glyphicon glyphicon-eye-open'});
diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx
index d650b23c2..ac800a988 100644
--- a/web/react/stores/channel_store.jsx
+++ b/web/react/stores/channel_store.jsx
@@ -308,7 +308,7 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => {
ChannelStore.storeChannels(action.channels);
ChannelStore.storeChannelMembers(action.members);
currentId = ChannelStore.getCurrentId();
- if (currentId) {
+ if (currentId && !document.hidden) {
ChannelStore.resetCounts(currentId);
}
ChannelStore.setUnreadCounts();
@@ -321,7 +321,7 @@ ChannelStore.dispatchToken = AppDispatcher.register((payload) => {
ChannelStore.pStoreChannelMember(action.member);
}
currentId = ChannelStore.getCurrentId();
- if (currentId) {
+ if (currentId && !document.hidden) {
ChannelStore.resetCounts(currentId);
}
ChannelStore.setUnreadCount(action.channel.id);
diff --git a/web/react/stores/socket_store.jsx b/web/react/stores/socket_store.jsx
index bc2bdbe64..e1b65fe14 100644
--- a/web/react/stores/socket_store.jsx
+++ b/web/react/stores/socket_store.jsx
@@ -188,7 +188,9 @@ function handleNewPostEvent(msg, translations) {
// Update channel state
if (ChannelStore.getCurrentId() === msg.channel_id) {
- if (window.isActive) {
+ if (document.hidden) {
+ AsyncClient.getChannel(msg.channel_id);
+ } else {
AsyncClient.updateLastViewedAt();
}
} else if (UserStore.getCurrentId() !== msg.user_id || post.type !== Constants.POST_TYPE_JOIN_LEAVE) {
diff --git a/web/react/utils/async_client.jsx b/web/react/utils/async_client.jsx
index d5fc10b8f..c8676f45d 100644
--- a/web/react/utils/async_client.jsx
+++ b/web/react/utils/async_client.jsx
@@ -779,13 +779,19 @@ export function getSuggestedCommands(command, suggestionId, component) {
var matches = [];
data.forEach((cmd) => {
if (('/' + cmd.trigger).indexOf(command) === 0) {
+ let s = '/' + cmd.trigger;
+ if (cmd.auto_complete_hint && cmd.auto_complete_hint.length !== 0) {
+ s += ' ' + cmd.auto_complete_hint;
+ }
matches.push({
- suggestion: '/' + cmd.trigger + ' ' + cmd.auto_complete_hint,
+ suggestion: s,
description: cmd.auto_complete_desc
});
}
});
+ matches = matches.sort((a, b) => a.suggestion.localeCompare(b.suggestion));
+
// pull out the suggested commands from the returned data
const terms = matches.map((suggestion) => suggestion.suggestion);
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 0a055d55c..428549d57 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -174,6 +174,7 @@ export default {
MENU_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='4px' height='16px' viewBox='0 0 8 32' enable-background='new 0 0 8 32' xml:space='preserve'> <g> <circle cx='4' cy='4.062' r='4'/> <circle cx='4' cy='16' r='4'/> <circle cx='4' cy='28' r='4'/> </g> </svg>",
COMMENT_ICON: "<svg version='1.1' id='Layer_2' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'width='15px' height='15px' viewBox='1 1.5 15 15' enable-background='new 1 1.5 15 15' xml:space='preserve'> <g> <g> <path fill='#211B1B' d='M14,1.5H3c-1.104,0-2,0.896-2,2v8c0,1.104,0.896,2,2,2h1.628l1.884,3l1.866-3H14c1.104,0,2-0.896,2-2v-8 C16,2.396,15.104,1.5,14,1.5z M15,11.5c0,0.553-0.447,1-1,1H8l-1.493,2l-1.504-1.991L5,12.5H3c-0.552,0-1-0.447-1-1v-8 c0-0.552,0.448-1,1-1h11c0.553,0,1,0.448,1,1V11.5z'/> </g> </g> </svg>",
REPLY_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'viewBox='-158 242 18 18' style='enable-background:new -158 242 18 18;' xml:space='preserve'> <path d='M-142.2,252.6c-2-3-4.8-4.7-8.3-4.8v-3.3c0-0.2-0.1-0.3-0.2-0.3s-0.3,0-0.4,0.1l-6.9,6.2c-0.1,0.1-0.1,0.2-0.1,0.3 c0,0.1,0,0.2,0.1,0.3l6.9,6.4c0.1,0.1,0.3,0.1,0.4,0.1c0.1-0.1,0.2-0.2,0.2-0.4v-3.8c4.2,0,7.4,0.4,9.6,4.4c0.1,0.1,0.2,0.2,0.3,0.2 c0,0,0.1,0,0.1,0c0.2-0.1,0.3-0.3,0.2-0.4C-140.2,257.3-140.6,255-142.2,252.6z M-150.8,252.5c-0.2,0-0.4,0.2-0.4,0.4v3.3l-6-5.5 l6-5.3v2.8c0,0.2,0.2,0.4,0.4,0.4c3.3,0,6,1.5,8,4.5c0.5,0.8,0.9,1.6,1.2,2.3C-144,252.8-147.1,252.5-150.8,252.5z'/> </svg>",
+ SCROLL_BOTTOM_ICON: "<svg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px'viewBox='-239 239 21 23' style='enable-background:new -239 239 21 23;' xml:space='preserve'> <path d='M-239,241.4l2.4-2.4l8.1,8.2l8.1-8.2l2.4,2.4l-10.5,10.6L-239,241.4z M-228.5,257.2l8.1-8.2l2.4,2.4l-10.5,10.6l-10.5-10.6 l2.4-2.4L-228.5,257.2z'/> </svg>",
UPDATE_TYPING_MS: 5000,
THEMES: {
default: {
diff --git a/web/react/utils/markdown.jsx b/web/react/utils/markdown.jsx
index 47b3a9a66..8b3602a89 100644
--- a/web/react/utils/markdown.jsx
+++ b/web/react/utils/markdown.jsx
@@ -151,6 +151,10 @@ class MattermostMarkdownRenderer extends marked.Renderer {
);
}
+ codespan(text) {
+ return '<pre class="text-nowrap">' + super.codespan(text) + '</pre>';
+ }
+
br() {
if (this.formattingOptions.singleline) {
return ' ';
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 896a94ac5..e2a5b9620 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -392,6 +392,10 @@ export function areObjectsEqual(x, y) {
return x.toString() === y.toString();
}
+ if (x instanceof Map && y instanceof Map) {
+ return areMapsEqual(x, y);
+ }
+
// At last checking prototypes as good a we can
if (!(x instanceof Object && y instanceof Object)) {
return false;
@@ -456,6 +460,24 @@ export function areObjectsEqual(x, y) {
return true;
}
+export function areMapsEqual(a, b) {
+ if (a.size !== b.size) {
+ return false;
+ }
+
+ for (const [key, value] of a) {
+ if (!b.has(key)) {
+ return false;
+ }
+
+ if (!areObjectsEqual(value, b.get(key))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
export function replaceHtmlEntities(text) {
var tagsToReplace = {
'&amp;': '&',
@@ -680,6 +702,7 @@ export function applyTheme(theme) {
}
if (theme.centerChannelColor) {
+ changeCss('.post-list__arrows', 'fill:' + changeOpacity(theme.centerChannelColor, 0.3), 1);
changeCss('.sidebar--left, .sidebar--right .sidebar--right__header', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
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('#archive-link-home', 'background:' + changeOpacity(theme.centerChannelColor, 0.15), 1);
diff --git a/web/sass-files/sass/partials/_markdown.scss b/web/sass-files/sass/partials/_markdown.scss
index 14e12ecd2..a08379ae1 100644
--- a/web/sass-files/sass/partials/_markdown.scss
+++ b/web/sass-files/sass/partials/_markdown.scss
@@ -39,6 +39,7 @@
padding: 4px 10px 5px 10px;
font-size: 13px;
opacity: 0.7;
+ z-index: 5;
}
.post__body {
@@ -53,6 +54,13 @@
code {
white-space: pre;
}
+ pre {
+ &.text-nowrap {
+ code {
+ white-space: nowrap;
+ }
+ }
+ }
}
.markdown__table {
background: #fff;
diff --git a/web/sass-files/sass/partials/_mentions.scss b/web/sass-files/sass/partials/_mentions.scss
index df6dd40a2..aa654e9e8 100644
--- a/web/sass-files/sass/partials/_mentions.scss
+++ b/web/sass-files/sass/partials/_mentions.scss
@@ -18,6 +18,13 @@
line-height: 36px;
font-size: 13px;
cursor: pointer;
+ white-space: nowrap;
+
+ .mention-align {
+ @include clearfix;
+ text-overflow: ellipsis;
+ width: calc(100% - 50px);
+ }
}
.mentions-text {
@@ -33,6 +40,15 @@
font-size: 20px;
text-align: center;
@include border-radius(32px);
+
+ .mention-align {
+ max-width: 80%;
+ overflow: hidden;
+ display: inline-block;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+
}
.mention-fullname {
diff --git a/web/sass-files/sass/partials/_modal.scss b/web/sass-files/sass/partials/_modal.scss
index b451adb75..db99e840b 100644
--- a/web/sass-files/sass/partials/_modal.scss
+++ b/web/sass-files/sass/partials/_modal.scss
@@ -103,7 +103,7 @@
background: rgba(0, 0, 0, 0.1);
}
span {
- font-family: 'Open Sans', sans-serif;
+ font-family: 'Open Sans', sans-serif;
line-height: 10px;
}
}
@@ -170,6 +170,12 @@
overflow: hidden;
text-overflow: ellipsis;
}
+ .more-description {
+ @include opacity(0.7);
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
tbody {
> tr {
&:hover td {
@@ -425,9 +431,9 @@
}
.modal-body.edit-modal-body {
- overflow: visible;
+ overflow: visible;
- .suggestion-content {
- max-height: 150px;
- }
+ .suggestion-content {
+ max-height: 150px;
+ }
}
diff --git a/web/sass-files/sass/partials/_popover.scss b/web/sass-files/sass/partials/_popover.scss
index 8a61758f1..bf762d2c9 100644
--- a/web/sass-files/sass/partials/_popover.scss
+++ b/web/sass-files/sass/partials/_popover.scss
@@ -150,6 +150,9 @@
}
.more-name {
margin-left: 6px;
+ max-width: 140px;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
}
}
diff --git a/web/sass-files/sass/partials/_post.scss b/web/sass-files/sass/partials/_post.scss
index cc22cc913..323691d89 100644
--- a/web/sass-files/sass/partials/_post.scss
+++ b/web/sass-files/sass/partials/_post.scss
@@ -264,32 +264,37 @@ body.ios {
line-height: 25px;
margin-left: -60px;
-webkit-font-smoothing: initial;
- @include single-transition(all, 0.3s, ease);
+ @include single-transition(all, 0.6s, ease);
@include translateY(-45px);
@include opacity(0);
display: none;
&.scrolling {
- @include single-transition(all, 0.3s, ease);
@include translateY(0);
@include opacity(0.8);
}
}
.post-list__arrows {
- background: url('../images/postArrows.png') center;
- @include background-size(28px 28px);
background-repeat: no-repeat;
width: 40px;
height: 40px;
+ text-align: center;
+ fill: #444;
position: absolute;
bottom: 0;
- left: 10px;
+ left: 9px;
z-index: 50;
@include opacity(0);
- @include single-transition(all, 0.3s);
+ @include single-transition(all, 0.6s);
display: none;
+ svg {
+ color: inherit;
+ width: 28px;
+ height: 28px;
+ }
+
&.scrolling {
display: block;
@include opacity(1);
@@ -487,7 +492,7 @@ body.ios {
position: absolute;
top: -2px;
left: -7px;
- font-size: 11px;
+ font-size: 11px;
line-height: 37px;
@include opacity(0);
}
diff --git a/web/sass-files/sass/partials/_settings.scss b/web/sass-files/sass/partials/_settings.scss
index bf296e913..1bbec566c 100644
--- a/web/sass-files/sass/partials/_settings.scss
+++ b/web/sass-files/sass/partials/_settings.scss
@@ -223,6 +223,8 @@
.section-describe {
@include opacity(0.7);
white-space:pre;
+ @include clearfix;
+ text-overflow: ellipsis;
}
.divider-dark {
@@ -341,21 +343,6 @@ h3 {
@include border-radius(50px);
margin-right: 8px;
}
- .member-name {
- font-weight:500;
- display: block;
- max-width: 80%;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .member-email {
- color:darkgrey;
- display: block;
- max-width: 80%;
- overflow: hidden;
- text-overflow: ellipsis;
- }
}
.member-role, .member-drop {
diff --git a/web/static/i18n/en.json b/web/static/i18n/en.json
index b4a68425e..ade6462e9 100644
--- a/web/static/i18n/en.json
+++ b/web/static/i18n/en.json
@@ -148,7 +148,6 @@
"admin.gitlab.userTitle": "User API Endpoint:",
"admin.gitlab.userDescription": "Enter https://<your-gitlab-url>/api/v3/user. Make sure you use HTTP or HTTPS in your URL depending on your server configuration.",
"admin.gitlab.save": "Save",
- "admin.image.storeDisabled": "Disable File Storage",
"admin.image.storeLocal": "Local File System",
"admin.image.storeAmazonS3": "Amazon S3",
"admin.image.localExample": "Ex \"./data/\"",
@@ -580,7 +579,7 @@
"delete_post.comment": "Comment",
"delete_post.post": "Post",
"delete_post.confirm": "Confirm {term} Delete",
- "delete_post.question": "Are you sure you want to delete this ${term}?",
+ "delete_post.question": "Are you sure you want to delete this {term}?",
"delete_post.cancel": "Cancel",
"delete_post.del": "Delete",
"edit_channel_header_modal.error": "This channel header is too long, please enter a shorter one",
@@ -902,6 +901,7 @@
"signup_user_completed.choosePwd": "Choose your password",
"signup_user_completed.create": "Create Account",
"signup_user_completed.or": "or",
+ "signup_user_completed.none": "No user creation method has been enabled. Please contact an administrator for access.",
"signup_user_completed.welcome": "Welcome to:",
"signup_user_completed.onSite": "on {siteName}",
"signup_user_completed.lets": "Let's create your account",
@@ -1063,37 +1063,38 @@
"user.settings.import_theme.submit": "Submit",
"user.settings.cmds.request_type_post": "POST",
"user.settings.cmds.request_type_get": "GET",
- "user.settings.cmds.add_display_name.placeholder": "Display Name",
+ "user.settings.cmds.add_display_name.placeholder": "Example: \"Search patient records\"",
"user.settings.cmds.add_username.placeholder": "Username",
"user.settings.cmds.add_trigger.placeholder": "Command trigger e.g. \"hello\" not including the slash",
- "user.settings.cmds.auto_complete_desc.placeholder": "A short description of what this commands does.",
- "user.settings.cmds.auto_complete_hint.placeholder": "[zipcode]",
+ "user.settings.cmds.auto_complete_desc.placeholder": "Example: \"Returns search results for patient records\"",
+ "user.settings.cmds.auto_complete_hint.placeholder": "Example: [Patient Name]",
+ "user.settings.cmds.auto_complete_desc_desc": "Optional short description of slash command for the autocomplete list.",
"user.settings.cmds.url.placeholder": "Must start with http:// or https://",
"user.settings.cmds.auto_complete.yes": "yes",
"user.settings.cmds.auto_complete.no": "no",
- "user.settings.cmds.trigger": "Trigger: ",
- "user.settings.cmds.display_name": "Display Name: ",
- "user.settings.cmds.username": "Username: ",
- "user.settings.cmds.icon_url": "Icon URL: ",
- "user.settings.cmds.auto_complete": "Auto Complete: ",
- "user.settings.cmds.auto_complete_desc": "Auto Complete Description: ",
- "user.settings.cmds.auto_complete_hint": "Auto Complete Hint: ",
- "user.settings.cmds.request_type": "Request Type: ",
- "user.settings.cmds.url": "URL: ",
+ "user.settings.cmds.trigger": "Command Trigger Word: ",
+ "user.settings.cmds.display_name": "Descriptive Label: ",
+ "user.settings.cmds.username": "Response Username: ",
+ "user.settings.cmds.icon_url": "Response Icon: ",
+ "user.settings.cmds.auto_complete": "Autocomplete: ",
+ "user.settings.cmds.auto_complete_desc": "Autocomplete Description: ",
+ "user.settings.cmds.auto_complete_hint": "Autocomplete Hint: ",
+ "user.settings.cmds.request_type": "Request Method: ",
+ "user.settings.cmds.url": "Request URL: ",
"user.settings.cmds.token": "Token: ",
"user.settings.cmds.regen": "Regen Token",
"user.settings.cmds.none": "None",
"user.settings.cmds.existing": "Existing commands",
- "user.settings.cmds.add_desc": "Create commands to send message events to an external integration. Please see <a href=\"http://mattermost.org/commands\">http://mattermost.org/commands</a> to learn more.",
+ "user.settings.cmds.add_desc": "Create slash commands to send events to external integrations and receive a response. For example typing `/patient Joe Smith` could bring back search results from your internal health records management system for the name 'Joe Smith'. Please see <a href=\"http://docs.mattermost.com/developer/slash-commands.html\">Slash commands documentation</a> for detailed instructions.",
"user.settings.cmds.add_new": "Add a new command",
- "user.settings.cmds.cmd_display_name": "Command display name.",
- "user.settings.cmds.username_desc": "The username to use when overriding the post.",
- "user.settings.cmds.icon_url_desc": "URL to an icon",
- "user.settings.cmds.trigger_desc": "Word to trigger on",
- "user.settings.cmds.auto_complete_help": "Show this command in autocomplete list",
- "user.settings.cmds.auto_complete_hint_desc": "List parameters to be passed to the command.",
- "user.settings.cmds.request_type_desc": "Command request type issued to the callback URL.",
- "user.settings.cmds.url_desc": "URL that will receive the HTTP POST or GET event",
+ "user.settings.cmds.cmd_display_name": "Brief description of slash command to show in listings.",
+ "user.settings.cmds.username_desc": "Choose a username override for responses for this slash command. Usernames can consist of up to 22 characters consisting of lowercase letters, numbers and they symbols \"-\", \"_\", and \".\" .",
+ "user.settings.cmds.icon_url_desc": "Choose a profile picture override for the post responses to this slash command. Enter the URL of a .png or .jpg file at least 128 pixels by 128 pixels.",
+ "user.settings.cmds.trigger_desc": "Examples: /patient, /client, /employee Reserved: /echo, /join, /logout, /me, /shrug",
+ "user.settings.cmds.auto_complete_help": " Show this command in the autocomplete list.",
+ "user.settings.cmds.auto_complete_hint_desc": "Optional hint in the autocomplete list about parameters needed for command.",
+ "user.settings.cmds.request_type_desc": "The type of command request issued to the Request URL.",
+ "user.settings.cmds.url_desc": "The callback URL to receive the HTTP POST or GET event request when the slash command is run.",
"user.settings.cmds.add": "Add",
"user.settings.hooks_in.channel": "Channel: ",
"user.settings.hooks_in.none": "None",
@@ -1183,8 +1184,8 @@
"user.settings.integrations.incomingWebhooksDescription": "Manage your incoming webhooks",
"user.settings.integrations.outWebhooks": "Outgoing Webhooks",
"user.settings.integrations.outWebhooksDescription": "Manage your outgoing webhooks",
- "user.settings.integrations.commands": "Commands",
- "user.settings.integrations.commandsDescription": "Manage your commands",
+ "user.settings.integrations.commands": "Slash Commands",
+ "user.settings.integrations.commandsDescription": "Manage your slash commands",
"user.settings.integrations.title": "Integration Settings",
"user.settings.modal.general": "General",
"user.settings.modal.security": "Security",
diff --git a/web/static/i18n/es.json b/web/static/i18n/es.json
index a3c7e13a7..e4ddd76ce 100644
--- a/web/static/i18n/es.json
+++ b/web/static/i18n/es.json
@@ -157,7 +157,6 @@
"admin.image.shareDescription": "Permitir a los usuarios compartir enlaces públicos para archivos e imágenes.",
"admin.image.shareTitle": "Compartir publicamente enlaces de archivos: ",
"admin.image.storeAmazonS3": "Amazon S3",
- "admin.image.storeDisabled": "Deshabilitar almacenamiento de archivos",
"admin.image.storeLocal": "Sistema local de archivos",
"admin.image.storeTitle": "Almacenar archivos en:",
"admin.image.thumbHeightDescription": "Alto de imágen miniatura subida. Actualizando este valor la imagen miniatura cambia en el futuro, pero no cambia para imagenes creadas en el pasado.",
@@ -933,6 +932,7 @@
"signup_user_completed.gitlab": "con GitLab",
"signup_user_completed.google": "con Google",
"signup_user_completed.lets": "Vamos a crear tu cuenta",
+ "signup_user_completed.none": "No está habilitado ningún método para crear usuarios. Por favor contacta a un administrador para obtener acceso.",
"signup_user_completed.onSite": "en {siteName}",
"signup_user_completed.or": "o",
"signup_user_completed.passwordLength": "Por favor ingresa al menos {min} caracteres",
@@ -1058,39 +1058,19 @@
"user.settings.advance.sendTitle": "Enviar mensajes con Ctrl + Retorno",
"user.settings.advance.title": "Configuración Avanzada",
"user.settings.cmds.add": "Agregar",
- "user.settings.cmds.add_desc": "Crea comandos que permitan enviar eventos a integraciones externas. Por favor revisa <a href=\"http://mattermost.org/commands\">http://mattermost.org/commands</a> para aprender más.",
- "user.settings.cmds.add_display_name.placeholder": "Nombre a mostrar",
"user.settings.cmds.add_new": "Agregar un nuevo comando",
"user.settings.cmds.add_trigger.placeholder": "Gatillador del Comando ej. \"hola\" no se debe incluir la barra",
"user.settings.cmds.add_username.placeholder": "Nombre de usuario",
- "user.settings.cmds.auto_complete": "Auto completado: ",
"user.settings.cmds.auto_complete.no": "no",
"user.settings.cmds.auto_complete.yes": "sí",
- "user.settings.cmds.auto_complete_desc": "Descripción del Auto Completado: ",
- "user.settings.cmds.auto_complete_desc.placeholder": "Una pequeña descripción de que hace el comando.",
"user.settings.cmds.auto_complete_help": "Mostrar este comando en la lista de auto completado.",
- "user.settings.cmds.auto_complete_hint": "Pista de auto completado: ",
- "user.settings.cmds.auto_complete_hint.placeholder": "[código postal]",
- "user.settings.cmds.auto_complete_hint_desc": "Lista de parámetros que recibe el comando.",
- "user.settings.cmds.cmd_display_name": "Nombre a mostrar del Comando.",
- "user.settings.cmds.display_name": "Nombre a mostrar: ",
"user.settings.cmds.existing": "Comandos existentes",
- "user.settings.cmds.icon_url": "URL del icono: ",
- "user.settings.cmds.icon_url_desc": "URL para un icono",
"user.settings.cmds.none": "Ninguno",
"user.settings.cmds.regen": "Regenerar Token",
- "user.settings.cmds.request_type": "Tipo de Solicitud: ",
- "user.settings.cmds.request_type_desc": "Tipo de solicitud emitido al callback URL por el Comando.",
"user.settings.cmds.request_type_get": "GET",
"user.settings.cmds.request_type_post": "POST",
"user.settings.cmds.token": "Token: ",
- "user.settings.cmds.trigger": "Gatillador: ",
- "user.settings.cmds.trigger_desc": "Palabra que gatilla la acción",
- "user.settings.cmds.url": "URL: ",
"user.settings.cmds.url.placeholder": "Debe comenzar con http:// o https://",
- "user.settings.cmds.url_desc": "URL que va a recibir el evento HTTP POST o GET",
- "user.settings.cmds.username": "Nombre de usuario: ",
- "user.settings.cmds.username_desc": "El nombre de usuario a utilizar cuando se genere el mensaje.",
"user.settings.custom_theme.awayIndicator": "Indicador Ausente",
"user.settings.custom_theme.buttonBg": "Fondo Botón",
"user.settings.custom_theme.buttonColor": "Texto Botón",
@@ -1193,8 +1173,6 @@
"user.settings.import_theme.importHeader": "Importar Tema de Slack",
"user.settings.import_theme.submit": "Enviar",
"user.settings.import_theme.submitError": "Formato inválido, por favor intenta copiando y pegando nuevamente.",
- "user.settings.integrations.commands": "Comandos",
- "user.settings.integrations.commandsDescription": "Administra tus comandos",
"user.settings.integrations.incomingWebhooks": "Webhooks de entrada",
"user.settings.integrations.incomingWebhooksDescription": "Administra tus webhooks de entrada",
"user.settings.integrations.outWebhooks": "Webhooks de salida",
diff --git a/web/static/images/postArrows.png b/web/static/images/postArrows.png
deleted file mode 100644
index 7b5919fc3..000000000
--- a/web/static/images/postArrows.png
+++ /dev/null
Binary files differ