Add rendering of quote posts in web UI (#34738)
This commit is contained in:
parent
f1a6f4333a
commit
97b9e8849d
14 changed files with 219 additions and 43 deletions
|
@ -69,6 +69,10 @@ export function importFetchedStatuses(statuses) {
|
||||||
processStatus(status.reblog);
|
processStatus(status.reblog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status.quote?.quoted_status) {
|
||||||
|
processStatus(status.quote.quoted_status);
|
||||||
|
}
|
||||||
|
|
||||||
if (status.poll?.id) {
|
if (status.poll?.id) {
|
||||||
pushUnique(polls, createPollFromServerJSON(status.poll, getState().polls[status.poll.id]));
|
pushUnique(polls, createPollFromServerJSON(status.poll, getState().polls[status.poll.id]));
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,12 +23,20 @@ export function normalizeFilterResult(result) {
|
||||||
|
|
||||||
export function normalizeStatus(status, normalOldStatus) {
|
export function normalizeStatus(status, normalOldStatus) {
|
||||||
const normalStatus = { ...status };
|
const normalStatus = { ...status };
|
||||||
|
|
||||||
normalStatus.account = status.account.id;
|
normalStatus.account = status.account.id;
|
||||||
|
|
||||||
if (status.reblog && status.reblog.id) {
|
if (status.reblog && status.reblog.id) {
|
||||||
normalStatus.reblog = status.reblog.id;
|
normalStatus.reblog = status.reblog.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status.quote?.quoted_status ?? status.quote?.quoted_status_id) {
|
||||||
|
normalStatus.quote = {
|
||||||
|
...status.quote,
|
||||||
|
quoted_status: status.quote.quoted_status?.id ?? status.quote?.quoted_status_id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (status.poll && status.poll.id) {
|
if (status.poll && status.poll.id) {
|
||||||
normalStatus.poll = status.poll.id;
|
normalStatus.poll = status.poll.id;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,14 +5,12 @@ import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
import { HotKeys } from 'react-hotkeys';
|
import { HotKeys } from 'react-hotkeys';
|
||||||
|
|
||||||
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
|
||||||
import PushPinIcon from '@/material-icons/400-24px/push_pin.svg?react';
|
|
||||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||||
import { ContentWarning } from 'mastodon/components/content_warning';
|
import { ContentWarning } from 'mastodon/components/content_warning';
|
||||||
import { FilterWarning } from 'mastodon/components/filter_warning';
|
import { FilterWarning } from 'mastodon/components/filter_warning';
|
||||||
|
@ -88,6 +86,7 @@ class Status extends ImmutablePureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
status: ImmutablePropTypes.map,
|
status: ImmutablePropTypes.map,
|
||||||
account: ImmutablePropTypes.record,
|
account: ImmutablePropTypes.record,
|
||||||
|
children: PropTypes.node,
|
||||||
previousId: PropTypes.string,
|
previousId: PropTypes.string,
|
||||||
nextInReplyToId: PropTypes.string,
|
nextInReplyToId: PropTypes.string,
|
||||||
rootId: PropTypes.string,
|
rootId: PropTypes.string,
|
||||||
|
@ -115,6 +114,7 @@ class Status extends ImmutablePureComponent {
|
||||||
onMoveUp: PropTypes.func,
|
onMoveUp: PropTypes.func,
|
||||||
onMoveDown: PropTypes.func,
|
onMoveDown: PropTypes.func,
|
||||||
showThread: PropTypes.bool,
|
showThread: PropTypes.bool,
|
||||||
|
isQuotedPost: PropTypes.bool,
|
||||||
getScrollPosition: PropTypes.func,
|
getScrollPosition: PropTypes.func,
|
||||||
updateScrollBottom: PropTypes.func,
|
updateScrollBottom: PropTypes.func,
|
||||||
cacheMediaWidth: PropTypes.func,
|
cacheMediaWidth: PropTypes.func,
|
||||||
|
@ -372,7 +372,7 @@ class Status extends ImmutablePureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, hidden, featured, unfocusable, unread, showThread, scrollKey, pictureInPicture, previousId, nextInReplyToId, rootId, skipPrepend, avatarSize = 46 } = this.props;
|
const { intl, hidden, featured, unfocusable, unread, showThread, isQuotedPost = false, scrollKey, pictureInPicture, previousId, nextInReplyToId, rootId, skipPrepend, avatarSize = 46, children } = this.props;
|
||||||
|
|
||||||
let { status, account, ...other } = this.props;
|
let { status, account, ...other } = this.props;
|
||||||
|
|
||||||
|
@ -543,7 +543,7 @@ class Status extends ImmutablePureComponent {
|
||||||
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted || unfocusable ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef} data-nosnippet={status.getIn(['account', 'noindex'], true) || undefined}>
|
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted || unfocusable ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef} data-nosnippet={status.getIn(['account', 'noindex'], true) || undefined}>
|
||||||
{!skipPrepend && prepend}
|
{!skipPrepend && prepend}
|
||||||
|
|
||||||
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted })} data-id={status.get('id')}>
|
<div className={classNames('status', `status-${status.get('visibility')}`, { 'status-reply': !!status.get('in_reply_to_id'), 'status--in-thread': !!rootId, 'status--first-in-thread': previousId && (!connectUp || connectToRoot), muted: this.props.muted, 'status--is-quote': isQuotedPost })} data-id={status.get('id')}>
|
||||||
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
|
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
|
||||||
|
|
||||||
<div onClick={this.handleHeaderClick} onAuxClick={this.handleHeaderClick} className='status__info'>
|
<div onClick={this.handleHeaderClick} onAuxClick={this.handleHeaderClick} className='status__info'>
|
||||||
|
@ -576,12 +576,16 @@ class Status extends ImmutablePureComponent {
|
||||||
{...statusContentProps}
|
{...statusContentProps}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
|
||||||
{media}
|
{media}
|
||||||
{hashtagBar}
|
{hashtagBar}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<StatusActionBar scrollKey={scrollKey} status={status} account={account} {...other} />
|
{!isQuotedPost &&
|
||||||
|
<StatusActionBar scrollKey={scrollKey} status={status} account={account} {...other} />
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</HotKeys>
|
</HotKeys>
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { TIMELINE_GAP, TIMELINE_SUGGESTIONS } from 'mastodon/actions/timelines';
|
||||||
import { RegenerationIndicator } from 'mastodon/components/regeneration_indicator';
|
import { RegenerationIndicator } from 'mastodon/components/regeneration_indicator';
|
||||||
import { InlineFollowSuggestions } from 'mastodon/features/home_timeline/components/inline_follow_suggestions';
|
import { InlineFollowSuggestions } from 'mastodon/features/home_timeline/components/inline_follow_suggestions';
|
||||||
|
|
||||||
import StatusContainer from '../containers/status_container';
|
import { StatusQuoteManager } from '../components/status_quoted';
|
||||||
|
|
||||||
import { LoadGap } from './load_gap';
|
import { LoadGap } from './load_gap';
|
||||||
import ScrollableList from './scrollable_list';
|
import ScrollableList from './scrollable_list';
|
||||||
|
@ -113,7 +113,7 @@ export default class StatusList extends ImmutablePureComponent {
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
key={statusId}
|
key={statusId}
|
||||||
id={statusId}
|
id={statusId}
|
||||||
onMoveUp={this.handleMoveUp}
|
onMoveUp={this.handleMoveUp}
|
||||||
|
@ -130,7 +130,7 @@ export default class StatusList extends ImmutablePureComponent {
|
||||||
|
|
||||||
if (scrollableContent && featuredStatusIds) {
|
if (scrollableContent && featuredStatusIds) {
|
||||||
scrollableContent = featuredStatusIds.map(statusId => (
|
scrollableContent = featuredStatusIds.map(statusId => (
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
key={`f-${statusId}`}
|
key={`f-${statusId}`}
|
||||||
id={statusId}
|
id={statusId}
|
||||||
featured
|
featured
|
||||||
|
|
117
app/javascript/mastodon/components/status_quoted.tsx
Normal file
117
app/javascript/mastodon/components/status_quoted.tsx
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import type { Map as ImmutableMap } from 'immutable';
|
||||||
|
|
||||||
|
import { Icon } from 'mastodon/components/icon';
|
||||||
|
import StatusContainer from 'mastodon/containers/status_container';
|
||||||
|
import { useAppSelector } from 'mastodon/store';
|
||||||
|
|
||||||
|
import QuoteIcon from '../../images/quote.svg?react';
|
||||||
|
|
||||||
|
const QuoteWrapper: React.FC<{
|
||||||
|
isError?: boolean;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}> = ({ isError, children }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classNames('status__quote', {
|
||||||
|
'status__quote--error': isError,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<Icon id='quote' icon={QuoteIcon} className='status__quote-icon' />
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type QuoteMap = ImmutableMap<'state' | 'quoted_status', string | null>;
|
||||||
|
|
||||||
|
export const QuotedStatus: React.FC<{ quote: QuoteMap }> = ({ quote }) => {
|
||||||
|
const quotedStatusId = quote.get('quoted_status');
|
||||||
|
const state = quote.get('state');
|
||||||
|
const status = useAppSelector((state) =>
|
||||||
|
quotedStatusId ? state.statuses.get(quotedStatusId) : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
let quoteError: React.ReactNode | null = null;
|
||||||
|
|
||||||
|
if (state === 'deleted') {
|
||||||
|
quoteError = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='status.quote_error.removed'
|
||||||
|
defaultMessage='This post was removed by its author.'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (state === 'unauthorized') {
|
||||||
|
quoteError = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='status.quote_error.unauthorized'
|
||||||
|
defaultMessage='This post cannot be displayed as you are not authorized to view it.'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (state === 'pending') {
|
||||||
|
quoteError = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='status.quote_error.pending_approval'
|
||||||
|
defaultMessage='This post is pending approval from the original author.'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (state === 'rejected' || state === 'revoked') {
|
||||||
|
quoteError = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='status.quote_error.rejected'
|
||||||
|
defaultMessage='This post cannot be displayed as the original author does not allow it to be quoted.'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if (!status || !quotedStatusId) {
|
||||||
|
quoteError = (
|
||||||
|
<FormattedMessage
|
||||||
|
id='status.quote_error.not_found'
|
||||||
|
defaultMessage='This post cannot be displayed.'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (quoteError) {
|
||||||
|
return <QuoteWrapper isError>{quoteError}</QuoteWrapper>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QuoteWrapper>
|
||||||
|
<StatusContainer
|
||||||
|
// @ts-expect-error Status isn't typed yet
|
||||||
|
isQuotedPost
|
||||||
|
id={quotedStatusId}
|
||||||
|
avatarSize={40}
|
||||||
|
/>
|
||||||
|
</QuoteWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface StatusQuoteManagerProps {
|
||||||
|
id: 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) => state.statuses.get(props.id));
|
||||||
|
const quote = status?.get('quote') as QuoteMap | undefined;
|
||||||
|
|
||||||
|
if (quote) {
|
||||||
|
return (
|
||||||
|
<StatusContainer {...props}>
|
||||||
|
<QuotedStatus quote={quote} />
|
||||||
|
</StatusContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <StatusContainer {...props} />;
|
||||||
|
};
|
|
@ -14,7 +14,7 @@ import { Account } from 'mastodon/components/account';
|
||||||
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
import { ColumnBackButton } from 'mastodon/components/column_back_button';
|
||||||
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
|
||||||
import { RemoteHint } from 'mastodon/components/remote_hint';
|
import { RemoteHint } from 'mastodon/components/remote_hint';
|
||||||
import StatusContainer from 'mastodon/containers/status_container';
|
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
|
||||||
import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header';
|
import { AccountHeader } from 'mastodon/features/account_timeline/components/account_header';
|
||||||
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
|
||||||
import Column from 'mastodon/features/ui/components/column';
|
import Column from 'mastodon/features/ui/components/column';
|
||||||
|
@ -142,9 +142,8 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||||
/>
|
/>
|
||||||
</h4>
|
</h4>
|
||||||
{featuredStatusIds.map((statusId) => (
|
{featuredStatusIds.map((statusId) => (
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
key={`f-${statusId}`}
|
key={`f-${statusId}`}
|
||||||
// @ts-expect-error inferred props are wrong
|
|
||||||
id={statusId}
|
id={statusId}
|
||||||
contextType='account'
|
contextType='account'
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -20,7 +20,7 @@ import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||||
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
|
||||||
import { Account } from 'mastodon/components/account';
|
import { Account } from 'mastodon/components/account';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import StatusContainer from 'mastodon/containers/status_container';
|
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
|
||||||
import { me } from 'mastodon/initial_state';
|
import { me } from 'mastodon/initial_state';
|
||||||
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
|
||||||
|
|
||||||
|
@ -175,7 +175,7 @@ class Notification extends ImmutablePureComponent {
|
||||||
|
|
||||||
renderMention (notification) {
|
renderMention (notification) {
|
||||||
return (
|
return (
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
id={notification.get('status')}
|
id={notification.get('status')}
|
||||||
withDismiss
|
withDismiss
|
||||||
hidden={this.props.hidden}
|
hidden={this.props.hidden}
|
||||||
|
@ -205,7 +205,7 @@ class Notification extends ImmutablePureComponent {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
id={notification.get('status')}
|
id={notification.get('status')}
|
||||||
account={notification.get('account')}
|
account={notification.get('account')}
|
||||||
muted
|
muted
|
||||||
|
@ -235,7 +235,7 @@ class Notification extends ImmutablePureComponent {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
id={notification.get('status')}
|
id={notification.get('status')}
|
||||||
account={notification.get('account')}
|
account={notification.get('account')}
|
||||||
muted
|
muted
|
||||||
|
@ -269,7 +269,7 @@ class Notification extends ImmutablePureComponent {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
id={notification.get('status')}
|
id={notification.get('status')}
|
||||||
account={notification.get('account')}
|
account={notification.get('account')}
|
||||||
contextType='notifications'
|
contextType='notifications'
|
||||||
|
@ -304,7 +304,7 @@ class Notification extends ImmutablePureComponent {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
id={notification.get('status')}
|
id={notification.get('status')}
|
||||||
account={notification.get('account')}
|
account={notification.get('account')}
|
||||||
contextType='notifications'
|
contextType='notifications'
|
||||||
|
@ -345,7 +345,7 @@ class Notification extends ImmutablePureComponent {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
id={notification.get('status')}
|
id={notification.get('status')}
|
||||||
account={account}
|
account={account}
|
||||||
contextType='notifications'
|
contextType='notifications'
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {
|
||||||
} from 'mastodon/actions/statuses';
|
} from 'mastodon/actions/statuses';
|
||||||
import type { IconProp } from 'mastodon/components/icon';
|
import type { IconProp } from 'mastodon/components/icon';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import Status from 'mastodon/containers/status_container';
|
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
|
||||||
import { getStatusHidden } from 'mastodon/selectors/filters';
|
import { getStatusHidden } from 'mastodon/selectors/filters';
|
||||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||||
|
|
||||||
|
@ -102,8 +102,7 @@ export const NotificationWithStatus: React.FC<{
|
||||||
{label}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Status
|
<StatusQuoteManager
|
||||||
// @ts-expect-error -- <Status> is not yet typed
|
|
||||||
id={statusId}
|
id={statusId}
|
||||||
contextType='notifications'
|
contextType='notifications'
|
||||||
withDismiss
|
withDismiss
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { ColumnHeader } from 'mastodon/components/column_header';
|
||||||
import { CompatibilityHashtag as Hashtag } from 'mastodon/components/hashtag';
|
import { CompatibilityHashtag as Hashtag } from 'mastodon/components/hashtag';
|
||||||
import { Icon } from 'mastodon/components/icon';
|
import { Icon } from 'mastodon/components/icon';
|
||||||
import ScrollableList from 'mastodon/components/scrollable_list';
|
import ScrollableList from 'mastodon/components/scrollable_list';
|
||||||
import Status from 'mastodon/containers/status_container';
|
import { StatusQuoteManager } from 'mastodon/components/status_quoted';
|
||||||
import { Search } from 'mastodon/features/compose/components/search';
|
import { Search } from 'mastodon/features/compose/components/search';
|
||||||
import { useSearchParam } from 'mastodon/hooks/useSearchParam';
|
import { useSearchParam } from 'mastodon/hooks/useSearchParam';
|
||||||
import type { Hashtag as HashtagType } from 'mastodon/models/tags';
|
import type { Hashtag as HashtagType } from 'mastodon/models/tags';
|
||||||
|
@ -53,8 +53,7 @@ const renderHashtags = (hashtags: HashtagType[]) =>
|
||||||
|
|
||||||
const renderStatuses = (statusIds: string[]) =>
|
const renderStatuses = (statusIds: string[]) =>
|
||||||
hidePeek<string>(statusIds).map((id) => (
|
hidePeek<string>(statusIds).map((id) => (
|
||||||
// @ts-expect-error inferred props are wrong
|
<StatusQuoteManager key={id} id={id} />
|
||||||
<Status key={id} id={id} />
|
|
||||||
));
|
));
|
||||||
|
|
||||||
type SearchType = 'all' | ApiSearchType;
|
type SearchType = 'all' | ApiSearchType;
|
||||||
|
@ -190,8 +189,7 @@ export const SearchResults: React.FC<{ multiColumn: boolean }> = ({
|
||||||
onClickMore={handleSelectStatuses}
|
onClickMore={handleSelectStatuses}
|
||||||
>
|
>
|
||||||
{results.statuses.slice(0, INITIAL_DISPLAY).map((id) => (
|
{results.statuses.slice(0, INITIAL_DISPLAY).map((id) => (
|
||||||
// @ts-expect-error inferred props are wrong
|
<StatusQuoteManager key={id} id={id} />
|
||||||
<Status key={id} id={id} />
|
|
||||||
))}
|
))}
|
||||||
</SearchSection>
|
</SearchSection>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import { IconLogo } from 'mastodon/components/logo';
|
||||||
import MediaGallery from 'mastodon/components/media_gallery';
|
import MediaGallery from 'mastodon/components/media_gallery';
|
||||||
import { PictureInPicturePlaceholder } from 'mastodon/components/picture_in_picture_placeholder';
|
import { PictureInPicturePlaceholder } from 'mastodon/components/picture_in_picture_placeholder';
|
||||||
import StatusContent from 'mastodon/components/status_content';
|
import StatusContent from 'mastodon/components/status_content';
|
||||||
|
import { QuotedStatus } from 'mastodon/components/status_quoted';
|
||||||
import { VisibilityIcon } from 'mastodon/components/visibility_icon';
|
import { VisibilityIcon } from 'mastodon/components/visibility_icon';
|
||||||
import { Audio } from 'mastodon/features/audio';
|
import { Audio } from 'mastodon/features/audio';
|
||||||
import scheduleIdleTask from 'mastodon/features/ui/util/schedule_idle_task';
|
import scheduleIdleTask from 'mastodon/features/ui/util/schedule_idle_task';
|
||||||
|
@ -371,6 +372,10 @@ export const DetailedStatus: React.FC<{
|
||||||
{...(statusContentProps as any)}
|
{...(statusContentProps as any)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{status.get('quote') && (
|
||||||
|
<QuotedStatus quote={status.get('quote')} />
|
||||||
|
)}
|
||||||
|
|
||||||
{media}
|
{media}
|
||||||
{hashtagBar}
|
{hashtagBar}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -6,8 +6,6 @@ import classNames from 'classnames';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
import { withRouter } from 'react-router-dom';
|
import { withRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import { createSelector } from '@reduxjs/toolkit';
|
|
||||||
import { List as ImmutableList } from 'immutable';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
@ -62,7 +60,7 @@ import {
|
||||||
} from '../../actions/statuses';
|
} from '../../actions/statuses';
|
||||||
import ColumnHeader from '../../components/column_header';
|
import ColumnHeader from '../../components/column_header';
|
||||||
import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
|
import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
|
||||||
import StatusContainer from '../../containers/status_container';
|
import { StatusQuoteManager } from '../../components/status_quoted';
|
||||||
import { deleteModal } from '../../initial_state';
|
import { deleteModal } from '../../initial_state';
|
||||||
import { makeGetStatus, makeGetPictureInPicture } from '../../selectors';
|
import { makeGetStatus, makeGetPictureInPicture } from '../../selectors';
|
||||||
import { getAncestorsIds, getDescendantsIds } from 'mastodon/selectors/contexts';
|
import { getAncestorsIds, getDescendantsIds } from 'mastodon/selectors/contexts';
|
||||||
|
@ -477,7 +475,7 @@ class Status extends ImmutablePureComponent {
|
||||||
const { params: { statusId } } = this.props;
|
const { params: { statusId } } = this.props;
|
||||||
|
|
||||||
return list.map((id, i) => (
|
return list.map((id, i) => (
|
||||||
<StatusContainer
|
<StatusQuoteManager
|
||||||
key={id}
|
key={id}
|
||||||
id={id}
|
id={id}
|
||||||
onMoveUp={this.handleMoveUp}
|
onMoveUp={this.handleMoveUp}
|
||||||
|
|
|
@ -863,6 +863,11 @@
|
||||||
"status.mute_conversation": "Mute conversation",
|
"status.mute_conversation": "Mute conversation",
|
||||||
"status.open": "Expand this post",
|
"status.open": "Expand this post",
|
||||||
"status.pin": "Feature on profile",
|
"status.pin": "Feature on profile",
|
||||||
|
"status.quote_error.not_found": "This post cannot be displayed.",
|
||||||
|
"status.quote_error.pending_approval": "This post is pending approval from the original author.",
|
||||||
|
"status.quote_error.rejected": "This post cannot be displayed as the original author does not allow it to be quoted.",
|
||||||
|
"status.quote_error.removed": "This post was removed by its author.",
|
||||||
|
"status.quote_error.unauthorized": "This post cannot be displayed as you are not authorized to view it.",
|
||||||
"status.read_more": "Read more",
|
"status.read_more": "Read more",
|
||||||
"status.reblog": "Boost",
|
"status.reblog": "Boost",
|
||||||
"status.reblog_private": "Boost with original visibility",
|
"status.reblog_private": "Boost with original visibility",
|
||||||
|
|
|
@ -1491,8 +1491,12 @@ body > [data-popper-placement] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--is-quote {
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
&--in-thread {
|
&--in-thread {
|
||||||
$thread-margin: 46px + 10px;
|
--thread-margin: calc(46px + 8px);
|
||||||
|
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
|
|
||||||
|
@ -1508,16 +1512,16 @@ body > [data-popper-placement] {
|
||||||
.hashtag-bar,
|
.hashtag-bar,
|
||||||
.content-warning,
|
.content-warning,
|
||||||
.filter-warning {
|
.filter-warning {
|
||||||
margin-inline-start: $thread-margin;
|
margin-inline-start: var(--thread-margin);
|
||||||
width: calc(100% - $thread-margin);
|
width: calc(100% - var(--thread-margin));
|
||||||
}
|
}
|
||||||
|
|
||||||
.more-from-author {
|
.more-from-author {
|
||||||
width: calc(100% - $thread-margin + 2px);
|
width: calc(100% - var(--thread-margin) + 2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__content__read-more-button {
|
.status__content__read-more-button {
|
||||||
margin-inline-start: $thread-margin;
|
margin-inline-start: var(--thread-margin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1873,6 +1877,41 @@ body > [data-popper-placement] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.status__quote {
|
||||||
|
position: relative;
|
||||||
|
margin-block-start: 16px;
|
||||||
|
margin-inline-start: 56px;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--nested-card-text);
|
||||||
|
background: var(--nested-card-background);
|
||||||
|
border: var(--nested-card-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.status__quote--error {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status__quote-icon {
|
||||||
|
position: absolute;
|
||||||
|
inset-block-start: 18px;
|
||||||
|
inset-inline-start: -50px;
|
||||||
|
display: block;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
padding: 5px;
|
||||||
|
color: #6a49ba;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.status__quote--error & {
|
||||||
|
inset-block-start: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.detailed-status__link {
|
.detailed-status__link {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
@ -2306,11 +2345,6 @@ a.account__display-name {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__avatar {
|
|
||||||
width: 46px;
|
|
||||||
height: 46px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.muted {
|
.muted {
|
||||||
.status__content,
|
.status__content,
|
||||||
.status__content p,
|
.status__content p,
|
||||||
|
@ -10515,6 +10549,7 @@ noscript {
|
||||||
line-height: 22px;
|
line-height: 22px;
|
||||||
color: $darker-text-color;
|
color: $darker-text-color;
|
||||||
-webkit-line-clamp: 4;
|
-webkit-line-clamp: 4;
|
||||||
|
line-clamp: 4;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
max-height: none;
|
max-height: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -10818,9 +10853,9 @@ noscript {
|
||||||
.content-warning {
|
.content-warning {
|
||||||
display: block;
|
display: block;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background: rgba($ui-highlight-color, 0.05);
|
background: var(--nested-card-background);
|
||||||
color: $secondary-text-color;
|
color: var(--nested-card-text);
|
||||||
border: 1px solid rgba($ui-highlight-color, 0.15);
|
border: var(--nested-card-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 8px (5px + 8px);
|
padding: 8px (5px + 8px);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
|
@ -27,6 +27,10 @@
|
||||||
--rich-text-container-color: rgba(87, 24, 60, 100%);
|
--rich-text-container-color: rgba(87, 24, 60, 100%);
|
||||||
--rich-text-text-color: rgba(255, 175, 212, 100%);
|
--rich-text-text-color: rgba(255, 175, 212, 100%);
|
||||||
--rich-text-decorations-color: rgba(128, 58, 95, 100%);
|
--rich-text-decorations-color: rgba(128, 58, 95, 100%);
|
||||||
|
--nested-card-background: color(from #{$ui-highlight-color} srgb r g b / 5%);
|
||||||
|
--nested-card-text: #{$secondary-text-color};
|
||||||
|
--nested-card-border: 1px solid
|
||||||
|
color(from #{$ui-highlight-color} srgb r g b / 15%);
|
||||||
--input-placeholder-color: #{$dark-text-color};
|
--input-placeholder-color: #{$dark-text-color};
|
||||||
--input-background-color: var(--surface-variant-background-color);
|
--input-background-color: var(--surface-variant-background-color);
|
||||||
--on-input-color: #{$secondary-text-color};
|
--on-input-color: #{$secondary-text-color};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue