From c56762a4e9ca25ce3467f2944729bfe9bb890114 Mon Sep 17 00:00:00 2001 From: Florian Orben Date: Sat, 7 Nov 2015 15:44:55 +0100 Subject: PLT-1049: Vine URLs should not generate preview links --- web/react/components/post_attachment_oembed.jsx | 83 ++++++ web/react/components/post_body.jsx | 71 +++-- .../components/post_body_additional_content.jsx | 20 +- web/react/providers.json | 324 +++++++++++++++++++++ 4 files changed, 478 insertions(+), 20 deletions(-) create mode 100644 web/react/components/post_attachment_oembed.jsx create mode 100644 web/react/providers.json (limited to 'web') diff --git a/web/react/components/post_attachment_oembed.jsx b/web/react/components/post_attachment_oembed.jsx new file mode 100644 index 000000000..f544dbc88 --- /dev/null +++ b/web/react/components/post_attachment_oembed.jsx @@ -0,0 +1,83 @@ +// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved. +// See License.txt for license information. + +export default class PostAttachmentOEmbed extends React.Component { + constructor(props) { + super(props); + this.fetchData = this.fetchData.bind(this); + + this.isLoading = false; + } + + componentWillMount() { + this.setState({data: {}}); + } + + componentWillReceiveProps(nextProps) { + this.fetchData(nextProps.link); + } + + fetchData(link) { + if (!this.isLoading) { + this.isLoading = true; + return $.ajax({ + url: 'https://noembed.com/embed?nowrap=on&url=' + encodeURIComponent(link), + dataType: 'jsonp', + success: (result) => { + this.isLoading = false; + if (result.error) { + this.setState({data: {}}); + } else { + this.setState({data: result}); + } + }, + error: () => { + this.setState({data: {}}); + } + }); + } + } + + render() { + if ($.isEmptyObject(this.state.data)) { + return
; + } + + return ( +
+
+
+

+ + {this.state.data.title} + +

+
+
+
+
+
+
+
+
+
+ ); + } +} + +PostAttachmentOEmbed.propTypes = { + link: React.PropTypes.string.isRequired +}; diff --git a/web/react/components/post_body.jsx b/web/react/components/post_body.jsx index 5a157b792..c4f653f68 100644 --- a/web/react/components/post_body.jsx +++ b/web/react/components/post_body.jsx @@ -9,6 +9,8 @@ const TextFormatting = require('../utils/text_formatting.jsx'); const twemoji = require('twemoji'); const PostBodyAdditionalContent = require('./post_body_additional_content.jsx'); +const providers = require('../providers.json'); + export default class PostBody extends React.Component { constructor(props) { super(props); @@ -23,7 +25,7 @@ export default class PostBody extends React.Component { this.createYoutubeEmbed = this.createYoutubeEmbed.bind(this); const linkData = Utils.extractLinks(this.props.post.message); - this.state = {links: linkData.links, message: linkData.text}; + this.state = {links: linkData.links, message: linkData.text, post: this.props.post}; } getAllChildNodes(nodeIn) { @@ -45,6 +47,12 @@ export default class PostBody extends React.Component { twemoji.parse(ReactDOM.findDOMNode(this), {size: Constants.EMOJI_SIZE}); } + componentWillMount() { + if (this.props.post.filenames.length === 0 && this.state.links && this.state.links.length > 0) { + this.embed = this.createEmbed(this.state.links[0]); + } + } + componentDidMount() { this.parseEmojis(); } @@ -55,19 +63,54 @@ export default class PostBody extends React.Component { componentWillReceiveProps(nextProps) { const linkData = Utils.extractLinks(nextProps.post.message); + if (this.props.post.filenames.length === 0 && this.state.links && this.state.links.length > 0) { + this.embed = this.createEmbed(linkData.links[0]); + } this.setState({links: linkData.links, message: linkData.text}); } createEmbed(link) { - let embed = this.createYoutubeEmbed(link); + const post = this.state.post; + + if (!link) { + if (post.type === 'oEmbed') { + post.props.oEmbedLink = ''; + post.type = ''; + } + return null; + } + + const trimmedLink = link.trim(); + + if (this.checkForOembedContent(trimmedLink)) { + post.props.oEmbedLink = trimmedLink; + post.type = 'oEmbed'; + this.setState({post}); + return ''; + } + + const embed = this.createYoutubeEmbed(link); if (embed != null) { return embed; } - embed = this.createGifEmbed(link); + if (link.substring(link.length - 4) === '.gif') { + return this.createGifEmbed(link, this.state.gifLoaded); + } + + return null; + } - return embed; + checkForOembedContent(link) { + for (let i = 0; i < providers.length; i++) { + for (let j = 0; j < providers[i].patterns.length; j++) { + if (link.match(providers[i].patterns[j])) { + return true; + } + } + } + return false; } loadGif(src) { @@ -80,18 +123,15 @@ export default class PostBody extends React.Component { const gif = new Image(); gif.onload = ( () => { + this.embed = this.createGifEmbed(src, true); this.setState({gifLoaded: true}); } ); gif.src = src; } - createGifEmbed(link) { - if (link.substring(link.length - 4) !== '.gif') { - return null; - } - - if (!this.state.gifLoaded) { + createGifEmbed(link, isLoaded) { + if (!isLoaded) { this.loadGif(link); return ( 0) { - embed = this.createEmbed(this.state.links[0]); - } - let fileAttachmentHolder = ''; if (filenames && filenames.length > 0) { fileAttachmentHolder = ( @@ -333,10 +368,10 @@ export default class PostBody extends React.Component { /> {fileAttachmentHolder} - {embed} + {this.embed} ); } diff --git a/web/react/components/post_body_additional_content.jsx b/web/react/components/post_body_additional_content.jsx index 8189ba2d3..0c2c44286 100644 --- a/web/react/components/post_body_additional_content.jsx +++ b/web/react/components/post_body_additional_content.jsx @@ -2,12 +2,14 @@ // See License.txt for license information. const PostAttachmentList = require('./post_attachment_list.jsx'); +const PostAttachmentOEmbed = require('./post_attachment_oembed.jsx'); export default class PostBodyAdditionalContent extends React.Component { constructor(props) { super(props); this.getSlackAttachment = this.getSlackAttachment.bind(this); + this.getOembedAttachment = this.getOembedAttachment.bind(this); this.getComponent = this.getComponent.bind(this); } @@ -25,17 +27,31 @@ export default class PostBodyAdditionalContent extends React.Component { ); } + getOembedAttachment() { + const link = this.props.post.props && this.props.post.props.oEmbedLink || ''; + return ( + + ); + } + getComponent() { - switch (this.state.type) { + switch (this.props.post.type) { case 'slack_attachment': return this.getSlackAttachment(); + case 'oEmbed': + return this.getOembedAttachment(); + default: + return ''; } } render() { let content = []; - if (this.state.shouldRender) { + if (Boolean(this.props.post.type)) { const component = this.getComponent(); if (component) { diff --git a/web/react/providers.json b/web/react/providers.json new file mode 100644 index 000000000..54eb70100 --- /dev/null +++ b/web/react/providers.json @@ -0,0 +1,324 @@ +[ + { + "patterns": [ + "http://(?:www\\.)?xkcd\\.com/\\d+/?" + ], + "name": "XKCD" + }, + { + "patterns": [ + "https?://soundcloud.com/.*/.*" + ], + "name": "SoundCloud" + }, + { + "patterns": [ + "https?://(?:www\\.)?flickr\\.com/.*", + "https?://flic\\.kr/p/[a-zA-Z0-9]+" + ], + "name": "Flickr" + }, + { + "patterns": [ + "http://www\\.ted\\.com/talks/.+\\.html" + ], + "name": "TED" + }, + { + "patterns": [ + "http://(?:www\\.)?theverge\\.com/\\d{4}/\\d{1,2}/\\d{1,2}/\\d+/[^/]+/?$" + ], + "name": "The Verge" + }, + { + "patterns": [ + "http://.*\\.viddler\\.com/.*" + ], + "name": "Viddler" + }, + { + "patterns": [ + "https?://(?:www\\.)?avclub\\.com/article/[^/]+/?$" + ], + "name": "The AV Club" + }, + { + "patterns": [ + "https?://(?:www\\.)?wired\\.com/([^/]+/)?\\d+/\\d+/[^/]+/?$" + ], + "name": "Wired" + }, + { + "patterns": [ + "http://www\\.theonion\\.com/articles/[^/]+/?" + ], + "name": "The Onion" + }, + { + "patterns": [ + "http://yfrog\\.com/[0-9a-zA-Z]+/?$" + ], + "name": "YFrog" + }, + { + "patterns": [ + "http://www\\.duffelblog\\.com/\\d{4}/\\d{1,2}/[^/]+/?$" + ], + "name": "The Duffel Blog" + }, + { + "patterns": [ + "http://www\\.clickhole\\.com/article/[^/]+/?" + ], + "name": "Clickhole" + }, + { + "patterns": [ + "https?://(?:www.)?skitch.com/([^/]+)/[^/]+/.+", + "http://skit.ch/[^/]+" + ], + "name": "Skitch" + }, + { + "patterns": [ + "https?://(alpha|posts|photos)\\.app\\.net/.*" + ], + "name": "ADN" + }, + { + "patterns": [ + "https?://gist\\.github\\.com/(?:[-0-9a-zA-Z]+/)?([0-9a-fA-f]+)" + ], + "name": "Gist" + }, + { + "patterns": [ + "https?://www\\.(dropbox\\.com/s/.+\\.(?:jpg|png|gif))", + "https?://db\\.tt/[a-zA-Z0-9]+" + ], + "name": "Dropbox" + }, + { + "patterns": [ + "https?://[^\\.]+\\.wikipedia\\.org/wiki/(?!Talk:)[^#]+(?:#(.+))?" + ], + "name": "Wikipedia" + }, + { + "patterns": [ + "http://www.traileraddict.com/trailer/[^/]+/trailer" + ], + "name": "TrailerAddict" + }, + { + "patterns": [ + "http://lockerz\\.com/[sd]/\\d+" + ], + "name": "Lockerz" + }, + { + "patterns": [ + "http://gifuk\\.com/s/[0-9a-f]{16}" + ], + "name": "GIFUK" + }, + { + "patterns": [ + "http://trailers\\.apple\\.com/trailers/[^/]+/[^/]+" + ], + "name": "iTunes Movie Trailers" + }, + { + "patterns": [ + "http://gfycat\\.com/([a-zA-Z]+)" + ], + "name": "Gfycat" + }, + { + "patterns": [ + "http://bash\\.org/\\?(\\d+)" + ], + "name": "Bash.org" + }, + { + "patterns": [ + "http://arstechnica\\.com/[^/]+/\\d+/\\d+/[^/]+/?$" + ], + "name": "Ars Technica" + }, + { + "patterns": [ + "http://imgur\\.com/gallery/[0-9a-zA-Z]+" + ], + "name": "Imgur" + }, + { + "patterns": [ + "http://www\\.asciiartfarts\\.com/[0-9]+\\.html" + ], + "name": "ASCII Art Farts" + }, + { + "patterns": [ + "http://www\\.monoprice\\.com/products/product\\.asp\\?.*p_id=\\d+" + ], + "name": "Monoprice" + }, + { + "patterns": [ + "http://boingboing\\.net/\\d{4}/\\d{2}/\\d{2}/[^/]+\\.html" + ], + "name": "Boing Boing" + }, + { + "patterns": [ + "https?://github\\.com/([^/]+)/([^/]+)/commit/(.+)", + "http://git\\.io/[_0-9a-zA-Z]+" + ], + "name": "Github Commit" + }, + { + "patterns": [ + "https?://open\\.spotify\\.com/(track|album)/([0-9a-zA-Z]{22})" + ], + "name": "Spotify" + }, + { + "patterns": [ + "https?://path\\.com/p/([0-9a-zA-Z]+)$" + ], + "name": "Path" + }, + { + "patterns": [ + "http://www.funnyordie.com/videos/[^/]+/.+" + ], + "name": "Funny or Die" + }, + { + "patterns": [ + "http://(?:www\\.)?twitpic\\.com/([^/]+)" + ], + "name": "Twitpic" + }, + { + "patterns": [ + "https?://www\\.giantbomb\\.com/videos/[^/]+/\\d+-\\d+/?" + ], + "name": "GiantBomb" + }, + { + "patterns": [ + "http://(?:www\\.)?beeradvocate\\.com/beer/profile/\\d+/\\d+" + ], + "name": "Beer Advocate" + }, + { + "patterns": [ + "http://(?:www\\.)?imdb.com/title/(tt\\d+)" + ], + "name": "IMDB" + }, + { + "patterns": [ + "http://cl\\.ly/(?:image/)?[0-9a-zA-Z]+/?$" + ], + "name": "CloudApp" + }, + { + "patterns": [ + "http://clyp\\.it/.*" + ], + "name": "Clyp" + }, + { + "patterns": [ + "http://www\\.hulu\\.com/watch/.*" + ], + "name": "Hulu" + }, + { + "patterns": [ + "https?://(?:www|mobile\\.)?twitter\\.com/(?:#!/)?[^/]+/status(?:es)?/(\\d+)/?$", + "https?://t\\.co/[a-zA-Z0-9]+" + ], + "name": "Twitter" + }, + { + "patterns": [ + "https?://(?:www\\.)?vimeo\\.com/.+" + ], + "name": "Vimeo" + }, + { + "patterns": [ + "http://www\\.amazon\\.com/(?:.+/)?[gd]p/(?:product/)?(?:tags-on-product/)?([a-zA-Z0-9]+)", + "http://amzn\\.com/([^/]+)" + ], + "name": "Amazon" + }, + { + "patterns": [ + "http://qik\\.com/video/.*" + ], + "name": "Qik" + }, + { + "patterns": [ + "http://www\\.rdio\\.com/artist/[^/]+/album/[^/]+/?", + "http://www\\.rdio\\.com/artist/[^/]+/album/[^/]+/track/[^/]+/?", + "http://www\\.rdio\\.com/people/[^/]+/playlists/\\d+/[^/]+" + ], + "name": "Rdio" + }, + { + "patterns": [ + "http://www\\.slideshare\\.net/.*/.*" + ], + "name": "SlideShare" + }, + { + "patterns": [ + "http://imgur\\.com/([0-9a-zA-Z]+)$" + ], + "name": "Imgur" + }, + { + "patterns": [ + "https?://instagr(?:\\.am|am\\.com)/p/.+" + ], + "name": "Instagram" + }, + { + "patterns": [ + "http://www\\.twitlonger\\.com/show/[a-zA-Z0-9]+", + "http://tl\\.gd/[^/]+" + ], + "name": "Twitlonger" + }, + { + "patterns": [ + "https?://vine.co/v/[a-zA-Z0-9]+" + ], + "name": "Vine" + }, + { + "patterns": [ + "http://www\\.urbandictionary\\.com/define\\.php\\?term=.+" + ], + "name": "Urban Dictionary" + }, + { + "patterns": [ + "http://picplz\\.com/user/[^/]+/pic/[^/]+" + ], + "name": "Picplz" + }, + { + "patterns": [ + "https?://(?:www\\.)?twitter\\.com/(?:#!/)?[^/]+/status(?:es)?/(\\d+)/photo/\\d+(?:/large|/)?$", + "https?://pic\\.twitter\\.com/.+" + ], + "name": "Twitter" + } +] \ No newline at end of file -- cgit v1.2.3-1-g7c22