summaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
authorFlorian Orben <florian.orben@gmail.com>2015-11-07 15:44:55 +0100
committerFlorian Orben <florian.orben@gmail.com>2015-11-10 23:44:20 +0100
commitc56762a4e9ca25ce3467f2944729bfe9bb890114 (patch)
tree05f7dbfaafc123b7cbb033aba32315afb275ece4 /web
parent6272f0f0252edcf86139d656ce13d3e3189da19e (diff)
downloadchat-c56762a4e9ca25ce3467f2944729bfe9bb890114.tar.gz
chat-c56762a4e9ca25ce3467f2944729bfe9bb890114.tar.bz2
chat-c56762a4e9ca25ce3467f2944729bfe9bb890114.zip
PLT-1049: Vine URLs should not generate preview links
Diffstat (limited to 'web')
-rw-r--r--web/react/components/post_attachment_oembed.jsx83
-rw-r--r--web/react/components/post_body.jsx71
-rw-r--r--web/react/components/post_body_additional_content.jsx20
-rw-r--r--web/react/providers.json324
4 files changed, 478 insertions, 20 deletions
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 <div></div>;
+ }
+
+ return (
+ <div
+ className='attachment attachment--oembed'
+ ref='attachment'
+ >
+ <div className='attachment__content'>
+ <div
+ className={'clearfix attachment__container'}
+ >
+ <h1
+ className='attachment__title'
+ >
+ <a
+ className='attachment__title-link'
+ href={this.state.data.url}
+ target='_blank'
+ >
+ {this.state.data.title}
+ </a>
+ </h1>
+ <div>
+ <div className={'attachment__body attachment__body--no_thumb'}>
+ <div
+ dangerouslySetInnerHTML={{__html: this.state.data.html}}
+ >
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+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 (
<img
@@ -112,7 +152,7 @@ export default class PostBody extends React.Component {
handleYoutubeTime(link) {
const timeRegex = /[\\?&]t=([0-9hms]+)/;
- const time = link.trim().match(timeRegex);
+ const time = link.match(timeRegex);
if (!time || !time[1]) {
return '';
}
@@ -301,11 +341,6 @@ export default class PostBody extends React.Component {
);
}
- let embed;
- if (filenames.length === 0 && this.state.links && this.state.links.length > 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 {
/>
</div>
<PostBodyAdditionalContent
- post={post}
+ post={this.state.post}
/>
{fileAttachmentHolder}
- {embed}
+ {this.embed}
</div>
);
}
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 (
+ <PostAttachmentOEmbed
+ key={'post_body_additional_content' + this.props.post.id}
+ link={link}
+ />
+ );
+ }
+
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