Merge branch 'redesign-repost' into 'develop'
Move status info to above Account See merge request soapbox-pub/soapbox!2120
This commit is contained in:
commit
63201c4acf
4 changed files with 117 additions and 87 deletions
|
@ -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).
|
||||||
|
|
|
@ -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} />
|
||||||
|
|
38
app/soapbox/components/statuses/status-info.tsx
Normal file
38
app/soapbox/components/statuses/status-info.tsx
Normal 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;
|
|
@ -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'
|
||||||
>
|
>
|
||||||
|
|
Loading…
Reference in a new issue