about summary refs log tree commit diff
path: root/app/javascript
diff options
context:
space:
mode:
authorClaire <claire.github-309c@sitedethib.com>2022-07-24 20:01:30 +0200
committerClaire <claire.github-309c@sitedethib.com>2022-07-25 11:57:09 +0200
commit18346f40443f1c01b45d94be1b0edce20b2c27be (patch)
treeb78b33927842f5dfd374fa5fb9509a809849e358 /app/javascript
parenteacde1a130a5764686a284d610890ba0f60b193e (diff)
Add option to share CW toggle state across instances of a post
Diffstat (limited to 'app/javascript')
-rw-r--r--app/javascript/flavours/glitch/actions/importer/index.js2
-rw-r--r--app/javascript/flavours/glitch/actions/importer/normalizer.js5
-rw-r--r--app/javascript/flavours/glitch/actions/statuses.js34
-rw-r--r--app/javascript/flavours/glitch/components/status.js80
-rw-r--r--app/javascript/flavours/glitch/containers/status_container.js17
-rw-r--r--app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js5
-rw-r--r--app/javascript/flavours/glitch/features/direct_timeline/containers/conversation_container.js1
-rw-r--r--app/javascript/flavours/glitch/features/local_settings/page/index.js9
-rw-r--r--app/javascript/flavours/glitch/features/status/index.js46
-rw-r--r--app/javascript/flavours/glitch/reducers/local_settings.js1
-rw-r--r--app/javascript/flavours/glitch/reducers/statuses.js21
-rw-r--r--app/javascript/flavours/glitch/util/content_warning.js33
12 files changed, 188 insertions, 66 deletions
diff --git a/app/javascript/flavours/glitch/actions/importer/index.js b/app/javascript/flavours/glitch/actions/importer/index.js
index f4372fb31..ec41fea6e 100644
--- a/app/javascript/flavours/glitch/actions/importer/index.js
+++ b/app/javascript/flavours/glitch/actions/importer/index.js
@@ -63,7 +63,7 @@ export function importFetchedStatuses(statuses) {
     const polls = [];
 
     function processStatus(status) {
-      pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id])));
+      pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id]), getState().get('local_settings')));
       pushUnique(accounts, status.account);
 
       if (status.reblog && status.reblog.id) {
diff --git a/app/javascript/flavours/glitch/actions/importer/normalizer.js b/app/javascript/flavours/glitch/actions/importer/normalizer.js
index c38af196a..c6acdbdbb 100644
--- a/app/javascript/flavours/glitch/actions/importer/normalizer.js
+++ b/app/javascript/flavours/glitch/actions/importer/normalizer.js
@@ -1,6 +1,7 @@
 import escapeTextContentForBrowser from 'escape-html';
 import emojify from 'flavours/glitch/util/emoji';
 import { unescapeHTML } from 'flavours/glitch/util/html';
+import { autoHideCW } from 'flavours/glitch/util/content_warning';
 
 const domParser = new DOMParser();
 
@@ -41,7 +42,7 @@ export function normalizeAccount(account) {
   return account;
 }
 
-export function normalizeStatus(status, normalOldStatus) {
+export function normalizeStatus(status, normalOldStatus, settings) {
   const normalStatus   = { ...status };
   normalStatus.account = status.account.id;
 
@@ -60,6 +61,7 @@ export function normalizeStatus(status, normalOldStatus) {
     normalStatus.search_index = normalOldStatus.get('search_index');
     normalStatus.contentHtml = normalOldStatus.get('contentHtml');
     normalStatus.spoilerHtml = normalOldStatus.get('spoilerHtml');
+    normalStatus.hidden = normalOldStatus.get('hidden');
   } else {
     const spoilerText   = normalStatus.spoiler_text || '';
     const searchContent = ([spoilerText, status.content].concat((status.poll && status.poll.options) ? status.poll.options.map(option => option.title) : [])).concat(status.media_attachments.map(att => att.description)).join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
@@ -68,6 +70,7 @@ export function normalizeStatus(status, normalOldStatus) {
     normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
     normalStatus.contentHtml  = emojify(normalStatus.content, emojiMap);
     normalStatus.spoilerHtml  = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
+    normalStatus.hidden       = (spoilerText.length > 0 || normalStatus.sensitive) && autoHideCW(settings, spoilerText);
   }
 
   return normalStatus;
diff --git a/app/javascript/flavours/glitch/actions/statuses.js b/app/javascript/flavours/glitch/actions/statuses.js
index 6ffcf181d..1f223f22e 100644
--- a/app/javascript/flavours/glitch/actions/statuses.js
+++ b/app/javascript/flavours/glitch/actions/statuses.js
@@ -24,6 +24,10 @@ export const STATUS_UNMUTE_REQUEST = 'STATUS_UNMUTE_REQUEST';
 export const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS';
 export const STATUS_UNMUTE_FAIL    = 'STATUS_UNMUTE_FAIL';
 
+export const STATUS_REVEAL   = 'STATUS_REVEAL';
+export const STATUS_HIDE     = 'STATUS_HIDE';
+export const STATUS_COLLAPSE = 'STATUS_COLLAPSE';
+
 export const REDRAFT = 'REDRAFT';
 
 export const STATUS_FETCH_SOURCE_REQUEST = 'STATUS_FETCH_SOURCE_REQUEST';
@@ -277,3 +281,33 @@ export function unmuteStatusFail(id, error) {
     error,
   };
 };
+
+export function hideStatus(ids) {
+  if (!Array.isArray(ids)) {
+    ids = [ids];
+  }
+
+  return {
+    type: STATUS_HIDE,
+    ids,
+  };
+};
+
+export function revealStatus(ids) {
+  if (!Array.isArray(ids)) {
+    ids = [ids];
+  }
+
+  return {
+    type: STATUS_REVEAL,
+    ids,
+  };
+};
+
+export function toggleStatusCollapse(id, isCollapsed) {
+  return {
+    type: STATUS_COLLAPSE,
+    id,
+    isCollapsed,
+  };
+}
diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js
index 219f7de7a..11c81765b 100644
--- a/app/javascript/flavours/glitch/components/status.js
+++ b/app/javascript/flavours/glitch/components/status.js
@@ -81,8 +81,8 @@ class Status extends ImmutablePureComponent {
     onBlock: PropTypes.func,
     onEmbed: PropTypes.func,
     onHeightChange: PropTypes.func,
+    onToggleHidden: PropTypes.func,
     muted: PropTypes.bool,
-    collapse: PropTypes.bool,
     hidden: PropTypes.bool,
     unread: PropTypes.bool,
     prepend: PropTypes.string,
@@ -121,7 +121,6 @@ class Status extends ImmutablePureComponent {
     'settings',
     'prepend',
     'muted',
-    'collapse',
     'notification',
     'hidden',
     'expanded',
@@ -149,14 +148,14 @@ class Status extends ImmutablePureComponent {
     let updated = false;
 
     // Make sure the state mirrors props we track…
-    if (nextProps.collapse !== prevState.collapseProp) {
-      update.collapseProp = nextProps.collapse;
-      updated = true;
-    }
     if (nextProps.expanded !== prevState.expandedProp) {
       update.expandedProp = nextProps.expanded;
       updated = true;
     }
+    if (nextProps.status?.get('hidden') !== prevState.statusPropHidden) {
+      update.statusPropHidden = nextProps.status?.get('hidden');
+      updated = true;
+    }
 
     // Update state based on new props
     if (!nextProps.settings.getIn(['collapsed', 'enabled'])) {
@@ -164,14 +163,19 @@ class Status extends ImmutablePureComponent {
         update.isCollapsed = false;
         updated = true;
       }
-    } else if (
-      nextProps.collapse !== prevState.collapseProp &&
-      nextProps.collapse !== undefined
+    }
+
+    // Handle uncollapsing toots when the shared CW state is expanded
+    if (nextProps.settings.getIn(['content_warnings', 'shared_state']) &&
+      nextProps.status?.get('spoiler_text')?.length && nextProps.status?.get('hidden') === false &&
+      prevState.statusPropHidden !== false && prevState.isCollapsed
     ) {
-      update.isCollapsed = nextProps.collapse;
-      if (nextProps.collapse) update.isExpanded = false;
+      update.isCollapsed = false;
       updated = true;
     }
+
+    // The “expanded” prop is used to one-off change the local state.
+    // It's used in the thread view when unfolding/re-folding all CWs at once.
     if (nextProps.expanded !== prevState.expandedProp &&
       nextProps.expanded !== undefined
     ) {
@@ -180,15 +184,9 @@ class Status extends ImmutablePureComponent {
       updated = true;
     }
 
-    if (nextProps.expanded === undefined &&
-      prevState.isExpanded === undefined &&
-      update.isExpanded === undefined
-    ) {
-      const isExpanded = autoUnfoldCW(nextProps.settings, nextProps.status);
-      if (isExpanded !== undefined) {
-        update.isExpanded = isExpanded;
-        updated = true;
-      }
+    if (prevState.isExpanded === undefined && update.isExpanded === undefined) {
+      update.isExpanded = autoUnfoldCW(nextProps.settings, nextProps.status);
+      updated = true;
     }
 
     if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) {
@@ -243,22 +241,18 @@ class Status extends ImmutablePureComponent {
 
     const autoCollapseSettings = settings.getIn(['collapsed', 'auto']);
 
-    if (function () {
-      switch (true) {
-      case !!collapse:
-      case !!autoCollapseSettings.get('all'):
-      case autoCollapseSettings.get('notifications') && !!muted:
-      case autoCollapseSettings.get('lengthy') && node.clientHeight > (
-        status.get('media_attachments').size && !muted ? 650 : 400
-      ):
-      case autoCollapseSettings.get('reblogs') && prepend === 'reblogged_by':
-      case autoCollapseSettings.get('replies') && status.get('in_reply_to_id', null) !== null:
-      case autoCollapseSettings.get('media') && !(status.get('spoiler_text').length) && !!status.get('media_attachments').size:
-        return true;
-      default:
-        return false;
-      }
-    }()) {
+    // Don't autocollapse if CW state is shared and status is explicitly revealed,
+    // as it could cause surprising changes when receiving notifications
+    if (settings.getIn(['content_warnings', 'shared_state']) && status.get('spoiler_text').length && !status.get('hidden')) return;
+
+    if (collapse ||
+      autoCollapseSettings.get('all') ||
+      (autoCollapseSettings.get('notifications') && muted) ||
+      (autoCollapseSettings.get('lengthy') && node.clientHeight > ((status.get('media_attachments').size && !muted) ? 650 : 400)) ||
+      (autoCollapseSettings.get('reblogs') && prepend === 'reblogged_by') ||
+      (autoCollapseSettings.get('replies') && status.get('in_reply_to_id', null) !== null) ||
+      (autoCollapseSettings.get('media') && !(status.get('spoiler_text').length) && status.get('media_attachments').size > 0)
+    ) {
       this.setCollapsed(true);
       // Hack to fix timeline jumps on second rendering when auto-collapsing
       this.setState({ autoCollapsed: true });
@@ -309,16 +303,20 @@ class Status extends ImmutablePureComponent {
   //  is enabled, so we don't have to.
   setCollapsed = (value) => {
     if (this.props.settings.getIn(['collapsed', 'enabled'])) {
-      this.setState({ isCollapsed: value });
       if (value) {
         this.setExpansion(false);
       }
+      this.setState({ isCollapsed: value });
     } else {
       this.setState({ isCollapsed: false });
     }
   }
 
   setExpansion = (value) => {
+    if (this.props.settings.getIn(['content_warnings', 'shared_state']) && this.props.status.get('hidden') === value) {
+      this.props.onToggleHidden(this.props.status);
+    }
+
     this.setState({ isExpanded: value });
     if (value) {
       this.setCollapsed(false);
@@ -365,7 +363,9 @@ class Status extends ImmutablePureComponent {
   }
 
   handleExpandedToggle = () => {
-    if (this.props.status.get('spoiler_text')) {
+    if (this.props.settings.getIn(['content_warnings', 'shared_state'])) {
+      this.props.onToggleHidden(this.props.status);
+    } else if (this.props.status.get('spoiler_text')) {
       this.setExpansion(!this.state.isExpanded);
     }
   };
@@ -505,7 +505,7 @@ class Status extends ImmutablePureComponent {
       usingPiP,
       ...other
     } = this.props;
-    const { isExpanded, isCollapsed, forceFilter } = this.state;
+    const { isCollapsed, forceFilter } = this.state;
     let background = null;
     let attachments = null;
 
@@ -528,6 +528,8 @@ class Status extends ImmutablePureComponent {
       return null;
     }
 
+    const isExpanded = settings.getIn(['content_warnings', 'shared_state']) ? !status.get('hidden') : this.state.isExpanded;
+
     const handlers = {
       reply: this.handleHotkeyReply,
       favourite: this.handleHotkeyFavourite,
diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js
index 358b89ab9..6c8f261e4 100644
--- a/app/javascript/flavours/glitch/containers/status_container.js
+++ b/app/javascript/flavours/glitch/containers/status_container.js
@@ -17,7 +17,14 @@ import {
   pin,
   unpin,
 } from 'flavours/glitch/actions/interactions';
-import { muteStatus, unmuteStatus, deleteStatus, editStatus } from 'flavours/glitch/actions/statuses';
+import {
+  muteStatus,
+  unmuteStatus,
+  deleteStatus,
+  hideStatus,
+  revealStatus,
+  editStatus
+} from 'flavours/glitch/actions/statuses';
 import { initMuteModal } from 'flavours/glitch/actions/mutes';
 import { initBlockModal } from 'flavours/glitch/actions/blocks';
 import { initReport } from 'flavours/glitch/actions/reports';
@@ -252,6 +259,14 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
     }
   },
 
+  onToggleHidden (status) {
+    if (status.get('hidden')) {
+      dispatch(revealStatus(status.get('id')));
+    } else {
+      dispatch(hideStatus(status.get('id')));
+    }
+  },
+
   deployPictureInPicture (status, type, mediaProps) {
     dispatch((_, getState) => {
       if (getState().getIn(['local_settings', 'media', 'pop_in_player'])) {
diff --git a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js
index 202d96676..7107c9db3 100644
--- a/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js
+++ b/app/javascript/flavours/glitch/features/direct_timeline/components/conversation.js
@@ -132,6 +132,8 @@ class Conversation extends ImmutablePureComponent {
   }
 
   handleShowMore = () => {
+    this.props.onToggleHidden(this.props.lastStatus);
+
     if (this.props.lastStatus.get('spoiler_text')) {
       this.setExpansion(!this.state.isExpanded);
     }
@@ -143,12 +145,13 @@ class Conversation extends ImmutablePureComponent {
 
   render () {
     const { accounts, lastStatus, unread, scrollKey, intl } = this.props;
-    const { isExpanded } = this.state;
 
     if (lastStatus === null) {
       return null;
     }
 
+    const isExpanded = this.props.settings.getIn(['content_warnings', 'shared_state']) ? !lastStatus.get('hidden') : this.state.isExpanded;
+
     const menu = [
       { text: intl.formatMessage(messages.open), action: this.handleClick },
       null,
diff --git a/app/javascript/flavours/glitch/features/direct_timeline/containers/conversation_container.js b/app/javascript/flavours/glitch/features/direct_timeline/containers/conversation_container.js
index b15ce9f0f..f5e5946e3 100644
--- a/app/javascript/flavours/glitch/features/direct_timeline/containers/conversation_container.js
+++ b/app/javascript/flavours/glitch/features/direct_timeline/containers/conversation_container.js
@@ -23,6 +23,7 @@ const mapStateToProps = () => {
       accounts: conversation.get('accounts').map(accountId => state.getIn(['accounts', accountId], null)),
       unread: conversation.get('unread'),
       lastStatus: lastStatusId && getStatus(state, { id: lastStatusId }),
+      settings: state.get('local_settings'),
     };
   };
 };
diff --git a/app/javascript/flavours/glitch/features/local_settings/page/index.js b/app/javascript/flavours/glitch/features/local_settings/page/index.js
index 94a87c484..ffa4e3409 100644
--- a/app/javascript/flavours/glitch/features/local_settings/page/index.js
+++ b/app/javascript/flavours/glitch/features/local_settings/page/index.js
@@ -305,6 +305,15 @@ class LocalSettingsPage extends React.PureComponent {
         <h1><FormattedMessage id='settings.content_warnings' defaultMessage='Content warnings' /></h1>
         <LocalSettingsPageItem
           settings={settings}
+          item={['content_warnings', 'shared_state']}
+          id='mastodon-settings--content_warnings-shared_state'
+          onChange={onChange}
+        >
+          <FormattedMessage id='settings.content_warnings_shared_state' defaultMessage='Show/hide content of all copies at once' />
+          <span className='hint'><FormattedMessage id='settings.content_warnings_shared_state_hint' defaultMessage='Reproduce upstream Mastodon behavior by having the Content Warning button affect all copies of a post at once. This will prevent automatic collapsing of any copy of a toot with unfolded CW' /></span>
+        </LocalSettingsPageItem>
+        <LocalSettingsPageItem
+          settings={settings}
           item={['content_warnings', 'media_outside']}
           id='mastodon-settings--content_warnings-media_outside'
           onChange={onChange}
diff --git a/app/javascript/flavours/glitch/features/status/index.js b/app/javascript/flavours/glitch/features/status/index.js
index 653fabeae..9c86d54db 100644
--- a/app/javascript/flavours/glitch/features/status/index.js
+++ b/app/javascript/flavours/glitch/features/status/index.js
@@ -26,7 +26,14 @@ import {
   directCompose,
 } from 'flavours/glitch/actions/compose';
 import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
-import { muteStatus, unmuteStatus, deleteStatus, editStatus } from 'flavours/glitch/actions/statuses';
+import {
+  muteStatus,
+  unmuteStatus,
+  deleteStatus,
+  editStatus,
+  hideStatus,
+  revealStatus
+} from 'flavours/glitch/actions/statuses';
 import { initMuteModal } from 'flavours/glitch/actions/mutes';
 import { initBlockModal } from 'flavours/glitch/actions/blocks';
 import { initReport } from 'flavours/glitch/actions/reports';
@@ -215,11 +222,19 @@ class Status extends ImmutablePureComponent {
     return updated ? update : null;
   }
 
-  handleExpandedToggle = () => {
-    if (this.props.status.get('spoiler_text')) {
+  handleToggleHidden = () => {
+    const { status } = this.props;
+
+    if (this.props.settings.getIn(['content_warnings', 'shared_state'])) {
+      if (status.get('hidden')) {
+        this.props.dispatch(revealStatus(status.get('id')));
+      } else {
+        this.props.dispatch(hideStatus(status.get('id')));
+      }
+    } else if (this.props.status.get('spoiler_text')) {
       this.setExpansion(!this.state.isExpanded);
     }
-  };
+  }
 
   handleToggleMediaVisibility = () => {
     this.setState({ showMedia: !this.state.showMedia });
@@ -354,7 +369,19 @@ class Status extends ImmutablePureComponent {
   }
 
   handleToggleAll = () => {
-    const { isExpanded } = this.state;
+    const { status, ancestorsIds, descendantsIds, settings } = this.props;
+    const statusIds = [status.get('id')].concat(ancestorsIds.toJS(), descendantsIds.toJS());
+    let { isExpanded } = this.state;
+
+    if (settings.getIn(['content_warnings', 'shared_state']))
+      isExpanded = !status.get('hidden');
+
+    if (!isExpanded) {
+      this.props.dispatch(revealStatus(statusIds));
+    } else {
+      this.props.dispatch(hideStatus(statusIds));
+    }
+
     this.setState({ isExpanded: !isExpanded, threadExpanded: !isExpanded });
   }
 
@@ -513,9 +540,8 @@ class Status extends ImmutablePureComponent {
 
   render () {
     let ancestors, descendants;
-    const { setExpansion } = this;
     const { status, settings, ancestorsIds, descendantsIds, intl, domain, multiColumn, usingPiP } = this.props;
-    const { fullscreen, isExpanded } = this.state;
+    const { fullscreen } = this.state;
 
     if (status === null) {
       return (
@@ -526,6 +552,8 @@ class Status extends ImmutablePureComponent {
       );
     }
 
+    const isExpanded = settings.getIn(['content_warnings', 'shared_state']) ? !status.get('hidden') : this.state.isExpanded;
+
     if (ancestorsIds && ancestorsIds.size > 0) {
       ancestors = <div>{this.renderChildren(ancestorsIds)}</div>;
     }
@@ -543,7 +571,7 @@ class Status extends ImmutablePureComponent {
       bookmark: this.handleHotkeyBookmark,
       mention: this.handleHotkeyMention,
       openProfile: this.handleHotkeyOpenProfile,
-      toggleSpoiler: this.handleExpandedToggle,
+      toggleSpoiler: this.handleToggleHidden,
       toggleSensitive: this.handleHotkeyToggleSensitive,
       openMedia: this.handleHotkeyOpenMedia,
     };
@@ -574,7 +602,7 @@ class Status extends ImmutablePureComponent {
                   onOpenVideo={this.handleOpenVideo}
                   onOpenMedia={this.handleOpenMedia}
                   expanded={isExpanded}
-                  onToggleHidden={this.handleExpandedToggle}
+                  onToggleHidden={this.handleToggleHidden}
                   domain={domain}
                   showMedia={this.state.showMedia}
                   onToggleMediaVisibility={this.handleToggleMediaVisibility}
diff --git a/app/javascript/flavours/glitch/reducers/local_settings.js b/app/javascript/flavours/glitch/reducers/local_settings.js
index 6d8feaf66..62ce29f0c 100644
--- a/app/javascript/flavours/glitch/reducers/local_settings.js
+++ b/app/javascript/flavours/glitch/reducers/local_settings.js
@@ -27,6 +27,7 @@ const initialState = ImmutableMap({
   content_warnings : ImmutableMap({
     filter       : null,
     media_outside: false,
+    shared_state : false,
   }),
   collapsed : ImmutableMap({
     enabled     : true,
diff --git a/app/javascript/flavours/glitch/reducers/statuses.js b/app/javascript/flavours/glitch/reducers/statuses.js
index 5db766b96..333e4b45c 100644
--- a/app/javascript/flavours/glitch/reducers/statuses.js
+++ b/app/javascript/flavours/glitch/reducers/statuses.js
@@ -10,6 +10,9 @@ import {
 import {
   STATUS_MUTE_SUCCESS,
   STATUS_UNMUTE_SUCCESS,
+  STATUS_REVEAL,
+  STATUS_HIDE,
+  STATUS_COLLAPSE,
 } from 'flavours/glitch/actions/statuses';
 import {
   TIMELINE_DELETE,
@@ -56,6 +59,24 @@ export default function statuses(state = initialState, action) {
     return state.setIn([action.id, 'muted'], true);
   case STATUS_UNMUTE_SUCCESS:
     return state.setIn([action.id, 'muted'], false);
+  case STATUS_REVEAL:
+    return state.withMutations(map => {
+      action.ids.forEach(id => {
+        if (!(state.get(id) === undefined)) {
+          map.setIn([id, 'hidden'], false);
+        }
+      });
+    });
+  case STATUS_HIDE:
+    return state.withMutations(map => {
+      action.ids.forEach(id => {
+        if (!(state.get(id) === undefined)) {
+          map.setIn([id, 'hidden'], true);
+        }
+      });
+    });
+  case STATUS_COLLAPSE:
+    return state.setIn([action.id, 'collapsed'], action.isCollapsed);
   case TIMELINE_DELETE:
     return deleteStatus(state, action.id, action.references);
   default:
diff --git a/app/javascript/flavours/glitch/util/content_warning.js b/app/javascript/flavours/glitch/util/content_warning.js
index baeb97881..383a34226 100644
--- a/app/javascript/flavours/glitch/util/content_warning.js
+++ b/app/javascript/flavours/glitch/util/content_warning.js
@@ -1,26 +1,31 @@
 import { expandSpoilers } from 'flavours/glitch/util/initial_state';
 
-export function autoUnfoldCW (settings, status) {
-  if (!expandSpoilers) {
+function _autoUnfoldCW(spoiler_text, skip_unfold_regex) {
+  if (!expandSpoilers)
     return false;
-  }
-
-  const rawRegex = settings.getIn(['content_warnings', 'filter']);
 
-  if (!rawRegex) {
+  if (!skip_unfold_regex)
     return true;
-  }
 
-  let regex      = null;
+  let regex = null;
 
   try {
-    regex = rawRegex && new RegExp(rawRegex.trim(), 'i');
+    regex = new RegExp(skip_unfold_regex.trim(), 'i');
   } catch (e) {
-    // Bad regex, don't affect filters
+    // Bad regex, skip filters
+    return true;
   }
 
-  if (!(status && regex)) {
-    return undefined;
-  }
-  return !regex.test(status.get('spoiler_text'));
+  return !regex.test(spoiler_text);
+}
+
+export function autoHideCW(settings, spoiler_text) {
+  return !_autoUnfoldCW(spoiler_text, settings.getIn(['content_warnings', 'filter']));
+}
+
+export function autoUnfoldCW(settings, status) {
+  if (!status)
+    return false;
+
+  return _autoUnfoldCW(status.get('spoiler_text'), settings.getIn(['content_warnings', 'filter']));
 }