Merge branch 'redesign-repost' into 'develop'

Move status info to above Account

See merge request soapbox-pub/soapbox!2120
This commit is contained in:
Chewbacca 2023-01-09 17:42:52 +00:00
commit 63201c4acf
4 changed files with 117 additions and 87 deletions

View file

@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Posts: letterbox images to 19:6 again. - Posts: letterbox images to 19:6 again.
- Status Info: moved context (repost, pinned) to improve UX.
### Fixed ### Fixed
- Layout: use accent color for "floating action button" (mobile compose button). - Layout: use accent color for "floating action button" (mobile compose button).

View file

@ -2,7 +2,7 @@ import classNames from 'clsx';
import React, { useEffect, useRef, useState } from 'react'; import React, { useEffect, useRef, useState } from 'react';
import { HotKeys } from 'react-hotkeys'; import { HotKeys } from 'react-hotkeys';
import { useIntl, FormattedMessage, defineMessages } from 'react-intl'; import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
import { NavLink, useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { mentionCompose, replyCompose } from 'soapbox/actions/compose'; import { mentionCompose, replyCompose } from 'soapbox/actions/compose';
import { toggleFavourite, toggleReblog } from 'soapbox/actions/interactions'; import { toggleFavourite, toggleReblog } from 'soapbox/actions/interactions';
@ -21,7 +21,8 @@ import StatusContent from './status-content';
import StatusMedia from './status-media'; import StatusMedia from './status-media';
import StatusReplyMentions from './status-reply-mentions'; import StatusReplyMentions from './status-reply-mentions';
import SensitiveContentOverlay from './statuses/sensitive-content-overlay'; import SensitiveContentOverlay from './statuses/sensitive-content-overlay';
import { Card, HStack, Stack, Text } from './ui'; import StatusInfo from './statuses/status-info';
import { Card, Stack, Text } from './ui';
import type { import type {
Account as AccountEntity, Account as AccountEntity,
@ -37,6 +38,7 @@ const messages = defineMessages({
export interface IStatus { export interface IStatus {
id?: string, id?: string,
avatarSize?: number,
status: StatusEntity, status: StatusEntity,
onClick?: () => void, onClick?: () => void,
muted?: boolean, muted?: boolean,
@ -56,6 +58,8 @@ export interface IStatus {
const Status: React.FC<IStatus> = (props) => { const Status: React.FC<IStatus> = (props) => {
const { const {
status, status,
accountAction,
avatarSize = 42,
focusable = true, focusable = true,
hoverable = true, hoverable = true,
onClick, onClick,
@ -84,7 +88,7 @@ const Status: React.FC<IStatus> = (props) => {
const [minHeight, setMinHeight] = useState(208); const [minHeight, setMinHeight] = useState(208);
const actualStatus = getActualStatus(status); const actualStatus = getActualStatus(status);
const isReblog = status.reblog && typeof status.reblog === 'object';
const statusUrl = `/@${actualStatus.getIn(['account', 'acct'])}/posts/${actualStatus.id}`; const statusUrl = `/@${actualStatus.getIn(['account', 'acct'])}/posts/${actualStatus.id}`;
// Track height changes we know about to compensate scrolling. // Track height changes we know about to compensate scrolling.
@ -201,8 +205,49 @@ const Status: React.FC<IStatus> = (props) => {
firstEmoji?.focus(); firstEmoji?.focus();
}; };
const renderStatusInfo = () => {
if (isReblog) {
return (
<StatusInfo
avatarSize={avatarSize}
to={`/@${status.getIn(['account', 'acct'])}`}
icon={<Icon src={require('@tabler/icons/repeat.svg')} className='text-green-600' />}
text={
<FormattedMessage
id='status.reblogged_by'
defaultMessage='{name} reposted'
values={{
name: (
<bdi className='truncate pr-1 rtl:pl-1'>
<strong
className='text-gray-800 dark:text-gray-200'
dangerouslySetInnerHTML={{
__html: String(status.getIn(['account', 'display_name_html'])),
}}
/>
</bdi>
),
}}
/>
}
/>
);
} else if (featured) {
return (
<StatusInfo
avatarSize={avatarSize}
icon={<Icon src={require('@tabler/icons/pinned.svg')} className='text-gray-600 dark:text-gray-400' />}
text={
<Text size='xs' theme='muted' weight='medium'>
<FormattedMessage id='status.pinned' defaultMessage='Pinned post' />
</Text>
}
/>
);
}
};
if (!status) return null; if (!status) return null;
let rebloggedByText, reblogElement, reblogElementMobile;
if (hidden) { if (hidden) {
return ( return (
@ -228,55 +273,8 @@ const Status: React.FC<IStatus> = (props) => {
); );
} }
let rebloggedByText;
if (status.reblog && typeof status.reblog === 'object') { if (status.reblog && typeof status.reblog === 'object') {
const displayNameHtml = { __html: String(status.getIn(['account', 'display_name_html'])) };
reblogElement = (
<NavLink
to={`/@${status.getIn(['account', 'acct'])}`}
onClick={(event) => event.stopPropagation()}
className='hidden sm:flex items-center text-gray-700 dark:text-gray-600 text-xs font-medium space-x-1 rtl:space-x-reverse hover:underline'
>
<Icon src={require('@tabler/icons/repeat.svg')} className='text-green-600' />
<HStack alignItems='center'>
<FormattedMessage
id='status.reblogged_by'
defaultMessage='{name} reposted'
values={{
name: <bdi className='max-w-[100px] truncate pr-1 rtl:px-1'>
<strong className='text-gray-800 dark:text-gray-200' dangerouslySetInnerHTML={displayNameHtml} />
</bdi>,
}}
/>
</HStack>
</NavLink>
);
reblogElementMobile = (
<div className='pb-5 -mt-2 sm:hidden truncate'>
<NavLink
to={`/@${status.getIn(['account', 'acct'])}`}
onClick={(event) => event.stopPropagation()}
className='flex items-center text-gray-700 dark:text-gray-600 text-xs font-medium space-x-1 hover:underline'
>
<Icon src={require('@tabler/icons/repeat.svg')} className='text-green-600' />
<span>
<FormattedMessage
id='status.reblogged_by'
defaultMessage='{name} reposted'
values={{
name: <bdi>
<strong className='text-gray-800 dark:text-gray-200' dangerouslySetInnerHTML={displayNameHtml} />
</bdi>,
}}
/>
</span>
</NavLink>
</div>
);
rebloggedByText = intl.formatMessage( rebloggedByText = intl.formatMessage(
messages.reblogged_by, messages.reblogged_by,
{ name: String(status.getIn(['account', 'acct'])) }, { name: String(status.getIn(['account', 'acct'])) },
@ -312,8 +310,6 @@ const Status: React.FC<IStatus> = (props) => {
react: handleHotkeyReact, react: handleHotkeyReact,
}; };
const accountAction = props.accountAction || reblogElement;
const isUnderReview = actualStatus.visibility === 'self'; const isUnderReview = actualStatus.visibility === 'self';
const isSensitive = actualStatus.hidden; const isSensitive = actualStatus.hidden;
@ -328,21 +324,9 @@ const Status: React.FC<IStatus> = (props) => {
onClick={handleClick} onClick={handleClick}
role='link' role='link'
> >
{featured && (
<div className='pt-4 px-4'>
<HStack alignItems='center' space={1}>
<Icon src={require('@tabler/icons/pinned.svg')} className='text-gray-600 dark:text-gray-400' />
<Text size='sm' theme='muted' weight='medium'>
<FormattedMessage id='status.pinned' defaultMessage='Pinned post' />
</Text>
</HStack>
</div>
)}
<Card <Card
variant={variant} variant={variant}
className={classNames('status__wrapper', `status-${actualStatus.visibility}`, { className={classNames('status__wrapper space-y-4', `status-${actualStatus.visibility}`, {
'py-6 sm:p-5': variant === 'rounded', 'py-6 sm:p-5': variant === 'rounded',
'status-reply': !!status.in_reply_to_id, 'status-reply': !!status.in_reply_to_id,
muted, muted,
@ -350,21 +334,20 @@ const Status: React.FC<IStatus> = (props) => {
})} })}
data-id={status.id} data-id={status.id}
> >
{reblogElementMobile} {renderStatusInfo()}
<div className='mb-4'> <AccountContainer
<AccountContainer key={String(actualStatus.getIn(['account', 'id']))}
key={String(actualStatus.getIn(['account', 'id']))} id={String(actualStatus.getIn(['account', 'id']))}
id={String(actualStatus.getIn(['account', 'id']))} timestamp={actualStatus.created_at}
timestamp={actualStatus.created_at} timestampUrl={statusUrl}
timestampUrl={statusUrl} action={accountAction}
action={accountAction} hideActions={!accountAction}
hideActions={!accountAction} showEdit={!!actualStatus.edited_at}
showEdit={!!actualStatus.edited_at} showProfileHoverCard={hoverable}
showProfileHoverCard={hoverable} withLinkToProfile={hoverable}
withLinkToProfile={hoverable} avatarSize={avatarSize}
/> />
</div>
<div className='status__content-wrapper'> <div className='status__content-wrapper'>
<StatusReplyMentions status={actualStatus} hoverable={hoverable} /> <StatusReplyMentions status={actualStatus} hoverable={hoverable} />

View file

@ -0,0 +1,38 @@
import React from 'react';
import { Link } from 'react-router-dom';
interface IStatusInfo {
avatarSize: number
to?: string
icon: React.ReactNode
text: React.ReactNode
}
const StatusInfo = (props: IStatusInfo) => {
const { avatarSize, to, icon, text } = props;
const onClick = (event: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
event.stopPropagation();
};
const Container = to ? Link : 'div';
const containerProps: any = to ? { onClick, to } : {};
return (
<Container
{...containerProps}
className='flex items-center text-gray-700 dark:text-gray-600 text-xs font-medium space-x-3 rtl:space-x-reverse hover:underline'
>
<div
className='flex justify-end'
style={{ width: avatarSize }}
>
{icon}
</div>
{text}
</Container>
);
};
export default StatusInfo;

View file

@ -151,6 +151,8 @@ const buildMessage = (
}); });
}; };
const avatarSize = 48;
interface INotificaton { interface INotificaton {
hidden?: boolean, hidden?: boolean,
notification: NotificationEntity, notification: NotificationEntity,
@ -290,7 +292,7 @@ const Notification: React.FC<INotificaton> = (props) => {
<AccountContainer <AccountContainer
id={account.id} id={account.id}
hidden={hidden} hidden={hidden}
avatarSize={48} avatarSize={avatarSize}
/> />
) : null; ) : null;
case 'follow_request': case 'follow_request':
@ -298,7 +300,7 @@ const Notification: React.FC<INotificaton> = (props) => {
<AccountContainer <AccountContainer
id={account.id} id={account.id}
hidden={hidden} hidden={hidden}
avatarSize={48} avatarSize={avatarSize}
actionType='follow_request' actionType='follow_request'
/> />
) : null; ) : null;
@ -307,7 +309,7 @@ const Notification: React.FC<INotificaton> = (props) => {
<AccountContainer <AccountContainer
id={notification.target.id} id={notification.target.id}
hidden={hidden} hidden={hidden}
avatarSize={48} avatarSize={avatarSize}
/> />
) : null; ) : null;
case 'favourite': case 'favourite':
@ -327,6 +329,7 @@ const Notification: React.FC<INotificaton> = (props) => {
hidden={hidden} hidden={hidden}
onMoveDown={handleMoveDown} onMoveDown={handleMoveDown}
onMoveUp={handleMoveUp} onMoveUp={handleMoveUp}
avatarSize={avatarSize}
/> />
) : null; ) : null;
default: default:
@ -358,13 +361,18 @@ const Notification: React.FC<INotificaton> = (props) => {
> >
<div className='p-4 focusable'> <div className='p-4 focusable'>
<div className='mb-2'> <div className='mb-2'>
<HStack alignItems='center' space={1.5}> <HStack alignItems='center' space={3}>
{renderIcon()} <div
className='flex justify-end'
style={{ flexBasis: avatarSize }}
>
{renderIcon()}
</div>
<div className='truncate'> <div className='truncate'>
<Text <Text
theme='muted' theme='muted'
size='sm' size='xs'
truncate truncate
data-testid='message' data-testid='message'
> >