Refactor status normalizer

merge-requests/1046/head
Alex Gleason 2022-02-20 02:27:29 -05:00
rodzic 1e4659248f
commit f7f18fac79
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 7211D1F99744FBB7
7 zmienionych plików z 75 dodań i 112 usunięć

Wyświetl plik

@ -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));
};
}

Wyświetl plik

@ -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(/<br\s*\/?>/g, '\n').replace(/<\/p><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 };

Wyświetl plik

@ -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 (
<div className='reply-mentions'>
<FormattedMessage
id='reply_mentions.reply'
defaultMessage='Replying to {accounts}{more}'
values={{
accounts: (<>
<HoverRefWrapper accountId={status.getIn(['account', 'id'])} inline>
<Link to={`/@${status.getIn(['account', 'acct'])}`} className='reply-mentions__account'>@{status.getIn(['account', 'username'])}</Link>
</HoverRefWrapper>
</>),
more: false,
}}
/>
</div>
);
} else {
// The reply-to is unknown. Rare, but it can happen.
return (
<div className='reply-mentions'>
<FormattedMessage
id='reply_mentions.reply_empty'
defaultMessage='Replying to post'
/>
</div>
);
}
return (
<div className='reply-mentions'>
<FormattedMessage
id='reply_mentions.reply_empty'
defaultMessage='Replying to post'
/>
</div>
);
}
// The typical case with a reply-to and a list of mentions.
return (
<div className='reply-mentions'>

Wyświetl plik

@ -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));
};

Wyświetl plik

@ -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));
};

Wyświetl plik

@ -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);

Wyświetl plik

@ -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(/<br\s*\/?>/g, '\n').replace(/<\/p><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);
});
};