summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-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/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/sidebar.jsx15
-rw-r--r--web/react/components/sidebar_right.jsx25
-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/utils/constants.jsx1
-rw-r--r--web/react/utils/utils.jsx181
-rw-r--r--web/sass-files/sass/partials/_command-box.scss17
-rw-r--r--web/web.go7
18 files changed, 257 insertions, 254 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/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/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..e63418ae8 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');
+ 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');
+ 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/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/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..ad4575c6e 100644
--- a/web/react/utils/utils.jsx
+++ b/web/react/utils/utils.jsx
@@ -255,163 +255,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 +450,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,14 +459,18 @@ 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);
@@ -641,7 +488,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 +636,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 +650,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);
diff --git a/web/sass-files/sass/partials/_command-box.scss b/web/sass-files/sass/partials/_command-box.scss
index 44eb9b8df..f1aa4dca2 100644
--- a/web/sass-files/sass/partials/_command-box.scss
+++ b/web/sass-files/sass/partials/_command-box.scss
@@ -4,20 +4,29 @@
width: 100%;
border: $border-gray;
bottom: 38px;
+ overflow: auto;
@extend %popover-box-shadow;
+ .sidebar--right & {
+ bottom: 100px;
+ }
}
.command-name {
position: relative;
width: 100%;
- background-color: #fff;
- height: 37px;
- line-height: 37px;
- padding: 2px 10px 2px 5px;
+ line-height: 24px;
+ padding: 5px 10px 8px;
z-index: 101;
+ font-size: 0.95em;
+ border-bottom: 1px solid #ddd;
&:hover {
background-color: #e8eaed;
}
+ .command__desc {
+ margin-left: 5px;
+ @include opacity(0.5);
+ line-height: normal;
+ }
}
.command-desc {
diff --git a/web/web.go b/web/web.go
index da7eff13d..bf985a5a0 100644
--- a/web/web.go
+++ b/web/web.go
@@ -850,7 +850,12 @@ func incomingWebhook(c *api.Context, w http.ResponseWriter, r *http.Request) {
r.ParseForm()
- props := model.MapFromJson(strings.NewReader(r.FormValue("payload")))
+ var props map[string]string
+ if r.Header.Get("Content-Type") == "application/json" {
+ props = model.MapFromJson(r.Body)
+ } else {
+ props = model.MapFromJson(strings.NewReader(r.FormValue("payload")))
+ }
text := props["text"]
if len(text) == 0 {