summaryrefslogtreecommitdiffstats
path: root/web/react
diff options
context:
space:
mode:
Diffstat (limited to 'web/react')
-rw-r--r--web/react/components/admin_console/email_settings.jsx4
-rw-r--r--web/react/components/admin_console/gitlab_settings.jsx67
-rw-r--r--web/react/components/command_list.jsx2
-rw-r--r--web/react/components/more_direct_channels.jsx2
-rw-r--r--web/react/components/navbar_dropdown.jsx2
-rw-r--r--web/react/components/popover_list_members.jsx26
-rw-r--r--web/react/components/post_body.jsx122
-rw-r--r--web/react/components/post_info.jsx2
-rw-r--r--web/react/components/post_list.jsx14
-rw-r--r--web/react/components/rename_channel_modal.jsx1
-rw-r--r--web/react/components/search_results_item.jsx7
-rw-r--r--web/react/components/setting_upload.jsx45
-rw-r--r--web/react/components/sidebar.jsx15
-rw-r--r--web/react/components/sidebar_right.jsx25
-rw-r--r--web/react/components/sidebar_right_menu.jsx15
-rw-r--r--web/react/components/team_signup_url_page.jsx4
-rw-r--r--web/react/components/team_signup_with_sso.jsx10
-rw-r--r--web/react/components/user_settings/import_theme_modal.jsx2
-rw-r--r--web/react/components/user_settings/manage_incoming_hooks.jsx16
-rw-r--r--web/react/components/user_settings/user_settings_notifications.jsx2
-rw-r--r--web/react/utils/constants.jsx1
-rw-r--r--web/react/utils/utils.jsx191
22 files changed, 297 insertions, 278 deletions
diff --git a/web/react/components/admin_console/email_settings.jsx b/web/react/components/admin_console/email_settings.jsx
index 3b5ad2a1a..762a4ab26 100644
--- a/web/react/components/admin_console/email_settings.jsx
+++ b/web/react/components/admin_console/email_settings.jsx
@@ -288,7 +288,7 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='feedbackName'
>
- {'Feedback Name:'}
+ {'Notification Display Name:'}
</label>
<div className='col-sm-8'>
<input
@@ -310,7 +310,7 @@ export default class EmailSettings extends React.Component {
className='control-label col-sm-4'
htmlFor='feedbackEmail'
>
- {'Feedback Email:'}
+ {'Notification Email Address:'}
</label>
<div className='col-sm-8'>
<input
diff --git a/web/react/components/admin_console/gitlab_settings.jsx b/web/react/components/admin_console/gitlab_settings.jsx
index eaea15b8f..759892ad3 100644
--- a/web/react/components/admin_console/gitlab_settings.jsx
+++ b/web/react/components/admin_console/gitlab_settings.jsx
@@ -40,7 +40,6 @@ export default class GitLabSettings extends React.Component {
config.GitLabSettings.Enable = React.findDOMNode(this.refs.Enable).checked;
config.GitLabSettings.Secret = React.findDOMNode(this.refs.Secret).value.trim();
config.GitLabSettings.Id = React.findDOMNode(this.refs.Id).value.trim();
- config.GitLabSettings.Scope = React.findDOMNode(this.refs.Scope).value.trim();
config.GitLabSettings.AuthEndpoint = React.findDOMNode(this.refs.AuthEndpoint).value.trim();
config.GitLabSettings.TokenEndpoint = React.findDOMNode(this.refs.TokenEndpoint).value.trim();
config.GitLabSettings.UserApiEndpoint = React.findDOMNode(this.refs.UserApiEndpoint).value.trim();
@@ -121,28 +120,6 @@ export default class GitLabSettings extends React.Component {
<div className='form-group'>
<label
className='control-label col-sm-4'
- htmlFor='Secret'
- >
- {'Secret:'}
- </label>
- <div className='col-sm-8'>
- <input
- type='text'
- className='form-control'
- id='Secret'
- ref='Secret'
- placeholder='Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
- defaultValue={this.props.config.GitLabSettings.Secret}
- onChange={this.handleChange}
- disabled={!this.state.Enable}
- />
- <p className='help-text'>{'Obtain this value via the instructions above for logging into GitLab.'}</p>
- </div>
- </div>
-
- <div className='form-group'>
- <label
- className='control-label col-sm-4'
htmlFor='Id'
>
{'Id:'}
@@ -165,22 +142,22 @@ export default class GitLabSettings extends React.Component {
<div className='form-group'>
<label
className='control-label col-sm-4'
- htmlFor='Scope'
+ htmlFor='Secret'
>
- {'Scope:'}
+ {'Secret:'}
</label>
<div className='col-sm-8'>
<input
type='text'
className='form-control'
- id='Scope'
- ref='Scope'
- placeholder='Not currently used by GitLab. Please leave blank'
- defaultValue={this.props.config.GitLabSettings.Scope}
+ id='Secret'
+ ref='Secret'
+ placeholder='Ex "jcuS8PuvcpGhpgHhlcpT1Mx42pnqMxQY"'
+ defaultValue={this.props.config.GitLabSettings.Secret}
onChange={this.handleChange}
disabled={!this.state.Enable}
/>
- <p className='help-text'>{'This field is not yet used by GitLab OAuth. Other OAuth providers may use this field to specify the scope of account data from OAuth provider that is sent to Mattermost.'}</p>
+ <p className='help-text'>{'Obtain this value via the instructions above for logging into GitLab.'}</p>
</div>
</div>
@@ -202,7 +179,7 @@ export default class GitLabSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.Enable}
/>
- <p className='help-text'>{'Enter <your-gitlab-url>/oauth/authorize (example http://localhost:3000/oauth/authorize).'}</p>
+ <p className='help-text'>{'Enter <your-gitlab-url>/oauth/authorize (example http://localhost:3000/oauth/authorize). Make sure you use HTTP or HTTPS in your URLs as appropriate.'}</p>
</div>
</div>
@@ -224,7 +201,7 @@ export default class GitLabSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.Enable}
/>
- <p className='help-text'>{'Enter <your-gitlab-url>/oauth/token.'}</p>
+ <p className='help-text'>{'Enter <your-gitlab-url>/oauth/token. Make sure you use HTTP or HTTPS in your URLs as appropriate.'}</p>
</div>
</div>
@@ -246,7 +223,7 @@ export default class GitLabSettings extends React.Component {
onChange={this.handleChange}
disabled={!this.state.Enable}
/>
- <p className='help-text'>{'Enter <your-gitlab-url>/api/v3/user.'}</p>
+ <p className='help-text'>{'Enter <your-gitlab-url>/api/v3/user. Make sure you use HTTP or HTTPS in your URLs as appropriate.'}</p>
</div>
</div>
@@ -272,6 +249,30 @@ export default class GitLabSettings extends React.Component {
}
}
+
+//config.GitLabSettings.Scope = React.findDOMNode(this.refs.Scope).value.trim();
+// <div className='form-group'>
+// <label
+// className='control-label col-sm-4'
+// htmlFor='Scope'
+// >
+// {'Scope:'}
+// </label>
+// <div className='col-sm-8'>
+// <input
+// type='text'
+// className='form-control'
+// id='Scope'
+// ref='Scope'
+// placeholder='Not currently used by GitLab. Please leave blank'
+// defaultValue={this.props.config.GitLabSettings.Scope}
+// onChange={this.handleChange}
+// disabled={!this.state.Allow}
+// />
+// <p className='help-text'>{'This field is not yet used by GitLab OAuth. Other OAuth providers may use this field to specify the scope of account data from OAuth provider that is sent to Mattermost.'}</p>
+// </div>
+// </div>
+
GitLabSettings.propTypes = {
config: React.PropTypes.object
};
diff --git a/web/react/components/command_list.jsx b/web/react/components/command_list.jsx
index 63bd57c2a..fea7085b7 100644
--- a/web/react/components/command_list.jsx
+++ b/web/react/components/command_list.jsx
@@ -96,4 +96,4 @@ CommandList.defaultProps = {
CommandList.propTypes = {
addCommand: React.PropTypes.func,
channelId: React.PropTypes.string
-}; \ No newline at end of file
+};
diff --git a/web/react/components/more_direct_channels.jsx b/web/react/components/more_direct_channels.jsx
index b7bce9b34..54d77c358 100644
--- a/web/react/components/more_direct_channels.jsx
+++ b/web/react/components/more_direct_channels.jsx
@@ -43,7 +43,7 @@ export default class MoreDirectChannels extends React.Component {
handleClick = function clickHandler(e) {
e.preventDefault();
- utils.switchChannel(channel, channel.teammate_username);
+ utils.switchChannel(channel);
$(React.findDOMNode(self.refs.modal)).modal('hide');
};
} else {
diff --git a/web/react/components/navbar_dropdown.jsx b/web/react/components/navbar_dropdown.jsx
index 4c01d2c43..57a78a0d4 100644
--- a/web/react/components/navbar_dropdown.jsx
+++ b/web/react/components/navbar_dropdown.jsx
@@ -62,7 +62,7 @@ export default class NavbarDropdown extends React.Component {
if (currentUser != null) {
isAdmin = Utils.isAdmin(currentUser.roles);
- isSystemAdmin = Utils.isInRole(currentUser.roles, 'system_admin');
+ isSystemAdmin = Utils.isSystemAdmin(currentUser.roles);
inviteLink = (
<li>
diff --git a/web/react/components/popover_list_members.jsx b/web/react/components/popover_list_members.jsx
index a2ca8b00f..95a88c3d6 100644
--- a/web/react/components/popover_list_members.jsx
+++ b/web/react/components/popover_list_members.jsx
@@ -1,6 +1,8 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
+var UserStore = require('../stores/user_store.jsx');
+
export default class PopoverListMembers extends React.Component {
componentDidMount() {
const originalLeave = $.fn.popover.Constructor.prototype.leave;
@@ -32,22 +34,28 @@ export default class PopoverListMembers extends React.Component {
}
render() {
let popoverHtml = '';
+ let count = 0;
+ let countText = '-';
const members = this.props.members;
- let count;
- if (members.length > 20) {
- count = '20+';
- } else {
- count = members.length || '-';
- }
+ const teamMembers = UserStore.getProfilesUsernameMap();
- if (members) {
+ if (members && teamMembers) {
members.sort(function compareByLocal(a, b) {
return a.username.localeCompare(b.username);
});
members.forEach(function addMemberElement(m) {
- popoverHtml += `<div class='text--nowrap'>${m.username}</div>`;
+ if (teamMembers[m.username] && teamMembers[m.username].delete_at <= 0) {
+ popoverHtml += `<div class='text--nowrap'>${m.username}</div>`;
+ count++;
+ }
});
+
+ if (count > 20) {
+ countText = '20+';
+ } else if (count > 0) {
+ countText = count.toString();
+ }
}
return (
@@ -63,7 +71,7 @@ export default class PopoverListMembers extends React.Component {
data-toggle='tooltip'
title='View Channel Members'
>
- {count}
+ {countText}
<span
className='fa fa-user'
aria-hidden='true'
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index 6e98e4aba..48b268700 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -12,7 +12,10 @@ export default class PostBody extends React.Component {
constructor(props) {
super(props);
+ this.receivedYoutubeData = false;
+
this.parseEmojis = this.parseEmojis.bind(this);
+ this.createYoutubeEmbed = this.createYoutubeEmbed.bind(this);
const linkData = Utils.extractLinks(this.props.post.message);
this.state = {links: linkData.links, message: linkData.text};
@@ -50,6 +53,123 @@ export default class PostBody extends React.Component {
this.setState({links: linkData.links, message: linkData.text});
}
+ handleYoutubeTime(link) {
+ const timeRegex = /[\\?&]t=([0-9hms]+)/;
+
+ const time = link.trim().match(timeRegex);
+ if (!time || !time[1]) {
+ return '';
+ }
+
+ const hours = time[1].match(/([0-9]+)h/);
+ const minutes = time[1].match(/([0-9]+)m/);
+ const seconds = time[1].match(/([0-9]+)s/);
+
+ let ticks = 0;
+
+ if (hours && hours[1]) {
+ ticks += parseInt(hours[1], 10) * 3600;
+ }
+
+ if (minutes && minutes[1]) {
+ ticks += parseInt(minutes[1], 10) * 60;
+ }
+
+ if (seconds && seconds[1]) {
+ ticks += parseInt(seconds[1], 10);
+ }
+
+ return '&start=' + ticks.toString();
+ }
+
+ createYoutubeEmbed(link) {
+ const ytRegex = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|watch\?(?:[a-zA-Z-_]+=[a-zA-Z0-9-_]+&)+v=)([^#\&\?]*).*/;
+
+ const match = link.trim().match(ytRegex);
+ if (!match || match[1].length !== 11) {
+ return null;
+ }
+
+ const youtubeId = match[1];
+ const time = this.handleYoutubeTime(link);
+
+ function onClick(e) {
+ var div = $(e.target).closest('.video-thumbnail__container')[0];
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('src',
+ 'https://www.youtube.com/embed/' +
+ div.id +
+ '?autoplay=1&autohide=1&border=0&wmode=opaque&fs=1&enablejsapi=1' +
+ time);
+ iframe.setAttribute('width', '480px');
+ iframe.setAttribute('height', '360px');
+ iframe.setAttribute('type', 'text/html');
+ iframe.setAttribute('frameborder', '0');
+ iframe.setAttribute('allowfullscreen', 'allowfullscreen');
+
+ div.parentNode.replaceChild(iframe, div);
+ }
+
+ function success(data) {
+ if (!data.items.length || !data.items[0].snippet) {
+ return null;
+ }
+ var metadata = data.items[0].snippet;
+ this.receivedYoutubeData = true;
+ this.setState({youtubeUploader: metadata.channelTitle, youtubeTitle: metadata.title});
+ }
+
+ if (global.window.config.GoogleDeveloperKey && !this.receivedYoutubeData) {
+ $.ajax({
+ async: true,
+ url: 'https://www.googleapis.com/youtube/v3/videos',
+ type: 'GET',
+ data: {part: 'snippet', id: youtubeId, key: global.window.config.GoogleDeveloperKey},
+ success
+ });
+ }
+
+ let header = 'Youtube';
+ if (this.state.youtubeTitle) {
+ header = header + ' - ';
+ }
+
+ let uploader = this.state.youtubeUploader;
+ if (!uploader) {
+ uploader = 'unknown';
+ }
+
+ return (
+ <div className='post-comment'>
+ <h4>
+ <span className='video-type'>{header}</span>
+ <span className='video-title'><a href={link}>{this.state.youtubeTitle}</a></span>
+ </h4>
+ <h4 className='video-uploader'>{uploader}</h4>
+ <div
+ className='video-div embed-responsive-item'
+ id={youtubeId}
+ onClick={onClick}
+ >
+ <div className='embed-responsive embed-responsive-4by3 video-div__placeholder'>
+ <div
+ id={youtubeId}
+ className='video-thumbnail__container'
+ >
+ <img
+ className='video-thumbnail'
+ src={'https://i.ytimg.com/vi/' + youtubeId + '/hqdefault.jpg'}
+ />
+ <div className='block'>
+ <span className='play-button'><span/></span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
render() {
const post = this.props.post;
const filenames = this.props.post.filenames;
@@ -133,7 +253,7 @@ export default class PostBody extends React.Component {
let embed;
if (filenames.length === 0 && this.state.links) {
- embed = Utils.getEmbed(this.state.links[0]);
+ embed = this.createYoutubeEmbed(this.state.links[0]);
}
let fileAttachmentHolder = '';
diff --git a/web/react/components/post_info.jsx b/web/react/components/post_info.jsx
index 824e7ef39..82e746dc0 100644
--- a/web/react/components/post_info.jsx
+++ b/web/react/components/post_info.jsx
@@ -150,7 +150,7 @@ export default class PostInfo extends React.Component {
var dropdown = this.createDropdown();
- let tooltip = <Tooltip>{utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}</Tooltip>;
+ let tooltip = <Tooltip id={post.id + 'tooltip'}>{utils.displayDate(post.create_at)} at ${utils.displayTime(post.create_at)}</Tooltip>;
return (
<ul className='post-header post-info'>
diff --git a/web/react/components/post_list.jsx b/web/react/components/post_list.jsx
index 3e1e075bb..a31967257 100644
--- a/web/react/components/post_list.jsx
+++ b/web/react/components/post_list.jsx
@@ -128,6 +128,15 @@ export default class PostList extends React.Component {
this.userHasSeenNew = true;
}
this.isUserScroll = true;
+
+ $('.top-visible-post').removeClass('top-visible-post');
+
+ $(React.findDOMNode(this.refs.postlistcontent)).children().each(function select() {
+ if ($(this).position().top + $(this).height() / 2 > 0) {
+ $(this).addClass('top-visible-post');
+ return false;
+ }
+ });
});
$('.post-list__content div .post').removeClass('post--last');
@@ -665,7 +674,10 @@ export default class PostList extends React.Component {
className={'post-list-holder-by-time ' + activeClass}
>
<div className='post-list__table'>
- <div className='post-list__content'>
+ <div
+ ref='postlistcontent'
+ className='post-list__content'
+ >
{moreMessages}
{postCtls}
</div>
diff --git a/web/react/components/rename_channel_modal.jsx b/web/react/components/rename_channel_modal.jsx
index 37958b649..9d514c741 100644
--- a/web/react/components/rename_channel_modal.jsx
+++ b/web/react/components/rename_channel_modal.jsx
@@ -78,7 +78,6 @@ export default class RenameChannelModal extends React.Component {
$(React.findDOMNode(this.refs.modal)).modal('hide');
AsyncClient.getChannel(channel.id);
- Utils.updateTabTitle(channel.display_name);
Utils.updateAddressBar(channel.name);
React.findDOMNode(this.refs.displayName).value = '';
diff --git a/web/react/components/search_results_item.jsx b/web/react/components/search_results_item.jsx
index 32b521560..bdefdbee8 100644
--- a/web/react/components/search_results_item.jsx
+++ b/web/react/components/search_results_item.jsx
@@ -47,13 +47,8 @@ export default class SearchResultsItem extends React.Component {
);
var postChannel = ChannelStore.get(this.props.post.channel_id);
- var teammate = '';
- if (postChannel.type === 'D') {
- teammate = utils.getDirectTeammate(this.props.post.channel_id).username;
- }
-
- utils.switchChannel(postChannel, teammate);
+ utils.switchChannel(postChannel);
}
render() {
diff --git a/web/react/components/setting_upload.jsx b/web/react/components/setting_upload.jsx
index fad27b355..ccb26cc58 100644
--- a/web/react/components/setting_upload.jsx
+++ b/web/react/components/setting_upload.jsx
@@ -7,11 +7,11 @@ export default class SettingsUpload extends React.Component {
this.doFileSelect = this.doFileSelect.bind(this);
this.doSubmit = this.doSubmit.bind(this);
- this.onFileSelect = this.onFileSelect.bind(this);
this.state = {
clientError: this.props.clientError,
- serverError: this.props.serverError
+ serverError: this.props.serverError,
+ filename: ''
};
}
@@ -24,9 +24,14 @@ export default class SettingsUpload extends React.Component {
doFileSelect(e) {
e.preventDefault();
+ var filename = $(e.target).val();
+ if (filename.substring(3, 11) === 'fakepath') {
+ filename = filename.substring(12);
+ }
this.setState({
clientError: '',
- serverError: ''
+ serverError: '',
+ filename
});
}
@@ -40,28 +45,28 @@ export default class SettingsUpload extends React.Component {
}
}
- onFileSelect(e) {
- var filename = $(e.target).val();
- if (filename.substring(3, 11) === 'fakepath') {
- filename = filename.substring(12);
- }
- $(e.target).closest('li').find('.file-status').addClass('hide');
- $(e.target).closest('li').find('.file-name').removeClass('hide').html(filename);
- }
-
render() {
- var clientError = null;
+ let clientError = null;
if (this.state.clientError) {
clientError = (
<div className='file-status'>{this.state.clientError}</div>
);
}
- var serverError = null;
+ let serverError = null;
if (this.state.serverError) {
serverError = (
<div className='file-status'>{this.state.serverError}</div>
);
}
+ let fileNameText = null;
+ let submitButtonClass = 'btn btn-sm btn-primary disabled';
+ if (this.state.filename) {
+ fileNameText = (
+ <div className='file-status file-name'>{this.state.filename}</div>
+ );
+ submitButtonClass = 'btn btn-sm btn-primary';
+ }
+
return (
<ul className='section-max'>
<li className='col-sm-12 section-title'>{this.props.title}</li>
@@ -70,21 +75,21 @@ export default class SettingsUpload extends React.Component {
<ul className='setting-list'>
<li className='setting-list-item'>
<span className='btn btn-sm btn-primary btn-file sel-btn'>
- Select file
+ {'Select file'}
<input
ref='uploadinput'
accept={this.props.fileTypesAccepted}
type='file'
- onChange={this.onFileSelect}
+ onChange={this.doFileSelect}
/>
</span>
<a
- className={'btn btn-sm btn-primary'}
+ className={submitButtonClass}
onClick={this.doSubmit}
>
- Import
+ {'Import'}
</a>
- <div className='file-status file-name hide'></div>
+ {fileNameText}
{serverError}
{clientError}
</li>
@@ -102,4 +107,4 @@ SettingsUpload.propTypes = {
clientError: React.PropTypes.string,
serverError: React.PropTypes.string,
helpText: React.PropTypes.object
-};
+}; \ No newline at end of file
diff --git a/web/react/components/sidebar.jsx b/web/react/components/sidebar.jsx
index 14664ed4d..6033f200f 100644
--- a/web/react/components/sidebar.jsx
+++ b/web/react/components/sidebar.jsx
@@ -268,14 +268,19 @@ export default class Sidebar extends React.Component {
}
}
updateTitle() {
- var channel = ChannelStore.getCurrent();
+ const channel = ChannelStore.getCurrent();
if (channel) {
+ let currentSiteName = '';
+ if (global.window.config.SiteName != null) {
+ currentSiteName = global.window.config.SiteName;
+ }
+
+ let currentChannelName = channel.display_name;
if (channel.type === 'D') {
- var teammateUsername = Utils.getDirectTeammate(channel.id).username;
- document.title = teammateUsername + ' ' + document.title.substring(document.title.lastIndexOf('-'));
- } else {
- document.title = channel.display_name + ' ' + document.title.substring(document.title.lastIndexOf('-'));
+ currentChannelName = Utils.getDirectTeammate(channel.id).username;
}
+
+ document.title = currentChannelName + ' - ' + this.props.teamDisplayName + ' ' + currentSiteName;
}
}
onScroll() {
diff --git a/web/react/components/sidebar_right.jsx b/web/react/components/sidebar_right.jsx
index 913715154..708cd04cb 100644
--- a/web/react/components/sidebar_right.jsx
+++ b/web/react/components/sidebar_right.jsx
@@ -14,9 +14,10 @@ export default class SidebarRight extends React.Component {
constructor(props) {
super(props);
+ this.plScrolledToBottom = true;
+
this.onSelectedChange = this.onSelectedChange.bind(this);
this.onSearchChange = this.onSearchChange.bind(this);
- this.resize = this.resize.bind(this);
this.state = getStateFromStores();
}
@@ -28,6 +29,14 @@ export default class SidebarRight extends React.Component {
PostStore.removeSearchChangeListener(this.onSearchChange);
PostStore.removeSelectedPostChangeListener(this.onSelectedChange);
}
+ componentDidUpdate() {
+ if (!this.plScrolledToBottom) {
+ $('.top-visible-post')[0].scrollIntoView();
+ } else {
+ var postHolder = $('.post-list-holder-by-time').not('.inactive');
+ postHolder.scrollTop(postHolder[0].scrollHeight);
+ }
+ }
onSelectedChange(fromSearch) {
var newState = getStateFromStores(fromSearch);
newState.from_search = fromSearch;
@@ -41,15 +50,15 @@ export default class SidebarRight extends React.Component {
this.setState(newState);
}
}
- resize() {
- var postHolder = $('.post-list-holder-by-time');
- postHolder[0].scrollTop = postHolder[0].scrollHeight - 224;
- }
render() {
+ var postHolder = $('.post-list-holder-by-time').not('.inactive');
+ const position = postHolder.scrollTop() + postHolder.height() + 14;
+ const bottom = postHolder[0].scrollHeight;
+ this.plScrolledToBottom = position >= bottom;
+
if (!(this.state.search_visible || this.state.post_right_visible)) {
$('.inner__wrap').removeClass('move--left').removeClass('move--right');
$('.sidebar--right').removeClass('move--left');
- this.resize();
return (
<div></div>
);
@@ -59,8 +68,8 @@ export default class SidebarRight extends React.Component {
$('.sidebar--left').removeClass('move--right');
$('.sidebar--right').addClass('move--left');
$('.sidebar--right').prepend('<div class="sidebar__overlay"></div>');
- this.resize();
- setTimeout(function overlayTimer() {
+
+ setTimeout(() => {
$('.sidebar__overlay').fadeOut('200', function fadeOverlay() {
$(this).remove();
});
diff --git a/web/react/components/sidebar_right_menu.jsx b/web/react/components/sidebar_right_menu.jsx
index f1341d9d7..2df2c8ffd 100644
--- a/web/react/components/sidebar_right_menu.jsx
+++ b/web/react/components/sidebar_right_menu.jsx
@@ -26,11 +26,14 @@ export default class SidebarRightMenu extends React.Component {
var inviteLink = '';
var teamSettingsLink = '';
var manageLink = '';
+ var consoleLink = '';
var currentUser = UserStore.getCurrentUser();
var isAdmin = false;
+ var isSystemAdmin = false;
if (currentUser != null) {
isAdmin = utils.isAdmin(currentUser.roles);
+ isSystemAdmin = utils.isSystemAdmin(currentUser.roles);
inviteLink = (
<li>
@@ -77,6 +80,17 @@ export default class SidebarRightMenu extends React.Component {
);
}
+ if (isSystemAdmin) {
+ consoleLink = (
+ <li>
+ <a
+ href='/admin_console'
+ >
+ <i className='glyphicon glyphicon-wrench'></i>System Console</a>
+ </li>
+ );
+ }
+
var siteName = '';
if (global.window.config.SiteName != null) {
siteName = global.window.config.SiteName;
@@ -107,6 +121,7 @@ export default class SidebarRightMenu extends React.Component {
{inviteLink}
{teamLink}
{manageLink}
+ {consoleLink}
<li>
<a
href='#'
diff --git a/web/react/components/team_signup_url_page.jsx b/web/react/components/team_signup_url_page.jsx
index 1b722d611..a3f89a217 100644
--- a/web/react/components/team_signup_url_page.jsx
+++ b/web/react/components/team_signup_url_page.jsx
@@ -35,8 +35,8 @@ export default class TeamSignupUrlPage extends React.Component {
if (cleanedName !== name || !urlRegex.test(name)) {
this.setState({nameError: "Use only lower case letters, numbers and dashes. Must start with a letter and can't end in a dash."});
return;
- } else if (cleanedName.length <= 3 || cleanedName.length > 15) {
- this.setState({nameError: 'Name must be 4 or more characters up to a maximum of 15'});
+ } else if (cleanedName.length <= 2 || cleanedName.length > 15) {
+ this.setState({nameError: 'Name must be 3 or more characters up to a maximum of 15'});
return;
}
diff --git a/web/react/components/team_signup_with_sso.jsx b/web/react/components/team_signup_with_sso.jsx
index a4972dd8d..14f281f7a 100644
--- a/web/react/components/team_signup_with_sso.jsx
+++ b/web/react/components/team_signup_with_sso.jsx
@@ -23,12 +23,14 @@ export default class SSOSignUpPage extends React.Component {
team.display_name = this.state.name;
- if (team.display_name.length <= 3) {
+ if (!team.display_name) {
+ state.nameError = 'Please enter a team name';
+ this.setState(state);
return;
}
- if (!team.display_name) {
- state.nameError = 'Please enter a team name';
+ if (team.display_name.length <= 2) {
+ state.nameError = 'Name must be 3 or more characters up to a maximum of 15';
this.setState(state);
return;
}
@@ -68,7 +70,7 @@ export default class SSOSignUpPage extends React.Component {
}
var disabled = false;
- if (this.state.name.length <= 3) {
+ if (this.state.name.length <= 2) {
disabled = true;
}
diff --git a/web/react/components/user_settings/import_theme_modal.jsx b/web/react/components/user_settings/import_theme_modal.jsx
index 48be83afe..0f5cb59f5 100644
--- a/web/react/components/user_settings/import_theme_modal.jsx
+++ b/web/react/components/user_settings/import_theme_modal.jsx
@@ -140,7 +140,7 @@ export default class ImportThemeModal extends React.Component {
>
<Modal.Body>
<p>
- {'To import a theme, go to a Slack team and look for “”Preferences” -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:'}
+ {'To import a theme, go to a Slack team and look for “Preferences -> Sidebar Theme”. Open the custom theme option, copy the theme color values and paste them here:'}
</p>
<div className='form-group less'>
<div className='col-sm-9'>
diff --git a/web/react/components/user_settings/manage_incoming_hooks.jsx b/web/react/components/user_settings/manage_incoming_hooks.jsx
index 1bbfbd162..fa2e2e5e4 100644
--- a/web/react/components/user_settings/manage_incoming_hooks.jsx
+++ b/web/react/components/user_settings/manage_incoming_hooks.jsx
@@ -21,7 +21,7 @@ export default class ManageIncomingHooks extends React.Component {
this.getHooks();
}
addNewHook() {
- let hook = {}; //eslint-disable-line prefer-const
+ const hook = {};
hook.channel_id = this.state.channelId;
Client.addIncomingHook(
@@ -40,13 +40,13 @@ export default class ManageIncomingHooks extends React.Component {
);
}
removeHook(id) {
- let data = {}; //eslint-disable-line prefer-const
+ const data = {};
data.id = id;
Client.deleteIncomingHook(
data,
() => {
- let hooks = this.state.hooks; //eslint-disable-line prefer-const
+ const hooks = this.state.hooks;
let index = -1;
for (let i = 0; i < hooks.length; i++) {
if (hooks[i].id === id) {
@@ -69,7 +69,7 @@ export default class ManageIncomingHooks extends React.Component {
getHooks() {
Client.listIncomingHooks(
(data) => {
- let state = this.state; //eslint-disable-line prefer-const
+ const state = this.state;
if (data) {
state.hooks = data;
@@ -93,9 +93,11 @@ export default class ManageIncomingHooks extends React.Component {
}
const channels = ChannelStore.getAll();
- let options = []; //eslint-disable-line prefer-const
+ const options = [];
channels.forEach((channel) => {
- options.push(<option value={channel.id}>{channel.name}</option>);
+ if (channel.type !== Constants.DM_CHANNEL) {
+ options.push(<option value={channel.id}>{channel.name}</option>);
+ }
});
let disableButton = '';
@@ -103,7 +105,7 @@ export default class ManageIncomingHooks extends React.Component {
disableButton = ' disable';
}
- let hooks = []; //eslint-disable-line prefer-const
+ const hooks = [];
this.state.hooks.forEach((hook) => {
const c = ChannelStore.get(hook.channel_id);
hooks.push(
diff --git a/web/react/components/user_settings/user_settings_notifications.jsx b/web/react/components/user_settings/user_settings_notifications.jsx
index ba14f019f..42c65ef5d 100644
--- a/web/react/components/user_settings/user_settings_notifications.jsx
+++ b/web/react/components/user_settings/user_settings_notifications.jsx
@@ -420,7 +420,7 @@ export default class NotificationsTab extends React.Component {
</label>
<br/>
</div>
- <div><br/>{'Email notifications are sent for mentions and direct messages after you have been away from ' + global.window.config.SiteName + ' for 5 minutes.'}</div>
+ <div><br/>{'Email notifications are sent for mentions and direct messages after you’ve been offline for more than 60 seconds or away from ' + global.window.config.SiteName + ' for more than 5 minutes.'}</div>
</div>
);
diff --git a/web/react/utils/constants.jsx b/web/react/utils/constants.jsx
index 75e80bc7e..da59f8e5a 100644
--- a/web/react/utils/constants.jsx
+++ b/web/react/utils/constants.jsx
@@ -111,6 +111,7 @@ module.exports = {
],
MONTHS: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
MAX_DMS: 20,
+ DM_CHANNEL: 'D',
MAX_POST_LEN: 4000,
EMOJI_SIZE: 16,
ONLINE_ICON_SVG: "<svg version='1.1' id='Layer_1' xmlns:dc='http://purl.org/dc/elements/1.1/' xmlns:cc='http://creativecommons.org/ns#' xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#' xmlns:svg='http://www.w3.org/2000/svg' xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd' xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape' sodipodi:docname='TRASH_1_4.svg' inkscape:version='0.48.4 r9939' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' width='12px' height='12px' viewBox='0 0 12 12' enable-background='new 0 0 12 12' xml:space='preserve'><sodipodi:namedview inkscape:cy='139.7898' inkscape:cx='26.358185' inkscape:zoom='1.18' showguides='true' showgrid='false' id='namedview6' guidetolerance='10' gridtolerance='10' objecttolerance='10' borderopacity='1' bordercolor='#666666' pagecolor='#ffffff' inkscape:current-layer='Layer_1' inkscape:window-maximized='1' inkscape:window-y='-8' inkscape:window-x='-8' inkscape:window-height='705' inkscape:window-width='1366' inkscape:guide-bbox='true' inkscape:pageshadow='2' inkscape:pageopacity='0'><sodipodi:guide position='50.036793,85.991376' orientation='1,0' id='guide2986'></sodipodi:guide><sodipodi:guide position='58.426196,66.216355' orientation='0,1' id='guide3047'></sodipodi:guide></sodipodi:namedview><g><g><path class='online--icon' d='M6,5.487c1.371,0,2.482-1.116,2.482-2.493c0-1.378-1.111-2.495-2.482-2.495S3.518,1.616,3.518,2.994C3.518,4.371,4.629,5.487,6,5.487z M10.452,8.545c-0.101-0.829-0.36-1.968-0.726-2.541C9.475,5.606,8.5,5.5,8.5,5.5S8.43,7.521,6,7.521C3.507,7.521,3.5,5.5,3.5,5.5S2.527,5.606,2.273,6.004C1.908,6.577,1.648,7.716,1.547,8.545C1.521,8.688,1.49,9.082,1.498,9.142c0.161,1.295,2.238,2.322,4.375,2.358C5.916,11.501,5.958,11.501,6,11.501c0.043,0,0.084,0,0.127-0.001c2.076-0.026,4.214-1.063,4.375-2.358C10.509,9.082,10.471,8.696,10.452,8.545z'/></g></g></svg>",
diff --git a/web/react/utils/utils.jsx b/web/react/utils/utils.jsx
index 556f3967c..8b20e2adf 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -78,6 +78,14 @@ export function isAdmin(roles) {
return false;
}
+export function isSystemAdmin(roles) {
+ if (isInRole(roles, 'system_admin')) {
+ return true;
+ }
+
+ return false;
+}
+
export function getDomainWithOutSub() {
var parts = window.location.host.split('.');
@@ -255,163 +263,6 @@ export function escapeRegExp(string) {
return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1');
}
-function handleYoutubeTime(link) {
- var timeRegex = /[\\?&]t=([0-9hms]+)/;
-
- var time = link.trim().match(timeRegex);
- if (!time || !time[1]) {
- return '';
- }
-
- var hours = time[1].match(/([0-9]+)h/);
- var minutes = time[1].match(/([0-9]+)m/);
- var seconds = time[1].match(/([0-9]+)s/);
-
- var ticks = 0;
-
- if (hours && hours[1]) {
- ticks += parseInt(hours[1], 10) * 3600;
- }
-
- if (minutes && minutes[1]) {
- ticks += parseInt(minutes[1], 10) * 60;
- }
-
- if (seconds && seconds[1]) {
- ticks += parseInt(seconds[1], 10);
- }
-
- return '&start=' + ticks.toString();
-}
-
-function getYoutubeEmbed(link) {
- var regex = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|watch\?(?:[a-zA-Z-_]+=[a-zA-Z0-9-_]+&)+v=)([^#\&\?]*).*/;
-
- var youtubeId = link.trim().match(regex)[1];
- var time = handleYoutubeTime(link);
-
- function onClick(e) {
- var div = $(e.target).closest('.video-thumbnail__container')[0];
- var iframe = document.createElement('iframe');
- iframe.setAttribute('src',
- 'https://www.youtube.com/embed/' +
- div.id +
- '?autoplay=1&autohide=1&border=0&wmode=opaque&fs=1&enablejsapi=1' +
- time);
- iframe.setAttribute('width', '480px');
- iframe.setAttribute('height', '360px');
- iframe.setAttribute('type', 'text/html');
- iframe.setAttribute('frameborder', '0');
- iframe.setAttribute('allowfullscreen', 'allowfullscreen');
-
- div.parentNode.replaceChild(iframe, div);
- }
-
- function success(data) {
- if (!data.items.length || !data.items[0].snippet) {
- return;
- }
- var metadata = data.items[0].snippet;
- $('.video-type.' + youtubeId).html('Youtube - ');
- $('.video-uploader.' + youtubeId).html(metadata.channelTitle);
- $('.video-title.' + youtubeId).find('a').html(metadata.title);
- }
-
- if (global.window.config.GoogleDeveloperKey) {
- $.ajax({
- async: true,
- url: 'https://www.googleapis.com/youtube/v3/videos',
- type: 'GET',
- data: {part: 'snippet', id: youtubeId, key: global.window.config.GoogleDeveloperKey},
- success: success
- });
- }
-
- return (
- <div className='post-comment'>
- <h4>
- <span className={'video-type ' + youtubeId}>YouTube</span>
- <span className={'video-title ' + youtubeId}><a href={link}></a></span>
- </h4>
- <h4 className={'video-uploader ' + youtubeId}></h4>
- <div
- className='video-div embed-responsive-item'
- id={youtubeId}
- onClick={onClick}
- >
- <div className='embed-responsive embed-responsive-4by3 video-div__placeholder'>
- <div
- id={youtubeId}
- className='video-thumbnail__container'
- >
- <img
- className='video-thumbnail'
- src={'https://i.ytimg.com/vi/' + youtubeId + '/hqdefault.jpg'}
- />
- <div className='block'>
- <span className='play-button'><span/></span>
- </div>
- </div>
- </div>
- </div>
- </div>
- );
-}
-
-export function getEmbed(link) {
- var ytRegex = /.*(?:youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|watch\?(?:[a-zA-Z-_]+=[a-zA-Z0-9-_]+&)+v=)([^#\&\?]*).*/;
-
- var match = link.trim().match(ytRegex);
- if (match && match[1].length === 11) {
- return getYoutubeEmbed(link);
- }
-
- // Generl embed feature turned off for now
- return '';
-
- // NEEDS REFACTORING WHEN TURNED BACK ON
- /*
- var id = parseInt((Math.random() * 1000000) + 1);
-
- $.ajax({
- type: 'GET',
- url: 'https://query.yahooapis.com/v1/public/yql',
- data: {
- q: 'select * from html where url="' + link + "\" and xpath='html/head'",
- format: 'json'
- },
- async: true
- }).done(function(data) {
- if(!data.query.results) {
- return;
- }
-
- var headerData = data.query.results.head;
-
- var description = ''
- for(var i = 0; i < headerData.meta.length; i++) {
- if(headerData.meta[i].name && (headerData.meta[i].name === 'description' || headerData.meta[i].name === 'Description')){
- description = headerData.meta[i].content;
- break;
- }
- }
-
- $('.embed-title.'+id).html(headerData.title);
- $('.embed-description.'+id).html(description);
- })
-
- return (
- <div className='post-comment'>
- <div className={'web-embed-data'}>
- <p className={'embed-title ' + id} />
- <p className={'embed-description ' + id} />
- <p className={'embed-link ' + id}>{link}</p>
- </div>
- </div>
- );
- */
-}
-
export function areStatesEqual(state1, state2) {
return JSON.stringify(state1) === JSON.stringify(state2);
}
@@ -607,7 +458,7 @@ export function applyTheme(theme) {
}
if (theme.centerChannelBg) {
- changeCss('.app__content, .markdown__table, .markdown__table tbody tr', 'background:' + theme.centerChannelBg, 1);
+ changeCss('.app__content, .markdown__table, .markdown__table tbody tr, .command-box', 'background:' + theme.centerChannelBg, 1);
changeCss('#post-list .post-list-holder-by-time', 'background:' + theme.centerChannelBg, 1);
changeCss('#post-create', 'background:' + theme.centerChannelBg, 1);
changeCss('.date-separator .separator__text, .new-separator .separator__text', 'background:' + theme.centerChannelBg, 1);
@@ -616,19 +467,23 @@ 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, .loading-screen .loading__content .round', 'color:' + theme.centerChannelColor, 1);
+ changeCss('.app__content, .post-create__container .post-create-body .btn-file, .post-create__container .post-create-footer .msg-typing, .loading-screen .loading__content .round, .command-name', 'color:' + theme.centerChannelColor, 1);
changeCss('#post-create', 'color:' + 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);
+ changeCss('.mentions--top, .command-box', '-moz-box-shadow:' + changeOpacity(theme.centerChannelColor, 0.2) + ' 1px -3px 12px', 1);
changeCss('.post-body hr', 'background:' + theme.centerChannelColor, 1);
changeCss('.channel-header .heading', 'color:' + theme.centerChannelColor, 1);
changeCss('.markdown__table tbody tr:nth-child(2n)', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.channel-header__info>div.dropdown .header-dropdown__icon', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1);
changeCss('.channel-header #member_popover', 'color:' + changeOpacity(theme.centerChannelColor, 0.8), 1);
- changeCss('.custom-textarea, .custom-textarea:focus, .preview-container .preview-div, .post-image__column .post-image__details, .sidebar--right .sidebar-right__body, .markdown__table th, .markdown__table td', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
+ changeCss('.custom-textarea, .custom-textarea:focus, .preview-container .preview-div, .post-image__column .post-image__details, .sidebar--right .sidebar-right__body, .markdown__table th, .markdown__table td, .command-box', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
+ changeCss('.command-name', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.custom-textarea', 'color:' + theme.centerChannelColor, 1);
changeCss('.post-image__column', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 2);
changeCss('.post-image__column .post-image__details', 'color:' + theme.centerChannelColor, 2);
changeCss('.post-image__column a, .post-image__column a:hover, .post-image__column a:focus', 'color:' + theme.centerChannelColor, 1);
- changeCss('.search-bar__container .search__form .search-bar', 'background:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
+ changeCss('@media(max-width: 768px){.search-bar__container .search__form .search-bar', 'background:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.search-bar__container .search__form', 'border-color:' + changeOpacity(theme.centerChannelColor, 0.2), 1);
changeCss('.channel-intro .channel-intro__content', 'background:' + changeOpacity(theme.centerChannelColor, 0.05), 1);
changeCss('.date-separator .separator__text', 'color:' + theme.centerChannelColor, 2);
@@ -641,7 +496,7 @@ export function applyTheme(theme) {
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, .sidebar--right .sidebar--right__header', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.date-separator.hovered--before:after, .new-separator.hovered--before:after', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
- changeCss('.date-separator.hovered--after:before, .new-separator.hovered--after:before', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
+ changeCss('.date-separator.hovered--after:before, .new-separator.hovered--after:before, .command-name:hover', 'background:' + changeOpacity(theme.centerChannelColor, 0.07), 1);
changeCss('.post.current--user:hover .post-body ', 'background: none;', 1);
changeCss('.sidebar--right', 'color:' + theme.centerChannelColor, 2);
}
@@ -789,16 +644,12 @@ export function isValidUsername(name) {
return error;
}
-export function updateTabTitle(name) {
- document.title = name + ' ' + document.title.substring(document.title.lastIndexOf('-'));
-}
-
export function updateAddressBar(channelName) {
var teamURL = window.location.href.split('/channels')[0];
history.replaceState('data', '', teamURL + '/channels/' + channelName);
}
-export function switchChannel(channel, teammateName) {
+export function switchChannel(channel) {
AppDispatcher.handleViewAction({
type: ActionTypes.CLICK_CHANNEL,
name: channel.name,
@@ -807,12 +658,6 @@ export function switchChannel(channel, teammateName) {
updateAddressBar(channel.name);
- if (channel.type === 'D' && teammateName) {
- updateTabTitle(teammateName);
- } else {
- updateTabTitle(channel.display_name);
- }
-
AsyncClient.getChannels(true, true, true);
AsyncClient.getChannelExtraInfo(true);
AsyncClient.getPosts(channel.id);