[Glitch] Revamp post filtering system
Port front-end changes from 02851848e9 to glitch-soc
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
			
			
This commit is contained in:
		@@ -1,26 +0,0 @@
 | 
			
		||||
import api from 'flavours/glitch/util/api';
 | 
			
		||||
 | 
			
		||||
export const FILTERS_FETCH_REQUEST = 'FILTERS_FETCH_REQUEST';
 | 
			
		||||
export const FILTERS_FETCH_SUCCESS = 'FILTERS_FETCH_SUCCESS';
 | 
			
		||||
export const FILTERS_FETCH_FAIL    = 'FILTERS_FETCH_FAIL';
 | 
			
		||||
 | 
			
		||||
export const fetchFilters = () => (dispatch, getState) => {
 | 
			
		||||
  dispatch({
 | 
			
		||||
    type: FILTERS_FETCH_REQUEST,
 | 
			
		||||
    skipLoading: true,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  api(getState)
 | 
			
		||||
    .get('/api/v1/filters')
 | 
			
		||||
    .then(({ data }) => dispatch({
 | 
			
		||||
      type: FILTERS_FETCH_SUCCESS,
 | 
			
		||||
      filters: data,
 | 
			
		||||
      skipLoading: true,
 | 
			
		||||
    }))
 | 
			
		||||
    .catch(err => dispatch({
 | 
			
		||||
      type: FILTERS_FETCH_FAIL,
 | 
			
		||||
      err,
 | 
			
		||||
      skipLoading: true,
 | 
			
		||||
      skipAlert: true,
 | 
			
		||||
    }));
 | 
			
		||||
};
 | 
			
		||||
@@ -5,6 +5,7 @@ export const ACCOUNTS_IMPORT = 'ACCOUNTS_IMPORT';
 | 
			
		||||
export const STATUS_IMPORT   = 'STATUS_IMPORT';
 | 
			
		||||
export const STATUSES_IMPORT = 'STATUSES_IMPORT';
 | 
			
		||||
export const POLLS_IMPORT    = 'POLLS_IMPORT';
 | 
			
		||||
export const FILTERS_IMPORT  = 'FILTERS_IMPORT';
 | 
			
		||||
 | 
			
		||||
function pushUnique(array, object) {
 | 
			
		||||
  if (array.every(element => element.id !== object.id)) {
 | 
			
		||||
@@ -28,6 +29,10 @@ export function importStatuses(statuses) {
 | 
			
		||||
  return { type: STATUSES_IMPORT, statuses };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function importFilters(filters) {
 | 
			
		||||
  return { type: FILTERS_IMPORT, filters };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function importPolls(polls) {
 | 
			
		||||
  return { type: POLLS_IMPORT, polls };
 | 
			
		||||
}
 | 
			
		||||
@@ -61,11 +66,16 @@ export function importFetchedStatuses(statuses) {
 | 
			
		||||
    const accounts = [];
 | 
			
		||||
    const normalStatuses = [];
 | 
			
		||||
    const polls = [];
 | 
			
		||||
    const filters = [];
 | 
			
		||||
 | 
			
		||||
    function processStatus(status) {
 | 
			
		||||
      pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id]), getState().get('local_settings')));
 | 
			
		||||
      pushUnique(accounts, status.account);
 | 
			
		||||
 | 
			
		||||
      if (status.filtered) {
 | 
			
		||||
        status.filtered.forEach(result => pushUnique(filters, result.filter));
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (status.reblog && status.reblog.id) {
 | 
			
		||||
        processStatus(status.reblog);
 | 
			
		||||
      }
 | 
			
		||||
@@ -80,6 +90,7 @@ export function importFetchedStatuses(statuses) {
 | 
			
		||||
    dispatch(importPolls(polls));
 | 
			
		||||
    dispatch(importFetchedAccounts(accounts));
 | 
			
		||||
    dispatch(importStatuses(normalStatuses));
 | 
			
		||||
    dispatch(importFilters(filters));
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -42,6 +42,14 @@ export function normalizeAccount(account) {
 | 
			
		||||
  return account;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function normalizeFilterResult(result) {
 | 
			
		||||
  const normalResult = { ...result };
 | 
			
		||||
 | 
			
		||||
  normalResult.filter = normalResult.filter.id;
 | 
			
		||||
 | 
			
		||||
  return normalResult;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function normalizeStatus(status, normalOldStatus, settings) {
 | 
			
		||||
  const normalStatus   = { ...status };
 | 
			
		||||
  normalStatus.account = status.account.id;
 | 
			
		||||
@@ -54,6 +62,10 @@ export function normalizeStatus(status, normalOldStatus, settings) {
 | 
			
		||||
    normalStatus.poll = status.poll.id;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (status.filtered) {
 | 
			
		||||
    normalStatus.filtered = status.filtered.map(normalizeFilterResult);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Only calculate these values when status first encountered and
 | 
			
		||||
  // when the underlying values change. Otherwise keep the ones
 | 
			
		||||
  // already in the reducer
 | 
			
		||||
 
 | 
			
		||||
@@ -12,10 +12,8 @@ import { saveSettings } from './settings';
 | 
			
		||||
import { defineMessages } from 'react-intl';
 | 
			
		||||
import { List as ImmutableList } from 'immutable';
 | 
			
		||||
import { unescapeHTML } from 'flavours/glitch/util/html';
 | 
			
		||||
import { getFiltersRegex } from 'flavours/glitch/selectors';
 | 
			
		||||
import { usePendingItems as preferPendingItems } from 'flavours/glitch/util/initial_state';
 | 
			
		||||
import compareId from 'flavours/glitch/util/compare_id';
 | 
			
		||||
import { searchTextFromRawStatus } from 'flavours/glitch/actions/importer/normalizer';
 | 
			
		||||
import { requestNotificationPermission } from 'flavours/glitch/util/notifications';
 | 
			
		||||
 | 
			
		||||
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
 | 
			
		||||
@@ -74,20 +72,17 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
 | 
			
		||||
    const showInColumn = activeFilter === 'all' ? getState().getIn(['settings', 'notifications', 'shows', notification.type], true) : activeFilter === notification.type;
 | 
			
		||||
    const showAlert    = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true);
 | 
			
		||||
    const playSound    = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true);
 | 
			
		||||
    const filters      = getFiltersRegex(getState(), { contextType: 'notifications' });
 | 
			
		||||
 | 
			
		||||
    let filtered = false;
 | 
			
		||||
 | 
			
		||||
    if (['mention', 'status'].includes(notification.type)) {
 | 
			
		||||
      const dropRegex   = filters[0];
 | 
			
		||||
      const regex       = filters[1];
 | 
			
		||||
      const searchIndex = searchTextFromRawStatus(notification.status);
 | 
			
		||||
    if (['mention', 'status'].includes(notification.type) && notification.status.filtered) {
 | 
			
		||||
      const filters = notification.status.filtered.filter(result => result.filter.context.includes('notifications'));
 | 
			
		||||
 | 
			
		||||
      if (dropRegex && dropRegex.test(searchIndex)) {
 | 
			
		||||
      if (filters.some(result => result.filter.filter_action === 'hide')) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      filtered = regex && regex.test(searchIndex);
 | 
			
		||||
      filtered = filters.length > 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (['follow_request'].includes(notification.type)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,6 @@ import {
 | 
			
		||||
  updateReaction as updateAnnouncementsReaction,
 | 
			
		||||
  deleteAnnouncement,
 | 
			
		||||
} from './announcements';
 | 
			
		||||
import { fetchFilters } from './filters';
 | 
			
		||||
import { getLocale } from 'mastodon/locales';
 | 
			
		||||
 | 
			
		||||
const { messages } = getLocale();
 | 
			
		||||
@@ -97,9 +96,6 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
 | 
			
		||||
        case 'conversation':
 | 
			
		||||
          dispatch(updateConversations(JSON.parse(data.payload)));
 | 
			
		||||
          break;
 | 
			
		||||
        case 'filters_changed':
 | 
			
		||||
          dispatch(fetchFilters());
 | 
			
		||||
          break;
 | 
			
		||||
        case 'announcement':
 | 
			
		||||
          dispatch(updateAnnouncements(JSON.parse(data.payload)));
 | 
			
		||||
          break;
 | 
			
		||||
 
 | 
			
		||||
@@ -455,8 +455,8 @@ class Status extends ImmutablePureComponent {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleUnfilterClick = e => {
 | 
			
		||||
    const { onUnfilter, status } = this.props;
 | 
			
		||||
    onUnfilter(status.get('reblog') ? status.get('reblog') : status, () => this.setState({ forceFilter: false }));
 | 
			
		||||
    this.setState({ forceFilter: false });
 | 
			
		||||
    e.preventDefault();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  handleFilterClick = () => {
 | 
			
		||||
@@ -557,8 +557,8 @@ class Status extends ImmutablePureComponent {
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const filtered = (status.get('filtered') || status.getIn(['reblog', 'filtered'])) && settings.get('filtering_behavior') !== 'content_warning';
 | 
			
		||||
    if (forceFilter === undefined ? filtered : forceFilter) {
 | 
			
		||||
    const matchedFilters = status.get('filtered') || status.getIn(['reblog', 'filtered']);
 | 
			
		||||
    if (this.state.forceFilter === undefined ? matchedFilters : this.state.forceFilter) {
 | 
			
		||||
      const minHandlers = this.props.muted ? {} : {
 | 
			
		||||
        moveUp: this.handleHotkeyMoveUp,
 | 
			
		||||
        moveDown: this.handleHotkeyMoveDown,
 | 
			
		||||
@@ -567,13 +567,11 @@ class Status extends ImmutablePureComponent {
 | 
			
		||||
      return (
 | 
			
		||||
        <HotKeys handlers={minHandlers}>
 | 
			
		||||
          <div className='status__wrapper status__wrapper--filtered focusable' tabIndex='0' ref={this.handleRef}>
 | 
			
		||||
            <FormattedMessage id='status.filtered' defaultMessage='Filtered' />
 | 
			
		||||
            {settings.get('filtering_behavior') !== 'upstream' && ' '}
 | 
			
		||||
            {settings.get('filtering_behavior') !== 'upstream' && (
 | 
			
		||||
              <button className='status__wrapper--filtered__button' onClick={this.handleUnfilterClick}>
 | 
			
		||||
                <FormattedMessage id='status.show_filter_reason' defaultMessage='(show why)' />
 | 
			
		||||
              </button>
 | 
			
		||||
            )}
 | 
			
		||||
            <FormattedMessage id='status.filtered' defaultMessage='Filtered' />: {matchedFilters.join(', ')}.
 | 
			
		||||
            {' '}
 | 
			
		||||
            <button className='status__wrapper--filtered__button' onClick={this.handleUnfilterClick}>
 | 
			
		||||
              <FormattedMessage id='status.show_filter_reason' defaultMessage='Show anyway' />
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
        </HotKeys>
 | 
			
		||||
      );
 | 
			
		||||
@@ -789,11 +787,11 @@ class Status extends ImmutablePureComponent {
 | 
			
		||||
 | 
			
		||||
          {!isCollapsed || !(muted || !settings.getIn(['collapsed', 'show_action_bar'])) ? (
 | 
			
		||||
            <StatusActionBar
 | 
			
		||||
              {...other}
 | 
			
		||||
              status={status}
 | 
			
		||||
              account={status.get('account')}
 | 
			
		||||
              showReplyCount={settings.get('show_reply_count')}
 | 
			
		||||
              onFilter={this.handleFilterClick}
 | 
			
		||||
              onFilter={matchedFilters && this.handleFilterClick}
 | 
			
		||||
              {...other}
 | 
			
		||||
            />
 | 
			
		||||
          ) : null}
 | 
			
		||||
          {notification ? (
 | 
			
		||||
 
 | 
			
		||||
@@ -271,10 +271,6 @@ class StatusActionBar extends ImmutablePureComponent {
 | 
			
		||||
      <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} />
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const filterButton = status.get('filtered') && (
 | 
			
		||||
      <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.hide)} icon='eye' onClick={this.handleFilterClick} />
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let replyButton = (
 | 
			
		||||
      <IconButton
 | 
			
		||||
        className='status__action-bar-button'
 | 
			
		||||
@@ -309,6 +305,10 @@ class StatusActionBar extends ImmutablePureComponent {
 | 
			
		||||
      reblogTitle = intl.formatMessage(messages.cannot_reblog);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const filterButton = this.props.onFilter && (
 | 
			
		||||
      <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.hide)} icon='eye' onClick={this.handleFilterClick} />
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <div className='status__action-bar'>
 | 
			
		||||
        {replyButton}
 | 
			
		||||
@@ -316,6 +316,7 @@ class StatusActionBar extends ImmutablePureComponent {
 | 
			
		||||
        <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
 | 
			
		||||
        {shareButton}
 | 
			
		||||
        <IconButton className='status__action-bar-button bookmark-icon' disabled={anonymousAccess} active={status.get('bookmarked')} pressed={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />
 | 
			
		||||
 | 
			
		||||
        {filterButton}
 | 
			
		||||
 | 
			
		||||
        <div className='status__action-bar-dropdown'>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import { connect } from 'react-redux';
 | 
			
		||||
import Status from 'flavours/glitch/components/status';
 | 
			
		||||
import { List as ImmutableList } from 'immutable';
 | 
			
		||||
import { makeGetStatus, regexFromFilters, toServerSideType } from 'flavours/glitch/selectors';
 | 
			
		||||
import { makeGetStatus } from 'flavours/glitch/selectors';
 | 
			
		||||
import {
 | 
			
		||||
  replyCompose,
 | 
			
		||||
  mentionCompose,
 | 
			
		||||
@@ -201,48 +201,6 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
 | 
			
		||||
    dispatch(initBlockModal(account));
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  onUnfilter (status, onConfirm) {
 | 
			
		||||
    dispatch((_, getState) => {
 | 
			
		||||
      let state = getState();
 | 
			
		||||
      const serverSideType = toServerSideType(contextType);
 | 
			
		||||
      const enabledFilters = state.get('filters', ImmutableList()).filter(filter => filter.get('context').includes(serverSideType) && (filter.get('expires_at') === null || Date.parse(filter.get('expires_at')) > (new Date()))).toArray();
 | 
			
		||||
      const searchIndex = status.get('search_index');
 | 
			
		||||
      const matchingFilters = enabledFilters.filter(filter => regexFromFilters([filter]).test(searchIndex));
 | 
			
		||||
      dispatch(openModal('CONFIRM', {
 | 
			
		||||
        message: [
 | 
			
		||||
          <FormattedMessage id='confirmations.unfilter' defaultMessage='Information about this filtered toot' />,
 | 
			
		||||
          <div className='filtered-status-info'>
 | 
			
		||||
            <Spoilers spoilerText={intl.formatMessage(messages.author)}>
 | 
			
		||||
              <AccountContainer id={status.getIn(['account', 'id'])} />
 | 
			
		||||
            </Spoilers>
 | 
			
		||||
            <Spoilers spoilerText={intl.formatMessage(messages.matchingFilters, {count: matchingFilters.size})}>
 | 
			
		||||
              <ul>
 | 
			
		||||
                {matchingFilters.map(filter => (
 | 
			
		||||
                  <li>
 | 
			
		||||
                    {filter.get('phrase')}
 | 
			
		||||
                    {!!filterEditLink && ' '}
 | 
			
		||||
                    {!!filterEditLink && (
 | 
			
		||||
                      <a
 | 
			
		||||
                        target='_blank'
 | 
			
		||||
                        className='filtered-status-edit-link'
 | 
			
		||||
                        title={intl.formatMessage(messages.editFilter)}
 | 
			
		||||
                        href={filterEditLink(filter.get('id'))}
 | 
			
		||||
                      >
 | 
			
		||||
                        <Icon id='pencil' />
 | 
			
		||||
                      </a>
 | 
			
		||||
                    )}
 | 
			
		||||
                  </li>
 | 
			
		||||
                ))}
 | 
			
		||||
              </ul>
 | 
			
		||||
            </Spoilers>
 | 
			
		||||
          </div>
 | 
			
		||||
        ],
 | 
			
		||||
        confirm: intl.formatMessage(messages.unfilterConfirm),
 | 
			
		||||
        onConfirm: onConfirm,
 | 
			
		||||
      }));
 | 
			
		||||
    });
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  onReport (status) {
 | 
			
		||||
    dispatch(initReport(status.get('account'), status));
 | 
			
		||||
  },
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,6 @@ import { debounce } from 'lodash';
 | 
			
		||||
import { uploadCompose, resetCompose, changeComposeSpoilerness } from 'flavours/glitch/actions/compose';
 | 
			
		||||
import { expandHomeTimeline } from 'flavours/glitch/actions/timelines';
 | 
			
		||||
import { expandNotifications, notificationsSetVisibility } from 'flavours/glitch/actions/notifications';
 | 
			
		||||
import { fetchFilters } from 'flavours/glitch/actions/filters';
 | 
			
		||||
import { fetchRules } from 'flavours/glitch/actions/rules';
 | 
			
		||||
import { clearHeight } from 'flavours/glitch/actions/height_cache';
 | 
			
		||||
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'flavours/glitch/actions/markers';
 | 
			
		||||
@@ -402,7 +401,7 @@ class UI extends React.Component {
 | 
			
		||||
    this.props.dispatch(fetchMarkers());
 | 
			
		||||
    this.props.dispatch(expandHomeTimeline());
 | 
			
		||||
    this.props.dispatch(expandNotifications());
 | 
			
		||||
    setTimeout(() => this.props.dispatch(fetchFilters()), 500);
 | 
			
		||||
 | 
			
		||||
    setTimeout(() => this.props.dispatch(fetchRules()), 3000);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,34 @@
 | 
			
		||||
import { FILTERS_FETCH_SUCCESS } from '../actions/filters';
 | 
			
		||||
import { List as ImmutableList, fromJS } from 'immutable';
 | 
			
		||||
import { FILTERS_IMPORT } from '../actions/importer';
 | 
			
		||||
import { Map as ImmutableMap, is, fromJS } from 'immutable';
 | 
			
		||||
 | 
			
		||||
export default function filters(state = ImmutableList(), action) {
 | 
			
		||||
const normalizeFilter = (state, filter) => {
 | 
			
		||||
  const normalizedFilter = fromJS({
 | 
			
		||||
    id: filter.id,
 | 
			
		||||
    title: filter.title,
 | 
			
		||||
    context: filter.context,
 | 
			
		||||
    filter_action: filter.filter_action,
 | 
			
		||||
    expires_at: filter.expires_at ? Date.parse(filter.expires_at) : null,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  if (is(state.get(filter.id), normalizedFilter)) {
 | 
			
		||||
    return state;
 | 
			
		||||
  } else {
 | 
			
		||||
    return state.set(filter.id, normalizedFilter);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const normalizeFilters = (state, filters) => {
 | 
			
		||||
  filters.forEach(filter => {
 | 
			
		||||
    state = normalizeFilter(state, filter);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return state;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function filters(state = ImmutableMap(), action) {
 | 
			
		||||
  switch(action.type) {
 | 
			
		||||
  case FILTERS_FETCH_SUCCESS:
 | 
			
		||||
    return fromJS(action.filters);
 | 
			
		||||
  case FILTERS_IMPORT:
 | 
			
		||||
    return normalizeFilters(state, action.filters);
 | 
			
		||||
  default:
 | 
			
		||||
    return state;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -41,15 +41,15 @@ export const toServerSideType = columnType => {
 | 
			
		||||
const escapeRegExp = string =>
 | 
			
		||||
  string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
 | 
			
		||||
 | 
			
		||||
export const regexFromFilters = filters => {
 | 
			
		||||
  if (filters.size === 0) {
 | 
			
		||||
const regexFromKeywords = keywords => {
 | 
			
		||||
  if (keywords.size === 0) {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return new RegExp(filters.map(filter => {
 | 
			
		||||
    let expr = escapeRegExp(filter.get('phrase'));
 | 
			
		||||
  return new RegExp(keywords.map(keyword_filter => {
 | 
			
		||||
    let expr = escapeRegExp(keyword_filter.get('keyword'));
 | 
			
		||||
 | 
			
		||||
    if (filter.get('whole_word')) {
 | 
			
		||||
    if (keyword_filter.get('whole_word')) {
 | 
			
		||||
      if (/^[\w]/.test(expr)) {
 | 
			
		||||
        expr = `\\b${expr}`;
 | 
			
		||||
      }
 | 
			
		||||
@@ -63,27 +63,15 @@ export const regexFromFilters = filters => {
 | 
			
		||||
  }).join('|'), 'i');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Memoize the filter regexps for each valid server contextType
 | 
			
		||||
const makeGetFiltersRegex = () => {
 | 
			
		||||
  let memo = {};
 | 
			
		||||
const getFilters = (state, { contextType }) => {
 | 
			
		||||
  if (!contextType) return null;
 | 
			
		||||
 | 
			
		||||
  return (state, { contextType }) => {
 | 
			
		||||
    if (!contextType) return ImmutableList();
 | 
			
		||||
  const serverSideType = toServerSideType(contextType);
 | 
			
		||||
  const now = new Date();
 | 
			
		||||
 | 
			
		||||
    const serverSideType = toServerSideType(contextType);
 | 
			
		||||
    const filters = state.get('filters', ImmutableList()).filter(filter => filter.get('context').includes(serverSideType) && (filter.get('expires_at') === null || Date.parse(filter.get('expires_at')) > (new Date())));
 | 
			
		||||
 | 
			
		||||
    if (!memo[serverSideType] || !is(memo[serverSideType].filters, filters)) {
 | 
			
		||||
      const dropRegex = regexFromFilters(filters.filter(filter => filter.get('irreversible')));
 | 
			
		||||
      const regex = regexFromFilters(filters);
 | 
			
		||||
      memo[serverSideType] = { filters: filters, results: [dropRegex, regex] };
 | 
			
		||||
    }
 | 
			
		||||
    return memo[serverSideType].results;
 | 
			
		||||
  };
 | 
			
		||||
  return state.get('filters').filter((filter) => filter.get('context').includes(serverSideType) && (filter.get('expires_at') === null || filter.get('expires_at') > now));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getFiltersRegex = makeGetFiltersRegex();
 | 
			
		||||
 | 
			
		||||
export const makeGetStatus = () => {
 | 
			
		||||
  return createSelector(
 | 
			
		||||
    [
 | 
			
		||||
@@ -91,56 +79,32 @@ export const makeGetStatus = () => {
 | 
			
		||||
      (state, { id }) => state.getIn(['statuses', state.getIn(['statuses', id, 'reblog'])]),
 | 
			
		||||
      (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]),
 | 
			
		||||
      (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]),
 | 
			
		||||
      (state, _) => state.getIn(['local_settings', 'filtering_behavior']),
 | 
			
		||||
      (state, _) => state.get('filters', ImmutableList()),
 | 
			
		||||
      (_, { contextType }) => contextType,
 | 
			
		||||
      getFiltersRegex,
 | 
			
		||||
      getFilters,
 | 
			
		||||
    ],
 | 
			
		||||
 | 
			
		||||
    (statusBase, statusReblog, accountBase, accountReblog, filteringBehavior, filters, contextType, filtersRegex) => {
 | 
			
		||||
    (statusBase, statusReblog, accountBase, accountReblog, filters) => {
 | 
			
		||||
      if (!statusBase) {
 | 
			
		||||
        return null;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const dropRegex = (accountReblog || accountBase).get('id') !== me && filtersRegex[0];
 | 
			
		||||
 | 
			
		||||
      if (dropRegex && dropRegex.test(statusBase.get('reblog') ? statusReblog.get('search_index') : statusBase.get('search_index'))) {
 | 
			
		||||
        return null;
 | 
			
		||||
      let filtered = false;
 | 
			
		||||
      if ((accountReblog || accountBase).get('id') !== me && filters) {
 | 
			
		||||
        let filterResults = statusReblog?.get('filtered') || statusBase.get('filtered') || ImmutableList();
 | 
			
		||||
        if (filterResults.some((result) => filters.getIn([result.get('filter'), 'filter_action']) === 'hide')) {
 | 
			
		||||
          return null;
 | 
			
		||||
        }
 | 
			
		||||
        if (!filterResults.isEmpty()) {
 | 
			
		||||
          filtered = filterResults.map(result => filters.getIn([result.get('filter'), 'title']));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const regex  = (accountReblog || accountBase).get('id') !== me && filtersRegex[1];
 | 
			
		||||
      let filtered = false;
 | 
			
		||||
 | 
			
		||||
      if (statusReblog) {
 | 
			
		||||
        filtered     = regex && regex.test(statusReblog.get('search_index'));
 | 
			
		||||
        statusReblog = statusReblog.set('account', accountReblog);
 | 
			
		||||
        statusReblog = statusReblog.set('filtered', filtered);
 | 
			
		||||
      } else {
 | 
			
		||||
        statusReblog = null;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      filtered = filtered || regex && regex.test(statusBase.get('search_index'));
 | 
			
		||||
 | 
			
		||||
      if (filtered && filteringBehavior === 'drop') {
 | 
			
		||||
        return null;
 | 
			
		||||
      } else if (filtered && filteringBehavior === 'content_warning') {
 | 
			
		||||
        let spoilerText = (statusReblog || statusBase).get('spoiler_text', '');
 | 
			
		||||
        const searchIndex = (statusReblog || statusBase).get('search_index');
 | 
			
		||||
        const serverSideType = toServerSideType(contextType);
 | 
			
		||||
        const enabledFilters = filters.filter(filter => filter.get('context').includes(serverSideType) && (filter.get('expires_at') === null || Date.parse(filter.get('expires_at')) > (new Date()))).toArray();
 | 
			
		||||
        const matchingFilters = enabledFilters.filter(filter => {
 | 
			
		||||
          const regexp = regexFromFilters([filter]);
 | 
			
		||||
          return regexp.test(searchIndex) && !regexp.test(spoilerText);
 | 
			
		||||
        });
 | 
			
		||||
        if (statusReblog) {
 | 
			
		||||
          statusReblog = statusReblog.set('spoiler_text', matchingFilters.map(filter => filter.get('phrase')).concat([spoilerText]).filter(cw => !!cw).join(', '));
 | 
			
		||||
          statusReblog = statusReblog.update('spoilerHtml', '', spoilerText => matchingFilters.map(filter => escapeTextContentForBrowser(filter.get('phrase'))).concat([spoilerText]).filter(cw => !!cw).join(', '));
 | 
			
		||||
        } else {
 | 
			
		||||
          statusBase = statusBase.set('spoiler_text', matchingFilters.map(filter => filter.get('phrase')).concat([spoilerText]).filter(cw => !!cw).join(', '));
 | 
			
		||||
          statusBase = statusBase.update('spoilerHtml', '', spoilerText => matchingFilters.map(filter => escapeTextContentForBrowser(filter.get('phrase'))).concat([spoilerText]).filter(cw => !!cw).join(', '));
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return statusBase.withMutations(map => {
 | 
			
		||||
        map.set('reblog', statusReblog);
 | 
			
		||||
        map.set('account', accountBase);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user