From b6c2e42c4578a3dfefd29534055fc8771233b28c Mon Sep 17 00:00:00 2001 From: Eugen Rochko <eugen@zeonfederated.com> Date: Wed, 10 Aug 2022 23:12:45 +0200 Subject: [PATCH] Add logged-out access to the web UI --- app/controllers/api/v2/search_controller.rb | 5 +- app/controllers/home_controller.rb | 3 +- app/javascript/mastodon/components/logo.js | 4 +- .../mastodon/components/server_banner.js | 18 ++++ .../mastodon/containers/mastodon.js | 2 +- .../features/account/components/header.js | 2 +- .../features/ui/components/columns_area.js | 4 +- .../features/ui/components/compose_panel.js | 24 +++++- .../features/ui/components/link_footer.js | 9 +- .../ui/components/navigation_panel.js | 83 ++++++++++++++----- .../features/ui/components/sign_in_banner.js | 11 +++ app/javascript/mastodon/features/ui/index.js | 15 ++-- app/javascript/mastodon/initial_state.js | 2 + .../styles/mastodon/components.scss | 44 +++++++++- app/serializers/initial_state_serializer.rb | 11 ++- 15 files changed, 189 insertions(+), 48 deletions(-) create mode 100644 app/javascript/mastodon/components/server_banner.js create mode 100644 app/javascript/mastodon/features/ui/components/sign_in_banner.js diff --git a/app/controllers/api/v2/search_controller.rb b/app/controllers/api/v2/search_controller.rb index a305601333..e384ecbaf1 100644 --- a/app/controllers/api/v2/search_controller.rb +++ b/app/controllers/api/v2/search_controller.rb @@ -5,8 +5,7 @@ class Api::V2::SearchController < Api::BaseController RESULTS_LIMIT = 20 - before_action -> { doorkeeper_authorize! :read, :'read:search' } - before_action :require_user! + before_action -> { authorize_if_got_token! :read, :'read:search' } def index @search = Search.new(search_results) @@ -24,7 +23,7 @@ class Api::V2::SearchController < Api::BaseController params[:q], current_account, limit_param(RESULTS_LIMIT), - search_params.merge(resolve: truthy_param?(:resolve), exclude_unreviewed: truthy_param?(:exclude_unreviewed)) + search_params.merge(resolve: user_signed_in? ? truthy_param?(:resolve) : false, exclude_unreviewed: truthy_param?(:exclude_unreviewed)) ) end diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb index 7e443eb9e7..24fd39f1a6 100644 --- a/app/controllers/home_controller.rb +++ b/app/controllers/home_controller.rb @@ -1,8 +1,7 @@ # frozen_string_literal: true class HomeController < ApplicationController - before_action :redirect_unauthenticated_to_permalinks! - before_action :authenticate_user! + # before_action :redirect_unauthenticated_to_permalinks! before_action :set_referrer_policy_header def index diff --git a/app/javascript/mastodon/components/logo.js b/app/javascript/mastodon/components/logo.js index d1c7f08a91..3570b3644b 100644 --- a/app/javascript/mastodon/components/logo.js +++ b/app/javascript/mastodon/components/logo.js @@ -1,8 +1,8 @@ import React from 'react'; const Logo = () => ( - <svg viewBox='0 0 216.4144 232.00976' className='logo'> - <use xlinkHref='#mastodon-svg-logo' /> + <svg viewBox='0 0 261 66' className='logo'> + <use xlinkHref='#logo-symbol-wordmark' /> </svg> ); diff --git a/app/javascript/mastodon/components/server_banner.js b/app/javascript/mastodon/components/server_banner.js new file mode 100644 index 0000000000..259c0ded3d --- /dev/null +++ b/app/javascript/mastodon/components/server_banner.js @@ -0,0 +1,18 @@ +import React from 'react'; +import { server, domain } from 'mastodon/initial_state'; + +const ServerBanner = () => ( + <div className='hero-widget'> + <div className='hero-widget__img'> + <img src={server.hero} alt={domain} /> + </div> + + <div className='hero-widget__text'> + <p>{server.description}</p> + + <a href='/about/more' className='button button--block button-secondary'>Learn more</a> + </div> + </div> +); + +export default ServerBanner; diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index f4bef46863..08241522cf 100644 --- a/app/javascript/mastodon/containers/mastodon.js +++ b/app/javascript/mastodon/containers/mastodon.js @@ -26,7 +26,7 @@ const createIdentityContext = state => ({ signedIn: !!state.meta.me, accountId: state.meta.me, accessToken: state.meta.access_token, - permissions: state.role.permissions, + permissions: state.role ? state.role.permissions : 0, }); export default class Mastodon extends React.PureComponent { diff --git a/app/javascript/mastodon/features/account/components/header.js b/app/javascript/mastodon/features/account/components/header.js index 1ad9341c79..c71d72ab17 100644 --- a/app/javascript/mastodon/features/account/components/header.js +++ b/app/javascript/mastodon/features/account/components/header.js @@ -322,7 +322,7 @@ class Header extends ImmutablePureComponent { </div> )} - {account.get('id') !== me && <AccountNoteContainer account={account} />} + {(account.get('id') !== me && this.context.identity.signedIn) && <AccountNoteContainer account={account} />} {account.get('note').length > 0 && account.get('note') !== '<p></p>' && <div className='account__header__content translate' dangerouslySetInnerHTML={content} />} diff --git a/app/javascript/mastodon/features/ui/components/columns_area.js b/app/javascript/mastodon/features/ui/components/columns_area.js index 68017a5f1e..83e10e003a 100644 --- a/app/javascript/mastodon/features/ui/components/columns_area.js +++ b/app/javascript/mastodon/features/ui/components/columns_area.js @@ -60,6 +60,7 @@ class ColumnsArea extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object.isRequired, + identity: PropTypes.object.isRequired, }; static propTypes = { @@ -212,11 +213,12 @@ class ColumnsArea extends ImmutablePureComponent { render () { const { columns, children, singleColumn, isModalOpen, intl } = this.props; const { shouldAnimate, renderComposePanel } = this.state; + const { signedIn } = this.context.identity; const columnIndex = getIndex(this.context.router.history.location.pathname); if (singleColumn) { - const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/publish' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>; + const floatingActionButton = (!signedIn || shouldHideFAB(this.context.router.history.location.pathname)) ? null : <Link key='floating-action-button' to='/publish' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>; const content = columnIndex !== -1 ? ( <ReactSwipeableViews key='content' hysteresis={0.2} threshold={15} index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }} disabled={disableSwiping}> diff --git a/app/javascript/mastodon/features/ui/components/compose_panel.js b/app/javascript/mastodon/features/ui/components/compose_panel.js index 3d0c48c7a9..17737fb999 100644 --- a/app/javascript/mastodon/features/ui/components/compose_panel.js +++ b/app/javascript/mastodon/features/ui/components/compose_panel.js @@ -6,10 +6,15 @@ import ComposeFormContainer from 'mastodon/features/compose/containers/compose_f import NavigationContainer from 'mastodon/features/compose/containers/navigation_container'; import LinkFooter from './link_footer'; import { changeComposing } from 'mastodon/actions/compose'; +import ServerBanner from 'mastodon/components/server_banner'; export default @connect() class ComposePanel extends React.PureComponent { + static contextTypes = { + identity: PropTypes.object.isRequired, + }; + static propTypes = { dispatch: PropTypes.func.isRequired, }; @@ -23,11 +28,26 @@ class ComposePanel extends React.PureComponent { } render() { + const { signedIn } = this.context.identity; + return ( <div className='compose-panel' onFocus={this.onFocus}> <SearchContainer openInRoute /> - <NavigationContainer onClose={this.onBlur} /> - <ComposeFormContainer singleColumn /> + + {!signedIn && ( + <React.Fragment> + <ServerBanner /> + <div className='flex-spacer' /> + </React.Fragment> + )} + + {signedIn && ( + <React.Fragment> + <NavigationContainer onClose={this.onBlur} /> + <ComposeFormContainer singleColumn /> + </React.Fragment> + )} + <LinkFooter withHotkeys /> </div> ); diff --git a/app/javascript/mastodon/features/ui/components/link_footer.js b/app/javascript/mastodon/features/ui/components/link_footer.js index bbb9b122a3..4f07a3aad3 100644 --- a/app/javascript/mastodon/features/ui/components/link_footer.js +++ b/app/javascript/mastodon/features/ui/components/link_footer.js @@ -49,20 +49,21 @@ class LinkFooter extends React.PureComponent { render () { const { withHotkeys } = this.props; + const { signedIn, permissions } = this.context.identity; return ( <div className='getting-started__footer'> <ul> - {((this.context.identity.permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS) && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>} + {((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS) && <li><a href='/invites' target='_blank'><FormattedMessage id='getting_started.invite' defaultMessage='Invite people' /></a> · </li>} {withHotkeys && <li><Link to='/keyboard-shortcuts'><FormattedMessage id='navigation_bar.keyboard_shortcuts' defaultMessage='Hotkeys' /></Link> · </li>} - <li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li> + {signedIn && (<li><a href='/auth/edit'><FormattedMessage id='getting_started.security' defaultMessage='Security' /></a> · </li>)} {!limitedFederationMode && <li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>} {profileDirectory && <li><Link to='/directory'><FormattedMessage id='getting_started.directory' defaultMessage='Profile directory' /></Link> · </li>} <li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li> <li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li> - <li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li> + {signedIn && (<li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>)} <li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li> - <li><a href='/auth/sign_out' onClick={this.handleLogoutClick}><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li> + {signedIn && (<li><a href='/auth/sign_out' onClick={this.handleLogoutClick}><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li>)} </ul> <p> diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.js b/app/javascript/mastodon/features/ui/components/navigation_panel.js index fe4ed5d772..882ee85760 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.js +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.js @@ -1,5 +1,6 @@ import React from 'react'; -import { NavLink, withRouter } from 'react-router-dom'; +import PropTypes from 'prop-types'; +import { NavLink } from 'react-router-dom'; import { FormattedMessage } from 'react-intl'; import Icon from 'mastodon/components/icon'; import { showTrends } from 'mastodon/initial_state'; @@ -7,30 +8,68 @@ import NotificationsCounterIcon from './notifications_counter_icon'; import FollowRequestsNavLink from './follow_requests_nav_link'; import ListPanel from './list_panel'; import TrendsContainer from 'mastodon/features/getting_started/containers/trends_container'; +import Logo from 'mastodon/components/logo'; +import SignInBanner from './sign_in_banner'; -const NavigationPanel = () => ( - <div className='navigation-panel'> - <NavLink className='column-link column-link--transparent' to='/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon className='column-link__icon' id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink> - <NavLink className='column-link column-link--transparent' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon className='column-link__icon' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink> - <FollowRequestsNavLink /> - <NavLink className='column-link column-link--transparent' to='/explore' data-preview-title-id='explore.title' data-preview-icon='hashtag'><Icon className='column-link__icon' id='hashtag' fixedWidth /><FormattedMessage id='explore.title' defaultMessage='Explore' /></NavLink> - <NavLink className='column-link column-link--transparent' to='/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink> - <NavLink className='column-link column-link--transparent' exact to='/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink> - <NavLink className='column-link column-link--transparent' to='/conversations'><Icon className='column-link__icon' id='at' fixedWidth /><FormattedMessage id='navigation_bar.direct' defaultMessage='Direct messages' /></NavLink> - <NavLink className='column-link column-link--transparent' to='/favourites'><Icon className='column-link__icon' id='star' fixedWidth /><FormattedMessage id='navigation_bar.favourites' defaultMessage='Favourites' /></NavLink> - <NavLink className='column-link column-link--transparent' to='/bookmarks'><Icon className='column-link__icon' id='bookmark' fixedWidth /><FormattedMessage id='navigation_bar.bookmarks' defaultMessage='Bookmarks' /></NavLink> - <NavLink className='column-link column-link--transparent' to='/lists'><Icon className='column-link__icon' id='list-ul' fixedWidth /><FormattedMessage id='navigation_bar.lists' defaultMessage='Lists' /></NavLink> +export default class NavigationPanel extends React.Component { - <ListPanel /> + static contextTypes = { + router: PropTypes.object.isRequired, + identity: PropTypes.object.isRequired, + }; - <hr /> + render () { + const { signedIn } = this.context.identity; - <a className='column-link column-link--transparent' href='/settings/preferences'><Icon className='column-link__icon' id='cog' fixedWidth /><FormattedMessage id='navigation_bar.preferences' defaultMessage='Preferences' /></a> - <a className='column-link column-link--transparent' href='/relationships'><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='navigation_bar.follows_and_followers' defaultMessage='Follows and followers' /></a> + return ( + <div className='navigation-panel'> + <Logo /> - {showTrends && <div className='flex-spacer' />} - {showTrends && <TrendsContainer />} - </div> -); + <hr /> -export default withRouter(NavigationPanel); + {signedIn && ( + <React.Fragment> + <NavLink className='column-link column-link--transparent' to='/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon className='column-link__icon' id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink> + <NavLink className='column-link column-link--transparent' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon className='column-link__icon' /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink> + <FollowRequestsNavLink /> + </React.Fragment> + )} + + <NavLink className='column-link column-link--transparent' to='/explore' data-preview-title-id='explore.title' data-preview-icon='hashtag'><Icon className='column-link__icon' id='hashtag' fixedWidth /><FormattedMessage id='explore.title' defaultMessage='Explore' /></NavLink> + <NavLink className='column-link column-link--transparent' to='/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink> + <NavLink className='column-link column-link--transparent' exact to='/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon className='column-link__icon' id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink> + + {!signedIn && ( + <React.Fragment> + <hr /> + <SignInBanner /> + </React.Fragment> + )} + + {signedIn && ( + <React.Fragment> + <NavLink className='column-link column-link--transparent' to='/conversations'><Icon className='column-link__icon' id='at' fixedWidth /><FormattedMessage id='navigation_bar.direct' defaultMessage='Direct messages' /></NavLink> + <NavLink className='column-link column-link--transparent' to='/favourites'><Icon className='column-link__icon' id='star' fixedWidth /><FormattedMessage id='navigation_bar.favourites' defaultMessage='Favourites' /></NavLink> + <NavLink className='column-link column-link--transparent' to='/bookmarks'><Icon className='column-link__icon' id='bookmark' fixedWidth /><FormattedMessage id='navigation_bar.bookmarks' defaultMessage='Bookmarks' /></NavLink> + <NavLink className='column-link column-link--transparent' to='/lists'><Icon className='column-link__icon' id='list-ul' fixedWidth /><FormattedMessage id='navigation_bar.lists' defaultMessage='Lists' /></NavLink> + + <ListPanel /> + + <hr /> + + <a className='column-link column-link--transparent' href='/settings/preferences'><Icon className='column-link__icon' id='cog' fixedWidth /><FormattedMessage id='navigation_bar.preferences' defaultMessage='Preferences' /></a> + <a className='column-link column-link--transparent' href='/relationships'><Icon className='column-link__icon' id='users' fixedWidth /><FormattedMessage id='navigation_bar.follows_and_followers' defaultMessage='Follows and followers' /></a> + </React.Fragment> + )} + + {showTrends && ( + <React.Fragment> + <div className='flex-spacer' /> + <TrendsContainer /> + </React.Fragment> + )} + </div> + ); + } + +} diff --git a/app/javascript/mastodon/features/ui/components/sign_in_banner.js b/app/javascript/mastodon/features/ui/components/sign_in_banner.js new file mode 100644 index 0000000000..2ab07628af --- /dev/null +++ b/app/javascript/mastodon/features/ui/components/sign_in_banner.js @@ -0,0 +1,11 @@ +import React from 'react'; +import { FormattedMessage } from 'react-intl'; + +const SignInBanner = () => ( + <div className='sign-in-banner'> + <p><FormattedMessage id='sign_in_banner.text' defaultMessage='Sign in to follow profiles or hashtags, favourite, share and reply to posts.' /></p> + <a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a> + </div> +); + +export default SignInBanner; diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js index 9a901f12a6..ecb91592e5 100644 --- a/app/javascript/mastodon/features/ui/index.js +++ b/app/javascript/mastodon/features/ui/index.js @@ -208,6 +208,7 @@ class UI extends React.PureComponent { static contextTypes = { router: PropTypes.object.isRequired, + identity: PropTypes.object.isRequired, }; static propTypes = { @@ -343,6 +344,8 @@ class UI extends React.PureComponent { } componentDidMount () { + const { signedIn } = this.context.identity; + window.addEventListener('focus', this.handleWindowFocus, false); window.addEventListener('blur', this.handleWindowBlur, false); window.addEventListener('beforeunload', this.handleBeforeUnload, false); @@ -359,16 +362,18 @@ class UI extends React.PureComponent { } // On first launch, redirect to the follow recommendations page - if (this.props.firstLaunch) { + if (signedIn && this.props.firstLaunch) { this.context.router.history.replace('/start'); this.props.dispatch(closeOnboarding()); } - this.props.dispatch(fetchMarkers()); - this.props.dispatch(expandHomeTimeline()); - this.props.dispatch(expandNotifications()); + if (signedIn) { + this.props.dispatch(fetchMarkers()); + this.props.dispatch(expandHomeTimeline()); + this.props.dispatch(expandNotifications()); - setTimeout(() => this.props.dispatch(fetchRules()), 3000); + setTimeout(() => this.props.dispatch(fetchRules()), 3000); + } this.hotkeys.__mousetrap__.stopCallback = (e, element) => { return ['TEXTAREA', 'SELECT', 'INPUT'].includes(element.tagName); diff --git a/app/javascript/mastodon/initial_state.js b/app/javascript/mastodon/initial_state.js index 7099752702..9cc75b6cbf 100644 --- a/app/javascript/mastodon/initial_state.js +++ b/app/javascript/mastodon/initial_state.js @@ -3,6 +3,7 @@ const initialState = element && JSON.parse(element.textContent); const getMeta = (prop) => initialState && initialState.meta && initialState.meta[prop]; +export const domain = getMeta('domain'); export const reduceMotion = getMeta('reduce_motion'); export const autoPlayGif = getMeta('auto_play_gif'); export const displayMedia = getMeta('display_media'); @@ -26,5 +27,6 @@ export const title = getMeta('title'); export const cropImages = getMeta('crop_images'); export const disableSwiping = getMeta('disable_swiping'); export const languages = initialState && initialState.languages; +export const server = initialState && initialState.server; export default initialState; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 3e202841b0..a9fdedbfb1 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -700,6 +700,15 @@ transition: height 0.4s ease, opacity 0.4s ease; } +.sign-in-banner { + padding: 10px; + + p { + color: $darker-text-color; + margin-bottom: 20px; + } +} + .emojione { font-size: inherit; vertical-align: middle; @@ -2660,6 +2669,26 @@ a.account__display-name { height: calc(100% - 10px); overflow-y: hidden; + .hero-widget { + box-shadow: none; + + &__text, + &__img, + &__img img { + border-radius: 0; + } + + &__text { + padding: 15px; + color: $secondary-text-color; + + strong { + font-weight: 700; + color: $primary-text-color; + } + } + } + .navigation-bar { padding-top: 20px; padding-bottom: 20px; @@ -2667,10 +2696,6 @@ a.account__display-name { min-height: 20px; } - .flex-spacer { - background: transparent; - } - .compose-form { flex: 1; overflow-y: hidden; @@ -2709,6 +2734,17 @@ a.account__display-name { flex: 0 0 auto; } + .logo { + height: 30px; + margin: 10px auto; + width: auto; + flex: 0 0 auto; + margin-left: 15px; + } +} + +.navigation-panel, +.compose-panel { hr { flex: 0 0 auto; border: 0; diff --git a/app/serializers/initial_state_serializer.rb b/app/serializers/initial_state_serializer.rb index 5eda877579..df076ffc62 100644 --- a/app/serializers/initial_state_serializer.rb +++ b/app/serializers/initial_state_serializer.rb @@ -1,9 +1,11 @@ # frozen_string_literal: true class InitialStateSerializer < ActiveModel::Serializer + include RoutingHelper + attributes :meta, :compose, :accounts, :media_attachments, :settings, - :languages + :languages, :server has_one :push_subscription, serializer: REST::WebPushSubscriptionSerializer has_one :role, serializer: REST::RoleSerializer @@ -82,6 +84,13 @@ class InitialStateSerializer < ActiveModel::Serializer LanguagesHelper::SUPPORTED_LOCALES.map { |(key, value)| [key, value[0], value[1]] } end + def server + { + hero: instance_presenter.hero&.file&.url || instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.png'), + description: instance_presenter.site_short_description.presence || I18n.t('about.about_mastodon_html'), + } + end + private def instance_presenter -- GitLab