Handle spoilers as titles, configurable

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-08-24 00:27:40 +02:00
parent 5d17752231
commit d33589d103
13 changed files with 146 additions and 38 deletions

View file

@ -30,6 +30,7 @@ const defaultSettings = ImmutableMap({
underlineLinks: false, underlineLinks: false,
autoPlayGif: true, autoPlayGif: true,
displayMedia: 'default', displayMedia: 'default',
displaySpoilers: false,
unfollowModal: true, unfollowModal: true,
boostModal: false, boostModal: false,
deleteModal: true, deleteModal: true,

View file

@ -43,8 +43,11 @@ const STATUS_UNMUTE_REQUEST = 'STATUS_UNMUTE_REQUEST' as const;
const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS' as const; const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS' as const;
const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL' as const; const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL' as const;
const STATUS_REVEAL = 'STATUS_REVEAL' as const; const STATUS_REVEAL_MEDIA = 'STATUS_REVEAL_MEDIA' as const;
const STATUS_HIDE = 'STATUS_HIDE' as const; const STATUS_HIDE_MEDIA = 'STATUS_HIDE_MEDIA' as const;
const STATUS_EXPAND_SPOILER = 'STATUS_EXPAND_SPOILER' as const;
const STATUS_COLLAPSE_SPOILER = 'STATUS_COLLAPSE_SPOILER' as const;
const STATUS_TRANSLATE_REQUEST = 'STATUS_TRANSLATE_REQUEST' as const; const STATUS_TRANSLATE_REQUEST = 'STATUS_TRANSLATE_REQUEST' as const;
const STATUS_TRANSLATE_SUCCESS = 'STATUS_TRANSLATE_SUCCESS' as const; const STATUS_TRANSLATE_SUCCESS = 'STATUS_TRANSLATE_SUCCESS' as const;
@ -219,33 +222,63 @@ const toggleMuteStatus = (status: Pick<Status, 'id' | 'muted'>) =>
} }
}; };
const hideStatus = (statusIds: string[] | string) => { const hideStatusMedia = (statusIds: string[] | string) => {
if (!Array.isArray(statusIds)) { if (!Array.isArray(statusIds)) {
statusIds = [statusIds]; statusIds = [statusIds];
} }
return { return {
type: STATUS_HIDE, type: STATUS_HIDE_MEDIA,
statusIds, statusIds,
}; };
}; };
const revealStatus = (statusIds: string[] | string) => { const revealStatusMedia = (statusIds: string[] | string) => {
if (!Array.isArray(statusIds)) { if (!Array.isArray(statusIds)) {
statusIds = [statusIds]; statusIds = [statusIds];
} }
return { return {
type: STATUS_REVEAL, type: STATUS_REVEAL_MEDIA,
statusIds, statusIds,
}; };
}; };
const toggleStatusHidden = (status: Pick<Status, 'id' | 'hidden'>) => { const toggleStatusMediaHidden = (status: Pick<Status, 'id' | 'hidden'>) => {
if (status.hidden) { if (status.hidden) {
return revealStatus(status.id); return revealStatusMedia(status.id);
} else { } else {
return hideStatus(status.id); return hideStatusMedia(status.id);
}
};
const collapseStatusSpoiler = (statusIds: string[] | string) => {
if (!Array.isArray(statusIds)) {
statusIds = [statusIds];
}
return {
type: STATUS_COLLAPSE_SPOILER,
statusIds,
};
};
const expandStatusSpoiler = (statusIds: string[] | string) => {
if (!Array.isArray(statusIds)) {
statusIds = [statusIds];
}
return {
type: STATUS_EXPAND_SPOILER,
statusIds,
};
};
const toggleStatusSpoilerExpanded = (status: Pick<Status, 'id' | 'expanded'>) => {
if (status.expanded) {
return collapseStatusSpoiler(status.id);
} else {
return expandStatusSpoiler(status.id);
} }
}; };
@ -359,8 +392,10 @@ export {
STATUS_UNMUTE_REQUEST, STATUS_UNMUTE_REQUEST,
STATUS_UNMUTE_SUCCESS, STATUS_UNMUTE_SUCCESS,
STATUS_UNMUTE_FAIL, STATUS_UNMUTE_FAIL,
STATUS_REVEAL, STATUS_REVEAL_MEDIA,
STATUS_HIDE, STATUS_HIDE_MEDIA,
STATUS_EXPAND_SPOILER,
STATUS_COLLAPSE_SPOILER,
STATUS_TRANSLATE_REQUEST, STATUS_TRANSLATE_REQUEST,
STATUS_TRANSLATE_SUCCESS, STATUS_TRANSLATE_SUCCESS,
STATUS_TRANSLATE_FAIL, STATUS_TRANSLATE_FAIL,
@ -377,9 +412,12 @@ export {
muteStatus, muteStatus,
unmuteStatus, unmuteStatus,
toggleMuteStatus, toggleMuteStatus,
hideStatus, hideStatusMedia,
revealStatus, revealStatusMedia,
toggleStatusHidden, toggleStatusMediaHidden,
expandStatusSpoiler,
collapseStatusSpoiler,
toggleStatusSpoilerExpanded,
translateStatus, translateStatus,
undoStatusTranslation, undoStatusTranslation,
unfilterStatus, unfilterStatus,

View file

@ -1,11 +1,13 @@
import clsx from 'clsx'; import clsx from 'clsx';
import parse, { Element, type HTMLReactParserOptions, domToReact, type DOMNode } from 'html-react-parser'; import parse, { Element, type HTMLReactParserOptions, domToReact, type DOMNode } from 'html-react-parser';
import React, { useState, useRef, useLayoutEffect, useMemo } from 'react'; import React, { useState, useRef, useLayoutEffect, useMemo } from 'react';
import { FormattedMessage } from 'react-intl'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { toggleStatusSpoilerExpanded } from 'soapbox/actions/statuses';
import Icon from 'soapbox/components/icon'; import Icon from 'soapbox/components/icon';
import { Text } from 'soapbox/components/ui'; import { Button, Text } from 'soapbox/components/ui';
import { useAppDispatch, useSettings } from 'soapbox/hooks';
import { onlyEmoji as isOnlyEmoji } from 'soapbox/utils/rich-content'; import { onlyEmoji as isOnlyEmoji } from 'soapbox/utils/rich-content';
import { getTextDirection } from '../utils/rtl'; import { getTextDirection } from '../utils/rtl';
@ -18,19 +20,27 @@ import Poll from './polls/poll';
import type { Sizes } from 'soapbox/components/ui/text/text'; import type { Sizes } from 'soapbox/components/ui/text/text';
import type { MinifiedStatus } from 'soapbox/reducers/statuses'; import type { MinifiedStatus } from 'soapbox/reducers/statuses';
const MAX_HEIGHT = 642; // 20px * 32 (+ 2px padding at the top) const MAX_HEIGHT = 322; // 20px * 16 (+ 2px padding at the top)
const BIG_EMOJI_LIMIT = 10; const BIG_EMOJI_LIMIT = 10;
const messages = defineMessages({
collapse: { id: 'status.spoiler.collapse', defaultMessage: 'Collapse' },
expand: { id: 'status.spoiler.expand', defaultMessage: 'Expand' },
});
interface IReadMoreButton { interface IReadMoreButton {
onClick: React.MouseEventHandler; onClick: React.MouseEventHandler;
} }
/** Button to expand a truncated status (due to too much content) */ /** Button to expand a truncated status (due to too much content) */
const ReadMoreButton: React.FC<IReadMoreButton> = ({ onClick }) => ( const ReadMoreButton: React.FC<IReadMoreButton> = ({ onClick }) => (
<div className='relative'>
<div className='absolute -top-16 h-16 w-full bg-gradient-to-b from-transparent to-white black:to-black dark:to-primary-900' />
<button className='flex items-center border-0 bg-transparent p-0 pt-2 text-gray-900 hover:underline active:underline dark:text-gray-300' onClick={onClick}> <button className='flex items-center border-0 bg-transparent p-0 pt-2 text-gray-900 hover:underline active:underline dark:text-gray-300' onClick={onClick}>
<FormattedMessage id='status.read_more' defaultMessage='Read more' /> <FormattedMessage id='status.read_more' defaultMessage='Read more' />
<Icon className='inline-block h-5 w-5' src={require('@tabler/icons/outline/chevron-right.svg')} /> <Icon className='inline-block h-5 w-5' src={require('@tabler/icons/outline/chevron-right.svg')} />
</button> </button>
</div>
); );
interface IStatusContent { interface IStatusContent {
@ -49,6 +59,10 @@ const StatusContent: React.FC<IStatusContent> = React.memo(({
translatable, translatable,
textSize = 'md', textSize = 'md',
}) => { }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const { displaySpoilers } = useSettings();
const [collapsed, setCollapsed] = useState(false); const [collapsed, setCollapsed] = useState(false);
const [onlyEmoji, setOnlyEmoji] = useState(false); const [onlyEmoji, setOnlyEmoji] = useState(false);
@ -73,6 +87,13 @@ const StatusContent: React.FC<IStatusContent> = React.memo(({
} }
}; };
const toggleExpanded: React.MouseEventHandler<HTMLButtonElement> = (e) => {
e.preventDefault();
e.stopPropagation();
dispatch(toggleStatusSpoilerExpanded(status));
};
useLayoutEffect(() => { useLayoutEffect(() => {
maybeSetCollapsed(); maybeSetCollapsed();
maybeSetOnlyEmoji(); maybeSetOnlyEmoji();
@ -152,7 +173,7 @@ const StatusContent: React.FC<IStatusContent> = React.memo(({
const className = clsx(baseClassName, { const className = clsx(baseClassName, {
'cursor-pointer': onClick, 'cursor-pointer': onClick,
'whitespace-normal': withSpoiler, 'whitespace-normal': withSpoiler,
'max-h-[300px]': collapsed, 'max-h-[200px]': collapsed,
'leading-normal big-emoji': onlyEmoji, 'leading-normal big-emoji': onlyEmoji,
}); });
@ -160,16 +181,33 @@ const StatusContent: React.FC<IStatusContent> = React.memo(({
? status.spoilerMapHtml[status.currentLanguage] || status.spoilerHtml ? status.spoilerMapHtml[status.currentLanguage] || status.spoilerHtml
: status.spoilerHtml; : status.spoilerHtml;
const expandable = !displaySpoilers;
const expanded = !withSpoiler || status.expanded || false;
const output = []; const output = [];
if (spoilerText) { if (spoilerText) {
output.push( output.push(
<Text className='mb-2' size='2xl' weight='medium'> <Text className='mb-2' size='2xl' weight='medium'>
<span dangerouslySetInnerHTML={{ __html: spoilerText }} /> <span dangerouslySetInnerHTML={{ __html: spoilerText }} />
{expandable && (
<Button
className='ml-2 align-middle'
type='button'
theme='muted'
size='xs'
onClick={toggleExpanded}
icon={expanded ? require('@tabler/icons/outline/chevron-up.svg') : require('@tabler/icons/outline/chevron-down.svg')}
>
{intl.formatMessage(expanded ? messages.collapse : messages.expand)}
</Button>
)}
</Text>, </Text>,
); );
} }
if (expandable && !expanded) return <>{output}</>;
if (onClick) { if (onClick) {
output.push( output.push(
<Markup <Markup

View file

@ -6,7 +6,7 @@ import { Link, 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';
import { openModal } from 'soapbox/actions/modals'; import { openModal } from 'soapbox/actions/modals';
import { toggleStatusHidden, unfilterStatus } from 'soapbox/actions/statuses'; import { toggleStatusMediaHidden, unfilterStatus } from 'soapbox/actions/statuses';
import TranslateButton from 'soapbox/components/translate-button'; import TranslateButton from 'soapbox/components/translate-button';
import AccountContainer from 'soapbox/containers/account-container'; import AccountContainer from 'soapbox/containers/account-container';
import QuotedStatus from 'soapbox/features/status/containers/quoted-status-container'; import QuotedStatus from 'soapbox/features/status/containers/quoted-status-container';
@ -172,7 +172,7 @@ const Status: React.FC<IStatus> = (props) => {
}; };
const handleHotkeyToggleSensitive = (): void => { const handleHotkeyToggleSensitive = (): void => {
dispatch(toggleStatusHidden(actualStatus)); dispatch(toggleStatusMediaHidden(actualStatus));
}; };
const handleHotkeyReact = (): void => { const handleHotkeyReact = (): void => {

View file

@ -2,7 +2,7 @@ import clsx from 'clsx';
import React from 'react'; import React from 'react';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import { toggleStatusHidden } from 'soapbox/actions/statuses'; import { toggleStatusMediaHidden } from 'soapbox/actions/statuses';
import { useAppDispatch, useSettings } from 'soapbox/hooks'; import { useAppDispatch, useSettings } from 'soapbox/hooks';
import { Button, HStack, Text } from '../ui'; import { Button, HStack, Text } from '../ui';
@ -42,7 +42,7 @@ const SensitiveContentOverlay = React.forwardRef<HTMLDivElement, ISensitiveConte
const toggleVisibility = (event: React.MouseEvent<HTMLButtonElement>) => { const toggleVisibility = (event: React.MouseEvent<HTMLButtonElement>) => {
event.stopPropagation(); event.stopPropagation();
dispatch(toggleStatusHidden(status)); dispatch(toggleStatusMediaHidden(status));
}; };
if (visible && !showHideButton) return null; if (visible && !showHideButton) return null;

View file

@ -6,7 +6,7 @@ import { mentionCompose } from 'soapbox/actions/compose';
import { reblog, favourite, unreblog, unfavourite } from 'soapbox/actions/interactions'; import { reblog, favourite, unreblog, unfavourite } from 'soapbox/actions/interactions';
import { openModal } from 'soapbox/actions/modals'; import { openModal } from 'soapbox/actions/modals';
import { getSettings } from 'soapbox/actions/settings'; import { getSettings } from 'soapbox/actions/settings';
import { toggleStatusHidden } from 'soapbox/actions/statuses'; import { toggleStatusMediaHidden } from 'soapbox/actions/statuses';
import Icon from 'soapbox/components/icon'; import Icon from 'soapbox/components/icon';
import RelativeTimestamp from 'soapbox/components/relative-timestamp'; import RelativeTimestamp from 'soapbox/components/relative-timestamp';
import { HStack, Text, Emoji } from 'soapbox/components/ui'; import { HStack, Text, Emoji } from 'soapbox/components/ui';
@ -267,7 +267,7 @@ const Notification: React.FC<INotification> = (props) => {
const handleHotkeyToggleSensitive = useCallback((e?: KeyboardEvent) => { const handleHotkeyToggleSensitive = useCallback((e?: KeyboardEvent) => {
if (status && typeof status === 'object') { if (status && typeof status === 'object') {
dispatch(toggleStatusHidden(status)); dispatch(toggleStatusMediaHidden(status));
} }
}, [status]); }, [status]);

View file

@ -153,6 +153,10 @@ const Preferences = () => {
/> />
</ListItem> </ListItem>
<ListItem label={<FormattedMessage id='preferences.fields.spoilers_display_label' defaultMessage='Automatically expand text behind spoilers' />}>
<SettingToggle settings={settings} settingPath={['displaySpoilers']} onChange={onToggleChange} />
</ListItem>
<ListItem label={<FormattedMessage id='preferences.fields.media_display_label' defaultMessage='Sensitive content' />}> <ListItem label={<FormattedMessage id='preferences.fields.media_display_label' defaultMessage='Sensitive content' />}>
<SelectDropdown <SelectDropdown
className='max-w-[200px]' className='max-w-[200px]'

View file

@ -10,7 +10,7 @@ import { type ComposeReplyAction, mentionCompose, replyCompose } from 'soapbox/a
import { reblog, toggleFavourite, unreblog } from 'soapbox/actions/interactions'; import { reblog, toggleFavourite, unreblog } from 'soapbox/actions/interactions';
import { openModal } from 'soapbox/actions/modals'; import { openModal } from 'soapbox/actions/modals';
import { getSettings } from 'soapbox/actions/settings'; import { getSettings } from 'soapbox/actions/settings';
import { toggleStatusHidden } from 'soapbox/actions/statuses'; import { toggleStatusMediaHidden } from 'soapbox/actions/statuses';
import ScrollableList from 'soapbox/components/scrollable-list'; import ScrollableList from 'soapbox/components/scrollable-list';
import StatusActionBar from 'soapbox/components/status-action-bar'; import StatusActionBar from 'soapbox/components/status-action-bar';
import Tombstone from 'soapbox/components/tombstone'; import Tombstone from 'soapbox/components/tombstone';
@ -198,7 +198,7 @@ const Thread: React.FC<IThread> = ({
}; };
const handleHotkeyToggleSensitive = () => { const handleHotkeyToggleSensitive = () => {
dispatch(toggleStatusHidden(status)); dispatch(toggleStatusMediaHidden(status));
}; };
const handleMoveUp = (id: string) => { const handleMoveUp = (id: string) => {

View file

@ -1180,6 +1180,7 @@
"preferences.fields.preserve_spoilers_label": "Preserve content warning when replying", "preferences.fields.preserve_spoilers_label": "Preserve content warning when replying",
"preferences.fields.privacy_label": "Default post privacy", "preferences.fields.privacy_label": "Default post privacy",
"preferences.fields.reduce_motion_label": "Reduce motion in animations", "preferences.fields.reduce_motion_label": "Reduce motion in animations",
"preferences.fields.spoilers_display_label": "Automatically expand text behind spoilers",
"preferences.fields.system_font_label": "Use system's default font", "preferences.fields.system_font_label": "Use system's default font",
"preferences.fields.theme": "Theme", "preferences.fields.theme": "Theme",
"preferences.fields.underline_links_label": "Always underline links in posts", "preferences.fields.underline_links_label": "Always underline links in posts",
@ -1474,6 +1475,8 @@
"status.show_less_all": "Show less for all", "status.show_less_all": "Show less for all",
"status.show_more_all": "Show more for all", "status.show_more_all": "Show more for all",
"status.show_original": "Show original", "status.show_original": "Show original",
"status.spoiler.collapse": "Collapse",
"status.spoiler.expand": "Expand",
"status.title": "Post details", "status.title": "Post details",
"status.title_direct": "Direct message", "status.title_direct": "Direct message",
"status.translate": "Translate", "status.translate": "Translate",

View file

@ -1180,6 +1180,7 @@
"preferences.fields.preserve_spoilers_label": "Pozostaw ostrzeżenie o zawartości, gdy odpowiadasz na wpis", "preferences.fields.preserve_spoilers_label": "Pozostaw ostrzeżenie o zawartości, gdy odpowiadasz na wpis",
"preferences.fields.privacy_label": "Prywatność wpisów", "preferences.fields.privacy_label": "Prywatność wpisów",
"preferences.fields.reduce_motion_label": "Ogranicz ruch w animacjach", "preferences.fields.reduce_motion_label": "Ogranicz ruch w animacjach",
"preferences.fields.spoilers_display_label": "Automatycznie rozwijaj tekst za spoilerami",
"preferences.fields.system_font_label": "Używaj domyślnej czcionki systemu", "preferences.fields.system_font_label": "Używaj domyślnej czcionki systemu",
"preferences.fields.theme": "Motyw", "preferences.fields.theme": "Motyw",
"preferences.fields.underline_links_label": "Zawsze podkreślaj odnośniki we wpisach", "preferences.fields.underline_links_label": "Zawsze podkreślaj odnośniki we wpisach",
@ -1474,6 +1475,8 @@
"status.show_less_all": "Zwiń wszystkie", "status.show_less_all": "Zwiń wszystkie",
"status.show_more_all": "Rozwiń wszystkie", "status.show_more_all": "Rozwiń wszystkie",
"status.show_original": "Pokaż oryginalny wpis", "status.show_original": "Pokaż oryginalny wpis",
"status.spoiler.collapse": "Zwiń",
"status.spoiler.expand": "Rozwiń",
"status.title": "Wpis", "status.title": "Wpis",
"status.title_direct": "Wiadomość bezpośrednia", "status.title_direct": "Wiadomość bezpośrednia",
"status.translate": "Przetłumacz wpis", "status.translate": "Przetłumacz wpis",

View file

@ -26,7 +26,8 @@ type CalculatedValues = {
spoilerHtml: string; spoilerHtml: string;
contentMapHtml?: Record<string, string>; contentMapHtml?: Record<string, string>;
spoilerMapHtml?: Record<string, string>; spoilerMapHtml?: Record<string, string>;
hidden?: boolean; expanded?: boolean | null;
hidden?: boolean | null;
translation?: Translation | null | false; translation?: Translation | null | false;
currentLanguage?: string; currentLanguage?: string;
}; };
@ -67,11 +68,11 @@ const calculateSpoiler = (text: string, emojiMap: any) => DOMPurify.sanitize(emo
const calculateStatus = (status: BaseStatus, oldStatus?: OldStatus): CalculatedValues => { const calculateStatus = (status: BaseStatus, oldStatus?: OldStatus): CalculatedValues => {
if (oldStatus && oldStatus.content === status.content && oldStatus.spoiler_text === status.spoiler_text) { if (oldStatus && oldStatus.content === status.content && oldStatus.spoiler_text === status.spoiler_text) {
const { const {
search_index, contentHtml, spoilerHtml, contentMapHtml, spoilerMapHtml, hidden, translation, currentLanguage, search_index, contentHtml, spoilerHtml, contentMapHtml, spoilerMapHtml, hidden, expanded, translation, currentLanguage,
} = oldStatus; } = oldStatus;
return { return {
search_index, contentHtml, spoilerHtml, contentMapHtml, spoilerMapHtml, hidden, translation, currentLanguage, search_index, contentHtml, spoilerHtml, contentMapHtml, spoilerMapHtml, hidden, expanded, translation, currentLanguage,
}; };
} else { } else {
const searchContent = buildSearchContent(status); const searchContent = buildSearchContent(status);
@ -159,7 +160,8 @@ const normalizeStatus = (status: BaseStatus & {
account: normalizeAccount(status.account), account: normalizeAccount(status.account),
accounts: status.accounts?.map(normalizeAccount), accounts: status.accounts?.map(normalizeAccount),
mentions, mentions,
hidden: status.sensitive, expanded: null,
hidden: null,
/** Rewrite `<p></p>` to empty string. */ /** Rewrite `<p></p>` to empty string. */
content: status.content === '<p></p>' ? '' : status.content, content: status.content === '<p></p>' ? '' : status.content,
filtered: status.filtered?.map(result => result.filter.title), filtered: status.filtered?.map(result => result.filter.title),

View file

@ -32,9 +32,9 @@ import {
STATUS_CREATE_FAIL, STATUS_CREATE_FAIL,
STATUS_DELETE_REQUEST, STATUS_DELETE_REQUEST,
STATUS_DELETE_FAIL, STATUS_DELETE_FAIL,
STATUS_HIDE, STATUS_HIDE_MEDIA,
STATUS_MUTE_SUCCESS, STATUS_MUTE_SUCCESS,
STATUS_REVEAL, STATUS_REVEAL_MEDIA,
STATUS_TRANSLATE_FAIL, STATUS_TRANSLATE_FAIL,
STATUS_TRANSLATE_REQUEST, STATUS_TRANSLATE_REQUEST,
STATUS_TRANSLATE_SUCCESS, STATUS_TRANSLATE_SUCCESS,
@ -42,6 +42,8 @@ import {
STATUS_UNFILTER, STATUS_UNFILTER,
STATUS_UNMUTE_SUCCESS, STATUS_UNMUTE_SUCCESS,
STATUS_LANGUAGE_CHANGE, STATUS_LANGUAGE_CHANGE,
STATUS_COLLAPSE_SPOILER,
STATUS_EXPAND_SPOILER,
} from '../actions/statuses'; } from '../actions/statuses';
import { TIMELINE_DELETE } from '../actions/timelines'; import { TIMELINE_DELETE } from '../actions/timelines';
@ -232,7 +234,7 @@ const statuses = (state = initialState, action: AnyAction): State => {
return state.setIn([action.statusId, 'muted'], true); return state.setIn([action.statusId, 'muted'], true);
case STATUS_UNMUTE_SUCCESS: case STATUS_UNMUTE_SUCCESS:
return state.setIn([action.statusId, 'muted'], false); return state.setIn([action.statusId, 'muted'], false);
case STATUS_REVEAL: case STATUS_REVEAL_MEDIA:
return state.withMutations(map => { return state.withMutations(map => {
action.statusIds.forEach((id: string) => { action.statusIds.forEach((id: string) => {
if (!(state.get(id) === undefined)) { if (!(state.get(id) === undefined)) {
@ -240,7 +242,7 @@ const statuses = (state = initialState, action: AnyAction): State => {
} }
}); });
}); });
case STATUS_HIDE: case STATUS_HIDE_MEDIA:
return state.withMutations(map => { return state.withMutations(map => {
action.statusIds.forEach((id: string) => { action.statusIds.forEach((id: string) => {
if (!(state.get(id) === undefined)) { if (!(state.get(id) === undefined)) {
@ -248,6 +250,22 @@ const statuses = (state = initialState, action: AnyAction): State => {
} }
}); });
}); });
case STATUS_EXPAND_SPOILER:
return state.withMutations(map => {
action.statusIds.forEach((id: string) => {
if (!(state.get(id) === undefined)) {
map.setIn([id, 'expanded'], true);
}
});
});
case STATUS_COLLAPSE_SPOILER:
return state.withMutations(map => {
action.statusIds.forEach((id: string) => {
if (!(state.get(id) === undefined)) {
map.setIn([id, 'expanded'], false);
}
});
});
case STATUS_DELETE_REQUEST: case STATUS_DELETE_REQUEST:
return decrementReplyCount(state, action.params); return decrementReplyCount(state, action.params);
case STATUS_DELETE_FAIL: case STATUS_DELETE_FAIL:

View file

@ -15,6 +15,7 @@ const settingsSchema = z.object({
underlineLinks: z.boolean().catch(false), underlineLinks: z.boolean().catch(false),
autoPlayGif: z.boolean().catch(true), autoPlayGif: z.boolean().catch(true),
displayMedia: z.enum(['default', 'hide_all', 'show_all']).catch('default'), displayMedia: z.enum(['default', 'hide_all', 'show_all']).catch('default'),
displaySpoilers: z.boolean().catch(false),
preserveSpoilers: z.boolean().catch(false), preserveSpoilers: z.boolean().catch(false),
unfollowModal: z.boolean().catch(false), unfollowModal: z.boolean().catch(false),
boostModal: z.boolean().catch(false), boostModal: z.boolean().catch(false),