From f7f18fac799586bebf9972659c3b125d4a50f03d Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 20 Feb 2022 02:27:29 -0500 Subject: [PATCH] Refactor status normalizer --- app/soapbox/actions/importer/index.js | 17 +----- app/soapbox/actions/importer/normalizer.js | 59 ------------------- .../components/status_reply_mentions.js | 39 +++--------- .../features/scheduled_statuses/builder.js | 8 +-- .../ui/util/pending_status_builder.js | 8 +-- app/soapbox/normalizers/status.js | 9 +++ app/soapbox/reducers/statuses.js | 47 +++++++++++++++ 7 files changed, 75 insertions(+), 112 deletions(-) diff --git a/app/soapbox/actions/importer/index.js b/app/soapbox/actions/importer/index.js index 3a019e636..8599e7dbc 100644 --- a/app/soapbox/actions/importer/index.js +++ b/app/soapbox/actions/importer/index.js @@ -1,8 +1,5 @@ -import { getSettings } from '../settings'; - import { normalizeAccount, - normalizeStatus, normalizePoll, } from './normalizer'; @@ -60,11 +57,6 @@ export function importFetchedStatus(status, idempotencyKey) { // Skip broken statuses if (isBroken(status)) return; - const normalOldStatus = getState().getIn(['statuses', status.id]); - const expandSpoilers = getSettings(getState()).get('expandSpoilers'); - - const normalizedStatus = normalizeStatus(status, normalOldStatus, expandSpoilers); - if (status.reblog?.id) { dispatch(importFetchedStatus(status.reblog)); } @@ -83,7 +75,7 @@ export function importFetchedStatus(status, idempotencyKey) { } dispatch(importFetchedAccount(status.account)); - dispatch(importStatus(normalizedStatus, idempotencyKey)); + dispatch(importStatus(status, idempotencyKey)); }; } @@ -106,17 +98,12 @@ const isBroken = status => { export function importFetchedStatuses(statuses) { return (dispatch, getState) => { const accounts = []; - const normalStatuses = []; const polls = []; function processStatus(status) { // Skip broken statuses if (isBroken(status)) return; - const normalOldStatus = getState().getIn(['statuses', status.id]); - const expandSpoilers = getSettings(getState()).get('expandSpoilers'); - - normalStatuses.push(normalizeStatus(status, normalOldStatus, expandSpoilers)); accounts.push(status.account); if (status.reblog?.id) { @@ -141,7 +128,7 @@ export function importFetchedStatuses(statuses) { dispatch(importPolls(polls)); dispatch(importFetchedAccounts(accounts)); - dispatch(importStatuses(normalStatuses)); + dispatch(importStatuses(statuses)); }; } diff --git a/app/soapbox/actions/importer/normalizer.js b/app/soapbox/actions/importer/normalizer.js index 7d40357f0..4de148c0b 100644 --- a/app/soapbox/actions/importer/normalizer.js +++ b/app/soapbox/actions/importer/normalizer.js @@ -1,12 +1,8 @@ import escapeTextContentForBrowser from 'escape-html'; -import { stripCompatibilityFeatures } from 'soapbox/utils/html'; - import emojify from '../../features/emoji/emoji'; import { unescapeHTML } from '../../utils/html'; -const domParser = new DOMParser(); - const makeEmojiMap = record => record.emojis.reduce((obj, emoji) => { obj[`:${emoji.shortcode}:`] = emoji; return obj; @@ -45,61 +41,6 @@ export function normalizeAccount(account) { return account; } -export function normalizeStatus(status, normalOldStatus, expandSpoilers) { - const normalStatus = { ...status }; - - // Some backends can return null, or omit these required fields - if (!normalStatus.emojis) normalStatus.emojis = []; - if (!normalStatus.spoiler_text) normalStatus.spoiler_text = ''; - - // Copy the pleroma object too, so we can modify our copy - if (status.pleroma) { - normalStatus.pleroma = { ...status.pleroma }; - } - - normalStatus.account = status.account.id; - - if (status.reblog?.id) { - normalStatus.reblog = status.reblog.id; - } - - if (status.poll?.id) { - normalStatus.poll = status.poll.id; - } - - if (status.pleroma?.quote?.id) { - // Normalize quote to the top-level, so delete the original for performance - normalStatus.quote = status.pleroma.quote.id; - delete normalStatus.pleroma.quote; - } else if (status.quote?.id) { - // Fedibird compatibility, because why not - normalStatus.quote = status.quote.id; - } else if (status.quote_id) { - // Fedibird: fall back to quote_id - normalStatus.quote = status.quote_id; - } - - // Only calculate these values when status first encountered - // Otherwise keep the ones already in the reducer - if (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?.options) ? status.poll.options.map(option => option.title) : [])).join('\n\n').replace(//g, '\n').replace(/<\/p>

/g, '\n\n'); - const emojiMap = makeEmojiMap(normalStatus); - - normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent; - normalStatus.contentHtml = stripCompatibilityFeatures(emojify(normalStatus.content, emojiMap)); - normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap); - normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive; - } - - return normalStatus; -} - export function normalizePoll(poll) { const normalPoll = { ...poll }; diff --git a/app/soapbox/components/status_reply_mentions.js b/app/soapbox/components/status_reply_mentions.js index 11664bb31..51c684841 100644 --- a/app/soapbox/components/status_reply_mentions.js +++ b/app/soapbox/components/status_reply_mentions.js @@ -43,39 +43,18 @@ class StatusReplyMentions extends ImmutablePureComponent { const to = status.get('mentions', []); // The post is a reply, but it has no mentions. + // Rare, but it can happen. if (to.size === 0) { - // The author is replying to themself. - if (status.get('in_reply_to_account_id') === status.getIn(['account', 'id'])) { - return ( -

- - - @{status.getIn(['account', 'username'])} - - ), - more: false, - }} - /> -
- ); - } else { - // The reply-to is unknown. Rare, but it can happen. - return ( -
- -
- ); - } + return ( +
+ +
+ ); } - // The typical case with a reply-to and a list of mentions. return (
diff --git a/app/soapbox/features/scheduled_statuses/builder.js b/app/soapbox/features/scheduled_statuses/builder.js index 05cef642c..d9ba1fdf9 100644 --- a/app/soapbox/features/scheduled_statuses/builder.js +++ b/app/soapbox/features/scheduled_statuses/builder.js @@ -1,6 +1,6 @@ import { fromJS } from 'immutable'; -import { normalizeStatus } from 'soapbox/actions/importer/normalizer'; +import { normalizeStatus } from 'soapbox/normalizers/status'; import { makeGetAccount } from 'soapbox/selectors'; export const buildStatus = (state, scheduledStatus) => { @@ -10,7 +10,7 @@ export const buildStatus = (state, scheduledStatus) => { const params = scheduledStatus.get('params'); const account = getAccount(state, me); - const status = normalizeStatus({ + const status = { account, application: null, bookmarked: false, @@ -40,7 +40,7 @@ export const buildStatus = (state, scheduledStatus) => { uri: `/scheduled_statuses/${scheduledStatus.get('id')}`, url: `/scheduled_statuses/${scheduledStatus.get('id')}`, visibility: params.get('visibility'), - }); + }; - return fromJS(status).set('account', account); + return normalizeStatus(fromJS(status)); }; diff --git a/app/soapbox/features/ui/util/pending_status_builder.js b/app/soapbox/features/ui/util/pending_status_builder.js index 6b55e4b7b..1e07b1dc3 100644 --- a/app/soapbox/features/ui/util/pending_status_builder.js +++ b/app/soapbox/features/ui/util/pending_status_builder.js @@ -1,7 +1,7 @@ import { fromJS } from 'immutable'; import { OrderedSet as ImmutableOrderedSet } from 'immutable'; -import { normalizeStatus } from 'soapbox/actions/importer/normalizer'; +import { normalizeStatus } from 'soapbox/normalizers/status'; import { makeGetAccount, makeGetStatus } from 'soapbox/selectors'; export const buildStatus = (state, pendingStatus, idempotencyKey) => { @@ -26,7 +26,7 @@ export const buildStatus = (state, pendingStatus, idempotencyKey) => { })); } - const status = normalizeStatus({ + const status = { account, application: null, bookmarked: false, @@ -57,7 +57,7 @@ export const buildStatus = (state, pendingStatus, idempotencyKey) => { uri: '', url: '', visibility: pendingStatus.get('visibility', 'public'), - }); + }; - return fromJS(status).set('account', account); + return normalizeStatus(fromJS(status)); }; diff --git a/app/soapbox/normalizers/status.js b/app/soapbox/normalizers/status.js index dc8886e35..806048172 100644 --- a/app/soapbox/normalizers/status.js +++ b/app/soapbox/normalizers/status.js @@ -2,6 +2,14 @@ import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import { accountToMention } from 'soapbox/utils/accounts'; +// Some backends can return null, or omit these required fields +const setRequiredFields = status => { + return status.merge({ + emojis: status.get('emojis') || [], + spoiler_text: status.get('spoiler_text') || '', + }); +}; + // Ensure attachments have required fields // https://docs.joinmastodon.org/entities/attachment/ const normalizeAttachment = attachment => { @@ -62,6 +70,7 @@ const addSelfMention = status => { export const normalizeStatus = status => { return status.withMutations(status => { + setRequiredFields(status); fixMentions(status); addSelfMention(status); normalizeAttachments(status); diff --git a/app/soapbox/reducers/statuses.js b/app/soapbox/reducers/statuses.js index c6e06e242..733386c7e 100644 --- a/app/soapbox/reducers/statuses.js +++ b/app/soapbox/reducers/statuses.js @@ -1,7 +1,10 @@ +import escapeTextContentForBrowser from 'escape-html'; import { Map as ImmutableMap, fromJS } from 'immutable'; +import emojify from 'soapbox/features/emoji/emoji'; import { normalizeStatus } from 'soapbox/normalizers/status'; import { simulateEmojiReact, simulateUnEmojiReact } from 'soapbox/utils/emoji_reacts'; +import { stripCompatibilityFeatures } from 'soapbox/utils/html'; import { EMOJI_REACT_REQUEST, @@ -27,6 +30,46 @@ import { } from '../actions/statuses'; import { TIMELINE_DELETE } from '../actions/timelines'; +const domParser = new DOMParser(); + +const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => { + obj[`:${emoji.get('shortcode')}:`] = emoji; + return obj; +}, {}); + +const minifyStatus = status => { + return status.merge({ + account: status.getIn(['account', 'id'], null), + reblog: status.getIn(['reblog', 'id'], null), + poll: status.getIn(['poll', 'id'], null), + quote: status.getIn(['quote', 'id']) || status.getIn(['pleroma', 'quote', 'id']) || null, + }); +}; + +// Only calculate these values when status first encountered +// Otherwise keep the ones already in the reducer +const calculateStatus = (status, oldStatus) => { + if (oldStatus) { + return status.merge({ + search_index: oldStatus.get('search_index'), + contentHtml: oldStatus.get('contentHtml'), + spoilerHtml: oldStatus.get('spoilerHtml'), + hidden: oldStatus.get('hidden'), + }); + } else { + const spoilerText = status.get('spoiler_text') || ''; + const searchContent = ([spoilerText, status.get('content')].concat(status.getIn(['poll', 'options']) ? status.getIn(['poll', 'options']).map(option => option.get('title')) : [])).join('\n\n').replace(//g, '\n').replace(/<\/p>

/g, '\n\n'); + const emojiMap = makeEmojiMap(status); + + return status.merge({ + search_index: domParser.parseFromString(searchContent, 'text/html').documentElement.textContent, + contentHtml: stripCompatibilityFeatures(emojify(status.get('content'), emojiMap)), + spoilerHtml: emojify(escapeTextContentForBrowser(spoilerText), emojiMap), + hidden: spoilerText.length > 0 || status.get('sensitive'), + }); + } +}; + const isQuote = status => { return Boolean(status.get('quote_id') || status.getIn(['pleroma', 'quote_url'])); }; @@ -45,9 +88,13 @@ const fixQuote = (state, status) => { }; const fixStatus = (state, status) => { + const oldStatus = state.get(status.get('id')); + return status.withMutations(status => { normalizeStatus(status); fixQuote(state, status); + calculateStatus(status, oldStatus); + minifyStatus(status); }); };