diff options
Diffstat (limited to 'web/react/components')
-rw-r--r-- | web/react/components/command_list.jsx | 2 | ||||
-rw-r--r-- | web/react/components/popover_list_members.jsx | 26 | ||||
-rw-r--r-- | web/react/components/post_body.jsx | 122 | ||||
-rw-r--r-- | web/react/components/post_info.jsx | 2 | ||||
-rw-r--r-- | web/react/components/post_list.jsx | 14 | ||||
-rw-r--r-- | web/react/components/sidebar_right.jsx | 25 | ||||
-rw-r--r-- | web/react/components/user_settings/import_theme_modal.jsx | 2 |
7 files changed, 171 insertions, 22 deletions
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/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/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'> |