import { useMemo } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Link } from 'react-router-dom';
import type { Map as ImmutableMap } from 'immutable';
import ArticleIcon from '@/material-icons/400-24px/article.svg?react';
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
import { Icon } from 'mastodon/components/icon';
import StatusContainer from 'mastodon/containers/status_container';
import type { Status } from 'mastodon/models/status';
import type { RootState } from 'mastodon/store';
import { useAppSelector } from 'mastodon/store';
import QuoteIcon from '../../images/quote.svg?react';
import { makeGetStatus } from '../selectors';
const MAX_QUOTE_POSTS_NESTING_LEVEL = 1;
const QuoteWrapper: React.FC<{
isError?: boolean;
children: React.ReactElement;
}> = ({ isError, children }) => {
return (
{children}
);
};
const QuoteLink: React.FC<{
status: Status;
}> = ({ status }) => {
const accountId = status.get('account') as string;
const account = useAppSelector((state) =>
accountId ? state.accounts.get(accountId) : undefined,
);
const quoteAuthorName = account?.display_name_html;
if (!quoteAuthorName) {
return null;
}
const quoteAuthorElement = (
);
const quoteUrl = `/@${account.get('acct')}/${status.get('id') as string}`;
return (
);
};
type QuoteMap = ImmutableMap<'state' | 'quoted_status', string | null>;
type GetStatusSelector = (
state: RootState,
props: { id?: string | null; contextType?: string },
) => Status | null;
export const QuotedStatus: React.FC<{
quote: QuoteMap;
contextType?: string;
variant?: 'full' | 'link';
nestingLevel?: number;
}> = ({ quote, contextType, nestingLevel = 1, variant = 'full' }) => {
const quotedStatusId = quote.get('quoted_status');
const quoteState = quote.get('state');
const status = useAppSelector((state) =>
quotedStatusId ? state.statuses.get(quotedStatusId) : undefined,
);
let quoteError: React.ReactNode = null;
// In order to find out whether the quoted post should be completely hidden
// due to a matching filter, we run it through the selector used by `status_container`.
// If this returns null even though `status` exists, it's because it's filtered.
const getStatus = useMemo(() => makeGetStatus(), []) as GetStatusSelector;
const statusWithExtraData = useAppSelector((state) =>
getStatus(state, { id: quotedStatusId, contextType }),
);
const isFilteredAndHidden = status && statusWithExtraData === null;
if (isFilteredAndHidden) {
quoteError = (
);
} else if (quoteState === 'deleted') {
quoteError = (
);
} else if (quoteState === 'unauthorized') {
quoteError = (
);
} else if (quoteState === 'pending') {
quoteError = (
);
} else if (quoteState === 'rejected' || quoteState === 'revoked') {
quoteError = (
);
} else if (!status || !quotedStatusId) {
quoteError = (
);
}
if (quoteError) {
return {quoteError};
}
if (variant === 'link' && status) {
return ;
}
const childQuote = status?.get('quote') as QuoteMap | undefined;
const canRenderChildQuote =
childQuote && nestingLevel <= MAX_QUOTE_POSTS_NESTING_LEVEL;
return (
{/* @ts-expect-error Status is not yet typed */}
{canRenderChildQuote && (
)}
);
};
interface StatusQuoteManagerProps {
id: string;
contextType?: string;
[key: string]: unknown;
}
/**
* This wrapper component takes a status ID and, if the associated status
* is a quote post, it renders the quote into `StatusContainer` as a child.
* It passes all other props through to `StatusContainer`.
*/
export const StatusQuoteManager = (props: StatusQuoteManagerProps) => {
const status = useAppSelector((state) => {
const status = state.statuses.get(props.id);
const reblogId = status?.get('reblog') as string | undefined;
return reblogId ? state.statuses.get(reblogId) : status;
});
const quote = status?.get('quote') as QuoteMap | undefined;
if (quote) {
return (
);
}
return ;
};