diff --git a/packages/pl-fe/src/components/parsed-content.tsx b/packages/pl-fe/src/components/parsed-content.tsx index 6c3a65625..61c2f6131 100644 --- a/packages/pl-fe/src/components/parsed-content.tsx +++ b/packages/pl-fe/src/components/parsed-content.tsx @@ -13,21 +13,39 @@ const nodesToText = (nodes: Array): string => interface IParsedContent { html: string; mentions?: Array; + /** Whether it's a status which has a quote. */ + hasQuote?: boolean; } -const ParsedContent: React.FC = (({ html, mentions }) => { +const ParsedContent: React.FC = (({ html, mentions, hasQuote }) => { return useMemo(() => { if (html.length === 0) { return null; } + const selectors: Array = []; + + // Explicit mentions + if (mentions) selectors.push('recipients-inline'); + + // Quote posting + if (hasQuote) selectors.push('quote-inline'); + const options: HTMLReactParserOptions = { replace(domNode) { - if (domNode instanceof Element && ['script', 'iframe'].includes(domNode.name)) { - return null; + if (!(domNode instanceof Element)) { + return; } - if (domNode instanceof Element && domNode.name === 'a') { + if (['script', 'iframe'].includes(domNode.name)) { + return <>; + } + + if (domNode.attribs.class?.split(' ').some(className => selectors.includes(className))) { + return <>; + } + + if (domNode.name === 'a') { const classes = domNode.attribs.class?.split(' '); const fallback = ( diff --git a/packages/pl-fe/src/components/status-content.tsx b/packages/pl-fe/src/components/status-content.tsx index 4c2e498a2..02f8f6103 100644 --- a/packages/pl-fe/src/components/status-content.tsx +++ b/packages/pl-fe/src/components/status-content.tsx @@ -176,7 +176,7 @@ const StatusContent: React.FC = React.memo(({ lang={status.language || undefined} size={textSize} > - + , ); } @@ -204,7 +204,7 @@ const StatusContent: React.FC = React.memo(({ lang={status.language || undefined} size={textSize} > - + , ); } diff --git a/packages/pl-fe/src/features/compose/components/reply-indicator.tsx b/packages/pl-fe/src/features/compose/components/reply-indicator.tsx index 27d4d3d9e..18195ec44 100644 --- a/packages/pl-fe/src/features/compose/components/reply-indicator.tsx +++ b/packages/pl-fe/src/features/compose/components/reply-indicator.tsx @@ -12,7 +12,7 @@ import type { Account, Status } from 'pl-fe/normalizers'; interface IReplyIndicator { className?: string; - status?: Pick & { account: Pick }; + status?: Pick & { account: Pick }; onCancel?: () => void; hideActions: boolean; } @@ -52,7 +52,7 @@ const ReplyIndicator: React.FC = ({ className, status, hideActi size='sm' direction={getTextDirection(status.search_index)} > - + {status.media_attachments.length > 0 && ( diff --git a/packages/pl-fe/src/features/ui/components/modals/compare-history-modal.tsx b/packages/pl-fe/src/features/ui/components/modals/compare-history-modal.tsx index 414a03fde..64db26be3 100644 --- a/packages/pl-fe/src/features/ui/components/modals/compare-history-modal.tsx +++ b/packages/pl-fe/src/features/ui/components/modals/compare-history-modal.tsx @@ -3,6 +3,7 @@ import { FormattedDate, FormattedMessage } from 'react-intl'; import { fetchHistory } from 'pl-fe/actions/history'; import AttachmentThumbs from 'pl-fe/components/attachment-thumbs'; +import { ParsedContent } from 'pl-fe/components/parsed-content'; import { HStack, Modal, Spinner, Stack, Text } from 'pl-fe/components/ui'; import { useAppDispatch, useAppSelector } from 'pl-fe/hooks'; @@ -18,6 +19,8 @@ const CompareHistoryModal: React.FC = const loading = useAppSelector(state => state.history.getIn([statusId, 'loading'])); const versions = useAppSelector(state => state.history.get(statusId)?.items); + const status = useAppSelector(state => state.statuses.get(statusId)); + const onClickClose = () => { onClose('COMPARE_HISTORY'); }; @@ -34,7 +37,7 @@ const CompareHistoryModal: React.FC = body = (
{versions?.map((version) => { - const content = { __html: version.contentHtml }; + const content = ; const spoilerContent = { __html: version.spoilerHtml }; const poll = typeof version.poll !== 'string' && version.poll; @@ -48,7 +51,9 @@ const CompareHistoryModal: React.FC = )} -
+
+ {content} +
{poll && (
diff --git a/packages/pl-fe/src/normalizers/status-edit.ts b/packages/pl-fe/src/normalizers/status-edit.ts index 1eb86553d..6b5d1ecdc 100644 --- a/packages/pl-fe/src/normalizers/status-edit.ts +++ b/packages/pl-fe/src/normalizers/status-edit.ts @@ -5,7 +5,6 @@ import escapeTextContentForBrowser from 'escape-html'; import DOMPurify from 'isomorphic-dompurify'; import emojify from 'pl-fe/features/emoji'; -import { stripCompatibilityFeatures } from 'pl-fe/utils/html'; import { makeEmojiMap } from 'pl-fe/utils/normalizers'; import { normalizePollEdit } from './poll'; @@ -20,7 +19,7 @@ const normalizeStatusEdit = (statusEdit: BaseStatusEdit) => { return { ...statusEdit, poll, - contentHtml: DOMPurify.sanitize(stripCompatibilityFeatures(emojify(statusEdit.content, emojiMap)), { ADD_ATTR: ['target'] }), + contentHtml: DOMPurify.sanitize(emojify(statusEdit.content, emojiMap), { ADD_ATTR: ['target'] }), spoilerHtml: DOMPurify.sanitize(emojify(escapeTextContentForBrowser(statusEdit.spoiler_text), emojiMap), { ADD_ATTR: ['target'] }), }; }; diff --git a/packages/pl-fe/src/normalizers/status.ts b/packages/pl-fe/src/normalizers/status.ts index fa2b6181e..e3e14f7bc 100644 --- a/packages/pl-fe/src/normalizers/status.ts +++ b/packages/pl-fe/src/normalizers/status.ts @@ -8,7 +8,7 @@ import DOMPurify from 'isomorphic-dompurify'; import { type Account as BaseAccount, type Status as BaseStatus, type MediaAttachment, mentionSchema, type Translation } from 'pl-api'; import emojify from 'pl-fe/features/emoji'; -import { stripCompatibilityFeatures, unescapeHTML } from 'pl-fe/utils/html'; +import { unescapeHTML } from 'pl-fe/utils/html'; import { makeEmojiMap } from 'pl-fe/utils/normalizers'; import { normalizeAccount } from './account'; @@ -62,7 +62,7 @@ const buildSearchContent = (status: Pick DOMPurify.sanitize(stripCompatibilityFeatures(emojify(text, emojiMap), hasQuote), { USE_PROFILES: { html: true } }); +const calculateContent = (text: string, emojiMap: any, hasQuote?: boolean) => DOMPurify.sanitize(emojify(text, emojiMap), { USE_PROFILES: { html: true } }); const calculateSpoiler = (text: string, emojiMap: any) => DOMPurify.sanitize(emojify(escapeTextContentForBrowser(text), emojiMap), { USE_PROFILES: { html: true } }); const calculateStatus = (status: BaseStatus, oldStatus?: OldStatus): CalculatedValues => { diff --git a/packages/pl-fe/src/normalizers/translation.ts b/packages/pl-fe/src/normalizers/translation.ts index 3fecf5d7d..d040d8b50 100644 --- a/packages/pl-fe/src/normalizers/translation.ts +++ b/packages/pl-fe/src/normalizers/translation.ts @@ -1,12 +1,11 @@ import emojify from 'pl-fe/features/emoji'; -import { stripCompatibilityFeatures } from 'pl-fe/utils/html'; import { makeEmojiMap } from 'pl-fe/utils/normalizers'; import type { Status, Translation as BaseTranslation } from 'pl-api'; const normalizeTranslation = (translation: BaseTranslation, status: Pick) => { const emojiMap = makeEmojiMap(status.emojis); - const content = stripCompatibilityFeatures(emojify(translation.content, emojiMap)); + const content = emojify(translation.content, emojiMap); return { ...translation, diff --git a/packages/pl-fe/src/utils/html.ts b/packages/pl-fe/src/utils/html.ts index e3c5b6300..930cb324b 100644 --- a/packages/pl-fe/src/utils/html.ts +++ b/packages/pl-fe/src/utils/html.ts @@ -6,29 +6,6 @@ const unescapeHTML = (html: string = ''): string => { return wrapper.textContent || ''; }; -/** Remove compatibility markup for features pl-fe supports. */ -const stripCompatibilityFeatures = (html: string, hasQuote = true): string => { - const node = document.createElement('div'); - node.innerHTML = html; - - const selectors = [ - // Explicit mentions - '.recipients-inline', - ]; - - // Quote posting - if (hasQuote) selectors.push('.quote-inline'); - - // Remove all instances of all selectors - selectors.forEach(selector => { - node.querySelectorAll(selector).forEach(elem => { - elem.remove(); - }); - }); - - return node.innerHTML; -}; - /** Convert HTML to plaintext. */ // https://stackoverflow.com/a/822486 const stripHTML = (html: string) => { @@ -39,6 +16,5 @@ const stripHTML = (html: string) => { export { unescapeHTML, - stripCompatibilityFeatures, stripHTML, };