summaryrefslogtreecommitdiffstats
path: root/webapp/components/post_view
diff options
context:
space:
mode:
authorDebanshu Kundu <debanshu.kundu@joshtechnologygroup.com>2017-01-20 23:11:13 +0530
committerenahum <nahumhbl@gmail.com>2017-01-20 14:41:13 -0300
commit3aaf71fdea914af1a7f2b2fb97bb6ae44132fcc4 (patch)
treeb954407a03a6c0ed9836d8b14d910fc52c8dc1dc /webapp/components/post_view
parentfefe4b70d9e69910a8e3acd6890497553b5eff2f (diff)
downloadchat-3aaf71fdea914af1a7f2b2fb97bb6ae44132fcc4.tar.gz
chat-3aaf71fdea914af1a7f2b2fb97bb6ae44132fcc4.tar.bz2
chat-3aaf71fdea914af1a7f2b2fb97bb6ae44132fcc4.zip
#4257 Added functionality to create previews for post links using open graph data from those links. (#4890)
Diffstat (limited to 'webapp/components/post_view')
-rw-r--r--webapp/components/post_view/components/post.jsx4
-rw-r--r--webapp/components/post_view/components/post_attachment_oembed.jsx108
-rw-r--r--webapp/components/post_view/components/post_attachment_opengraph.jsx212
-rw-r--r--webapp/components/post_view/components/post_body.jsx4
-rw-r--r--webapp/components/post_view/components/post_body_additional_content.jsx64
-rw-r--r--webapp/components/post_view/components/post_list.jsx16
-rw-r--r--webapp/components/post_view/components/providers.json376
7 files changed, 253 insertions, 531 deletions
diff --git a/webapp/components/post_view/components/post.jsx b/webapp/components/post_view/components/post.jsx
index 8ba3438a0..896002a6c 100644
--- a/webapp/components/post_view/components/post.jsx
+++ b/webapp/components/post_view/components/post.jsx
@@ -289,6 +289,7 @@ export default class Post extends React.Component {
compactDisplay={this.props.compactDisplay}
previewCollapsed={this.props.previewCollapsed}
isCommentMention={this.props.isCommentMention}
+ childComponentDidUpdateFunction={this.props.childComponentDidUpdateFunction}
/>
</div>
</div>
@@ -317,5 +318,6 @@ Post.propTypes = {
useMilitaryTime: React.PropTypes.bool.isRequired,
isFlagged: React.PropTypes.bool,
status: React.PropTypes.string,
- isBusy: React.PropTypes.bool
+ isBusy: React.PropTypes.bool,
+ childComponentDidUpdateFunction: React.PropTypes.func
};
diff --git a/webapp/components/post_view/components/post_attachment_oembed.jsx b/webapp/components/post_view/components/post_attachment_oembed.jsx
deleted file mode 100644
index 359c7cc35..000000000
--- a/webapp/components/post_view/components/post_attachment_oembed.jsx
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
-// See License.txt for license information.
-
-import $ from 'jquery';
-import React from 'react';
-
-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) {
- if (nextProps.link !== this.props.link) {
- this.isLoading = false;
- this.fetchData(nextProps.link);
- }
- }
-
- componentDidMount() {
- this.fetchData(this.props.link);
- }
-
- fetchData(link) {
- if (!this.isLoading) {
- this.isLoading = true;
- let url = 'https://noembed.com/embed?nowrap=on';
- url += '&url=' + encodeURIComponent(link);
- url += '&maxheight=' + this.props.provider.height;
- return $.ajax({
- url,
- dataType: 'jsonp',
- success: (result) => {
- this.isLoading = false;
- if (result.error) {
- this.setState({data: {}});
- } else {
- this.setState({data: result});
- }
- },
- error: () => {
- this.setState({data: {}});
- }
- });
- }
- return null;
- }
-
- render() {
- let data = {};
- let content;
- if ($.isEmptyObject(this.state.data)) {
- content = <div style={{height: this.props.provider.height}}/>;
- } else {
- data = this.state.data;
- content = (
- <div
- style={{height: this.props.provider.height}}
- dangerouslySetInnerHTML={{__html: data.html}}
- />
- );
- }
-
- 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={data.url}
- target='_blank'
- rel='noopener noreferrer'
- >
- {data.title}
- </a>
- </h1>
- <div >
- <div
- className={'attachment__body attachment__body--no_thumb'}
- >
- {content}
- </div>
- </div>
- </div>
- </div>
- </div>
- );
- }
-}
-
-PostAttachmentOEmbed.propTypes = {
- link: React.PropTypes.string.isRequired,
- provider: React.PropTypes.object.isRequired
-};
diff --git a/webapp/components/post_view/components/post_attachment_opengraph.jsx b/webapp/components/post_view/components/post_attachment_opengraph.jsx
new file mode 100644
index 000000000..20beaed51
--- /dev/null
+++ b/webapp/components/post_view/components/post_attachment_opengraph.jsx
@@ -0,0 +1,212 @@
+// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+import React from 'react';
+
+import OpenGraphStore from 'stores/opengraph_store.jsx';
+import * as Utils from 'utils/utils.jsx';
+import * as CommonUtils from 'utils/commons.jsx';
+import {requestOpenGraphMetadata} from 'actions/global_actions.jsx';
+
+export default class PostAttachmentOpenGraph extends React.Component {
+ constructor(props) {
+ super(props);
+ this.imageDimentions = { // Image dimentions in pixels.
+ height: 150,
+ width: 150
+ };
+ this.maxDescriptionLength = 300;
+ this.descriptionEllipsis = '...';
+ this.fetchData = this.fetchData.bind(this);
+ this.onOpenGraphMetadataChange = this.onOpenGraphMetadataChange.bind(this);
+ this.toggleImageVisibility = this.toggleImageVisibility.bind(this);
+ this.onImageLoad = this.onImageLoad.bind(this);
+ }
+
+ componentWillMount() {
+ this.setState({
+ data: {},
+ imageLoaded: false,
+ imageVisible: this.props.previewCollapsed.startsWith('false')
+ });
+ this.fetchData(this.props.link);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ this.setState({imageVisible: nextProps.previewCollapsed.startsWith('false')});
+ if (!Utils.areObjectsEqual(nextProps.link, this.props.link)) {
+ this.fetchData(nextProps.link);
+ }
+ }
+
+ shouldComponentUpdate(nextProps, nextState) {
+ if (nextState.imageVisible !== this.state.imageVisible) {
+ return true;
+ }
+ if (nextState.imageLoaded !== this.state.imageLoaded) {
+ return true;
+ }
+ if (!Utils.areObjectsEqual(nextState.data, this.state.data)) {
+ return true;
+ }
+ return false;
+ }
+
+ componentDidMount() {
+ OpenGraphStore.addUrlDataChangeListener(this.onOpenGraphMetadataChange);
+ }
+
+ componentDidUpdate() {
+ if (this.props.childComponentDidUpdateFunction) {
+ this.props.childComponentDidUpdateFunction();
+ }
+ }
+
+ componentWillUnmount() {
+ OpenGraphStore.removeUrlDataChangeListener(this.onOpenGraphMetadataChange);
+ }
+
+ onOpenGraphMetadataChange(url) {
+ if (url === this.props.link) {
+ this.fetchData(url);
+ }
+ }
+
+ fetchData(url) {
+ const data = OpenGraphStore.getOgInfo(url);
+ this.setState({data, imageLoaded: false});
+ if (Utils.isEmptyObject(data)) {
+ requestOpenGraphMetadata(url);
+ }
+ }
+
+ getBestImageUrl() {
+ const nearestPointData = CommonUtils.getNearestPoint(this.imageDimentions, this.state.data.images, 'width', 'height');
+
+ const bestImage = nearestPointData.nearestPoint;
+ const bestImageLte = nearestPointData.nearestPointLte; // Best image <= 150px height and width
+
+ let finalBestImage;
+
+ if (
+ !Utils.isEmptyObject(bestImageLte) &&
+ bestImageLte.height <= this.imageDimentions.height &&
+ bestImageLte.width <= this.imageDimentions.width
+ ) {
+ finalBestImage = bestImageLte;
+ } else {
+ finalBestImage = bestImage;
+ }
+
+ return finalBestImage.secure_url || finalBestImage.url;
+ }
+
+ toggleImageVisibility() {
+ this.setState({imageVisible: !this.state.imageVisible});
+ }
+
+ onImageLoad() {
+ this.setState({imageLoaded: true});
+ }
+
+ loadImage(src) {
+ const img = new Image();
+ img.onload = this.onImageLoad;
+ img.src = src;
+ }
+
+ imageToggleAnchoreTag(imageUrl) {
+ if (imageUrl) {
+ return (
+ <a
+ className={'post__embed-visibility'}
+ data-expanded={this.state.imageVisible}
+ aria-label='Toggle Embed Visibility'
+ onClick={this.toggleImageVisibility}
+ />
+ );
+ }
+ return null;
+ }
+
+ imageTag(imageUrl) {
+ if (imageUrl && this.state.imageVisible) {
+ return (
+ <img
+ className={this.state.imageLoaded ? 'attachment__image' : 'attachment__image loading'}
+ src={this.state.imageLoaded ? imageUrl : null}
+ />
+ );
+ }
+ return null;
+ }
+
+ render() {
+ if (Utils.isEmptyObject(this.state.data) || Utils.isEmptyObject(this.state.data.description)) {
+ return null;
+ }
+
+ const data = this.state.data;
+ const imageUrl = this.getBestImageUrl();
+ var description = data.description;
+
+ if (description.length > this.maxDescriptionLength) {
+ description = description.substring(0, this.maxDescriptionLength - this.descriptionEllipsis.length) + this.descriptionEllipsis;
+ }
+
+ if (imageUrl && this.state.imageVisible) {
+ this.loadImage(imageUrl);
+ }
+
+ return (
+ <div
+ className='attachment attachment--oembed'
+ ref='attachment'
+ >
+ <div className='attachment__content'>
+ <div
+ className={'clearfix attachment__container'}
+ >
+ <span className='sitename'>{data.site_name}</span>
+ <h1
+ className='attachment__title has-link'
+ >
+ <a
+ className='attachment__title-link'
+ href={data.url || this.props.link}
+ target='_blank'
+ rel='noopener noreferrer'
+ title={data.title || data.url || this.props.link}
+ >
+ {data.title || data.url || this.props.link}
+ </a>
+ </h1>
+ <div >
+ <div
+ className={'attachment__body attachment__body--no_thumb'}
+ >
+ <div>
+ <div>
+ {description} &nbsp;
+ {this.imageToggleAnchoreTag(imageUrl)}
+ </div>
+ {this.imageTag(imageUrl)}
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+PostAttachmentOpenGraph.defaultProps = {
+ previewCollapsed: 'false'
+};
+
+PostAttachmentOpenGraph.propTypes = {
+ link: React.PropTypes.string.isRequired,
+ childComponentDidUpdateFunction: React.PropTypes.func,
+ previewCollapsed: React.PropTypes.string
+};
diff --git a/webapp/components/post_view/components/post_body.jsx b/webapp/components/post_view/components/post_body.jsx
index 60e682e8d..10c24aab2 100644
--- a/webapp/components/post_view/components/post_body.jsx
+++ b/webapp/components/post_view/components/post_body.jsx
@@ -188,6 +188,7 @@ export default class PostBody extends React.Component {
message={messageWrapper}
compactDisplay={this.props.compactDisplay}
previewCollapsed={this.props.previewCollapsed}
+ childComponentDidUpdateFunction={this.props.childComponentDidUpdateFunction}
/>
);
}
@@ -221,5 +222,6 @@ PostBody.propTypes = {
handleCommentClick: React.PropTypes.func.isRequired,
compactDisplay: React.PropTypes.bool,
previewCollapsed: React.PropTypes.string,
- isCommentMention: React.PropTypes.bool
+ isCommentMention: React.PropTypes.bool,
+ childComponentDidUpdateFunction: React.PropTypes.func
};
diff --git a/webapp/components/post_view/components/post_body_additional_content.jsx b/webapp/components/post_view/components/post_body_additional_content.jsx
index e6c1f3b06..cad618de0 100644
--- a/webapp/components/post_view/components/post_body_additional_content.jsx
+++ b/webapp/components/post_view/components/post_body_additional_content.jsx
@@ -2,12 +2,11 @@
// See License.txt for license information.
import PostAttachmentList from './post_attachment_list.jsx';
-import PostAttachmentOEmbed from './post_attachment_oembed.jsx';
+import PostAttachmentOpenGraph from './post_attachment_opengraph.jsx';
import PostImage from './post_image.jsx';
import YoutubeVideo from 'components/youtube_video.jsx';
import Constants from 'utils/constants.jsx';
-import OEmbedProviders from './providers.json';
import * as Utils from 'utils/utils.jsx';
import React from 'react';
@@ -17,7 +16,6 @@ export default class PostBodyAdditionalContent extends React.Component {
super(props);
this.getSlackAttachment = this.getSlackAttachment.bind(this);
- this.getOEmbedProvider = this.getOEmbedProvider.bind(this);
this.generateToggleableEmbed = this.generateToggleableEmbed.bind(this);
this.generateStaticEmbed = this.generateStaticEmbed.bind(this);
this.toggleEmbedVisibility = this.toggleEmbedVisibility.bind(this);
@@ -72,18 +70,6 @@ export default class PostBodyAdditionalContent extends React.Component {
);
}
- getOEmbedProvider(link) {
- for (let i = 0; i < OEmbedProviders.length; i++) {
- for (let j = 0; j < OEmbedProviders[i].patterns.length; j++) {
- if (link.match(OEmbedProviders[i].patterns[j])) {
- return OEmbedProviders[i];
- }
- }
- }
-
- return null;
- }
-
isLinkImage(link) {
const regex = /.+\/(.+\.(?:jpg|gif|bmp|png|jpeg))(?:\?.*)?$/i;
const match = link.match(regex);
@@ -152,38 +138,20 @@ export default class PostBodyAdditionalContent extends React.Component {
}
const link = Utils.extractFirstLink(this.props.post.message);
- if (!link) {
- return null;
- }
-
- if (Utils.isFeatureEnabled(Constants.PRE_RELEASE_FEATURES.EMBED_PREVIEW)) {
- const provider = this.getOEmbedProvider(link);
-
- if (provider) {
- return (
- <PostAttachmentOEmbed
- provider={provider}
- link={link}
- />
- );
- }
+ if (link && Utils.isFeatureEnabled(Constants.PRE_RELEASE_FEATURES.EMBED_PREVIEW)) {
+ return (
+ <PostAttachmentOpenGraph
+ link={link}
+ childComponentDidUpdateFunction={this.props.childComponentDidUpdateFunction}
+ previewCollapsed={this.props.previewCollapsed}
+ />
+ );
}
return null;
}
render() {
- const staticEmbed = this.generateStaticEmbed();
-
- if (staticEmbed) {
- return (
- <div>
- {this.props.message}
- {staticEmbed}
- </div>
- );
- }
-
if (this.isLinkToggleable() && !this.state.linkLoadError) {
const messageWithToggle = [];
@@ -224,6 +192,17 @@ export default class PostBodyAdditionalContent extends React.Component {
);
}
+ const staticEmbed = this.generateStaticEmbed();
+
+ if (staticEmbed) {
+ return (
+ <div>
+ {this.props.message}
+ {staticEmbed}
+ </div>
+ );
+ }
+
return this.props.message;
}
}
@@ -235,5 +214,6 @@ PostBodyAdditionalContent.propTypes = {
post: React.PropTypes.object.isRequired,
message: React.PropTypes.element.isRequired,
compactDisplay: React.PropTypes.bool,
- previewCollapsed: React.PropTypes.string
+ previewCollapsed: React.PropTypes.string,
+ childComponentDidUpdateFunction: React.PropTypes.func
};
diff --git a/webapp/components/post_view/components/post_list.jsx b/webapp/components/post_view/components/post_list.jsx
index e3724b688..7550db348 100644
--- a/webapp/components/post_view/components/post_list.jsx
+++ b/webapp/components/post_view/components/post_list.jsx
@@ -45,6 +45,7 @@ export default class PostList extends React.Component {
this.scrollToBottom = this.scrollToBottom.bind(this);
this.scrollToBottomAnimated = this.scrollToBottomAnimated.bind(this);
this.handleKeyDown = this.handleKeyDown.bind(this);
+ this.childComponentDidUpdate = this.childComponentDidUpdate.bind(this);
this.jumpToPostNode = null;
this.wasAtBottom = true;
@@ -347,6 +348,7 @@ export default class PostList extends React.Component {
isFlagged={isFlagged}
status={status}
isBusy={this.props.isBusy}
+ childComponentDidUpdateFunction={this.childComponentDidUpdate}
/>
);
@@ -492,6 +494,12 @@ export default class PostList extends React.Component {
);
}
+ checkAndUpdateScrolling() {
+ if (this.props.postList != null && this.refs.postlist) {
+ this.updateScrolling();
+ }
+ }
+
componentDidMount() {
if (this.props.postList != null) {
this.updateScrolling();
@@ -509,9 +517,11 @@ export default class PostList extends React.Component {
}
componentDidUpdate() {
- if (this.props.postList != null && this.refs.postlist) {
- this.updateScrolling();
- }
+ this.checkAndUpdateScrolling();
+ }
+
+ childComponentDidUpdate() {
+ this.checkAndUpdateScrolling();
}
render() {
diff --git a/webapp/components/post_view/components/providers.json b/webapp/components/post_view/components/providers.json
deleted file mode 100644
index b5899c225..000000000
--- a/webapp/components/post_view/components/providers.json
+++ /dev/null
@@ -1,376 +0,0 @@
-[
- {
- "patterns": [
- "http://(?:www\\.)?xkcd\\.com/\\d+/?"
- ],
- "name": "XKCD",
- "height": 110
- },
- {
- "patterns": [
- "https?://soundcloud.com/.*/.*"
- ],
- "name": "SoundCloud",
- "height": 140
- },
- {
- "patterns": [
- "https?://(?:www\\.)?flickr\\.com/.*",
- "https?://flic\\.kr/p/[a-zA-Z0-9]+"
- ],
- "name": "Flickr",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.ted\\.com/talks/.+\\.html"
- ],
- "name": "TED",
- "height": 110
- },
- {
- "patterns": [
- "http://(?:www\\.)?theverge\\.com/\\d{4}/\\d{1,2}/\\d{1,2}/\\d+/[^/]+/?$"
- ],
- "name": "The Verge",
- "height": 110
- },
- {
- "patterns": [
- "http://.*\\.viddler\\.com/.*"
- ],
- "name": "Viddler",
- "height": 110
- },
- {
- "patterns": [
- "https?://(?:www\\.)?avclub\\.com/article/[^/]+/?$"
- ],
- "name": "The AV Club",
- "height": 110
- },
- {
- "patterns": [
- "https?://(?:www\\.)?wired\\.com/([^/]+/)?\\d+/\\d+/[^/]+/?$"
- ],
- "name": "Wired",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.theonion\\.com/articles/[^/]+/?"
- ],
- "name": "The Onion",
- "height": 110
- },
- {
- "patterns": [
- "http://yfrog\\.com/[0-9a-zA-Z]+/?$"
- ],
- "name": "YFrog",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.duffelblog\\.com/\\d{4}/\\d{1,2}/[^/]+/?$"
- ],
- "name": "The Duffel Blog",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.clickhole\\.com/article/[^/]+/?"
- ],
- "name": "Clickhole",
- "height": 110
- },
- {
- "patterns": [
- "https?://(?:www.)?skitch.com/([^/]+)/[^/]+/.+",
- "http://skit.ch/[^/]+"
- ],
- "name": "Skitch",
- "height": 110
- },
- {
- "patterns": [
- "https?://(alpha|posts|photos)\\.app\\.net/.*"
- ],
- "name": "ADN",
- "height": 110
- },
- {
- "patterns": [
- "https?://gist\\.github\\.com/(?:[-0-9a-zA-Z]+/)?([0-9a-fA-f]+)"
- ],
- "name": "Gist",
- "height": 110
- },
- {
- "patterns": [
- "https?://www\\.(dropbox\\.com/s/.+\\.(?:jpg|png|gif))",
- "https?://db\\.tt/[a-zA-Z0-9]+"
- ],
- "name": "Dropbox",
- "height": 110
- },
- {
- "patterns": [
- "https?://[^\\.]+\\.wikipedia\\.org/wiki/(?!Talk:)[^#]+(?:#(.+))?"
- ],
- "name": "Wikipedia",
- "height": 110
- },
- {
- "patterns": [
- "http://www.traileraddict.com/trailer/[^/]+/trailer"
- ],
- "name": "TrailerAddict",
- "height": 110
- },
- {
- "patterns": [
- "http://lockerz\\.com/[sd]/\\d+"
- ],
- "name": "Lockerz",
- "height": 110
- },
- {
- "patterns": [
- "http://gifuk\\.com/s/[0-9a-f]{16}"
- ],
- "name": "GIFUK",
- "height": 110
- },
- {
- "patterns": [
- "http://trailers\\.apple\\.com/trailers/[^/]+/[^/]+"
- ],
- "name": "iTunes Movie Trailers",
- "height": 110
- },
- {
- "patterns": [
- "http://gfycat\\.com/([a-zA-Z]+)"
- ],
- "name": "Gfycat",
- "height": 110
- },
- {
- "patterns": [
- "http://bash\\.org/\\?(\\d+)"
- ],
- "name": "Bash.org",
- "height": 110
- },
- {
- "patterns": [
- "http://arstechnica\\.com/[^/]+/\\d+/\\d+/[^/]+/?$"
- ],
- "name": "Ars Technica",
- "height": 110
- },
- {
- "patterns": [
- "http://imgur\\.com/gallery/[0-9a-zA-Z]+"
- ],
- "name": "Imgur",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.asciiartfarts\\.com/[0-9]+\\.html"
- ],
- "name": "ASCII Art Farts",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.monoprice\\.com/products/product\\.asp\\?.*p_id=\\d+"
- ],
- "name": "Monoprice",
- "height": 110
- },
- {
- "patterns": [
- "http://boingboing\\.net/\\d{4}/\\d{2}/\\d{2}/[^/]+\\.html"
- ],
- "name": "Boing Boing",
- "height": 110
- },
- {
- "patterns": [
- "https?://github\\.com/([^/]+)/([^/]+)/commit/(.+)",
- "http://git\\.io/[_0-9a-zA-Z]+"
- ],
- "name": "Github Commit",
- "height": 110
- },
- {
- "patterns": [
- "https?://open\\.spotify\\.com/(track|album)/([0-9a-zA-Z]{22})"
- ],
- "name": "Spotify",
- "height": 110
- },
- {
- "patterns": [
- "https?://path\\.com/p/([0-9a-zA-Z]+)$"
- ],
- "name": "Path",
- "height": 110
- },
- {
- "patterns": [
- "http://www.funnyordie.com/videos/[^/]+/.+"
- ],
- "name": "Funny or Die",
- "height": 110
- },
- {
- "patterns": [
- "http://(?:www\\.)?twitpic\\.com/([^/]+)"
- ],
- "name": "Twitpic",
- "height": 110
- },
- {
- "patterns": [
- "https?://www\\.giantbomb\\.com/videos/[^/]+/\\d+-\\d+/?"
- ],
- "name": "GiantBomb",
- "height": 110
- },
- {
- "patterns": [
- "http://(?:www\\.)?beeradvocate\\.com/beer/profile/\\d+/\\d+"
- ],
- "name": "Beer Advocate",
- "height": 110
- },
- {
- "patterns": [
- "http://(?:www\\.)?imdb.com/title/(tt\\d+)"
- ],
- "name": "IMDB",
- "height": 110
- },
- {
- "patterns": [
- "http://cl\\.ly/(?:image/)?[0-9a-zA-Z]+/?$"
- ],
- "name": "CloudApp",
- "height": 110
- },
- {
- "patterns": [
- "http://clyp\\.it/.*"
- ],
- "name": "Clyp",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.hulu\\.com/watch/.*"
- ],
- "name": "Hulu",
- "height": 110
- },
- {
- "patterns": [
- "https?://(?:www|mobile\\.)?twitter\\.com/(?:#!/)?[^/]+/status(?:es)?/(\\d+)/?$",
- "https?://t\\.co/[a-zA-Z0-9]+"
- ],
- "name": "Twitter",
- "height": 110
- },
- {
- "patterns": [
- "https?://(?:www\\.)?vimeo\\.com/.+"
- ],
- "name": "Vimeo",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.amazon\\.com/(?:.+/)?[gd]p/(?:product/)?(?:tags-on-product/)?([a-zA-Z0-9]+)",
- "http://amzn\\.com/([^/]+)"
- ],
- "name": "Amazon",
- "height": 110
- },
- {
- "patterns": [
- "http://qik\\.com/video/.*"
- ],
- "name": "Qik",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.rdio\\.com/artist/[^/]+/album/[^/]+/?",
- "http://www\\.rdio\\.com/artist/[^/]+/album/[^/]+/track/[^/]+/?",
- "http://www\\.rdio\\.com/people/[^/]+/playlists/\\d+/[^/]+"
- ],
- "name": "Rdio",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.slideshare\\.net/.*/.*"
- ],
- "name": "SlideShare",
- "height": 110
- },
- {
- "patterns": [
- "http://imgur\\.com/([0-9a-zA-Z]+)$"
- ],
- "name": "Imgur",
- "height": 110
- },
- {
- "patterns": [
- "https?://instagr(?:\\.am|am\\.com)/p/.+"
- ],
- "name": "Instagram",
- "height": 110
- },
- {
- "patterns": [
- "http://www\\.twitlonger\\.com/show/[a-zA-Z0-9]+",
- "http://tl\\.gd/[^/]+"
- ],
- "name": "Twitlonger",
- "height": 110
- },
- {
- "patterns": [
- "https?://vine.co/v/[a-zA-Z0-9]+"
- ],
- "name": "Vine",
- "height": 490
- },
- {
- "patterns": [
- "http://www\\.urbandictionary\\.com/define\\.php\\?term=.+"
- ],
- "name": "Urban Dictionary",
- "height": 110
- },
- {
- "patterns": [
- "http://picplz\\.com/user/[^/]+/pic/[^/]+"
- ],
- "name": "Picplz",
- "height": 110
- },
- {
- "patterns": [
- "https?://(?:www\\.)?twitter\\.com/(?:#!/)?[^/]+/status(?:es)?/(\\d+)/photo/\\d+(?:/large|/)?$",
- "https?://pic\\.twitter\\.com/.+"
- ],
- "name": "Twitter",
- "height": 110
- }
-]