diff options
Diffstat (limited to 'web/react/components')
-rw-r--r-- | web/react/components/post_body.jsx | 122 | ||||
-rw-r--r-- | web/react/components/post_list.jsx | 14 | ||||
-rw-r--r-- | web/react/components/sidebar_right.jsx | 25 |
3 files changed, 151 insertions, 10 deletions
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_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(); }); |