Fix #463 - Fetch and display previews of URLs using OpenGraph tags

This commit is contained in:
Eugen Rochko
2017-01-20 01:00:14 +01:00
parent 8d0284f8d9
commit f0de621e76
26 changed files with 302 additions and 7 deletions

View File

@ -0,0 +1,40 @@
import api from '../api';
export const STATUS_CARD_FETCH_REQUEST = 'STATUS_CARD_FETCH_REQUEST';
export const STATUS_CARD_FETCH_SUCCESS = 'STATUS_CARD_FETCH_SUCCESS';
export const STATUS_CARD_FETCH_FAIL = 'STATUS_CARD_FETCH_FAIL';
export function fetchStatusCard(id) {
return (dispatch, getState) => {
dispatch(fetchStatusCardRequest(id));
api(getState).get(`/api/v1/statuses/${id}/card`).then(response => {
dispatch(fetchStatusCardSuccess(id, response.data));
}).catch(error => {
dispatch(fetchStatusCardFail(id, error));
});
};
};
export function fetchStatusCardRequest(id) {
return {
type: STATUS_CARD_FETCH_REQUEST,
id
};
};
export function fetchStatusCardSuccess(id, card) {
return {
type: STATUS_CARD_FETCH_SUCCESS,
id,
card
};
};
export function fetchStatusCardFail(id, error) {
return {
type: STATUS_CARD_FETCH_FAIL,
id,
error
};
};

View File

@ -1,6 +1,7 @@
import api from '../api';
import { deleteFromTimelines } from './timelines';
import { fetchStatusCard } from './cards';
export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST';
export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS';
@ -31,6 +32,7 @@ export function fetchStatus(id) {
api(getState).get(`/api/v1/statuses/${id}`).then(response => {
dispatch(fetchStatusSuccess(response.data, skipLoading));
dispatch(fetchContext(id));
dispatch(fetchStatusCard(id));
}).catch(error => {
dispatch(fetchStatusFail(id, error, skipLoading));
});

View File

@ -0,0 +1,96 @@
import PureRenderMixin from 'react-addons-pure-render-mixin';
import ImmutablePropTypes from 'react-immutable-proptypes';
const outerStyle = {
display: 'flex',
cursor: 'pointer',
fontSize: '14px',
border: '1px solid #363c4b',
borderRadius: '4px',
color: '#616b86',
marginTop: '14px',
textDecoration: 'none',
overflow: 'hidden'
};
const contentStyle = {
flex: '2',
padding: '8px',
paddingLeft: '14px'
};
const titleStyle = {
display: 'block',
fontWeight: '500',
marginBottom: '5px',
color: '#d9e1e8'
};
const descriptionStyle = {
color: '#d9e1e8'
};
const imageOuterStyle = {
flex: '1',
background: '#373b4a'
};
const imageStyle = {
display: 'block',
width: '100%',
height: 'auto',
margin: '0',
borderRadius: '4px 0 0 4px'
};
const hostStyle = {
display: 'block',
marginTop: '5px',
fontSize: '13px'
};
const getHostname = url => {
const parser = document.createElement('a');
parser.href = url;
return parser.hostname;
};
const Card = React.createClass({
propTypes: {
card: ImmutablePropTypes.map
},
mixins: [PureRenderMixin],
render () {
const { card } = this.props;
if (card === null) {
return null;
}
let image = '';
if (card.get('image')) {
image = (
<div style={imageOuterStyle}>
<img src={card.get('image')} alt={card.get('title')} style={imageStyle} />
</div>
);
}
return (
<a style={outerStyle} href={card.get('url')} className='status-card'>
{image}
<div style={contentStyle}>
<strong style={titleStyle}>{card.get('title')}</strong>
<p style={descriptionStyle}>{card.get('description')}</p>
<span style={hostStyle}>{getHostname(card.get('url'))}</span>
</div>
</a>
);
}
});
export default Card;

View File

@ -7,6 +7,7 @@ import MediaGallery from '../../../components/media_gallery';
import VideoPlayer from '../../../components/video_player';
import { Link } from 'react-router';
import { FormattedDate, FormattedNumber } from 'react-intl';
import CardContainer from '../containers/card_container';
const DetailedStatus = React.createClass({
@ -42,6 +43,8 @@ const DetailedStatus = React.createClass({
} else {
media = <MediaGallery sensitive={status.get('sensitive')} media={status.get('media_attachments')} height={300} onOpenMedia={this.props.onOpenMedia} />;
}
} else {
media = <CardContainer statusId={status.get('id')} />;
}
if (status.get('application')) {

View File

@ -0,0 +1,8 @@
import { connect } from 'react-redux';
import Card from '../components/card';
const mapStateToProps = (state, { statusId }) => ({
card: state.getIn(['cards', statusId], null)
});
export default connect(mapStateToProps)(Card);

View File

@ -0,0 +1,14 @@
import { STATUS_CARD_FETCH_SUCCESS } from '../actions/cards';
import Immutable from 'immutable';
const initialState = Immutable.Map();
export default function cards(state = initialState, action) {
switch(action.type) {
case STATUS_CARD_FETCH_SUCCESS:
return state.set(action.id, Immutable.fromJS(action.card));
default:
return state;
}
};

View File

@ -13,6 +13,7 @@ import search from './search';
import notifications from './notifications';
import settings from './settings';
import status_lists from './status_lists';
import cards from './cards';
export default combineReducers({
timelines,
@ -28,5 +29,6 @@ export default combineReducers({
relationships,
search,
notifications,
settings
settings,
cards
});

View File

@ -680,3 +680,9 @@ button.active i.fa-retweet {
transition-duration: 0.9s;
background-position: 0 -209px;
}
.status-card {
&:hover {
background: #363c4b;
}
}