summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
Diffstat (limited to 'web')
-rw-r--r--web/react/components/post_body.jsx125
-rw-r--r--web/react/components/youtube_video.jsx175
-rw-r--r--web/react/stores/channel_store.jsx2
3 files changed, 184 insertions, 118 deletions
diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx
index dcbe56399..b1657f0eb 100644
--- a/web/react/components/post_body.jsx
+++ b/web/react/components/post_body.jsx
@@ -10,6 +10,7 @@ const PreReleaseFeatures = Constants.PRE_RELEASE_FEATURES;
import * as TextFormatting from '../utils/text_formatting.jsx';
import twemoji from 'twemoji';
import PostBodyAdditionalContent from './post_body_additional_content.jsx';
+import YoutubeVideo from './youtube_video.jsx';
import providers from './providers.json';
@@ -17,7 +18,6 @@ export default class PostBody extends React.Component {
constructor(props) {
super(props);
- this.receivedYoutubeData = false;
this.isImgLoading = false;
this.handleUserChange = this.handleUserChange.bind(this);
@@ -25,7 +25,6 @@ export default class PostBody extends React.Component {
this.createEmbed = this.createEmbed.bind(this);
this.createImageEmbed = this.createImageEmbed.bind(this);
this.loadImg = this.loadImg.bind(this);
- this.createYoutubeEmbed = this.createYoutubeEmbed.bind(this);
const linkData = Utils.extractLinks(this.props.post.message);
const profiles = UserStore.getProfiles();
@@ -120,10 +119,13 @@ export default class PostBody extends React.Component {
}
}
- const embed = this.createYoutubeEmbed(link);
-
- if (embed != null) {
- return embed;
+ if (YoutubeVideo.isYoutubeLink(link)) {
+ return (
+ <YoutubeVideo
+ channelId={post.channel_id}
+ link={link}
+ />
+ );
}
for (let i = 0; i < Constants.IMAGE_TYPES.length; i++) {
@@ -184,117 +186,6 @@ export default class PostBody extends React.Component {
);
}
- handleYoutubeTime(link) {
- const timeRegex = /[\\?&]t=([0-9hms]+)/;
-
- const time = link.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 = /(?:http|https):\/\/(?:www\.)?(?:(?:youtube\.com\/(?:(?:v\/)|(\/u\/\w\/)|(?:(?:watch|embed\/watch)(?:\/|.*v=))|(?:embed\/)|(?:user\/[^\/]+\/u\/[0-9]\/)))|(?:youtu\.be\/))([^#\&\?]*)/;
-
- const match = link.trim().match(ytRegex);
- if (!match || match[2].length !== 11) {
- return null;
- }
-
- const youtubeId = match[2];
- 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({youtubeTitle: metadata.title});
- }
-
- if (global.window.mm_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.mm_config.GoogleDeveloperKey},
- success: success.bind(this)
- });
- }
-
- let header = 'Youtube';
- if (this.state.youtubeTitle) {
- header = header + ' - ';
- }
-
- return (
- <div>
- <h4>
- <span className='video-type'>{header}</span>
- <span className='video-title'><a href={link}>{this.state.youtubeTitle}</a></span>
- </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;
diff --git a/web/react/components/youtube_video.jsx b/web/react/components/youtube_video.jsx
new file mode 100644
index 000000000..bf3c43840
--- /dev/null
+++ b/web/react/components/youtube_video.jsx
@@ -0,0 +1,175 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import ChannelStore from '../stores/channel_store.jsx';
+
+const ytRegex = /(?:http|https):\/\/(?:www\.)?(?:(?:youtube\.com\/(?:(?:v\/)|(\/u\/\w\/)|(?:(?:watch|embed\/watch)(?:\/|.*v=))|(?:embed\/)|(?:user\/[^\/]+\/u\/[0-9]\/)))|(?:youtu\.be\/))([^#\&\?]*)/;
+
+export default class YoutubeVideo extends React.Component {
+ constructor(props) {
+ super(props);
+
+ this.updateStateFromProps = this.updateStateFromProps.bind(this);
+ this.handleReceivedMetadata = this.handleReceivedMetadata.bind(this);
+
+ this.play = this.play.bind(this);
+ this.stop = this.stop.bind(this);
+ this.stopOnChannelChange = this.stopOnChannelChange.bind(this);
+
+ this.state = {
+ playing: false,
+ title: ''
+ };
+ }
+
+ componentWillMount() {
+ this.updateStateFromProps(this.props);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.updateStateFromProps(nextProps);
+ }
+
+ updateStateFromProps(props) {
+ const link = props.link;
+
+ const match = link.trim().match(ytRegex);
+ if (!match || match[2].length !== 11) {
+ return;
+ }
+
+ this.setState({
+ videoId: match[2],
+ time: this.handleYoutubeTime(link)
+ });
+ }
+
+ handleYoutubeTime(link) {
+ const timeRegex = /[\\?&]t=([0-9hms]+)/;
+
+ const time = link.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();
+ }
+
+ componentDidMount() {
+ if (global.window.mm_config.GoogleDeveloperKey) {
+ $.ajax({
+ async: true,
+ url: 'https://www.googleapis.com/youtube/v3/videos',
+ type: 'GET',
+ data: {part: 'snippet', id: this.state.videoId, key: global.window.mm_config.GoogleDeveloperKey},
+ success: this.handleReceivedMetadata
+ });
+ }
+ }
+
+ handleReceivedMetadata(data) {
+ if (!data.items.length || !data.items[0].snippet) {
+ return null;
+ }
+ var metadata = data.items[0].snippet;
+ this.setState({
+ receivedYoutubeData: true,
+ title: metadata.title
+ });
+ }
+
+ play() {
+ this.setState({playing: true});
+
+ if (ChannelStore.getCurrentId() === this.props.channelId) {
+ ChannelStore.addChangeListener(this.stopOnChannelChange);
+ }
+ }
+
+ stop() {
+ this.setState({playing: false});
+ }
+
+ stopOnChannelChange() {
+ if (ChannelStore.getCurrentId() !== this.props.channelId) {
+ this.stop();
+ }
+ }
+
+ render() {
+ let header = 'Youtube';
+ if (this.state.title) {
+ header = header + ' - ';
+ }
+
+ let content;
+ if (this.state.playing) {
+ content = (
+ <iframe
+ src={'https://www.youtube.com/embed/' + this.state.videoId + '?autoplay=1&autohide=1&border=0&wmode=opaque&fs=1&enablejsapi=1' + this.state.time}
+ width='480px'
+ height='360px'
+ type='text/html'
+ frameBorder='0'
+ allowFullScreen='allowfullscreen'
+ />
+ );
+ } else {
+ content = (
+ <div className='embed-responsive embed-responsive-4by3 video-div__placeholder'>
+ <div className='video-thumbnail__container'>
+ <img
+ className='video-thumbnail'
+ src={'https://i.ytimg.com/vi/' + this.state.videoId + '/hqdefault.jpg'}
+ />
+ <div className='block'>
+ <span className='play-button'><span/></span>
+ </div>
+ </div>
+ </div>
+ );
+ }
+
+ return (
+ <div>
+ <h4>
+ <span className='video-type'>{header}</span>
+ <span className='video-title'><a href={this.props.link}>{this.state.title}</a></span>
+ </h4>
+ <div
+ className='video-div embed-responsive-item'
+ onClick={this.play}
+ >
+ {content}
+ </div>
+ </div>
+ );
+ }
+
+ static isYoutubeLink(link) {
+ return link.trim().match(ytRegex);
+ }
+}
+
+YoutubeVideo.propTypes = {
+ channelId: React.PropTypes.string.isRequired,
+ link: React.PropTypes.string.isRequired
+};
diff --git a/web/react/stores/channel_store.jsx b/web/react/stores/channel_store.jsx
index afc960fcf..93d996e0b 100644
--- a/web/react/stores/channel_store.jsx
+++ b/web/react/stores/channel_store.jsx
@@ -18,7 +18,7 @@ class ChannelStoreClass extends EventEmitter {
constructor(props) {
super(props);
- this.setMaxListeners(11);
+ this.setMaxListeners(15);
this.emitChange = this.emitChange.bind(this);
this.addChangeListener = this.addChangeListener.bind(this);