Skip to content

Commit

Permalink
[Glitch] Add author links on the explore page in web UI
Browse files Browse the repository at this point in the history
Port ed6d243 to glitch-soc

Signed-off-by: Claire <[email protected]>
  • Loading branch information
Gargron authored and ClearlyClaire committed Jun 17, 2024
1 parent 454dc40 commit fd36977
Show file tree
Hide file tree
Showing 10 changed files with 187 additions and 84 deletions.
8 changes: 6 additions & 2 deletions app/javascript/flavours/glitch/actions/importer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,17 @@ export function importFetchedStatuses(statuses) {
status.filtered.forEach(result => pushUnique(filters, result.filter));
}

if (status.reblog && status.reblog.id) {
if (status.reblog?.id) {
processStatus(status.reblog);
}

if (status.poll && status.poll.id) {
if (status.poll?.id) {
pushUnique(polls, normalizePoll(status.poll, getState().getIn(['polls', status.poll.id])));
}

if (status.card?.author_account) {
pushUnique(accounts, status.card.author_account);
}
}

statuses.forEach(processStatus);
Expand Down
4 changes: 4 additions & 0 deletions app/javascript/flavours/glitch/actions/importer/normalizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export function normalizeStatus(status, normalOldStatus, settings) {
normalStatus.poll = status.poll.id;
}

if (status.card?.author_account) {
normalStatus.card = { ...status.card, author_account: status.card.author_account.id };
}

if (status.filtered) {
normalStatus.filtered = status.filtered.map(normalizeFilterResult);
}
Expand Down
9 changes: 6 additions & 3 deletions app/javascript/flavours/glitch/actions/trends.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import api, { getLinks } from '../api';

import { importFetchedStatuses } from './importer';
import { importFetchedStatuses, importFetchedAccounts } from './importer';

export const TRENDS_TAGS_FETCH_REQUEST = 'TRENDS_TAGS_FETCH_REQUEST';
export const TRENDS_TAGS_FETCH_SUCCESS = 'TRENDS_TAGS_FETCH_SUCCESS';
Expand Down Expand Up @@ -49,8 +49,11 @@ export const fetchTrendingLinks = () => (dispatch) => {
dispatch(fetchTrendingLinksRequest());

api()
.get('/api/v1/trends/links')
.then(({ data }) => dispatch(fetchTrendingLinksSuccess(data)))
.get('/api/v1/trends/links', { params: { limit: 20 } })
.then(({ data }) => {
dispatch(importFetchedAccounts(data.map(link => link.author_account).filter(account => !!account)));
dispatch(fetchTrendingLinksSuccess(data));
})
.catch(err => dispatch(fetchTrendingLinksFail(err)));
};

Expand Down
19 changes: 19 additions & 0 deletions app/javascript/flavours/glitch/components/more_from_author.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import PropTypes from 'prop-types';

import { FormattedMessage } from 'react-intl';

import { AuthorLink } from 'flavours/glitch/features/explore/components/author_link';

export const MoreFromAuthor = ({ accountId }) => (
<div className='more-from-author'>
<svg viewBox='0 0 79 79' className='logo logo--icon' role='img'>
<use xlinkHref='#logo-symbol-icon' />
</svg>

<FormattedMessage id='link_preview.more_from_author' defaultMessage='More from {name}' values={{ name: <AuthorLink accountId={accountId} /> }} />
</div>
);

MoreFromAuthor.propTypes = {
accountId: PropTypes.string.isRequired,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import PropTypes from 'prop-types';

import { Avatar } from 'flavours/glitch/components/avatar';
import { Permalink } from 'flavours/glitch/components/permalink';
import { useAppSelector } from 'flavours/glitch/store';

export const AuthorLink = ({ accountId }) => {
const account = useAppSelector(state => state.getIn(['accounts', accountId]));

return (
<Permalink href={account.get('url')} to={`/@${account.get('acct')}`} className='story__details__shared__author-link'>
<Avatar account={account} size={16} />
<bdi dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} />
</Permalink>
);
};

AuthorLink.propTypes = {
accountId: PropTypes.string.isRequired,
};
118 changes: 73 additions & 45 deletions app/javascript/flavours/glitch/features/explore/components/story.jsx
Original file line number Diff line number Diff line change
@@ -1,61 +1,89 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { useState, useCallback } from 'react';

import { FormattedMessage } from 'react-intl';

import classNames from 'classnames';


import { Blurhash } from 'flavours/glitch/components/blurhash';
import { accountsCountRenderer } from 'flavours/glitch/components/hashtag';
import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
import { ShortNumber } from 'flavours/glitch/components/short_number';
import { Skeleton } from 'flavours/glitch/components/skeleton';

export default class Story extends PureComponent {

static propTypes = {
url: PropTypes.string,
title: PropTypes.string,
lang: PropTypes.string,
publisher: PropTypes.string,
publishedAt: PropTypes.string,
author: PropTypes.string,
sharedTimes: PropTypes.number,
thumbnail: PropTypes.string,
thumbnailDescription: PropTypes.string,
blurhash: PropTypes.string,
expanded: PropTypes.bool,
};

state = {
thumbnailLoaded: false,
};

handleImageLoad = () => this.setState({ thumbnailLoaded: true });

render () {
const { expanded, url, title, lang, publisher, author, publishedAt, sharedTimes, thumbnail, thumbnailDescription, blurhash } = this.props;

const { thumbnailLoaded } = this.state;

return (
<a className={classNames('story', { expanded })} href={url} target='blank' rel='noopener'>
<div className='story__details'>
<div className='story__details__publisher'>{publisher ? <span lang={lang}>{publisher}</span> : <Skeleton width={50} />}{publishedAt && <> · <RelativeTimestamp timestamp={publishedAt} /></>}</div>
<div className='story__details__title' lang={lang}>{title ? title : <Skeleton />}</div>
<div className='story__details__shared'>{author && <><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{author}</strong> }} /> · </>}{typeof sharedTimes === 'number' ? <ShortNumber value={sharedTimes} renderer={accountsCountRenderer} /> : <Skeleton width={100} />}</div>
import { AuthorLink } from './author_link';

const sharesCountRenderer = (displayNumber, pluralReady) => (
<FormattedMessage
id='link_preview.shares'
defaultMessage='{count, plural, one {{counter} post} other {{counter} posts}}'
values={{
count: pluralReady,
counter: <strong>{displayNumber}</strong>,
}}
/>
);

export const Story = ({
url,
title,
lang,
publisher,
publishedAt,
author,
authorAccount,
sharedTimes,
thumbnail,
thumbnailDescription,
blurhash,
expanded
}) => {
const [thumbnailLoaded, setThumbnailLoaded] = useState(false);

const handleImageLoad = useCallback(() => {
setThumbnailLoaded(true);
}, [setThumbnailLoaded]);

return (
<div className={classNames('story', { expanded })}>
<div className='story__details'>
<div className='story__details__publisher'>
{publisher ? <span lang={lang}>{publisher}</span> : <Skeleton width={50} />}{publishedAt && <> · <RelativeTimestamp timestamp={publishedAt} /></>}
</div>

<div className='story__thumbnail'>
{thumbnail ? (
<>
<div className={classNames('story__thumbnail__preview', { 'story__thumbnail__preview--hidden': thumbnailLoaded })}><Blurhash hash={blurhash} /></div>
<img src={thumbnail} onLoad={this.handleImageLoad} alt={thumbnailDescription} title={thumbnailDescription} lang={lang} />
</>
) : <Skeleton />}
<a className='story__details__title' lang={lang} href={url} target='blank' rel='noopener'>
{title ? title : <Skeleton />}
</a>

<div className='story__details__shared'>
{author ? <FormattedMessage id='link_preview.author' className='story__details__shared__author' defaultMessage='By {name}' values={{ name: authorAccount ? <AuthorLink accountId={authorAccount} /> : <strong>{author}</strong> }} /> : <span />}
{typeof sharedTimes === 'number' ? <span className='story__details__shared__pill'><ShortNumber value={sharedTimes} renderer={sharesCountRenderer} /></span> : <Skeleton width='10ch' />}
</div>
</div>

<a className='story__thumbnail' href={url} target='blank' rel='noopener'>
{thumbnail ? (
<>
<div className={classNames('story__thumbnail__preview', { 'story__thumbnail__preview--hidden': thumbnailLoaded })}><Blurhash hash={blurhash} /></div>
<img src={thumbnail} onLoad={handleImageLoad} alt={thumbnailDescription} title={thumbnailDescription} lang={lang} />
</>
) : <Skeleton />}
</a>
);
}
</div>
);
};

}
Story.propTypes = {
url: PropTypes.string,
title: PropTypes.string,
lang: PropTypes.string,
publisher: PropTypes.string,
publishedAt: PropTypes.string,
author: PropTypes.string,
authorAccount: PropTypes.string,
sharedTimes: PropTypes.number,
thumbnail: PropTypes.string,
thumbnailDescription: PropTypes.string,
blurhash: PropTypes.string,
expanded: PropTypes.bool,
};
3 changes: 2 additions & 1 deletion app/javascript/flavours/glitch/features/explore/links.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { DismissableBanner } from 'flavours/glitch/components/dismissable_banner
import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';

