From 6b2952d1dd2c21a8a8854f1df5fa532f8fed5af4 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 24 Jul 2023 13:47:28 +0200 Subject: [PATCH] Change design of link previews in web UI (#26136) --- .../features/status/components/card.jsx | 39 +++--- app/javascript/mastodon/locales/en.json | 1 + .../styles/mastodon-light/diff.scss | 6 +- .../styles/mastodon-light/variables.scss | 2 +- .../styles/mastodon/components.scss | 130 ++++++++---------- app/lib/link_details_extractor.rb | 5 + .../rest/preview_card_serializer.rb | 6 +- 7 files changed, 90 insertions(+), 99 deletions(-) diff --git a/app/javascript/mastodon/features/status/components/card.jsx b/app/javascript/mastodon/features/status/components/card.jsx index d23bbf16a7..7cf324760e 100644 --- a/app/javascript/mastodon/features/status/components/card.jsx +++ b/app/javascript/mastodon/features/status/components/card.jsx @@ -12,6 +12,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'; import { Blurhash } from 'mastodon/components/blurhash'; import { Icon } from 'mastodon/components/icon'; +import { RelativeTimestamp } from 'mastodon/components/relative_timestamp'; import { useBlurhash } from 'mastodon/initial_state'; const IDNA_PREFIX = 'xn--'; @@ -57,14 +58,9 @@ export default class Card extends PureComponent { static propTypes = { card: ImmutablePropTypes.map, onOpenMedia: PropTypes.func.isRequired, - compact: PropTypes.bool, sensitive: PropTypes.bool, }; - static defaultProps = { - compact: false, - }; - state = { previewLoaded: false, embedded: false, @@ -148,7 +144,7 @@ export default class Card extends PureComponent { } render () { - const { card, compact } = this.props; + const { card } = this.props; const { embedded, revealed } = this.state; if (card === null) { @@ -156,29 +152,24 @@ export default class Card extends PureComponent { } const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name'); - const horizontal = (!compact && card.get('width') > card.get('height')) || card.get('type') !== 'link' || embedded; const interactive = card.get('type') !== 'link'; - const className = classnames('status-card', { horizontal, compact, interactive }); - const title = interactive ? {card.get('title')} : {card.get('title')}; const language = card.get('language') || ''; const description = (
- {title} - {!(horizontal || compact) &&

{card.get('description')}

} - {provider} + {provider}{card.get('published_at') && <> ยท } + {card.get('title')} + {card.get('author_name').length > 0 && {card.get('author_name')} }} />}
); const thumbnailStyle = { - visibility: revealed? null : 'hidden', + visibility: revealed ? null : 'hidden', + aspectRatio: `${card.get('width')} / ${card.get('height')}` }; - if (horizontal) { - thumbnailStyle.aspectRatio = (compact && !embedded) ? '16 / 9' : `${card.get('width')} / ${card.get('height')}`; - } + let embed; - let embed = ''; let canvas = ( ); + let thumbnail = ; + let spoilerButton = ( ); + spoilerButton = (
{spoilerButton} @@ -219,19 +213,20 @@ export default class Card extends PureComponent {
- {horizontal && } +
)} + {!revealed && spoilerButton}
); } return ( -
+
{embed} - {!compact && description} + {description}
); } else if (card.get('image')) { @@ -243,14 +238,14 @@ export default class Card extends PureComponent { ); } else { embed = ( -
+
); } return ( - + {embed} {description} diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index cf4e802eb4..cc9bffd80f 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -363,6 +363,7 @@ "lightbox.previous": "Previous", "limited_account_hint.action": "Show profile anyway", "limited_account_hint.title": "This profile has been hidden by the moderators of {domain}.", + "link_preview.author": "By {name}", "lists.account.add": "Add to list", "lists.account.remove": "Remove from list", "lists.delete": "Delete list", diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss index f7835ac2f4..df89d9a5a5 100644 --- a/app/javascript/styles/mastodon-light/diff.scss +++ b/app/javascript/styles/mastodon-light/diff.scss @@ -77,6 +77,10 @@ html { background: $white; } +.column-header { + border-bottom: 0; +} + .column-header__button.active { color: $ui-highlight-color; @@ -414,7 +418,7 @@ html { .column-header__collapsible-inner { background: darken($ui-base-color, 4%); border: 1px solid lighten($ui-base-color, 8%); - border-top: 0; + border-bottom: 0; } .dashboard__quick-access, diff --git a/app/javascript/styles/mastodon-light/variables.scss b/app/javascript/styles/mastodon-light/variables.scss index 250e200fc6..ac8a831215 100644 --- a/app/javascript/styles/mastodon-light/variables.scss +++ b/app/javascript/styles/mastodon-light/variables.scss @@ -5,7 +5,7 @@ $white: #ffffff; $classic-base-color: #282c37; $classic-primary-color: #9baec8; $classic-secondary-color: #d9e1e8; -$classic-highlight-color: #6364ff; +$classic-highlight-color: #858afa; $blurple-600: #563acc; // Iris $blurple-500: #6364ff; // Brand purple diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 64c245de34..b06a7130f8 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -252,13 +252,14 @@ &.overlayed { box-sizing: content-box; - background: rgba($base-overlay-background, 0.6); - color: rgba($primary-text-color, 0.7); + background: rgba($black, 0.65); + backdrop-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%); + color: rgba($white, 0.7); border-radius: 4px; padding: 2px; &:hover { - background: rgba($base-overlay-background, 0.9); + background: rgba($black, 0.9); } } @@ -1352,6 +1353,10 @@ body > [data-popper-placement] { } } +.scrollable > div:first-child .detailed-status { + border-top: 0; +} + .detailed-status__meta { margin-top: 16px; color: $dark-text-color; @@ -3504,12 +3509,10 @@ button.icon-button.active i.fa-retweet { } .status-card { + display: block; position: relative; - display: flex; font-size: 14px; - border: 1px solid lighten($ui-base-color, 8%); - border-radius: 4px; - color: $dark-text-color; + color: $darker-text-color; margin-top: 14px; text-decoration: none; overflow: hidden; @@ -3563,8 +3566,29 @@ button.icon-button.active i.fa-retweet { a.status-card { cursor: pointer; - &:hover { - background: lighten($ui-base-color, 8%); + &:hover, + &:focus, + &:active { + .status-card__title, + .status-card__host, + .status-card__author { + color: $highlight-text-color; + } + } +} + +.status-card a { + color: inherit; + text-decoration: none; + + &:hover, + &:focus, + &:active { + .status-card__title, + .status-card__host, + .status-card__author { + color: $highlight-text-color; + } } } @@ -3590,42 +3614,42 @@ a.status-card { .status-card__title { display: block; - font-weight: 500; - margin-bottom: 5px; - color: $darker-text-color; + font-weight: 700; + font-size: 19px; + line-height: 24px; + color: $primary-text-color; overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - text-decoration: none; } .status-card__content { flex: 1 1 auto; overflow: hidden; - padding: 14px 14px 14px 8px; -} - -.status-card__description { - color: $darker-text-color; - overflow: hidden; - display: -webkit-box; - -webkit-box-orient: vertical; - -webkit-line-clamp: 2; + padding: 15px 0; + padding-bottom: 0; } .status-card__host { display: block; - margin-top: 5px; - font-size: 13px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + font-size: 14px; + margin-bottom: 8px; +} + +.status-card__author { + display: block; + margin-top: 8px; + font-size: 14px; + color: $primary-text-color; + + strong { + font-weight: 500; + } } .status-card__image { - flex: 0 0 100px; + width: 100%; background: lighten($ui-base-color, 8%); position: relative; + border-radius: 8px; & > .fa { font-size: 21px; @@ -3637,50 +3661,8 @@ a.status-card { } } -.status-card.horizontal { - display: block; - - .status-card__image { - width: 100%; - } - - .status-card__image-image, - .status-card__image-preview { - border-radius: 4px 4px 0 0; - } - - .status-card__title { - white-space: inherit; - } -} - -.status-card.compact { - border-color: lighten($ui-base-color, 4%); - - &.interactive { - border: 0; - } - - .status-card__content { - padding: 8px; - padding-top: 10px; - } - - .status-card__title { - white-space: nowrap; - } - - .status-card__image { - flex: 0 0 60px; - } -} - -a.status-card.compact:hover { - background-color: lighten($ui-base-color, 4%); -} - .status-card__image-image { - border-radius: 4px 0 0 4px; + border-radius: 8px; display: block; margin: 0; width: 100%; @@ -3691,7 +3673,7 @@ a.status-card.compact:hover { } .status-card__image-preview { - border-radius: 4px 0 0 4px; + border-radius: 8px; display: block; margin: 0; width: 100%; diff --git a/app/lib/link_details_extractor.rb b/app/lib/link_details_extractor.rb index f0aeec0b3e..a350cadf90 100644 --- a/app/lib/link_details_extractor.rb +++ b/app/lib/link_details_extractor.rb @@ -124,6 +124,7 @@ class LinkDetailsExtractor author_url: author_url || '', embed_url: embed_url || '', language: language, + created_at: published_at, } end @@ -159,6 +160,10 @@ class LinkDetailsExtractor html_entities.decode(structured_data&.description || opengraph_tag('og:description') || meta_tag('description')) end + def published_at + structured_data&.date_published || opengraph_tag('article:published_time') + end + def image valid_url_or_nil(opengraph_tag('og:image')) end diff --git a/app/serializers/rest/preview_card_serializer.rb b/app/serializers/rest/preview_card_serializer.rb index 08bc07edd4..db44abb382 100644 --- a/app/serializers/rest/preview_card_serializer.rb +++ b/app/serializers/rest/preview_card_serializer.rb @@ -6,7 +6,7 @@ class REST::PreviewCardSerializer < ActiveModel::Serializer attributes :url, :title, :description, :language, :type, :author_name, :author_url, :provider_name, :provider_url, :html, :width, :height, - :image, :embed_url, :blurhash + :image, :embed_url, :blurhash, :published_at def image object.image? ? full_asset_url(object.image.url(:original)) : nil @@ -15,4 +15,8 @@ class REST::PreviewCardSerializer < ActiveModel::Serializer def html Sanitize.fragment(object.html, Sanitize::Config::MASTODON_OEMBED) end + + def published_at + object.created_at + end end