From f7765acf9d92951a616f41b738d5d23ede58c162 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Thu, 15 Feb 2018 07:04:28 +0100 Subject: [PATCH] Fix #5173: Click card to embed external content (#6471) --- .../features/status/components/card.js | 150 +++++++++++------- .../styles/mastodon/components.scss | 63 ++++++-- app/services/fetch_link_card_service.rb | 16 +- 3 files changed, 150 insertions(+), 79 deletions(-) diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.js index 2f6a7831e99..8a638347167 100644 --- a/app/javascript/mastodon/features/status/components/card.js +++ b/app/javascript/mastodon/features/status/components/card.js @@ -20,6 +20,16 @@ const getHostname = url => { return parser.hostname; }; +const trim = (text, len) => { + const cut = text.indexOf(' ', len); + + if (cut === -1) { + return text; + } + + return text.substring(0, cut) + (text.length > len ? '…' : ''); +}; + export default class Card extends React.PureComponent { static propTypes = { @@ -33,9 +43,16 @@ export default class Card extends React.PureComponent { }; state = { - width: 0, + width: 280, + embedded: false, }; + componentWillReceiveProps (nextProps) { + if (this.props.card !== nextProps.card) { + this.setState({ embedded: false }); + } + } + handlePhotoClick = () => { const { card, onOpenMedia } = this.props; @@ -57,56 +74,14 @@ export default class Card extends React.PureComponent { ); }; - renderLink () { - const { card, maxDescription } = this.props; - const { width } = this.state; - const horizontal = card.get('width') > card.get('height') && (card.get('width') + 100 >= width); - - let image = ''; - let provider = card.get('provider_name'); - - if (card.get('image')) { - image = ( -
- {card.get('title')} -
- ); - } - - if (provider.length < 1) { - provider = decodeIDNA(getHostname(card.get('url'))); - } - - const className = classnames('status-card', { horizontal }); - - return ( - - {image} - -
- {card.get('title')} - {!horizontal &&

{(card.get('description') || '').substring(0, maxDescription)}

} - {provider} -
-
- ); - } - - renderPhoto () { + handleEmbedClick = () => { const { card } = this.props; - return ( - {card.get('title')} - ); + if (card.get('type') === 'photo') { + this.handlePhotoClick(); + } else { + this.setState({ embedded: true }); + } } setRef = c => { @@ -125,7 +100,7 @@ export default class Card extends React.PureComponent { return (
@@ -133,23 +108,76 @@ export default class Card extends React.PureComponent { } render () { - const { card } = this.props; + const { card, maxDescription } = this.props; + const { width, embedded } = this.state; if (card === null) { return null; } - switch(card.get('type')) { - case 'link': - return this.renderLink(); - case 'photo': - return this.renderPhoto(); - case 'video': - return this.renderVideo(); - case 'rich': - default: - return null; + const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name'); + const horizontal = card.get('width') > card.get('height') && (card.get('width') + 100 >= width) || card.get('type') !== 'link'; + const className = classnames('status-card', { horizontal }); + const interactive = card.get('type') !== 'link'; + const title = interactive ? {card.get('title')} : {card.get('title')}; + const ratio = card.get('width') / card.get('height'); + const height = card.get('width') > card.get('height') ? (width / ratio) : (width * ratio); + + const description = ( +
+ {title} + {!horizontal &&

{trim(card.get('description') || '', maxDescription)}

} + {provider} +
+ ); + + let embed = ''; + let thumbnail =
; + + if (interactive) { + if (embedded) { + embed = this.renderVideo(); + } else { + let iconVariant = 'play'; + + if (card.get('type') === 'photo') { + iconVariant = 'search-plus'; + } + + embed = ( +
+ {thumbnail} + +
+
+ + +
+
+
+ ); + } + + return ( +
+ {embed} + {description} +
+ ); + } else if (card.get('image')) { + embed = ( +
+ {thumbnail} +
+ ); } + + return ( + + {embed} + {description} + + ); } } diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index fe895809a48..d1fbabfc544 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -2208,7 +2208,6 @@ .status-card { display: flex; - cursor: pointer; font-size: 14px; border: 1px solid lighten($ui-base-color, 8%); border-radius: 4px; @@ -2217,20 +2216,58 @@ text-decoration: none; overflow: hidden; - &:hover { - background: lighten($ui-base-color, 8%); + &__actions { + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + display: flex; + justify-content: center; + align-items: center; + + & > div { + background: rgba($base-shadow-color, 0.6); + border-radius: 4px; + padding: 12px 9px; + flex: 0 0 auto; + display: flex; + justify-content: center; + align-items: center; + } + + button, + a { + display: inline; + color: $primary-text-color; + background: transparent; + border: 0; + padding: 0 5px; + text-decoration: none; + opacity: 0.6; + font-size: 18px; + line-height: 18px; + + &:hover, + &:active, + &:focus { + opacity: 1; + } + } + + a { + font-size: 19px; + position: relative; + bottom: -1px; + } } } -.status-card-video, -.status-card-rich, -.status-card-photo { - margin-top: 14px; - overflow: hidden; +a.status-card { + cursor: pointer; - iframe { - width: 100%; - height: auto; + &:hover { + background: lighten($ui-base-color, 8%); } } @@ -2258,6 +2295,7 @@ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; + text-decoration: none; } .status-card__content { @@ -2279,6 +2317,7 @@ .status-card__image { flex: 0 0 100px; background: lighten($ui-base-color, 8%); + position: relative; } .status-card.horizontal { @@ -2304,6 +2343,8 @@ width: 100%; height: 100%; object-fit: cover; + background-size: cover; + background-position: center center; } .load-more { diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index 3e31a414570..8f252e64c68 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -94,14 +94,16 @@ class FetchLinkCardService < BaseService @card.image_remote_url = embed.thumbnail_url if embed.respond_to?(:thumbnail_url) when 'photo' return false unless embed.respond_to?(:url) + @card.embed_url = embed.url @card.image_remote_url = embed.url @card.width = embed.width.presence || 0 @card.height = embed.height.presence || 0 when 'video' - @card.width = embed.width.presence || 0 - @card.height = embed.height.presence || 0 - @card.html = Formatter.instance.sanitize(embed.html, Sanitize::Config::MASTODON_OEMBED) + @card.width = embed.width.presence || 0 + @card.height = embed.height.presence || 0 + @card.html = Formatter.instance.sanitize(embed.html, Sanitize::Config::MASTODON_OEMBED) + @card.image_remote_url = embed.thumbnail_url if embed.respond_to?(:thumbnail_url) when 'rich' # Most providers rely on