import Story from './components/story';
import { Story } from './components/story';

const mapStateToProps = state => ({
links: state.getIn(['trends', 'links', 'items']),
Expand Down Expand Up @@ -75,6 +75,7 @@ class Links extends PureComponent {
publisher={link.get('provider_name')}
publishedAt={link.get('published_at')}
author={link.get('author_name')}
authorAccount={link.getIn(['author_account', 'id'])}
sharedTimes={link.getIn(['history', 0, 'accounts']) * 1 + link.getIn(['history', 1, 'accounts']) * 1}
thumbnail={link.get('image')}
thumbnailDescription={link.get('image_description')}
Expand Down
19 changes: 2 additions & 17 deletions app/javascript/flavours/glitch/features/status/components/card.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import DescriptionIcon from '@/material-icons/400-24px/description-fill.svg?react';
import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react';
import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react';
import { Avatar } from 'flavours/glitch/components/avatar';
import { Blurhash } from 'flavours/glitch/components/blurhash';
import { Icon } from 'flavours/glitch/components/icon';
import { Permalink } from 'flavours/glitch/components/permalink';
import { MoreFromAuthor } from 'flavours/glitch/components/more_from_author';
import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
import { useBlurhash } from 'flavours/glitch/initial_state';
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
Expand Down Expand Up @@ -48,20 +47,6 @@ const addAutoPlay = html => {
return html;
};

const MoreFromAuthor = ({ author }) => (
<div className='more-from-author'>
<svg viewBox='0 0 79 79' className='logo logo--icon' role='img'>
<use xlinkHref='#logo-symbol-icon' />
</svg>

<FormattedMessage id='link_preview.more_from_author' defaultMessage='More from {name}' values={{ name: <Permalink href={author.get('url')} to={`/@${author.get('acct')}`}><Avatar account={author} size={16} /> {author.get('display_name')}</Permalink> }} />
</div>
);

MoreFromAuthor.propTypes = {
author: ImmutablePropTypes.map,
};

export default class Card extends PureComponent {

static propTypes = {
Expand Down Expand Up @@ -248,7 +233,7 @@ export default class Card extends PureComponent {
{description}
</a>

{showAuthor && <MoreFromAuthor author={card.get('author_account')} />}
{showAuthor && <MoreFromAuthor accountId={card.get('author_account')} />}
</>
);
}
Expand Down
69 changes: 53 additions & 16 deletions app/javascript/flavours/glitch/styles/components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -9379,43 +9379,80 @@ noscript {
display: flex;
align-items: center;
color: $primary-text-color;
text-decoration: none;
padding: 15px;
padding: 16px;
border-bottom: 1px solid var(--background-border-color);
gap: 15px;
gap: 16px;

&:last-child {
border-bottom: 0;
}

&:hover,
&:active,
&:focus {
color: $highlight-text-color;

.story__details__publisher,
.story__details__shared {
color: $highlight-text-color;
}
}

&__details {
flex: 1 1 auto;

&__publisher {
color: $darker-text-color;
margin-bottom: 8px;
font-size: 14px;
line-height: 20px;
}

&__title {
display: block;
font-size: 19px;
line-height: 24px;
font-weight: 500;
margin-bottom: 8px;
text-decoration: none;
color: $primary-text-color;

&:hover,
&:active,
&:focus {
color: $highlight-text-color;
}
}

&__shared {
display: flex;
align-items: center;
color: $darker-text-color;
gap: 8px;
justify-content: space-between;
font-size: 14px;
line-height: 20px;

& > span {
display: flex;
align-items: center;
gap: 4px;
}

&__pill {
background: var(--surface-variant-background-color);
border-radius: 4px;
color: inherit;
text-decoration: none;
padding: 4px 12px;
font-size: 12px;
font-weight: 500;
line-height: 16px;
}

&__author-link {
display: inline-flex;
align-items: center;
gap: 4px;
color: $primary-text-color;
font-weight: 500;
text-decoration: none;

&:hover,
&:active,
&:focus {
color: $highlight-text-color;
}
}
}

strong {
Expand Down Expand Up @@ -10486,14 +10523,14 @@ noscript {
color: inherit;
text-decoration: none;
padding: 4px 12px;
background: $ui-base-color;
background: var(--surface-variant-background-color);
border-radius: 4px;
font-weight: 500;

&:hover,
&:focus,
&:active {
background: lighten($ui-base-color, 4%);
background: var(--surface-variant-active-background-color);
}
}

Expand Down
Loading

0 comments on commit fd36977

Please sign in to comment.