diff --git a/src/actions/importer/index.ts b/src/actions/importer/index.ts index 5afb880c0..ec2e65471 100644 --- a/src/actions/importer/index.ts +++ b/src/actions/importer/index.ts @@ -3,8 +3,6 @@ import { Entities } from 'soapbox/entity-store/entities'; import { Group, accountSchema, groupSchema } from 'soapbox/schemas'; import { filteredArray } from 'soapbox/schemas/utils'; -import { getSettings } from '../settings'; - import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; @@ -45,17 +43,9 @@ const importGroup = (group: Group) => const importGroups = (groups: Group[]) => importEntities(groups, Entities.GROUPS); -const importStatus = (status: APIEntity, idempotencyKey?: string) => - (dispatch: AppDispatch, getState: () => RootState) => { - const expandSpoilers = getSettings(getState()).get('expandSpoilers'); - return dispatch({ type: STATUS_IMPORT, status, idempotencyKey, expandSpoilers }); - }; +const importStatus = (status: APIEntity, idempotencyKey?: string) => ({ type: STATUS_IMPORT, status, idempotencyKey }); -const importStatuses = (statuses: APIEntity[]) => - (dispatch: AppDispatch, getState: () => RootState) => { - const expandSpoilers = getSettings(getState()).get('expandSpoilers'); - return dispatch({ type: STATUSES_IMPORT, statuses, expandSpoilers }); - }; +const importStatuses = (statuses: APIEntity[]) => ({ type: STATUSES_IMPORT, statuses }); const importPolls = (polls: APIEntity[]) => ({ type: POLLS_IMPORT, polls }); diff --git a/src/actions/settings.ts b/src/actions/settings.ts index ae26af50a..102cbca19 100644 --- a/src/actions/settings.ts +++ b/src/actions/settings.ts @@ -31,7 +31,6 @@ const defaultSettings = ImmutableMap({ underlineLinks: false, autoPlayGif: true, displayMedia: 'default', - expandSpoilers: false, unfollowModal: false, boostModal: false, deleteModal: true, diff --git a/src/actions/status-quotes.test.ts b/src/actions/status-quotes.test.ts index 85edb8994..62b369280 100644 --- a/src/actions/status-quotes.test.ts +++ b/src/actions/status-quotes.test.ts @@ -46,7 +46,7 @@ describe('fetchStatusQuotes()', () => { { type: 'STATUS_QUOTES_FETCH_REQUEST', statusId }, { type: 'POLLS_IMPORT', polls: [] }, { type: 'ACCOUNTS_IMPORT', accounts: [status.account] }, - { type: 'STATUSES_IMPORT', statuses: [status], expandSpoilers: false }, + { type: 'STATUSES_IMPORT', statuses: [status] }, { type: 'STATUS_QUOTES_FETCH_SUCCESS', statusId, statuses: [status], next: null }, ]; await store.dispatch(fetchStatusQuotes(statusId)); @@ -118,7 +118,7 @@ describe('expandStatusQuotes()', () => { { type: 'STATUS_QUOTES_EXPAND_REQUEST', statusId }, { type: 'POLLS_IMPORT', polls: [] }, { type: 'ACCOUNTS_IMPORT', accounts: [status.account] }, - { type: 'STATUSES_IMPORT', statuses: [status], expandSpoilers: false }, + { type: 'STATUSES_IMPORT', statuses: [status] }, { type: 'STATUS_QUOTES_EXPAND_SUCCESS', statusId, statuses: [status], next: null }, ]; await store.dispatch(expandStatusQuotes(statusId)); diff --git a/src/components/media-gallery.tsx b/src/components/media-gallery.tsx index 5eef12bcc..507c7ce3f 100644 --- a/src/components/media-gallery.tsx +++ b/src/components/media-gallery.tsx @@ -297,7 +297,6 @@ export interface IMediaGallery { defaultWidth?: number; cacheWidth?: (width: number) => void; visible?: boolean; - onToggleVisibility?: () => void; displayMedia?: string; compact?: boolean; className?: string; diff --git a/src/components/quoted-status.tsx b/src/components/quoted-status.tsx index b90e753e6..eb8acf4c8 100644 --- a/src/components/quoted-status.tsx +++ b/src/components/quoted-status.tsx @@ -6,8 +6,6 @@ import { useHistory } from 'react-router-dom'; import StatusMedia from 'soapbox/components/status-media'; import { Stack } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; -import { useSettings } from 'soapbox/hooks'; -import { defaultMediaVisibility } from 'soapbox/utils/status'; import EventPreview from './event-preview'; import OutlineBox from './outline-box'; @@ -36,11 +34,8 @@ const QuotedStatus: React.FC = ({ status, onCancel, compose }) => const intl = useIntl(); const history = useHistory(); - const { displayMedia } = useSettings(); - const overlay = useRef(null); - const [showMedia, setShowMedia] = useState(defaultMediaVisibility(status, displayMedia)); const [minHeight, setMinHeight] = useState(208); useEffect(() => { @@ -71,10 +66,6 @@ const QuotedStatus: React.FC = ({ status, onCancel, compose }) => } }; - const handleToggleMediaVisibility = () => { - setShowMedia(!showMedia); - }; - if (!status) { return null; } @@ -116,16 +107,12 @@ const QuotedStatus: React.FC = ({ status, onCancel, compose }) => {status.event ? : ( - {(status.hidden) && ( - - )} + = ({ status, onCancel, compose }) => {status.quote && } {status.media_attachments.size > 0 && ( - + )} diff --git a/src/components/status-media.tsx b/src/components/status-media.tsx index 77be4b031..9117db9f2 100644 --- a/src/components/status-media.tsx +++ b/src/components/status-media.tsx @@ -5,7 +5,8 @@ import AttachmentThumbs from 'soapbox/components/attachment-thumbs'; import PreviewCard from 'soapbox/components/preview-card'; import PlaceholderCard from 'soapbox/features/placeholder/components/placeholder-card'; import { MediaGallery, Video, Audio } from 'soapbox/features/ui/util/async-components'; -import { useAppDispatch } from 'soapbox/hooks'; +import { useAppDispatch, useSettings } from 'soapbox/hooks'; +import { defaultMediaVisibility } from 'soapbox/utils/status'; import type { List as ImmutableList } from 'immutable'; import type { Status, Attachment } from 'soapbox/types/entities'; @@ -19,8 +20,6 @@ interface IStatusMedia { onClick?: () => void; /** Whether or not the media is concealed behind a NSFW banner. */ showMedia?: boolean; - /** Callback when visibility is toggled (eg clicked through NSFW). */ - onToggleVisibility?: () => void; } /** Render media attachments for a status. */ @@ -28,10 +27,12 @@ const StatusMedia: React.FC = ({ status, muted = false, onClick, - showMedia = true, - onToggleVisibility = () => { }, + showMedia, }) => { const dispatch = useAppDispatch(); + const { displayMedia } = useSettings(); + + const visible = showMedia || (status.hidden === null ? defaultMediaVisibility(status, displayMedia) : status.hidden); const size = status.media_attachments.size; const firstAttachment = status.media_attachments.first(); @@ -75,7 +76,7 @@ const StatusMedia: React.FC = ({ alt={video.description} aspectRatio={Number(video.meta.getIn(['original', 'aspect']))} height={285} - visible={showMedia} + visible={visible} inline /> @@ -105,8 +106,7 @@ const StatusMedia: React.FC = ({ sensitive={status.sensitive} height={285} onOpenMedia={openMedia} - visible={showMedia} - onToggleVisibility={onToggleVisibility} + visible={visible} /> ); diff --git a/src/components/status.test.tsx b/src/components/status.test.tsx index b1c2cdeab..d9643b504 100644 --- a/src/components/status.test.tsx +++ b/src/components/status.test.tsx @@ -34,11 +34,5 @@ describe('', () => { render(, undefined, state); expect(screen.getByTestId('status-action-bar')).toBeInTheDocument(); }); - - it('is not rendered if status is under review', () => { - const inReviewStatus = status.set('visibility', 'self'); - render(, undefined, state); - expect(screen.queryAllByTestId('status-action-bar')).toHaveLength(0); - }); }); }); diff --git a/src/components/status.tsx b/src/components/status.tsx index e7de643ac..a8f6f3b4c 100644 --- a/src/components/status.tsx +++ b/src/components/status.tsx @@ -12,7 +12,7 @@ import AccountContainer from 'soapbox/containers/account-container'; import QuotedStatus from 'soapbox/features/status/containers/quoted-status-container'; import { HotKeys } from 'soapbox/features/ui/components/hotkeys'; import { useAppDispatch, useSettings } from 'soapbox/hooks'; -import { defaultMediaVisibility, textForScreenReader, getActualStatus } from 'soapbox/utils/status'; +import { textForScreenReader, getActualStatus } from 'soapbox/utils/status'; import EventPreview from './event-preview'; import StatusActionBar from './status-action-bar'; @@ -77,12 +77,11 @@ const Status: React.FC = (props) => { const history = useHistory(); const dispatch = useAppDispatch(); - const { displayMedia, boostModal } = useSettings(); + const { boostModal } = useSettings(); const didShowCard = useRef(false); const node = useRef(null); const overlay = useRef(null); - const [showMedia, setShowMedia] = useState(defaultMediaVisibility(status, displayMedia)); const [minHeight, setMinHeight] = useState(208); const actualStatus = getActualStatus(status); @@ -97,20 +96,12 @@ const Status: React.FC = (props) => { didShowCard.current = Boolean(!muted && !hidden && status?.card); }, []); - useEffect(() => { - setShowMedia(defaultMediaVisibility(status, displayMedia)); - }, [status.id]); - useEffect(() => { if (overlay.current) { setMinHeight(overlay.current.getBoundingClientRect().height); } }, [overlay.current]); - const handleToggleMediaVisibility = (): void => { - setShowMedia(!showMedia); - }; - const handleClick = (e?: React.MouseEvent): void => { e?.stopPropagation(); @@ -188,12 +179,8 @@ const Status: React.FC = (props) => { } }; - const handleHotkeyToggleHidden = (): void => { - dispatch(toggleStatusHidden(actualStatus)); - }; - const handleHotkeyToggleSensitive = (): void => { - handleToggleMediaVisibility(); + dispatch(toggleStatusHidden(actualStatus)); }; const handleHotkeyReact = (): void => { @@ -377,14 +364,11 @@ const Status: React.FC = (props) => { openProfile: handleHotkeyOpenProfile, moveUp: handleHotkeyMoveUp, moveDown: handleHotkeyMoveDown, - toggleHidden: handleHotkeyToggleHidden, toggleSensitive: handleHotkeyToggleSensitive, openMedia: handleHotkeyOpenMedia, react: handleHotkeyReact, }; - const isUnderReview = actualStatus.visibility === 'self'; - const isSensitive = actualStatus.hidden; const isSoftDeleted = status.tombstone?.reason === 'deleted'; if (isSoftDeleted) { @@ -439,16 +423,12 @@ const Status: React.FC = (props) => { - {(isUnderReview || isSensitive) && ( - - )} + {actualStatus.event ? : ( @@ -467,8 +447,6 @@ const Status: React.FC = (props) => { status={actualStatus} muted={muted} onClick={handleClick} - showMedia={showMedia} - onToggleVisibility={handleToggleMediaVisibility} /> {quote} @@ -478,7 +456,7 @@ const Status: React.FC = (props) => { )} - {(!hideActionBar && !isUnderReview) && ( + {!hideActionBar && (
diff --git a/src/components/statuses/sensitive-content-overlay.test.tsx b/src/components/statuses/sensitive-content-overlay.test.tsx index 4f93ef58c..7628db97d 100644 --- a/src/components/statuses/sensitive-content-overlay.test.tsx +++ b/src/components/statuses/sensitive-content-overlay.test.tsx @@ -38,57 +38,6 @@ describe('', () => { }); }); - describe('when the Status is marked as in review', () => { - beforeEach(() => { - status = normalizeStatus({ visibility: 'self', sensitive: false }) as ReducerStatus; - }); - - it('displays the "Under review" warning', () => { - render(); - expect(screen.getByTestId('sensitive-overlay')).toHaveTextContent('Content Under Review'); - }); - - it('allows the user to delete the status', () => { - render(); - expect(screen.getByTestId('icon-button')).toBeInTheDocument(); - }); - - it('can be toggled', () => { - render(); - - fireEvent.click(screen.getByTestId('button')); - expect(screen.getByTestId('sensitive-overlay')).not.toHaveTextContent('Content Under Review'); - expect(screen.getByTestId('sensitive-overlay')).toHaveTextContent('Hide'); - - fireEvent.click(screen.getByTestId('button')); - expect(screen.getByTestId('sensitive-overlay')).toHaveTextContent('Content Under Review'); - expect(screen.getByTestId('sensitive-overlay')).not.toHaveTextContent('Hide'); - }); - }); - - describe('when the Status is marked as in review and sensitive', () => { - beforeEach(() => { - status = normalizeStatus({ visibility: 'self', sensitive: true }) as ReducerStatus; - }); - - it('displays the "Under review" warning', () => { - render(); - expect(screen.getByTestId('sensitive-overlay')).toHaveTextContent('Content Under Review'); - }); - - it('can be toggled', () => { - render(); - - fireEvent.click(screen.getByTestId('button')); - expect(screen.getByTestId('sensitive-overlay')).not.toHaveTextContent('Content Under Review'); - expect(screen.getByTestId('sensitive-overlay')).toHaveTextContent('Hide'); - - fireEvent.click(screen.getByTestId('button')); - expect(screen.getByTestId('sensitive-overlay')).toHaveTextContent('Content Under Review'); - expect(screen.getByTestId('sensitive-overlay')).not.toHaveTextContent('Hide'); - }); - }); - describe('when the Status is marked as sensitive and displayMedia set to "show_all"', () => { let store: any; @@ -100,12 +49,6 @@ describe('', () => { })); }); - it('displays the "Under review" warning', () => { - render(, undefined, store); - expect(screen.getByTestId('sensitive-overlay')).not.toHaveTextContent('Sensitive content'); - expect(screen.getByTestId('sensitive-overlay')).toHaveTextContent('Hide'); - }); - it('can be toggled', () => { render(, undefined, store); diff --git a/src/components/statuses/sensitive-content-overlay.tsx b/src/components/statuses/sensitive-content-overlay.tsx index 6812b9531..d18dd4d8f 100644 --- a/src/components/statuses/sensitive-content-overlay.tsx +++ b/src/components/statuses/sensitive-content-overlay.tsx @@ -1,13 +1,10 @@ import clsx from 'clsx'; -import React, { useEffect, useMemo, useState } from 'react'; +import React from 'react'; import { defineMessages, useIntl } from 'react-intl'; -import { openModal } from 'soapbox/actions/modals'; -import { deleteStatus } from 'soapbox/actions/statuses'; -import { useAppDispatch, useOwnAccount, useSettings, useSoapboxConfig } from 'soapbox/hooks'; -import { defaultMediaVisibility } from 'soapbox/utils/status'; +import { toggleStatusHidden } from 'soapbox/actions/statuses'; +import { useAppDispatch, useSettings } from 'soapbox/hooks'; -import DropdownMenu from '../dropdown-menu'; import { Button, HStack, Text } from '../ui'; import type { Status as StatusEntity } from 'soapbox/types/entities'; @@ -19,73 +16,36 @@ const messages = defineMessages({ deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this post?' }, hide: { id: 'moderation_overlay.hide', defaultMessage: 'Hide content' }, sensitiveTitle: { id: 'status.sensitive_warning', defaultMessage: 'Sensitive content' }, - underReviewTitle: { id: 'moderation_overlay.title', defaultMessage: 'Content Under Review' }, - underReviewSubtitle: { id: 'moderation_overlay.subtitle', defaultMessage: 'This Post has been sent to Moderation for review and is only visible to you. If you believe this is an error please contact Support.' }, sensitiveSubtitle: { id: 'status.sensitive_warning.subtitle', defaultMessage: 'This content may not be suitable for all audiences.' }, - contact: { id: 'moderation_overlay.contact', defaultMessage: 'Contact' }, show: { id: 'moderation_overlay.show', defaultMessage: 'Show Content' }, }); interface ISensitiveContentOverlay { status: StatusEntity; - onToggleVisibility?(): void; - visible?: boolean; } const SensitiveContentOverlay = React.forwardRef((props, ref) => { - const { onToggleVisibility, status } = props; + const { status } = props; - const { account } = useOwnAccount(); const dispatch = useAppDispatch(); const intl = useIntl(); - const { displayMedia, deleteModal } = useSettings(); - const { links } = useSoapboxConfig(); + const { displayMedia } = useSettings(); - const isUnderReview = status.visibility === 'self'; - const isOwnStatus = status.getIn(['account', 'id']) === account?.id; + let visible = !status.sensitive; - const [visible, setVisible] = useState(defaultMediaVisibility(status, displayMedia)); + if (status.hidden !== null) visible = status.hidden; + else if (displayMedia === 'show_all') visible = true; + else if (displayMedia === 'hide_all' && status.media_attachments.size) visible = false; + + const showHideButton = status.sensitive || (status.media_attachments.size && displayMedia === 'hide_all'); const toggleVisibility = (event: React.MouseEvent) => { event.stopPropagation(); - if (onToggleVisibility) { - onToggleVisibility(); - } else { - setVisible((prevValue) => !prevValue); - } + dispatch(toggleStatusHidden(status)); }; - const handleDeleteStatus = () => { - if (!deleteModal) { - dispatch(deleteStatus(status.id, false)); - } else { - dispatch(openModal('CONFIRM', { - icon: require('@tabler/icons/outline/trash.svg'), - heading: intl.formatMessage(messages.deleteHeading), - message: intl.formatMessage(messages.deleteMessage), - confirm: intl.formatMessage(messages.deleteConfirm), - onConfirm: () => dispatch(deleteStatus(status.id, false)), - })); - } - }; - - const menu = useMemo(() => { - return [ - { - text: intl.formatMessage(messages.delete), - action: handleDeleteStatus, - icon: require('@tabler/icons/outline/trash.svg'), - destructive: true, - }, - ]; - }, []); - - useEffect(() => { - if (typeof props.visible !== 'undefined') { - setVisible(!!props.visible); - } - }, [props.visible]); + if (visible && !showHideButton) return null; return (
- {intl.formatMessage(isUnderReview ? messages.underReviewTitle : messages.sensitiveTitle)} + {intl.formatMessage(messages.sensitiveTitle)} - {intl.formatMessage(isUnderReview ? messages.underReviewSubtitle : messages.sensitiveSubtitle)} + {intl.formatMessage(messages.sensitiveSubtitle)} {status.spoiler_text && ( @@ -126,27 +86,6 @@ const SensitiveContentOverlay = React.forwardRef - {isUnderReview ? ( - <> - {links.get('support') && ( - event.stopPropagation()} - > - - - )} - - ) : null} - - - {(isUnderReview && isOwnStatus) ? ( - - ) : null}
diff --git a/src/features/admin/components/report-status.tsx b/src/features/admin/components/report-status.tsx index d5ca1719e..858a25e7a 100644 --- a/src/features/admin/components/report-status.tsx +++ b/src/features/admin/components/report-status.tsx @@ -49,7 +49,7 @@ const ReportStatus: React.FC = ({ status }) => { - +
diff --git a/src/features/event/event-information.tsx b/src/features/event/event-information.tsx index d84d6096b..d739220d3 100644 --- a/src/features/event/event-information.tsx +++ b/src/features/event/event-information.tsx @@ -9,9 +9,8 @@ import StatusMedia from 'soapbox/components/status-media'; import TranslateButton from 'soapbox/components/translate-button'; import { HStack, Icon, Stack, Text } from 'soapbox/components/ui'; import QuotedStatus from 'soapbox/features/status/containers/quoted-status-container'; -import { useAppDispatch, useAppSelector, useSettings, useSoapboxConfig } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector, useSoapboxConfig } from 'soapbox/hooks'; import { makeGetStatus } from 'soapbox/selectors'; -import { defaultMediaVisibility } from 'soapbox/utils/status'; import type { Status as StatusEntity } from 'soapbox/types/entities'; @@ -28,10 +27,8 @@ const EventInformation: React.FC = ({ params }) => { const status = useAppSelector(state => getStatus(state, { id: params.statusId })) as StatusEntity; const { tileServer } = useSoapboxConfig(); - const { displayMedia } = useSettings(); const [isLoaded, setIsLoaded] = useState(!!status); - const [showMedia, setShowMedia] = useState(defaultMediaVisibility(status, displayMedia)); useEffect(() => { dispatch(fetchStatus(params.statusId)).then(() => { @@ -39,14 +36,8 @@ const EventInformation: React.FC = ({ params }) => { }).catch(() => { setIsLoaded(true); }); - - setShowMedia(defaultMediaVisibility(status, displayMedia)); }, [params.statusId]); - const handleToggleMediaVisibility = () => { - setShowMedia(!showMedia); - }; - const handleShowMap: React.MouseEventHandler = (e) => { e.preventDefault(); @@ -195,11 +186,7 @@ const EventInformation: React.FC = ({ params }) => { )} - + {status.quote && status.pleroma.get('quote_visible', true) && ( diff --git a/src/features/notifications/components/notification.tsx b/src/features/notifications/components/notification.tsx index c1a44133e..b429fc899 100644 --- a/src/features/notifications/components/notification.tsx +++ b/src/features/notifications/components/notification.tsx @@ -6,7 +6,7 @@ import { mentionCompose } from 'soapbox/actions/compose'; import { reblog, favourite, unreblog, unfavourite } from 'soapbox/actions/interactions'; import { openModal } from 'soapbox/actions/modals'; import { getSettings } from 'soapbox/actions/settings'; -import { hideStatus, revealStatus } from 'soapbox/actions/statuses'; +import { toggleStatusHidden } from 'soapbox/actions/statuses'; import Icon from 'soapbox/components/icon'; import { HStack, Text, Emoji } from 'soapbox/components/ui'; import AccountContainer from 'soapbox/containers/account-container'; @@ -182,7 +182,7 @@ const Notification: React.FC = (props) => { openProfile: handleOpenProfile, moveUp: handleMoveUp, moveDown: handleMoveDown, - toggleHidden: handleHotkeyToggleHidden, + toggleSensitive: handleHotkeyToggleSensitive, }); const handleOpen = () => { @@ -236,13 +236,9 @@ const Notification: React.FC = (props) => { } }, [status]); - const handleHotkeyToggleHidden = useCallback((e?: KeyboardEvent) => { + const handleHotkeyToggleSensitive = useCallback((e?: KeyboardEvent) => { if (status && typeof status === 'object') { - if (status.hidden) { - dispatch(revealStatus(status.id)); - } else { - dispatch(hideStatus(status.id)); - } + dispatch(toggleStatusHidden(status)); } }, [status]); diff --git a/src/features/preferences/index.tsx b/src/features/preferences/index.tsx index e72c1523a..3e2cddcc7 100644 --- a/src/features/preferences/index.tsx +++ b/src/features/preferences/index.tsx @@ -78,7 +78,7 @@ const languages = { const messages = defineMessages({ heading: { id: 'column.preferences', defaultMessage: 'Preferences' }, displayPostsDefault: { id: 'preferences.fields.display_media.default', defaultMessage: 'Hide posts marked as sensitive' }, - displayPostsHideAll: { id: 'preferences.fields.display_media.hide_all', defaultMessage: 'Always hide posts' }, + displayPostsHideAll: { id: 'preferences.fields.display_media.hide_all', defaultMessage: 'Always hide media posts' }, displayPostsShowAll: { id: 'preferences.fields.display_media.show_all', defaultMessage: 'Always show posts' }, privacy_public: { id: 'preferences.options.privacy_public', defaultMessage: 'Public' }, privacy_unlisted: { id: 'preferences.options.privacy_unlisted', defaultMessage: 'Unlisted' }, @@ -201,10 +201,6 @@ const Preferences = () => { - {features.spoilers && }> - - } - }> diff --git a/src/features/status/components/detailed-status.tsx b/src/features/status/components/detailed-status.tsx index 2c1bbee9a..6efda27ac 100644 --- a/src/features/status/components/detailed-status.tsx +++ b/src/features/status/components/detailed-status.tsx @@ -19,17 +19,13 @@ import type { Group, Status as StatusEntity } from 'soapbox/types/entities'; interface IDetailedStatus { status: StatusEntity; - showMedia?: boolean; withMedia?: boolean; onOpenCompareHistoryModal: (status: StatusEntity) => void; - onToggleMediaVisibility: () => void; } const DetailedStatus: React.FC = ({ status, onOpenCompareHistoryModal, - onToggleMediaVisibility, - showMedia, withMedia = true, }) => { const intl = useIntl(); @@ -89,9 +85,6 @@ const DetailedStatus: React.FC = ({ const { account } = actualStatus; if (!account || typeof account !== 'object') return null; - const isUnderReview = actualStatus.visibility === 'self'; - const isSensitive = actualStatus.hidden; - let statusTypeIcon = null; let quote; @@ -133,16 +126,12 @@ const DetailedStatus: React.FC = ({ - {(isUnderReview || isSensitive) && ( - - )} + = ({ {(withMedia && (quote || actualStatus.card || actualStatus.media_attachments.size > 0)) && ( - + {quote} diff --git a/src/features/status/components/thread.tsx b/src/features/status/components/thread.tsx index b2b2393fc..6155eb1bc 100644 --- a/src/features/status/components/thread.tsx +++ b/src/features/status/components/thread.tsx @@ -1,7 +1,7 @@ import { createSelector } from '@reduxjs/toolkit'; import clsx from 'clsx'; import { List as ImmutableList, OrderedSet as ImmutableOrderedSet } from 'immutable'; -import React, { useEffect, useRef, useState } from 'react'; +import React, { useEffect, useRef } from 'react'; import { useIntl } from 'react-intl'; import { useHistory } from 'react-router-dom'; import { type VirtuosoHandle } from 'react-virtuoso'; @@ -10,7 +10,7 @@ import { mentionCompose, replyCompose } from 'soapbox/actions/compose'; import { favourite, reblog, unfavourite, unreblog } from 'soapbox/actions/interactions'; import { openModal } from 'soapbox/actions/modals'; import { getSettings } from 'soapbox/actions/settings'; -import { hideStatus, revealStatus } from 'soapbox/actions/statuses'; +import { toggleStatusHidden } from 'soapbox/actions/statuses'; import ScrollableList from 'soapbox/components/scrollable-list'; import StatusActionBar from 'soapbox/components/status-action-bar'; import Tombstone from 'soapbox/components/tombstone'; @@ -18,10 +18,10 @@ import { Stack } from 'soapbox/components/ui'; import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder-status'; import { HotKeys } from 'soapbox/features/ui/components/hotkeys'; import PendingStatus from 'soapbox/features/ui/components/pending-status'; -import { useAppDispatch, useAppSelector, useSettings } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; import { RootState } from 'soapbox/store'; import { type Account, type Status } from 'soapbox/types/entities'; -import { defaultMediaVisibility, textForScreenReader } from 'soapbox/utils/status'; +import { textForScreenReader } from 'soapbox/utils/status'; import DetailedStatus from './detailed-status'; import ThreadStatus from './thread-status'; @@ -90,9 +90,6 @@ const Thread = (props: IThread) => { const dispatch = useAppDispatch(); const history = useHistory(); const intl = useIntl(); - const { displayMedia } = useSettings(); - - const isUnderReview = status?.visibility === 'self'; const { ancestorsIds, descendantsIds } = useAppSelector((state) => { let ancestorsIds = ImmutableOrderedSet(); @@ -116,16 +113,10 @@ const Thread = (props: IThread) => { let initialTopMostItemIndex = ancestorsIds.size; if (!useWindowScroll && initialTopMostItemIndex !== 0) initialTopMostItemIndex = ancestorsIds.size + 1; - const [showMedia, setShowMedia] = useState(status?.visibility === 'self' ? false : defaultMediaVisibility(status, displayMedia)); - const node = useRef(null); const statusRef = useRef(null); const scroller = useRef(null); - const handleToggleMediaVisibility = () => { - setShowMedia(!showMedia); - }; - const handleHotkeyReact = () => { if (statusRef.current) { const firstEmoji: HTMLButtonElement | null = statusRef.current.querySelector('.emoji-react-selector .emoji-react-selector__emoji'); @@ -178,14 +169,6 @@ const Thread = (props: IThread) => { } }; - const handleToggleHidden = (status: Status) => { - if (status.hidden) { - dispatch(revealStatus(status.id)); - } else { - dispatch(hideStatus(status.id)); - } - }; - const handleHotkeyMoveUp = () => { handleMoveUp(status!.id); }; @@ -218,12 +201,8 @@ const Thread = (props: IThread) => { history.push(`/@${status!.getIn(['account', 'acct'])}`); }; - const handleHotkeyToggleHidden = () => { - handleToggleHidden(status!); - }; - const handleHotkeyToggleSensitive = () => { - handleToggleMediaVisibility(); + dispatch(toggleStatusHidden(status)); }; const handleMoveUp = (id: string) => { @@ -317,11 +296,6 @@ const Thread = (props: IThread) => { }); }; - // Reset media visibility if status changes. - useEffect(() => { - setShowMedia(status?.visibility === 'self' ? false : defaultMediaVisibility(status, displayMedia)); - }, [status.id]); - // Scroll focused status into view when thread updates. useEffect(() => { scroller.current?.scrollToIndex({ @@ -351,7 +325,6 @@ const Thread = (props: IThread) => { boost: handleHotkeyBoost, mention: handleHotkeyMention, openProfile: handleHotkeyOpenProfile, - toggleHidden: handleHotkeyToggleHidden, toggleSensitive: handleHotkeyToggleSensitive, openMedia: handleHotkeyOpenMedia, react: handleHotkeyReact, @@ -370,24 +343,18 @@ const Thread = (props: IThread) => { - {!isUnderReview ? ( - <> -
+
- - - ) : null} +
diff --git a/src/features/ui/util/global-hotkeys.tsx b/src/features/ui/util/global-hotkeys.tsx index e413ff2db..74932e20d 100644 --- a/src/features/ui/util/global-hotkeys.tsx +++ b/src/features/ui/util/global-hotkeys.tsx @@ -33,8 +33,7 @@ const keyMap = { goToBlocked: 'g b', goToMuted: 'g m', goToRequests: 'g r', - toggleHidden: 'x', - toggleSensitive: 'h', + toggleSensitive: ['h', 'x'], openMedia: 'a', }; diff --git a/src/locales/en.json b/src/locales/en.json index dc532bdeb..a144fd6c4 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -1017,11 +1017,8 @@ "missing_description_modal.text": "You have not entered a description for all attachments. Continue anyway?", "missing_indicator.label": "Not found", "missing_indicator.sublabel": "This resource could not be found", - "moderation_overlay.contact": "Contact", "moderation_overlay.hide": "Hide content", "moderation_overlay.show": "Show Content", - "moderation_overlay.subtitle": "This Post has been sent to Moderation for review and is only visible to you. If you believe this is an error please contact Support.", - "moderation_overlay.title": "Content Under Review", "mute_modal.auto_expire": "Automatically expire mute?", "mute_modal.duration": "Duration", "mute_modal.hide_notifications": "Hide notifications from this user?", @@ -1176,9 +1173,8 @@ "preferences.fields.demo_hint": "Use the default Soapbox logo and color scheme. Useful for taking screenshots.", "preferences.fields.demo_label": "Demo mode", "preferences.fields.display_media.default": "Hide posts marked as sensitive", - "preferences.fields.display_media.hide_all": "Always hide posts", + "preferences.fields.display_media.hide_all": "Always hide media posts", "preferences.fields.display_media.show_all": "Always show posts", - "preferences.fields.expand_spoilers_label": "Always expand posts marked with content warnings", "preferences.fields.language_label": "Display Language", "preferences.fields.media_display_label": "Sensitive content", "preferences.fields.missing_description_modal_label": "Show confirmation dialog before sending a post without media descriptions", diff --git a/src/normalizers/status.ts b/src/normalizers/status.ts index 4e5941a3d..ac32d10ff 100644 --- a/src/normalizers/status.ts +++ b/src/normalizers/status.ts @@ -20,7 +20,7 @@ import { maybeFromJS } from 'soapbox/utils/normalizers'; import type { Account, Attachment, Card, Emoji, Group, Mention, Poll, EmbeddedEntity, EmojiReaction } from 'soapbox/types/entities'; export type StatusApprovalStatus = 'pending' | 'approval' | 'rejected'; -export type StatusVisibility = 'public' | 'unlisted' | 'private' | 'direct' | 'self' | 'group'; +export type StatusVisibility = 'public' | 'unlisted' | 'private' | 'direct' | 'group'; export type EventJoinMode = 'free' | 'restricted' | 'invite'; export type EventJoinState = 'pending' | 'reject' | 'accept'; @@ -88,7 +88,7 @@ export const StatusRecord = ImmutableRecord({ // Internal fields contentHtml: '', expectsCard: false, - hidden: false, + hidden: null as boolean | null, search_index: '', showFiltered: true, spoilerHtml: '', diff --git a/src/reducers/statuses.test.ts b/src/reducers/statuses.test.ts index 824e53e56..2d53961cb 100644 --- a/src/reducers/statuses.test.ts +++ b/src/reducers/statuses.test.ts @@ -106,14 +106,6 @@ describe('statuses reducer', () => { expect(hidden).toBe(true); }); - it('expands CWs when expandSpoilers is enabled', async () => { - const status = await import('soapbox/__fixtures__/status-cw.json'); - const action = { type: STATUS_IMPORT, status, expandSpoilers: true }; - - const hidden = reducer(undefined, action).getIn(['107831528995252317', 'hidden']); - expect(hidden).toBe(false); - }); - it('parses custom emojis', async () => { const status = await import('soapbox/__fixtures__/status-custom-emoji.json'); const action = { type: STATUS_IMPORT, status }; diff --git a/src/reducers/statuses.ts b/src/reducers/statuses.ts index b857ba540..a47f09110 100644 --- a/src/reducers/statuses.ts +++ b/src/reducers/statuses.ts @@ -104,7 +104,6 @@ const buildSearchContent = (status: StatusRecord): string => { export const calculateStatus = ( status: StatusRecord, oldStatus?: StatusRecord, - expandSpoilers: boolean = false, ): StatusRecord => { if (oldStatus && oldStatus.content === status.content && oldStatus.spoiler_text === status.spoiler_text) { return status.merge({ @@ -122,7 +121,6 @@ export const calculateStatus = ( search_index: domParser.parseFromString(searchContent, 'text/html').documentElement.textContent || '', contentHtml: DOMPurify.sanitize(stripCompatibilityFeatures(emojify(status.content, emojiMap)), { USE_PROFILES: { html: true } }), spoilerHtml: DOMPurify.sanitize(emojify(escapeTextContentForBrowser(spoilerText), emojiMap), { USE_PROFILES: { html: true } }), - hidden: expandSpoilers ? false : spoilerText.length > 0 || status.sensitive, }); } }; @@ -153,22 +151,22 @@ const fixQuote = (status: StatusRecord, oldStatus?: StatusRecord): StatusRecord } }; -const fixStatus = (state: State, status: APIEntity, expandSpoilers: boolean): ReducerStatus => { +const fixStatus = (state: State, status: APIEntity): ReducerStatus => { const oldStatus = state.get(status.id); return normalizeStatus(status).withMutations(status => { fixTranslation(status, oldStatus); fixQuote(status, oldStatus); - calculateStatus(status, oldStatus, expandSpoilers); + calculateStatus(status, oldStatus); minifyStatus(status); }) as ReducerStatus; }; -const importStatus = (state: State, status: APIEntity, expandSpoilers: boolean): State => - state.set(status.id, fixStatus(state, status, expandSpoilers)); +const importStatus = (state: State, status: APIEntity): State => + state.set(status.id, fixStatus(state, status)); -const importStatuses = (state: State, statuses: APIEntities, expandSpoilers: boolean): State => - state.withMutations(mutable => statuses.forEach(status => importStatus(mutable, status, expandSpoilers))); +const importStatuses = (state: State, statuses: APIEntities): State => + state.withMutations(mutable => statuses.forEach(status => importStatus(mutable, status))); const deleteStatus = (state: State, id: string, references: Array) => { references.forEach(ref => { @@ -271,9 +269,9 @@ const initialState: State = ImmutableMap(); export default function statuses(state = initialState, action: AnyAction): State { switch (action.type) { case STATUS_IMPORT: - return importStatus(state, action.status, action.expandSpoilers); + return importStatus(state, action.status); case STATUSES_IMPORT: - return importStatuses(state, action.statuses, action.expandSpoilers); + return importStatuses(state, action.statuses); case STATUS_CREATE_REQUEST: return action.editing ? state : incrementReplyCount(state, action.params); case STATUS_CREATE_FAIL: diff --git a/src/schemas/soapbox/settings.ts b/src/schemas/soapbox/settings.ts index e052720ba..2632a1766 100644 --- a/src/schemas/soapbox/settings.ts +++ b/src/schemas/soapbox/settings.ts @@ -15,7 +15,6 @@ const settingsSchema = z.object({ underlineLinks: z.boolean().catch(false), autoPlayGif: z.boolean().catch(true), displayMedia: z.enum(['default', 'hide_all', 'show_all']).catch('default'), - expandSpoilers: z.boolean().catch(false), preserveSpoilers: z.boolean().catch(false), unfollowModal: z.boolean().catch(false), boostModal: z.boolean().catch(false), diff --git a/src/schemas/status.ts b/src/schemas/status.ts index 865b3a02e..a5c21d7b8 100644 --- a/src/schemas/status.ts +++ b/src/schemas/status.ts @@ -111,7 +111,7 @@ const transformStatus = ({ pleroma, ...status }: expectsCard: false, event: pleroma?.event, filtered: [], - hidden: false, + hidden: null as boolean | null, pleroma: pleroma ? (() => { const { event, ...rest } = pleroma; return rest; diff --git a/src/utils/status.ts b/src/utils/status.ts index 8d99752cd..79853920b 100644 --- a/src/utils/status.ts +++ b/src/utils/status.ts @@ -11,12 +11,6 @@ export const defaultMediaVisibility =