Update content & placement of "sensitive content" warning on collection page (#39069)
This commit is contained in:
parent
99b72f60ad
commit
dcb6dbbc86
@ -67,9 +67,14 @@ interface LinkProps
|
|||||||
extends React.ComponentPropsWithoutRef<typeof Link>, ContentProps {}
|
extends React.ComponentPropsWithoutRef<typeof Link>, ContentProps {}
|
||||||
|
|
||||||
export const ListItemLink = polymorphicForwardRef<'h3', LinkProps>(
|
export const ListItemLink = polymorphicForwardRef<'h3', LinkProps>(
|
||||||
({ as, subtitle, children, className, ...otherProps }, ref) => {
|
({ as, subtitle, subtitleId, children, className, ...otherProps }, ref) => {
|
||||||
return (
|
return (
|
||||||
<ListItemContent ref={ref} as={as} subtitle={subtitle}>
|
<ListItemContent
|
||||||
|
ref={ref}
|
||||||
|
as={as}
|
||||||
|
subtitle={subtitle}
|
||||||
|
subtitleId={subtitleId}
|
||||||
|
>
|
||||||
<Link className={classNames(className, 'focusable')} {...otherProps}>
|
<Link className={classNames(className, 'focusable')} {...otherProps}>
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
@ -82,9 +87,14 @@ interface ButtonProps
|
|||||||
extends React.ComponentPropsWithoutRef<'button'>, ContentProps {}
|
extends React.ComponentPropsWithoutRef<'button'>, ContentProps {}
|
||||||
|
|
||||||
export const ListItemButton = polymorphicForwardRef<'h3', ButtonProps>(
|
export const ListItemButton = polymorphicForwardRef<'h3', ButtonProps>(
|
||||||
({ as, subtitle, children, className, ...otherProps }, ref) => {
|
({ as, subtitle, subtitleId, children, className, ...otherProps }, ref) => {
|
||||||
return (
|
return (
|
||||||
<ListItemContent as={as} ref={ref} subtitle={subtitle}>
|
<ListItemContent
|
||||||
|
as={as}
|
||||||
|
ref={ref}
|
||||||
|
subtitle={subtitle}
|
||||||
|
subtitleId={subtitleId}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
type='button'
|
type='button'
|
||||||
className={classNames(className, 'focusable')}
|
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';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
@ -14,7 +14,6 @@ import {
|
|||||||
AccountListItemFollowButton,
|
AccountListItemFollowButton,
|
||||||
} from 'mastodon/components/account_list_item';
|
} from 'mastodon/components/account_list_item';
|
||||||
import { Button } from 'mastodon/components/button';
|
import { Button } from 'mastodon/components/button';
|
||||||
import { Callout } from 'mastodon/components/callout';
|
|
||||||
import {
|
import {
|
||||||
Article,
|
Article,
|
||||||
ItemList,
|
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 & {
|
type CollectionItemWithAccount = CollectionAccountItem & {
|
||||||
account?: Account | null;
|
account?: Account | null;
|
||||||
};
|
};
|
||||||
@ -203,35 +158,30 @@ export const CollectionAccountsList: React.FC<{
|
|||||||
values={{ count: collection.item_count }}
|
values={{ count: collection.item_count }}
|
||||||
/>
|
/>
|
||||||
</h3>
|
</h3>
|
||||||
<SensitiveScreen
|
<ItemList emptyMessage={intl.formatMessage(messages.empty)}>
|
||||||
sensitive={!isOwnCollection && collection.sensitive}
|
<TruncatedListItems
|
||||||
focusTargetRef={listHeadingRef}
|
visibleItems={visibleAccounts}
|
||||||
>
|
truncatedItems={hiddenAccounts}
|
||||||
<ItemList emptyMessage={intl.formatMessage(messages.empty)}>
|
toggleButton={{
|
||||||
<TruncatedListItems
|
icon: VisibilityOffIcon,
|
||||||
visibleItems={visibleAccounts}
|
title: (
|
||||||
truncatedItems={hiddenAccounts}
|
<FormattedMessage
|
||||||
toggleButton={{
|
id='collections.hidden_accounts_link'
|
||||||
icon: VisibilityOffIcon,
|
defaultMessage='{count, plural, one {# hidden account} other {# hidden accounts}}'
|
||||||
title: (
|
values={{ count: hiddenAccounts.length }}
|
||||||
<FormattedMessage
|
/>
|
||||||
id='collections.hidden_accounts_link'
|
),
|
||||||
defaultMessage='{count, plural, one {# hidden account} other {# hidden accounts}}'
|
subtitle: (
|
||||||
values={{ count: hiddenAccounts.length }}
|
<FormattedMessage
|
||||||
/>
|
id='collections.hidden_accounts_description'
|
||||||
),
|
defaultMessage='You’ve blocked or muted {count, plural, one {this user} other {these users}}'
|
||||||
subtitle: (
|
values={{ count: hiddenAccounts.length }}
|
||||||
<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>
|
||||||
}}
|
|
||||||
renderListItem={renderListItem}
|
|
||||||
/>
|
|
||||||
</ItemList>
|
|
||||||
</SensitiveScreen>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
@ -138,9 +138,40 @@ export const PendingNote: React.FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
|
const SensitiveContentNote: React.FC<{ onReveal: () => void }> = ({
|
||||||
collection,
|
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 intl = useIntl();
|
||||||
const { name, description, tag, account_id, items } = collection;
|
const { name, description, tag, account_id, items } = collection;
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@ -181,7 +212,9 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
|
|||||||
<div className={classes.titleWithMenu}>
|
<div className={classes.titleWithMenu}>
|
||||||
<div className={classes.titleWrapper}>
|
<div className={classes.titleWrapper}>
|
||||||
{tag && <Badge label={`#${tag.name}`} icon={null} />}
|
{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} />
|
<AuthorNote id={account_id} />
|
||||||
</div>
|
</div>
|
||||||
<div className={classes.headerButtonWrapper}>
|
<div className={classes.headerButtonWrapper}>
|
||||||
@ -199,7 +232,9 @@ const CollectionHeader: React.FC<{ collection: ApiCollectionJSON }> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{description && <p className={classes.description}>{description}</p>}
|
{withDescription && description && (
|
||||||
|
<p className={classes.description}>{description}</p>
|
||||||
|
)}
|
||||||
{hasPendingAccounts && <PendingNote />}
|
{hasPendingAccounts && <PendingNote />}
|
||||||
{isCurrentUserInCollection && (
|
{isCurrentUserInCollection && (
|
||||||
<RevokeControls
|
<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<{
|
export const CollectionDetailPage: React.FC<{
|
||||||
multiColumn?: boolean;
|
multiColumn?: boolean;
|
||||||
}> = ({ multiColumn }) => {
|
}> = ({ multiColumn }) => {
|
||||||
@ -241,10 +322,7 @@ export const CollectionDetailPage: React.FC<{
|
|||||||
|
|
||||||
<Scrollable>
|
<Scrollable>
|
||||||
{collection ? (
|
{collection ? (
|
||||||
<>
|
<ColumnContent collection={collection} />
|
||||||
<CollectionHeader collection={collection} />
|
|
||||||
<CollectionAccountsList collection={collection} />
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<LoadingIndicator />
|
<LoadingIndicator />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -64,7 +64,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sensitiveScreen {
|
.sensitiveScreen {
|
||||||
margin: 16px;
|
margin-inline: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.columnSubheading {
|
.columnSubheading {
|
||||||
|
|||||||
@ -394,7 +394,7 @@
|
|||||||
"collections.detail.loading": "Loading collection…",
|
"collections.detail.loading": "Loading collection…",
|
||||||
"collections.detail.revoke_inclusion": "Remove me",
|
"collections.detail.revoke_inclusion": "Remove me",
|
||||||
"collections.detail.sensitive_content": "Sensitive content",
|
"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.share": "Share this collection",
|
||||||
"collections.detail.you_are_in_this_collection": "You're featured in this collection",
|
"collections.detail.you_are_in_this_collection": "You're featured in this collection",
|
||||||
"collections.edit_details": "Edit details",
|
"collections.edit_details": "Edit details",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user