PLT-1049: Vine URLs should not generate preview links
Этот коммит содержится в:
83
web/react/components/post_attachment_oembed.jsx
Обычный файл
83
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
|
||||
};
|
||||
@@ -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 embed;
|
||||
return null;
|
||||
}
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
324
web/react/providers.json
Обычный файл
324
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"
|
||||
}
|
||||
]
|
||||
Ссылка в новой задаче
Block a user