From bd64fc43040e98dc6f31f0b4b989c1209758472c Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 5 Dec 2022 11:27:11 -0600 Subject: [PATCH] normalizeStatus: add internal fields --- app/soapbox/normalizers/status.ts | 20 +++++++++++++++++++ app/soapbox/utils/normalizers.ts | 33 +++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/app/soapbox/normalizers/status.ts b/app/soapbox/normalizers/status.ts index beca2d38b..feec927a0 100644 --- a/app/soapbox/normalizers/status.ts +++ b/app/soapbox/normalizers/status.ts @@ -3,6 +3,7 @@ * Converts API statuses into our internal format. * @see {@link https://docs.joinmastodon.org/entities/status/} */ +import escapeTextContentForBrowser from 'escape-html'; import { Map as ImmutableMap, List as ImmutableList, @@ -10,12 +11,15 @@ import { fromJS, } from 'immutable'; +import emojify from 'soapbox/features/emoji/emoji'; import { normalizeAccount } from 'soapbox/normalizers/account'; import { normalizeAttachment } from 'soapbox/normalizers/attachment'; import { normalizeCard } from 'soapbox/normalizers/card'; import { normalizeEmoji } from 'soapbox/normalizers/emoji'; import { normalizeMention } from 'soapbox/normalizers/mention'; import { normalizePoll } from 'soapbox/normalizers/poll'; +import { stripCompatibilityFeatures } from 'soapbox/utils/html'; +import { buildSearchContent, makeEmojiMap } from 'soapbox/utils/normalizers'; import type { ReducerAccount } from 'soapbox/reducers/accounts'; import type { Account, Attachment, Card, Emoji, Mention, Poll, EmbeddedEntity } from 'soapbox/types/entities'; @@ -25,6 +29,8 @@ export type StatusVisibility = 'public' | 'unlisted' | 'private' | 'direct' | 's /** Maximum number of times to recurse sub-entities. */ const MAX_DEPTH = 1; +const domParser = new DOMParser(); + // https://docs.joinmastodon.org/entities/status/ export const StatusRecord = ImmutableRecord({ account: null as EmbeddedEntity, @@ -190,6 +196,19 @@ const normalizeReblogQuote = (status: ImmutableMap, depth = 0) => { }); }; +const addInternalFields = (status: ImmutableMap) => { + const spoilerText = status.get('spoiler_text'); + const searchContent = buildSearchContent(status); + const emojiMap = makeEmojiMap(status.get('emojis')); + + 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: expandSpoilers ? false : spoilerText.length > 0 || status.get('sensitive'), + }); +}; + export const normalizeStatus = (status: Record, depth = 0) => { return StatusRecord( ImmutableMap(fromJS(status)).withMutations(status => { @@ -205,6 +224,7 @@ export const normalizeStatus = (status: Record, depth = 0) => { fixFiltered(status); fixSensitivity(status); normalizeReblogQuote(status, depth); + addInternalFields(status); }), ); }; diff --git a/app/soapbox/utils/normalizers.ts b/app/soapbox/utils/normalizers.ts index 6604f1caa..e550889e6 100644 --- a/app/soapbox/utils/normalizers.ts +++ b/app/soapbox/utils/normalizers.ts @@ -1,3 +1,7 @@ +import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; + +import { unescapeHTML } from './html'; + /** Use new value only if old value is undefined */ export const mergeDefined = (oldVal: any, newVal: any) => oldVal === undefined ? newVal : oldVal; @@ -10,3 +14,32 @@ export const makeEmojiMap = (emojis: any) => emojis.reduce((obj: any, emoji: any export const normalizeId = (id: any): string | null => { return typeof id === 'string' ? id : null; }; + +/** Gets titles of poll options from status. */ +const getPollOptionTitles = (status: ImmutableMap): ImmutableList => { + const poll = status.get('poll'); + + if (poll && typeof poll === 'object') { + return poll.get('options').map((option: ImmutableMap) => option.get('title')); + } else { + return ImmutableList(); + } +}; + +/** Gets usernames of mentioned users from status. */ +const getMentionedUsernames = (status: ImmutableMap): ImmutableList => { + return status.get('mentions').map((mention: ImmutableMap) => `@${mention.get('acct')}`); +}; + +/** Creates search text from the status. */ +export const buildSearchContent = (status: ImmutableMap): string => { + const pollOptionTitles = getPollOptionTitles(status); + const mentionedUsernames = getMentionedUsernames(status); + + const fields = ImmutableList([ + status.get('spoiler_text'), + status.get('content'), + ]).concat(pollOptionTitles).concat(mentionedUsernames); + + return unescapeHTML(fields.join('\n\n')) || ''; +}; \ No newline at end of file