Admin Reports: restyle with Tailwind
This commit is contained in:
parent
c483a60ef1
commit
5ef9a93371
5 changed files with 68 additions and 198 deletions
|
@ -51,7 +51,7 @@ const families = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Sizes = keyof typeof sizes
|
export type Sizes = keyof typeof sizes
|
||||||
type Tags = 'abbr' | 'p' | 'span' | 'pre' | 'time' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label'
|
type Tags = 'abbr' | 'p' | 'span' | 'pre' | 'time' | 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'label' | 'blockquote'
|
||||||
type Directions = 'ltr' | 'rtl'
|
type Directions = 'ltr' | 'rtl'
|
||||||
|
|
||||||
interface IText extends Pick<React.HTMLAttributes<HTMLParagraphElement>, 'dangerouslySetInnerHTML' | 'tabIndex' | 'lang'> {
|
interface IText extends Pick<React.HTMLAttributes<HTMLParagraphElement>, 'dangerouslySetInnerHTML' | 'tabIndex' | 'lang'> {
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
import noop from 'lodash/noop';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useIntl, defineMessages } from 'react-intl';
|
import { useIntl, defineMessages } from 'react-intl';
|
||||||
|
|
||||||
import { openModal } from 'soapbox/actions/modals';
|
|
||||||
import { deleteStatusModal } from 'soapbox/actions/moderation';
|
import { deleteStatusModal } from 'soapbox/actions/moderation';
|
||||||
import StatusContent from 'soapbox/components/status-content';
|
import StatusContent from 'soapbox/components/status-content';
|
||||||
|
import StatusMedia from 'soapbox/components/status-media';
|
||||||
|
import { HStack, Stack } from 'soapbox/components/ui';
|
||||||
import DropdownMenu from 'soapbox/containers/dropdown-menu-container';
|
import DropdownMenu from 'soapbox/containers/dropdown-menu-container';
|
||||||
import Bundle from 'soapbox/features/ui/components/bundle';
|
|
||||||
import { MediaGallery, Video, Audio } from 'soapbox/features/ui/util/async-components';
|
|
||||||
import { useAppDispatch } from 'soapbox/hooks';
|
import { useAppDispatch } from 'soapbox/hooks';
|
||||||
|
|
||||||
import type { AdminReport, Attachment, Status } from 'soapbox/types/entities';
|
import type { AdminReport, Status } from 'soapbox/types/entities';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
viewStatus: { id: 'admin.reports.actions.view_status', defaultMessage: 'View post' },
|
viewStatus: { id: 'admin.reports.actions.view_status', defaultMessage: 'View post' },
|
||||||
|
@ -26,10 +24,6 @@ const ReportStatus: React.FC<IReportStatus> = ({ status }) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const handleOpenMedia = (media: Attachment, index: number) => {
|
|
||||||
dispatch(openModal('MEDIA', { media, status, index }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleDeleteStatus = () => {
|
const handleDeleteStatus = () => {
|
||||||
dispatch(deleteStatusModal(intl, status.id));
|
dispatch(deleteStatusModal(intl, status.id));
|
||||||
};
|
};
|
||||||
|
@ -49,84 +43,20 @@ const ReportStatus: React.FC<IReportStatus> = ({ status }) => {
|
||||||
}];
|
}];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMedia = () => {
|
|
||||||
const firstAttachment = status.media_attachments.get(0);
|
|
||||||
|
|
||||||
if (firstAttachment) {
|
|
||||||
if (status.media_attachments.some(item => item.type === 'unknown')) {
|
|
||||||
// Do nothing
|
|
||||||
} else if (firstAttachment.type === 'video') {
|
|
||||||
const video = firstAttachment;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Bundle fetchComponent={Video} >
|
|
||||||
{(Component: any) => (
|
|
||||||
<Component
|
|
||||||
preview={video.preview_url}
|
|
||||||
blurhash={video.blurhash}
|
|
||||||
src={video.url}
|
|
||||||
alt={video.description}
|
|
||||||
aspectRatio={video.meta.getIn(['original', 'aspect'])}
|
|
||||||
width={239}
|
|
||||||
height={110}
|
|
||||||
inline
|
|
||||||
sensitive={status.sensitive}
|
|
||||||
onOpenVideo={noop}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Bundle>
|
|
||||||
);
|
|
||||||
} else if (firstAttachment.type === 'audio') {
|
|
||||||
const audio = firstAttachment;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Bundle fetchComponent={Audio}>
|
|
||||||
{(Component: any) => (
|
|
||||||
<Component
|
|
||||||
src={audio.url}
|
|
||||||
alt={audio.description}
|
|
||||||
inline
|
|
||||||
sensitive={status.sensitive}
|
|
||||||
onOpenAudio={noop}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Bundle>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Bundle fetchComponent={MediaGallery}>
|
|
||||||
{(Component: any) => (
|
|
||||||
<Component
|
|
||||||
media={status.media_attachments}
|
|
||||||
sensitive={status.sensitive}
|
|
||||||
height={110}
|
|
||||||
onOpenMedia={handleOpenMedia}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Bundle>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const media = getMedia();
|
|
||||||
const menu = makeMenu();
|
const menu = makeMenu();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='admin-report__status'>
|
<HStack space={2} alignItems='start'>
|
||||||
<div className='admin-report__status-content'>
|
<Stack space={2} grow>
|
||||||
<StatusContent status={status} />
|
<StatusContent status={status} />
|
||||||
{media}
|
<StatusMedia status={status} />
|
||||||
</div>
|
</Stack>
|
||||||
<div className='admin-report__status-actions'>
|
|
||||||
<DropdownMenu
|
<DropdownMenu
|
||||||
items={menu}
|
items={menu}
|
||||||
src={require('@tabler/icons/dots-vertical.svg')}
|
src={require('@tabler/icons/dots-vertical.svg')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</HStack>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { deactivateUserModal, deleteUserModal } from 'soapbox/actions/moderation
|
||||||
import snackbar from 'soapbox/actions/snackbar';
|
import snackbar from 'soapbox/actions/snackbar';
|
||||||
import Avatar from 'soapbox/components/avatar';
|
import Avatar from 'soapbox/components/avatar';
|
||||||
import HoverRefWrapper from 'soapbox/components/hover-ref-wrapper';
|
import HoverRefWrapper from 'soapbox/components/hover-ref-wrapper';
|
||||||
import { Accordion, Button, HStack } from 'soapbox/components/ui';
|
import { Accordion, Button, Stack, HStack, Text } from 'soapbox/components/ui';
|
||||||
import DropdownMenu from 'soapbox/containers/dropdown-menu-container';
|
import DropdownMenu from 'soapbox/containers/dropdown-menu-container';
|
||||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||||
import { makeGetReport } from 'soapbox/selectors';
|
import { makeGetReport } from 'soapbox/selectors';
|
||||||
|
@ -82,49 +82,68 @@ const Report: React.FC<IReport> = ({ id }) => {
|
||||||
const reporterAcct = account.acct as string;
|
const reporterAcct = account.acct as string;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='admin-report' key={report.id}>
|
<HStack space={3} className='p-3' key={report.id}>
|
||||||
<div className='admin-report__avatar'>
|
<HoverRefWrapper accountId={targetAccount.id} inline>
|
||||||
<HoverRefWrapper accountId={targetAccount.id as string} inline>
|
|
||||||
<Link to={`/@${acct}`} title={acct}>
|
<Link to={`/@${acct}`} title={acct}>
|
||||||
<Avatar account={targetAccount} size={32} />
|
<Avatar account={targetAccount} size={32} />
|
||||||
</Link>
|
</Link>
|
||||||
</HoverRefWrapper>
|
</HoverRefWrapper>
|
||||||
</div>
|
|
||||||
<div className='admin-report__content'>
|
<Stack space={3} grow>
|
||||||
<h4 className='admin-report__title'>
|
<Text tag='h4' weight='bold' truncate>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='admin.reports.report_title'
|
id='admin.reports.report_title'
|
||||||
defaultMessage='Report on {acct}'
|
defaultMessage='Report on {acct}'
|
||||||
values={{ acct: (
|
values={{ acct: (
|
||||||
<HoverRefWrapper accountId={account.id as string} inline>
|
<HoverRefWrapper accountId={account.id} inline>
|
||||||
<Link to={`/@${acct}`} title={acct}>@{acct}</Link>
|
<Link to={`/@${acct}`} title={acct}>@{acct}</Link>
|
||||||
</HoverRefWrapper>
|
</HoverRefWrapper>
|
||||||
) }}
|
) }}
|
||||||
/>
|
/>
|
||||||
</h4>
|
</Text>
|
||||||
<div className='admin-report__statuses'>
|
|
||||||
{statusCount > 0 && (
|
{statusCount > 0 && (
|
||||||
<Accordion
|
<Accordion
|
||||||
headline={`Reported posts (${statusCount})`}
|
headline={`Reported posts (${statusCount})`}
|
||||||
expanded={accordionExpanded}
|
expanded={accordionExpanded}
|
||||||
onToggle={handleAccordionToggle}
|
onToggle={handleAccordionToggle}
|
||||||
>
|
>
|
||||||
{statuses.map(status => <ReportStatus report={report} status={status} key={status.id} />)}
|
<Stack space={4}>
|
||||||
|
{statuses.map(status => (
|
||||||
|
<ReportStatus
|
||||||
|
key={status.id}
|
||||||
|
report={report}
|
||||||
|
status={status}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
<div className='admin-report__quote'>
|
<Stack>
|
||||||
{(report.comment || '').length > 0 && (
|
{(report.comment || '').length > 0 && (
|
||||||
<blockquote className='md' dangerouslySetInnerHTML={{ __html: report.comment }} />
|
<Text
|
||||||
|
tag='blockquote'
|
||||||
|
dangerouslySetInnerHTML={{ __html: report.comment }}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<span className='byline'>
|
|
||||||
—
|
<HStack space={1}>
|
||||||
<HoverRefWrapper accountId={account.id as string} inline>
|
<Text theme='muted' tag='span'>—</Text>
|
||||||
<Link to={`/@${reporterAcct}`} title={reporterAcct}>@{reporterAcct}</Link>
|
|
||||||
|
<HoverRefWrapper accountId={account.id} inline>
|
||||||
|
<Link
|
||||||
|
to={`/@${reporterAcct}`}
|
||||||
|
title={reporterAcct}
|
||||||
|
className='text-primary-600 dark:text-accent-blue hover:underline'
|
||||||
|
>
|
||||||
|
@{reporterAcct}
|
||||||
|
</Link>
|
||||||
</HoverRefWrapper>
|
</HoverRefWrapper>
|
||||||
</span>
|
</HStack>
|
||||||
</div>
|
</Stack>
|
||||||
</div>
|
</Stack>
|
||||||
|
|
||||||
<HStack space={2} alignItems='start'>
|
<HStack space={2} alignItems='start'>
|
||||||
<Button onClick={handleCloseReport}>
|
<Button onClick={handleCloseReport}>
|
||||||
<FormattedMessage id='admin.reports.actions.close' defaultMessage='Close' />
|
<FormattedMessage id='admin.reports.actions.close' defaultMessage='Close' />
|
||||||
|
@ -132,7 +151,7 @@ const Report: React.FC<IReport> = ({ id }) => {
|
||||||
|
|
||||||
<DropdownMenu items={menu} src={require('@tabler/icons/dots-vertical.svg')} />
|
<DropdownMenu items={menu} src={require('@tabler/icons/dots-vertical.svg')} />
|
||||||
</HStack>
|
</HStack>
|
||||||
</div>
|
</HStack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ const Reports: React.FC = () => {
|
||||||
showLoading={showLoading}
|
showLoading={showLoading}
|
||||||
scrollKey='admin-reports'
|
scrollKey='admin-reports'
|
||||||
emptyMessage={intl.formatMessage(messages.emptyMessage)}
|
emptyMessage={intl.formatMessage(messages.emptyMessage)}
|
||||||
|
className='divide-y divide-solid divide-gray-200 dark:divide-gray-800'
|
||||||
>
|
>
|
||||||
{reports.map(report => report && <Report id={report} key={report} />)}
|
{reports.map(report => report && <Report id={report} key={report} />)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
|
|
|
@ -56,86 +56,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-report {
|
|
||||||
padding: 15px;
|
|
||||||
display: flex;
|
|
||||||
border-bottom: 1px solid var(--brand-color--faint);
|
|
||||||
|
|
||||||
&__content {
|
|
||||||
padding: 0 16px;
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__title {
|
|
||||||
font-weight: bold;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__quote {
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: var(--brand-color--hicontrast);
|
|
||||||
}
|
|
||||||
|
|
||||||
.byline {
|
|
||||||
font-size: 12px;
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
height: fit-content;
|
|
||||||
margin-left: auto;
|
|
||||||
|
|
||||||
.icon-button {
|
|
||||||
padding-left: 10px;
|
|
||||||
|
|
||||||
> div {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
> .svg-icon {
|
|
||||||
height: 20px;
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__status-content {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__status {
|
|
||||||
display: flex;
|
|
||||||
border-bottom: 1px solid var(--accent-color--med);
|
|
||||||
padding: 10px 0;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status__content {
|
|
||||||
flex: 1;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-actions {
|
|
||||||
padding: 3px 10px;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.logentry {
|
.logentry {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue