pl-fe: Move hashtags from last line of the post to a separate component
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
5f4dde2590
commit
42ed1c4141
8 changed files with 211 additions and 101 deletions
|
@ -1,4 +1,3 @@
|
|||
import clsx from 'clsx';
|
||||
import React from 'react';
|
||||
import { TransitionMotion, spring } from 'react-motion';
|
||||
|
||||
|
@ -40,7 +39,7 @@ const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, emoj
|
|||
return (
|
||||
<TransitionMotion styles={styles} willEnter={willEnter} willLeave={willLeave}>
|
||||
{items => (
|
||||
<div className={clsx('flex flex-wrap items-center gap-1', { 'reactions-bar--empty': visibleReactions.length === 0 })}>
|
||||
<div className='flex flex-wrap items-center gap-1'>
|
||||
{items.map(({ key, data, style }) => (
|
||||
<Reaction
|
||||
key={key}
|
||||
|
|
62
packages/pl-fe/src/components/hashtags-bar.tsx
Normal file
62
packages/pl-fe/src/components/hashtags-bar.tsx
Normal file
|
@ -0,0 +1,62 @@
|
|||
// Adapted from Mastodon https://github.com/mastodon/mastodon/blob/main/app/javascript/mastodon/components/hashtag_bar.tsx
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import HStack from './ui/hstack';
|
||||
import Text from './ui/text';
|
||||
|
||||
// Fit on a single line on desktop
|
||||
const VISIBLE_HASHTAGS = 3;
|
||||
|
||||
interface IHashtagsBar {
|
||||
hashtags: Array<string>;
|
||||
}
|
||||
|
||||
const HashtagsBar: React.FC<IHashtagsBar> = ({ hashtags }) => {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const handleClick = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
|
||||
setExpanded(true);
|
||||
}, []);
|
||||
|
||||
if (hashtags.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const revealedHashtags = expanded
|
||||
? hashtags
|
||||
: hashtags.slice(0, VISIBLE_HASHTAGS);
|
||||
|
||||
return (
|
||||
<HStack space={2} wrap>
|
||||
{revealedHashtags.map((hashtag) => (
|
||||
<Link
|
||||
key={hashtag}
|
||||
to={`/tags/${hashtag}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
className='flex items-center rounded-sm bg-gray-100 px-1.5 py-1 text-center text-xs font-medium text-primary-600 black:bg-primary-900 dark:bg-primary-700 dark:text-white'
|
||||
>
|
||||
<Text size='xs' weight='semibold' theme='inherit'>
|
||||
#<span>{hashtag}</span>
|
||||
</Text>
|
||||
</Link>
|
||||
))}
|
||||
|
||||
{!expanded && hashtags.length > VISIBLE_HASHTAGS && (
|
||||
<button onClick={handleClick}>
|
||||
<Text className='hover:underline' size='xs' weight='semibold' theme='muted'>
|
||||
<FormattedMessage
|
||||
id='hashtags.and_other'
|
||||
defaultMessage='…and {count, plural, other {# more}}'
|
||||
values={{ count: hashtags.length - VISIBLE_HASHTAGS }}
|
||||
/>
|
||||
</Text>
|
||||
</button>
|
||||
)}
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
|
||||
export { HashtagsBar as default };
|
|
@ -1,5 +1,8 @@
|
|||
/* eslint-disable no-redeclare */
|
||||
import parse, { Element, type HTMLReactParserOptions, domToReact, type DOMNode } from 'html-react-parser';
|
||||
import DOMPurify from 'isomorphic-dompurify';
|
||||
import groupBy from 'lodash/groupBy';
|
||||
import minBy from 'lodash/minBy';
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
|
@ -26,9 +29,47 @@ interface IParsedContent {
|
|||
emojis?: Array<CustomEmoji>;
|
||||
}
|
||||
|
||||
const ParsedContent: React.FC<IParsedContent> = React.memo(({ html, mentions, hasQuote, emojis }) => {
|
||||
// Adapted from Mastodon https://github.com/mastodon/mastodon/blob/main/app/javascript/mastodon/components/hashtag_bar.tsx
|
||||
const normalizeHashtag = (hashtag: string) =>(
|
||||
!!hashtag && hashtag.startsWith('#') ? hashtag.slice(1) : hashtag
|
||||
).normalize('NFKC');
|
||||
|
||||
const uniqueHashtagsWithCaseHandling = (hashtags: string[]) => {
|
||||
const groups = groupBy(hashtags, (tag) =>
|
||||
tag.normalize('NFKD').toLowerCase(),
|
||||
);
|
||||
|
||||
return Object.values(groups).map((tags) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- we know that the array has at least one element
|
||||
const firstTag = tags[0]!;
|
||||
|
||||
if (tags.length === 1) return firstTag;
|
||||
|
||||
// The best match is the one where we have the less difference between upper and lower case letter count
|
||||
const best = minBy(tags, (tag) => {
|
||||
const upperCase = Array.from(tag).reduce(
|
||||
(acc, char) => (acc += char.toUpperCase() === char ? 1 : 0),
|
||||
0,
|
||||
);
|
||||
|
||||
const lowerCase = tag.length - upperCase;
|
||||
|
||||
return Math.abs(lowerCase - upperCase);
|
||||
});
|
||||
|
||||
return best ?? firstTag;
|
||||
});
|
||||
};
|
||||
|
||||
function parseContent(props: IParsedContent): ReturnType<typeof domToReact>;
|
||||
function parseContent(props: IParsedContent, extractHashtags: true): {
|
||||
hashtags: Array<string>;
|
||||
content: ReturnType<typeof domToReact>;
|
||||
};
|
||||
|
||||
function parseContent({ html, mentions, hasQuote, emojis }: IParsedContent, extractHashtags = false) {
|
||||
if (html.length === 0) {
|
||||
return null;
|
||||
return extractHashtags ? { content: null, hashtags: [] } : null;
|
||||
}
|
||||
|
||||
const emojiMap = emojis ? makeEmojiMap(emojis) : undefined;
|
||||
|
@ -41,8 +82,10 @@ const ParsedContent: React.FC<IParsedContent> = React.memo(({ html, mentions, ha
|
|||
// Quote posting
|
||||
if (hasQuote) selectors.push('quote-inline');
|
||||
|
||||
const hashtags: Array<string> = [];
|
||||
|
||||
const options: HTMLReactParserOptions = {
|
||||
replace(domNode) {
|
||||
replace(domNode, index) {
|
||||
if (!(domNode instanceof Element)) {
|
||||
return;
|
||||
}
|
||||
|
@ -104,6 +147,25 @@ const ParsedContent: React.FC<IParsedContent> = React.memo(({ html, mentions, ha
|
|||
|
||||
return fallback;
|
||||
}
|
||||
|
||||
if (extractHashtags && domNode.type === 'tag' && domNode.parent === null && domNode.next === null) {
|
||||
for (const child of domNode.children) {
|
||||
switch (child.type) {
|
||||
case 'text':
|
||||
if (child.data.trim().length) return;
|
||||
break;
|
||||
case 'tag':
|
||||
if (child.name !== 'a') return;
|
||||
if (!child.attribs.class?.split(' ').includes('hashtag')) return;
|
||||
hashtags.push(normalizeHashtag(nodesToText([child])));
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return <></>;
|
||||
}
|
||||
},
|
||||
|
||||
transform(reactNode, _domNode, index) {
|
||||
|
@ -115,7 +177,16 @@ const ParsedContent: React.FC<IParsedContent> = React.memo(({ html, mentions, ha
|
|||
},
|
||||
};
|
||||
|
||||
return parse(DOMPurify.sanitize(html, { ADD_ATTR: ['target'], USE_PROFILES: { html: true } }), options);
|
||||
}, (prevProps, nextProps) => prevProps.html === nextProps.html);
|
||||
const content = parse(DOMPurify.sanitize(html, { ADD_ATTR: ['target'], USE_PROFILES: { html: true } }), options);
|
||||
|
||||
export { ParsedContent };
|
||||
if (extractHashtags) return {
|
||||
content,
|
||||
hashtags: uniqueHashtagsWithCaseHandling(hashtags),
|
||||
};
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
const ParsedContent: React.FC<IParsedContent> = React.memo((props) => parseContent(props), (prevProps, nextProps) => prevProps.html === nextProps.html);
|
||||
|
||||
export { ParsedContent, parseContent };
|
||||
|
|
|
@ -100,7 +100,7 @@ const QuotedStatus: React.FC<IQuotedStatus> = ({ status, onCancel, compose }) =>
|
|||
<StatusContent
|
||||
status={status}
|
||||
collapsable
|
||||
quote
|
||||
isQuote
|
||||
/>
|
||||
|
||||
{status.quote_id && <QuotedStatusIndicator statusId={status.quote_id} />}
|
||||
|
|
|
@ -8,15 +8,20 @@ import Button from 'pl-fe/components/ui/button';
|
|||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import Emojify from 'pl-fe/features/emoji/emojify';
|
||||
import QuotedStatus from 'pl-fe/features/status/containers/quoted-status-container';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { useSettings } from 'pl-fe/hooks/use-settings';
|
||||
import { onlyEmoji as isOnlyEmoji } from 'pl-fe/utils/rich-content';
|
||||
|
||||
import { getTextDirection } from '../utils/rtl';
|
||||
|
||||
import HashtagsBar from './hashtags-bar';
|
||||
import Markup from './markup';
|
||||
import { ParsedContent } from './parsed-content';
|
||||
import { parseContent } from './parsed-content';
|
||||
import Poll from './polls/poll';
|
||||
import StatusMedia from './status-media';
|
||||
import SensitiveContentOverlay from './statuses/sensitive-content-overlay';
|
||||
import TranslateButton from './translate-button';
|
||||
|
||||
import type { Sizes } from 'pl-fe/components/ui/text';
|
||||
import type { MinifiedStatus } from 'pl-fe/reducers/statuses';
|
||||
|
@ -60,8 +65,9 @@ interface IStatusContent {
|
|||
collapsable?: boolean;
|
||||
translatable?: boolean;
|
||||
textSize?: Sizes;
|
||||
quote?: boolean;
|
||||
isQuote?: boolean;
|
||||
preview?: boolean;
|
||||
withMedia?: boolean;
|
||||
}
|
||||
|
||||
/** Renders the text content of a status */
|
||||
|
@ -71,8 +77,9 @@ const StatusContent: React.FC<IStatusContent> = React.memo(({
|
|||
collapsable = false,
|
||||
translatable,
|
||||
textSize = 'md',
|
||||
quote = false,
|
||||
isQuote = false,
|
||||
preview,
|
||||
withMedia,
|
||||
}) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const { displaySpoilers } = useSettings();
|
||||
|
@ -89,7 +96,7 @@ const StatusContent: React.FC<IStatusContent> = React.memo(({
|
|||
|
||||
if ((collapsable || preview) && !collapsed) {
|
||||
// 20px * x lines (+ 2px padding at the top)
|
||||
if (node.current.clientHeight > (preview ? 82 : quote ? 202 : 282)) {
|
||||
if (node.current.clientHeight > (preview ? 82 : isQuote ? 202 : 282)) {
|
||||
setCollapsed(true);
|
||||
}
|
||||
}
|
||||
|
@ -126,6 +133,13 @@ const StatusContent: React.FC<IStatusContent> = React.memo(({
|
|||
[status.content, status.translation, status.currentLanguage],
|
||||
);
|
||||
|
||||
const { content: parsedContent, hashtags } = useMemo(() => parseContent({
|
||||
html: content,
|
||||
mentions: status.mentions,
|
||||
hasQuote: !!status.quote_id,
|
||||
emojis: status.emojis,
|
||||
}, true), [content]);
|
||||
|
||||
useEffect(() => {
|
||||
setLineClamp(!spoilerNode.current || spoilerNode.current.clientHeight >= 96);
|
||||
}, [spoilerNode.current]);
|
||||
|
@ -140,8 +154,8 @@ const StatusContent: React.FC<IStatusContent> = React.memo(({
|
|||
const className = clsx('relative text-ellipsis break-words text-gray-900 focus:outline-none dark:text-gray-100', {
|
||||
'cursor-pointer': onClick,
|
||||
'overflow-hidden': collapsed,
|
||||
'max-h-[200px]': collapsed && !quote && !preview,
|
||||
'max-h-[120px]': collapsed && quote,
|
||||
'max-h-[200px]': collapsed && !isQuote && !preview,
|
||||
'max-h-[120px]': collapsed && isQuote,
|
||||
'max-h-[80px]': collapsed && preview,
|
||||
'leading-normal big-emoji': onlyEmoji,
|
||||
});
|
||||
|
@ -177,6 +191,33 @@ const StatusContent: React.FC<IStatusContent> = React.memo(({
|
|||
|
||||
if (expandable && !expanded) return <>{output}</>;
|
||||
|
||||
let quote;
|
||||
|
||||
if (withMedia && status.quote_id) {
|
||||
if ((status.quote_visible ?? true) === false) {
|
||||
quote = (
|
||||
<div className='quoted-status-tombstone'>
|
||||
<p><FormattedMessage id='statuses.quote_tombstone' defaultMessage='Post is unavailable.' /></p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
quote = <QuotedStatus statusId={status.quote_id} />;
|
||||
}
|
||||
}
|
||||
|
||||
const media = withMedia && ((quote || status.card || status.media_attachments.length > 0)) && (
|
||||
<Stack space={4}>
|
||||
{(status.media_attachments.length > 0 || (status.card && !quote)) && (
|
||||
<div className='relative'>
|
||||
<SensitiveContentOverlay status={status} />
|
||||
<StatusMedia status={status} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{quote}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
if (onClick) {
|
||||
if (status.content) {
|
||||
output.push(
|
||||
|
@ -189,7 +230,7 @@ const StatusContent: React.FC<IStatusContent> = React.memo(({
|
|||
lang={status.language || undefined}
|
||||
size={textSize}
|
||||
>
|
||||
<ParsedContent html={content} mentions={status.mentions} hasQuote={!!status.quote_id} emojis={status.emojis} />
|
||||
{parsedContent}
|
||||
</Markup>,
|
||||
);
|
||||
}
|
||||
|
@ -197,13 +238,25 @@ const StatusContent: React.FC<IStatusContent> = React.memo(({
|
|||
const hasPoll = !!status.poll_id;
|
||||
|
||||
if (collapsed) {
|
||||
output.push(<ReadMoreButton onClick={onClick} key='read-more' quote={quote} poll={hasPoll} />);
|
||||
output.push(<ReadMoreButton onClick={onClick} key='read-more' quote={isQuote} poll={hasPoll} />);
|
||||
}
|
||||
|
||||
if (status.poll_id) {
|
||||
output.push(<Poll id={status.poll_id} key='poll' status={status} />);
|
||||
}
|
||||
|
||||
if (translatable) {
|
||||
output.push(<TranslateButton status={status} />);
|
||||
}
|
||||
|
||||
if (media) {
|
||||
output.push(media);
|
||||
}
|
||||
|
||||
if (hashtags.length) {
|
||||
output.push(<HashtagsBar key='hashtags' hashtags={hashtags} />);
|
||||
}
|
||||
|
||||
return <Stack space={4} className={clsx({ 'bg-gray-100 dark:bg-primary-800 rounded-md p-4': hasPoll })}>{output}</Stack>;
|
||||
} else {
|
||||
if (status.content) {
|
||||
|
@ -217,19 +270,23 @@ const StatusContent: React.FC<IStatusContent> = React.memo(({
|
|||
lang={status.language || undefined}
|
||||
size={textSize}
|
||||
>
|
||||
<ParsedContent html={content} mentions={status.mentions} hasQuote={!!status.quote_id} emojis={status.emojis} />
|
||||
{parsedContent}
|
||||
</Markup>,
|
||||
);
|
||||
}
|
||||
|
||||
if (collapsed) {
|
||||
output.push(<ReadMoreButton onClick={() => {}} key='read-more' quote={quote} preview={preview} />);
|
||||
output.push(<ReadMoreButton onClick={() => {}} key='read-more' quote={isQuote} preview={preview} />);
|
||||
}
|
||||
|
||||
if (status.poll_id) {
|
||||
output.push(<Poll id={status.poll_id} key='poll' status={status} />);
|
||||
}
|
||||
|
||||
if (translatable) {
|
||||
output.push(<TranslateButton status={status} />);
|
||||
}
|
||||
|
||||
return <>{output}</>;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -6,7 +6,6 @@ import { Link, useHistory } from 'react-router-dom';
|
|||
import { mentionCompose, replyCompose } from 'pl-fe/actions/compose';
|
||||
import { toggleFavourite, toggleReblog } from 'pl-fe/actions/interactions';
|
||||
import { toggleStatusMediaHidden, unfilterStatus } from 'pl-fe/actions/statuses';
|
||||
import TranslateButton from 'pl-fe/components/translate-button';
|
||||
import Card from 'pl-fe/components/ui/card';
|
||||
import Icon from 'pl-fe/components/ui/icon';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
|
@ -14,7 +13,6 @@ import Text from 'pl-fe/components/ui/text';
|
|||
import AccountContainer from 'pl-fe/containers/account-container';
|
||||
import Emojify from 'pl-fe/features/emoji/emojify';
|
||||
import StatusTypeIcon from 'pl-fe/features/status/components/status-type-icon';
|
||||
import QuotedStatus from 'pl-fe/features/status/containers/quoted-status-container';
|
||||
import { HotKeys } from 'pl-fe/features/ui/components/hotkeys';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||
|
@ -27,10 +25,8 @@ import EventPreview from './event-preview';
|
|||
import StatusActionBar from './status-action-bar';
|
||||
import StatusContent from './status-content';
|
||||
import StatusLanguagePicker from './status-language-picker';
|
||||
import StatusMedia from './status-media';
|
||||
import StatusReactionsBar from './status-reactions-bar';
|
||||
import StatusReplyMentions from './status-reply-mentions';
|
||||
import SensitiveContentOverlay from './statuses/sensitive-content-overlay';
|
||||
import StatusInfo from './statuses/status-info';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -341,20 +337,6 @@ const Status: React.FC<IStatus> = (props) => {
|
|||
);
|
||||
}
|
||||
|
||||
let quote;
|
||||
|
||||
if (actualStatus.quote_id) {
|
||||
if ((actualStatus.quote_visible ?? true) === false) {
|
||||
quote = (
|
||||
<div className='quoted-status-tombstone'>
|
||||
<p><FormattedMessage id='statuses.quote_tombstone' defaultMessage='Post is unavailable.' /></p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
quote = <QuotedStatus statusId={actualStatus.quote_id} />;
|
||||
}
|
||||
}
|
||||
|
||||
const handlers = muted ? undefined : {
|
||||
reply: handleHotkeyReply,
|
||||
favourite: handleHotkeyFavourite,
|
||||
|
@ -423,26 +405,8 @@ const Status: React.FC<IStatus> = (props) => {
|
|||
onClick={handleClick}
|
||||
collapsable
|
||||
translatable
|
||||
withMedia
|
||||
/>
|
||||
|
||||
<TranslateButton status={actualStatus} />
|
||||
|
||||
{(quote || actualStatus.card || actualStatus.media_attachments.length > 0) && (
|
||||
<Stack space={4}>
|
||||
{(actualStatus.media_attachments.length > 0 || (actualStatus.card && !quote)) && (
|
||||
<div className='relative'>
|
||||
<SensitiveContentOverlay status={actualStatus} />
|
||||
<StatusMedia
|
||||
status={actualStatus}
|
||||
muted={muted}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{quote}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
|
|
|
@ -4,13 +4,10 @@ import { FormattedDate, FormattedMessage, useIntl } from 'react-intl';
|
|||
import { fetchStatus } from 'pl-fe/actions/statuses';
|
||||
import MissingIndicator from 'pl-fe/components/missing-indicator';
|
||||
import StatusContent from 'pl-fe/components/status-content';
|
||||
import StatusMedia from 'pl-fe/components/status-media';
|
||||
import TranslateButton from 'pl-fe/components/translate-button';
|
||||
import HStack from 'pl-fe/components/ui/hstack';
|
||||
import Icon from 'pl-fe/components/ui/icon';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import QuotedStatus from 'pl-fe/features/status/containers/quoted-status-container';
|
||||
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
|
||||
import { useAppSelector } from 'pl-fe/hooks/use-app-selector';
|
||||
import { usePlFeConfig } from 'pl-fe/hooks/use-pl-fe-config';
|
||||
|
@ -185,18 +182,10 @@ const EventInformation: React.FC<IEventInformation> = ({ params }) => {
|
|||
<FormattedMessage id='event.description' defaultMessage='Description' />
|
||||
</Text>
|
||||
|
||||
<StatusContent status={status} translatable />
|
||||
|
||||
<TranslateButton status={status} />
|
||||
<StatusContent status={status} translatable withMedia />
|
||||
</Stack>
|
||||
)}
|
||||
|
||||
<StatusMedia status={status} />
|
||||
|
||||
{status.quote_id && (status.quote_visible ?? true) && (
|
||||
<QuotedStatus statusId={status.quote_id} />
|
||||
)}
|
||||
|
||||
{renderEventLocation()}
|
||||
|
||||
{renderEventDate()}
|
||||
|
|
|
@ -5,18 +5,14 @@ import { Link } from 'react-router-dom';
|
|||
import Account from 'pl-fe/components/account';
|
||||
import StatusContent from 'pl-fe/components/status-content';
|
||||
import StatusLanguagePicker from 'pl-fe/components/status-language-picker';
|
||||
import StatusMedia from 'pl-fe/components/status-media';
|
||||
import StatusReactionsBar from 'pl-fe/components/status-reactions-bar';
|
||||
import StatusReplyMentions from 'pl-fe/components/status-reply-mentions';
|
||||
import SensitiveContentOverlay from 'pl-fe/components/statuses/sensitive-content-overlay';
|
||||
import StatusInfo from 'pl-fe/components/statuses/status-info';
|
||||
import TranslateButton from 'pl-fe/components/translate-button';
|
||||
import HStack from 'pl-fe/components/ui/hstack';
|
||||
import Icon from 'pl-fe/components/ui/icon';
|
||||
import Stack from 'pl-fe/components/ui/stack';
|
||||
import Text from 'pl-fe/components/ui/text';
|
||||
import Emojify from 'pl-fe/features/emoji/emojify';
|
||||
import QuotedStatus from 'pl-fe/features/status/containers/quoted-status-container';
|
||||
|
||||
import StatusInteractionBar from './status-interaction-bar';
|
||||
import StatusTypeIcon from './status-type-icon';
|
||||
|
@ -86,20 +82,6 @@ const DetailedStatus: React.FC<IDetailedStatus> = ({
|
|||
const { account } = actualStatus;
|
||||
if (!account || typeof account !== 'object') return null;
|
||||
|
||||
let quote;
|
||||
|
||||
if (actualStatus.quote_id) {
|
||||
if (actualStatus.quote_visible === false) {
|
||||
quote = (
|
||||
<div className='quoted-actualStatus-tombstone'>
|
||||
<p><FormattedMessage id='status.quote_tombstone' defaultMessage='Post is unavailable.' /></p>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
quote = <QuotedStatus statusId={actualStatus.quote_id} />;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='border-box'>
|
||||
<div ref={node} className='detailed-actualStatus' tabIndex={-1}>
|
||||
|
@ -123,22 +105,8 @@ const DetailedStatus: React.FC<IDetailedStatus> = ({
|
|||
status={actualStatus}
|
||||
textSize='lg'
|
||||
translatable
|
||||
withMedia
|
||||
/>
|
||||
|
||||
<TranslateButton status={actualStatus} />
|
||||
|
||||
{(withMedia && (quote || actualStatus.card || actualStatus.media_attachments.length > 0)) && (
|
||||
<Stack space={4}>
|
||||
{(actualStatus.media_attachments.length > 0 || (actualStatus.card && !quote)) && (
|
||||
<div className='relative'>
|
||||
<SensitiveContentOverlay status={status} />
|
||||
<StatusMedia status={actualStatus} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{quote}
|
||||
</Stack>
|
||||
)}
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
|
|
Loading…
Reference in a new issue