Update content & placement of "sensitive content" warning on collection page (#39069)
This commit is contained in:
@@ -67,9 +67,14 @@ interface LinkProps
|
||||
extends React.ComponentPropsWithoutRef<typeof Link>, ContentProps {}
|
||||
|
||||
export const ListItemLink = polymorphicForwardRef<'h3', LinkProps>(
|
||||
({ as, subtitle, children, className, ...otherProps }, ref) => {
|
||||
({ as, subtitle, subtitleId, children, className, ...otherProps }, ref) => {
|
||||
return (
|
||||
<ListItemContent ref={ref} as={as} subtitle={subtitle}>
|
||||
<ListItemContent
|
||||
ref={ref}
|
||||
as={as}
|
||||
subtitle={subtitle}
|
||||
subtitleId={subtitleId}
|
||||
>
|
||||
<Link className={classNames(className, 'focusable')} {...otherProps}>
|
||||
{children}
|
||||
</Link>
|
||||
@@ -82,9 +87,14 @@ interface ButtonProps
|
||||
extends React.ComponentPropsWithoutRef<'button'>, ContentProps {}
|
||||
|
||||
export const ListItemButton = polymorphicForwardRef<'h3', ButtonProps>(
|
||||
({ as, subtitle, children, className, ...otherProps }, ref) => {
|
||||
({ as, subtitle, subtitleId, children, className, ...otherProps }, ref) => {
|
||||
return (
|
||||
<ListItemContent as={as} ref={ref} subtitle={subtitle}>
|
||||
<ListItemContent
|
||||
as={as}
|
||||
ref={ref}
|
||||
subtitle={subtitle}
|
||||
subtitleId={subtitleId}
|
||||
>
|
||||
<button
|
||||
type='button'
|
||||
className={classNames(className, 'focusable')}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
import { useCallback, useMemo, useRef } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
AccountListItemFollowButton,
|
||||
} from 'mastodon/components/account_list_item';
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import { Callout } from 'mastodon/components/callout';
|
||||
import {
|
||||
Article,
|
||||
ItemList,
|
||||
@@ -35,50 +34,6 @@ const messages = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
const SensitiveScreen: React.FC<{
|
||||
sensitive: boolean | undefined;
|
||||
focusTargetRef: React.RefObject<HTMLHeadingElement>;
|
||||
children: React.ReactNode;
|
||||
}> = ({ sensitive, focusTargetRef, children }) => {
|
||||
const [isVisible, setIsVisible] = useState(!sensitive);
|
||||
|
||||
const showAnyway = useCallback(() => {
|
||||
setIsVisible(true);
|
||||
setTimeout(() => {
|
||||
focusTargetRef.current?.focus();
|
||||
}, 0);
|
||||
}, [focusTargetRef]);
|
||||
|
||||
if (isVisible) {
|
||||
return children;
|
||||
}
|
||||
|
||||
return (
|
||||
<Callout
|
||||
variant='warning'
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='collections.detail.sensitive_content'
|
||||
defaultMessage='Sensitive content'
|
||||
/>
|
||||
}
|
||||
primaryLabel={
|
||||
<FormattedMessage
|
||||
id='content_warning.show_short'
|
||||
defaultMessage='Show'
|
||||
/>
|
||||
}
|
||||
onPrimary={showAnyway}
|
||||
className={classes.sensitiveScreen}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='collections.detail.sensitive_note'
|
||||
defaultMessage='The description and accounts may not be suitable for all viewers.'
|
||||
/>
|
||||
</Callout>
|
||||
);
|
||||
};
|
||||
|
||||
type CollectionItemWithAccount = CollectionAccountItem & {
|
||||
account?: Account | null;
|
||||
};
|
||||
@@ -203,35 +158,30 @@ export const CollectionAccountsList: React.FC<{
|
||||
values={{ count: collection.item_count }}
|
||||
/>
|
||||
</h3>
|
||||
<SensitiveScreen
|
||||
sensitive={!isOwnCollection && collection.sensitive}
|
||||
focusTargetRef={listHeadingRef}
|
||||
>
|
||||
<ItemList emptyMessage={intl.formatMessage(messages.empty)}>
|
||||
<TruncatedListItems
|
||||
visibleItems={visibleAccounts}
|
||||
truncatedItems={hiddenAccounts}
|
||||
toggleButton={{
|
||||
icon: VisibilityOffIcon,
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id='collections.hidden_accounts_link'
|
||||
defaultMessage='{count, plural, one {# hidden account} other {# hidden accounts}}'
|
||||
values={{ count: hiddenAccounts.length }}
|
||||
/>
|
||||
),
|
||||
subtitle: (
|
||||
<FormattedMessage
|
||||
id='collections.hidden_accounts_description'
|
||||
defaultMessage='You’ve blocked or muted {count, plural, one {this user} other {these users}}'
|
||||
values={{ count: hiddenAccounts.length }}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
renderListItem={renderListItem}
|
||||
/>
|
||||
</ItemList>
|
||||
</SensitiveScreen>
|
||||
<ItemList emptyMessage={intl.formatMessage(messages.empty)}>
|
||||
<TruncatedListItems
|
||||
visibleItems={visibleAccounts}
|
||||
truncatedItems={hiddenAccounts}
|
||||
toggleButton={{
|
||||
icon: VisibilityOffIcon,
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id='collections.hidden_accounts_link'
|
||||
defaultMessage='{count, plural, one {# hidden account} other {# hidden accounts}}'
|
||||
values={{ count: hiddenAccounts.length }}
|
||||
/>
|
||||
),
|
||||
subtitle: (
|
||||
<FormattedMessage
|
||||
id='collections.hidden_accounts_description'
|
||||
defaultMessage='You’ve blocked or muted {count, plural, one {this user} other {these users}}'
|
||||
values={{ count: hiddenAccounts.length }}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
renderListItem={renderListItem}
|
||||
/>
|
||||
</ItemList>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
@@ -138,9 +138,40 @@ export const PendingNote: React.FC = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
|
||||
collection,
|
||||
const SensitiveContentNote: React.FC<{ onReveal: () => void }> = ({
|
||||
onReveal,
|
||||
}) => {
|
||||
return (
|
||||
<Callout
|
||||
variant='warning'
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='collections.detail.sensitive_content'
|
||||
defaultMessage='Sensitive content'
|
||||
/>
|
||||
}
|
||||
primaryLabel={
|
||||
<FormattedMessage
|
||||
id='content_warning.show_short'
|
||||
defaultMessage='Show'
|
||||
/>
|
||||
}
|
||||
onPrimary={onReveal}
|
||||
className={classes.sensitiveScreen}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='collections.detail.sensitive_note'
|
||||
defaultMessage='The description and accounts may not be suitable for all viewers.'
|
||||
/>
|
||||
</Callout>
|
||||
);
|
||||
};
|
||||
|
||||
const CollectionHeader: React.FC<{
|
||||
collection: ApiCollectionJSON;
|
||||
withDescription: boolean;
|
||||
headingRef: React.RefObject<HTMLHeadingElement>;
|
||||
}> = ({ collection, withDescription, headingRef }) => {
|
||||
const intl = useIntl();
|
||||
const { name, description, tag, account_id, items } = collection;
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -181,7 +212,9 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
|
||||
<div className={classes.titleWithMenu}>
|
||||
<div className={classes.titleWrapper}>
|
||||
{tag && <Badge label={`#${tag.name}`} icon={null} />}
|
||||
<h2 className={classes.name}>{name}</h2>
|
||||
<h2 className={classes.name} ref={headingRef} tabIndex={-1}>
|
||||
{name}
|
||||
</h2>
|
||||
<AuthorNote id={account_id} />
|
||||
</div>
|
||||
<div className={classes.headerButtonWrapper}>
|
||||
@@ -199,7 +232,9 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{description && <p className={classes.description}>{description}</p>}
|
||||
{withDescription && description && (
|
||||
<p className={classes.description}>{description}</p>
|
||||
)}
|
||||
{hasPendingAccounts && <PendingNote />}
|
||||
{isCurrentUserInCollection && (
|
||||
<RevokeControls
|
||||
@@ -211,6 +246,52 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
|
||||
);
|
||||
};
|
||||
|
||||
function useRevealSensitiveContent({
|
||||
sensitive,
|
||||
}: {
|
||||
sensitive: boolean | undefined;
|
||||
}) {
|
||||
const postRevealFocusTargetRef = useRef<HTMLHeadingElement>(null);
|
||||
const [isContentVisible, setIsContentVisible] = useState(!sensitive);
|
||||
|
||||
const revealContent = useCallback(() => {
|
||||
setIsContentVisible(true);
|
||||
setTimeout(() => {
|
||||
postRevealFocusTargetRef.current?.focus();
|
||||
}, 0);
|
||||
}, [postRevealFocusTargetRef]);
|
||||
|
||||
return {
|
||||
isContentVisible,
|
||||
revealContent,
|
||||
postRevealFocusTargetRef,
|
||||
};
|
||||
}
|
||||
|
||||
const ColumnContent: React.FC<{
|
||||
collection: ApiCollectionJSON;
|
||||
}> = ({ collection }) => {
|
||||
const { isContentVisible, revealContent, postRevealFocusTargetRef } =
|
||||
useRevealSensitiveContent({
|
||||
sensitive: collection.sensitive && collection.account_id !== me,
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<CollectionHeader
|
||||
collection={collection}
|
||||
headingRef={postRevealFocusTargetRef}
|
||||
withDescription={isContentVisible}
|
||||
/>
|
||||
{isContentVisible ? (
|
||||
<CollectionAccountsList collection={collection} />
|
||||
) : (
|
||||
<SensitiveContentNote onReveal={revealContent} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const CollectionDetailPage: React.FC<{
|
||||
multiColumn?: boolean;
|
||||
}> = ({ multiColumn }) => {
|
||||
@@ -241,10 +322,7 @@ export const CollectionDetailPage: React.FC<{
|
||||
|
||||
<Scrollable>
|
||||
{collection ? (
|
||||
<>
|
||||
<CollectionHeader collection={collection} />
|
||||
<CollectionAccountsList collection={collection} />
|
||||
</>
|
||||
<ColumnContent collection={collection} />
|
||||
) : (
|
||||
<LoadingIndicator />
|
||||
)}
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
}
|
||||
|
||||
.sensitiveScreen {
|
||||
margin: 16px;
|
||||
margin-inline: 16px;
|
||||
}
|
||||
|
||||
.columnSubheading {
|
||||
|
||||
@@ -394,7 +394,7 @@
|
||||
"collections.detail.loading": "Loading collection…",
|
||||
"collections.detail.revoke_inclusion": "Remove me",
|
||||
"collections.detail.sensitive_content": "Sensitive content",
|
||||
"collections.detail.sensitive_note": "This collection contains accounts and content that may be sensitive to some users.",
|
||||
"collections.detail.sensitive_note": "The description and accounts may not be suitable for all viewers.",
|
||||
"collections.detail.share": "Share this collection",
|
||||
"collections.detail.you_are_in_this_collection": "You're featured in this collection",
|
||||
"collections.edit_details": "Edit details",
|
||||
|
||||
Reference in New Issue
Block a user