Merge pull request #132 from Y-zu-don-maintenance-org/features/quote
Features/quote
This commit is contained in:
commit
c4712ed22c
@ -8,9 +8,9 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||
|
||||
def index
|
||||
cache_if_unauthenticated!
|
||||
@statuses = load_statuses
|
||||
@statuses = load_statuses
|
||||
accounts = @statuses.filter_map { |status| status.quote&.account }.uniq
|
||||
account_ids = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
|
||||
accounts = Account.where(id: account_ids)
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer,
|
||||
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
|
||||
account_relationships: AccountRelationshipsPresenter.new(accounts, current_user&.account_id)
|
||||
|
@ -6,9 +6,9 @@ class Api::V1::BookmarksController < Api::BaseController
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
def index
|
||||
@statuses = load_statuses
|
||||
@statuses = load_statuses
|
||||
accounts = @statuses.filter_map { |status| status.quote&.account }.uniq
|
||||
account_ids = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
|
||||
accounts = Account.where(id: account_ids)
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer,
|
||||
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
|
||||
account_relationships: AccountRelationshipsPresenter.new(accounts, current_user&.account_id)
|
||||
|
@ -6,9 +6,9 @@ class Api::V1::FavouritesController < Api::BaseController
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
def index
|
||||
@statuses = load_statuses
|
||||
@statuses = load_statuses
|
||||
accounts = @statuses.filter_map { |status| status.quote&.account }.uniq
|
||||
account_ids = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
|
||||
accounts = Account.where(id: account_ids)
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer,
|
||||
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
|
||||
account_relationships: AccountRelationshipsPresenter.new(accounts, current_user&.account_id)
|
||||
|
@ -47,10 +47,10 @@ class Api::V1::StatusesController < Api::BaseController
|
||||
loaded_ancestors = cache_collection(ancestors_results, Status)
|
||||
loaded_descendants = cache_collection(descendants_results, Status)
|
||||
|
||||
@context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants)
|
||||
statuses = [@status] + @context.ancestors + @context.descendants
|
||||
@context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants)
|
||||
statuses = [@status] + @context.ancestors + @context.descendants
|
||||
accounts = statuses.filter_map { |status| status.quote&.account }.uniq
|
||||
account_ids = statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
|
||||
accounts = Account.where(id: account_ids)
|
||||
|
||||
render json: @context, serializer: REST::ContextSerializer,
|
||||
relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id),
|
||||
|
@ -10,8 +10,8 @@ class Api::V1::Timelines::HomeController < Api::V1::Timelines::BaseController
|
||||
with_read_replica do
|
||||
@statuses = load_statuses
|
||||
@relationships = StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||
accounts = @statuses.filter_map { |status| status.quote&.account }.uniq
|
||||
account_ids = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
|
||||
accounts = Account.where(id: account_ids)
|
||||
|
||||
@account_relationships = AccountRelationshipsPresenter.new(accounts, current_user&.account_id)
|
||||
end
|
||||
|
@ -9,8 +9,8 @@ class Api::V1::Timelines::ListController < Api::V1::Timelines::BaseController
|
||||
PERMITTED_PARAMS = %i(limit).freeze
|
||||
|
||||
def show
|
||||
accounts = @statuses.filter_map { |status| status.quote&.account }.uniq
|
||||
account_ids = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
|
||||
accounts = Account.where(id: account_ids)
|
||||
|
||||
render json: @statuses,
|
||||
each_serializer: REST::StatusSerializer,
|
||||
|
@ -7,9 +7,9 @@ class Api::V1::Timelines::PublicController < Api::V1::Timelines::BaseController
|
||||
|
||||
def show
|
||||
cache_if_unauthenticated!
|
||||
@statuses = load_statuses
|
||||
@statuses = load_statuses
|
||||
accounts = @statuses.filter_map { |status| status.quote&.account }.uniq
|
||||
account_ids = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
|
||||
accounts = Account.where(id: account_ids)
|
||||
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer,
|
||||
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
|
||||
|
@ -8,9 +8,9 @@ class Api::V1::Timelines::TagController < Api::V1::Timelines::BaseController
|
||||
|
||||
def show
|
||||
cache_if_unauthenticated!
|
||||
@statuses = load_statuses
|
||||
@statuses = load_statuses
|
||||
accounts = @statuses.filter_map { |status| status.quote&.account }.uniq
|
||||
account_ids = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
|
||||
accounts = Account.where(id: account_ids)
|
||||
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer,
|
||||
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
|
||||
|
@ -25,7 +25,6 @@ export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL';
|
||||
export const COMPOSE_REPLY = 'COMPOSE_REPLY';
|
||||
export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL';
|
||||
export const COMPOSE_QUOTE = 'COMPOSE_QUOTE';
|
||||
export const COMPOSE_QUOTE_CANCEL = 'COMPOSE_QUOTE_CANCEL';
|
||||
export const COMPOSE_DIRECT = 'COMPOSE_DIRECT';
|
||||
export const COMPOSE_MENTION = 'COMPOSE_MENTION';
|
||||
export const COMPOSE_RESET = 'COMPOSE_RESET';
|
||||
@ -137,13 +136,7 @@ export function quoteCompose(status, routerHistory) {
|
||||
|
||||
ensureComposeIsVisible(getState, routerHistory);
|
||||
};
|
||||
};
|
||||
|
||||
export function cancelQuoteCompose() {
|
||||
return {
|
||||
type: COMPOSE_QUOTE_CANCEL,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function resetCompose() {
|
||||
return {
|
||||
|
@ -361,7 +361,7 @@ export function hideQuote(ids) {
|
||||
type: QUOTE_HIDE,
|
||||
ids,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export function revealQuote(ids) {
|
||||
if (!Array.isArray(ids)) {
|
||||
@ -372,4 +372,4 @@ export function revealQuote(ids) {
|
||||
type: QUOTE_REVEAL,
|
||||
ids,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ export const quote = (status, muted, quoteMuted, handleQuoteClick, handleExpande
|
||||
return (
|
||||
<div>
|
||||
<div className='status__info'>
|
||||
{identity(quoteStatus, null, null, true)}
|
||||
{identity(quoteStatus, null, true)}
|
||||
</div>
|
||||
<StatusContent status={quoteStatus} onClick={handleQuoteClick} expanded={!status.get('quote_hidden')} onExpandedToggle={handleExpandedQuoteToggle} quote />
|
||||
{media(quoteStatus, true)}
|
||||
@ -146,7 +146,6 @@ export const quote = (status, muted, quoteMuted, handleQuoteClick, handleExpande
|
||||
<div
|
||||
className={classNames('quote-status', `status-${quoteStatus.get('visibility')}`, { muted: muted })}
|
||||
data-id={quoteStatus.get('id')}
|
||||
dataurl={quoteStatus.get('url')}
|
||||
>
|
||||
{quoteInner}
|
||||
</div>
|
||||
@ -238,7 +237,7 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
handleToggleQuoteMediaVisibility = () => {
|
||||
this.setState({ showQuoteMedia: !this.state.showQuoteMedia });
|
||||
}
|
||||
};
|
||||
|
||||
handleClick = e => {
|
||||
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
|
||||
@ -253,10 +252,6 @@ class Status extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
handlePrependAccountClick = e => {
|
||||
this.handleAccountClick(e, false);
|
||||
};
|
||||
|
||||
handleAccountClick = (e, proper = true) => {
|
||||
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
|
||||
return;
|
||||
}
|
||||
@ -266,17 +261,29 @@ class Status extends ImmutablePureComponent {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
this._openProfile(proper);
|
||||
this._openProfile(false);
|
||||
};
|
||||
|
||||
handleQuoteClick = () => {
|
||||
if (!this.props) {
|
||||
handleAccountClick = (e) => {
|
||||
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { status } = this.props;
|
||||
this.props.history.push(`/statuses/${status.getIn(['reblog', 'quote', 'id'], status.getIn(['quote', 'id']))}`);
|
||||
}
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
const acct = e.currentTarget.getAttribute('data-acct');
|
||||
this.props.history.push(`/@${acct}`);
|
||||
};
|
||||
|
||||
handleQuoteClick = () => {
|
||||
if (this.props.history) {
|
||||
const status = this._properStatus();
|
||||
this.props.history.push(`/@${status.getIn(['quote', 'account', 'acct'])}/${status.getIn(['quote', 'id'])}`);
|
||||
}
|
||||
};
|
||||
|
||||
handleQuoteUserClick = () =>{
|
||||
if (!this.props) {
|
||||
@ -301,7 +308,7 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
handleExpandedQuoteToggle = () => {
|
||||
this.props.onQuoteToggleHidden(this._properStatus());
|
||||
}
|
||||
};
|
||||
|
||||
getAttachmentAspectRatio () {
|
||||
const attachments = this._properStatus().get('media_attachments');
|
||||
@ -554,9 +561,10 @@ class Status extends ImmutablePureComponent {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const media = (status, quote = false) => {
|
||||
if (pictureInPicture.get('inUse')) {
|
||||
return <PictureInPicturePlaceholder aspectRatio={this.getAttachmentAspectRatio()} />;
|
||||
return <PictureInPicturePlaceholder aspectRatio={this.getAttachmentAspectRatio()} width={this.props.cachedMediaWidth} />;
|
||||
} else if (status.get('media_attachments').size > 0) {
|
||||
const language = status.getIn(['translation', 'language']) || status.get('language');
|
||||
|
||||
@ -655,9 +663,9 @@ class Status extends ImmutablePureComponent {
|
||||
return <AvatarOverlay account={status.get('account')} friend={account} />;
|
||||
}
|
||||
};
|
||||
|
||||
const identity = (status, account, _0, quote = false) => (
|
||||
<a onClick={quote ? this.handleQuoteUserClick : this.handleAccountClick} href={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} className='status__display-name' rel='noopener noreferrer'>
|
||||
|
||||
const identity = (status, account) => (
|
||||
<a onClick={this.handleAccountClick} data-acct={status.getIn(['account', 'acct'])} href={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
|
||||
<div className='status__avatar'>
|
||||
{statusAvatar(status, account)}
|
||||
</div>
|
||||
@ -721,4 +729,4 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
}
|
||||
|
||||
export default withOptionalRouter(injectIntl(Status));
|
||||
export default connect(mapStateToProps)(withOptionalRouter(injectIntl(Status)));
|
||||
|
@ -11,6 +11,7 @@ import { connect } from 'react-redux';
|
||||
|
||||
import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg';
|
||||
import BookmarkBorderIcon from '@/material-icons/400-24px/bookmark.svg?react';
|
||||
import FormatQuoteIcon from '@/material-icons/400-24px/format_quote.svg?react';
|
||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
@ -160,8 +161,8 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
handleQuoteClick = () => {
|
||||
this.props.onQuote(this.props.status, this.props.history);
|
||||
}
|
||||
this.props.onQuote(this.props.status);
|
||||
};
|
||||
|
||||
handleBookmarkClick = () => {
|
||||
this.props.onBookmark(this.props.status);
|
||||
@ -262,7 +263,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
} else {
|
||||
return intl.formatMessage(messages.cannot_quote);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render () {
|
||||
const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props;
|
||||
@ -406,8 +407,8 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
<div className='status__action-bar'>
|
||||
<IconButton className='status__action-bar__button' title={replyTitle} icon={isReply ? 'reply' : replyIcon} iconComponent={isReply ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} counter={status.get('replies_count')} />
|
||||
<IconButton className={classNames('status__action-bar__button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={reblogIconComponent} onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
|
||||
<IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
|
||||
<IconButton className='status__action-bar__button' disabled={!publicStatus} title={StatusActionBar.quoteTitle(intl, messages, publicStatus)} icon='quote' iconComponent={QuoteIcon} onClick={this.handleQuoteClick} />
|
||||
<IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
|
||||
<IconButton className='status__action-bar__button' disabled={!publicStatus} title={StatusActionBar.quoteTitle(intl, messages, publicStatus)} icon='format-quote' iconComponent={FormatQuoteIcon} onClick={this.handleQuoteClick} />
|
||||
<IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} />
|
||||
|
||||
{filterButton}
|
||||
|
@ -241,7 +241,7 @@ class StatusContent extends PureComponent {
|
||||
};
|
||||
|
||||
render () {
|
||||
const { status, intl, statusContent, quote } = this.props;
|
||||
const { status, intl, quote, statusContent } = this.props;
|
||||
|
||||
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
|
||||
const renderReadMore = this.props.onClick && status.get('collapsed');
|
||||
|
@ -29,6 +29,7 @@ import { CharacterCounter } from './character_counter';
|
||||
import { EditIndicator } from './edit_indicator';
|
||||
import { NavigationBar } from './navigation_bar';
|
||||
import { PollForm } from "./poll_form";
|
||||
import { QuoteIndicator } from './quote_indicator';
|
||||
import { ReplyIndicator } from './reply_indicator';
|
||||
|
||||
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
|
||||
@ -296,6 +297,8 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
<UploadFormContainer />
|
||||
<PollForm />
|
||||
|
||||
<QuoteIndicator />
|
||||
|
||||
<div className='compose-form__footer'>
|
||||
<div className='compose-form__dropdowns'>
|
||||
<PrivacyDropdownContainer disabled={this.props.isEditing} />
|
||||
|
@ -0,0 +1,62 @@
|
||||
import { useCallback } from "react";
|
||||
|
||||
import { FormattedMessage, defineMessages, useIntl } from "react-intl";
|
||||
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
|
||||
import BarChart4BarsIcon from 'mastodon/../material-icons/400-24px/bar_chart_4_bars.svg?react';
|
||||
import CloseIcon from 'mastodon/../material-icons/400-24px/close.svg?react';
|
||||
import PhotoLibraryIcon from 'mastodon/../material-icons/400-24px/photo_library.svg?react';
|
||||
import { cancelReplyCompose } from "mastodon/actions/compose";
|
||||
import { Avatar } from "mastodon/components/avatar";
|
||||
import { DisplayName } from "mastodon/components/display_name";
|
||||
import { Icon } from "mastodon/components/icon";
|
||||
import { IconButton } from "mastodon/components/icon_button";
|
||||
|
||||
const messages = defineMessages({
|
||||
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
|
||||
});
|
||||
|
||||
export const QuoteIndicator = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useDispatch();
|
||||
const id = useSelector(state => state.getIn(['compose', 'quote_from']));
|
||||
const status = useSelector(state => state.getIn(['statuses', id]));
|
||||
const account = useSelector(state => state.getIn(['accounts', status?.get('account')]));
|
||||
|
||||
const handleCancelClick = useCallback(() => {
|
||||
dispatch(cancelReplyCompose());
|
||||
}, [dispatch]);
|
||||
|
||||
if (!status) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const content = { __html: status.get('contentHtml') };
|
||||
|
||||
return (
|
||||
<div className='edit-indicator'>
|
||||
<div className='edit-indicator__header'>
|
||||
<Link to={`/@${account.get('acct')}`} className='status__display-name'>
|
||||
<Avatar account={account} size={46} />
|
||||
<DisplayName account={account} />
|
||||
</Link>
|
||||
|
||||
<div className='edit-indicator__cancel'>
|
||||
<IconButton title={intl.formatMessage(messages.cancel)} icon='times' iconComponent={CloseIcon} onClick={handleCancelClick} inverted />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='edit-indicator__content translate' dangerouslySetInnerHTML={content} />
|
||||
|
||||
{(status.get('poll') || status.get('media_attachments').size > 0) && (
|
||||
<div className='edit-indicator__attachments'>
|
||||
{status.get('poll') && <><Icon icon={BarChart4BarsIcon} /><FormattedMessage id='reply_indicator.poll' defaultMessage='Poll' /></>}
|
||||
{status.get('media_attachments').size > 0 && <><Icon icon={PhotoLibraryIcon} /><FormattedMessage id='reply_indicator.attachments' defaultMessage='{count, plural, one {# attachment} other {# attachments}}' values={{ count: status.get('media_attachments').size }} /></>}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
@ -167,7 +167,7 @@ class Footer extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
dispatch(quoteCompose(status, router.history));
|
||||
}
|
||||
};
|
||||
|
||||
handleQuoteClick = () => {
|
||||
const { dispatch, askReplyConfirmation, intl } = this.props;
|
||||
@ -181,7 +181,7 @@ class Footer extends ImmutablePureComponent {
|
||||
} else {
|
||||
this._performQuote();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleOpenClick = e => {
|
||||
if (e.button !== 0 || !history) {
|
||||
|
@ -11,6 +11,7 @@ import { connect } from 'react-redux';
|
||||
|
||||
import BookmarkIcon from '@/material-icons/400-24px/bookmark-fill.svg?react';
|
||||
import BookmarkBorderIcon from '@/material-icons/400-24px/bookmark.svg?react';
|
||||
import FormatQuoteIcon from '@/material-icons/400-24px/format_quote.svg?react';
|
||||
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
|
||||
import RepeatIcon from '@/material-icons/400-24px/repeat.svg?react';
|
||||
import ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
|
||||
@ -111,8 +112,8 @@ class ActionBar extends PureComponent {
|
||||
};
|
||||
|
||||
handleQuoteClick = () => {
|
||||
this.props.onQuote(this.props.status, this.props.history);
|
||||
}
|
||||
this.props.onQuote(this.props.status);
|
||||
};
|
||||
|
||||
handleFavouriteClick = () => {
|
||||
this.props.onFavourite(this.props.status);
|
||||
@ -324,7 +325,7 @@ class ActionBar extends PureComponent {
|
||||
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} iconComponent={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={reblogIconComponent} onClick={this.handleReblogClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton disabled={!publicStatus} title={StatusActionBar.quoteTitle(intl, messages, publicStatus)} icon='quote' iconComponent={QuoteIcon} onClick={this.handleQuoteClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton disabled={!publicStatus} title={StatusActionBar.quoteTitle(intl, messages, publicStatus)} icon='format-quote' iconComponent={FormatQuoteIcon} onClick={this.handleQuoteClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} /></div>
|
||||
<div className='detailed-status__action-bar-dropdown'>
|
||||
<DropdownMenuContainer icon='ellipsis-h' iconComponent={MoreHorizIcon} status={status} items={menu} direction='left' title={intl.formatMessage(messages.more)} />
|
||||
|
@ -152,9 +152,9 @@ export default class Card extends PureComponent {
|
||||
};
|
||||
|
||||
if (largeImage && card.get('type') === 'video') {
|
||||
thumbnailStyle.aspectRatio = `16 / 9`;
|
||||
thumbnailStyle.aspectRatio = `${quote ? 8 : 16} / 9`;
|
||||
} else if (largeImage) {
|
||||
thumbnailStyle.aspectRatio = '1.91 / 1';
|
||||
thumbnailStyle.aspectRatio = `1.91 / ${quote ? 2 : 1}`;
|
||||
} else {
|
||||
thumbnailStyle.aspectRatio = 1;
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { FormattedDate, FormattedMessage } from 'react-intl';
|
||||
import { FormattedDate, FormattedMessage, injectIntl } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Link, withRouter } from 'react-router-dom';
|
||||
@ -62,7 +62,8 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||
handleAccountClick = (e) => {
|
||||
if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.props.history) {
|
||||
e.preventDefault();
|
||||
this.props.history.push(`/@${this.props.status.getIn(['account', 'acct'])}`);
|
||||
const acct = e.currentTarget.getAttribute('data-acct');
|
||||
this.props.history.push(`/@${acct}`);
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
@ -78,17 +79,15 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||
|
||||
handleExpandedQuoteToggle = () => {
|
||||
this.props.onQuoteToggleHidden(this.props.status);
|
||||
}
|
||||
};
|
||||
|
||||
handleQuoteClick = () => {
|
||||
if (!this.props) {
|
||||
return;
|
||||
if (this.props.history) {
|
||||
const status = this._properStatus();
|
||||
this.props.history.push(`/@${status.getIn(['quote', 'account', 'acct'])}/${status.getIn(['quote', 'id'])}`);
|
||||
}
|
||||
};
|
||||
|
||||
const { status } = this.props;
|
||||
this.props.history.push(`/statuses/${status.getIn(['quote', 'id'])}`);
|
||||
}
|
||||
|
||||
handleQuoteUserClick = () =>{
|
||||
if (!this.props) {
|
||||
return;
|
||||
@ -161,7 +160,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||
render () {
|
||||
const status = this._properStatus();
|
||||
const outerStyle = { boxSizing: 'border-box' };
|
||||
const { compact, pictureInPicture, quoteMuted } = this.props;
|
||||
const { compact, pictureInPicture, quoteMuted } = this.props;
|
||||
|
||||
if (!status) {
|
||||
return null;
|
||||
@ -180,11 +179,19 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||
|
||||
const language = status.getIn(['translation', 'language']) || status.get('language');
|
||||
|
||||
const identity = (status, _0, _1, quote = false) => (
|
||||
<a href={`/@${status.getIn(['account', 'acct'])}`} onClick={quote ? this.handleQuoteUserClick : this.handleAccountClick} data-acct={status.getIn(['account', 'acct'])} className='detailed-status__display-name'>
|
||||
<div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={46} /></div>
|
||||
<DisplayName account={status.get('account')} localDomain={this.props.domain} />
|
||||
</a>
|
||||
const identity = (status, _, quote) => (
|
||||
<>
|
||||
{status.get('visibility') === 'direct' && (
|
||||
<div className='status__prepend'>
|
||||
<div className='status__prepend-icon-wrapper'><Icon id='at' className='status__prepend-icon' fixedWidth /></div>
|
||||
<FormattedMessage id='status.direct_indicator' defaultMessage='Private mention' />
|
||||
</div>
|
||||
)}
|
||||
<a href={`/@${status.getIn(['account', 'acct'])}`} data-acct={status.getIn(['account', 'acct'])} onClick={this.handleAccountClick} className='detailed-status__display-name'>
|
||||
<div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={quote ? 20 : 48} /></div>
|
||||
<DisplayName account={status.get('account')} localDomain={this.props.domain} />
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
|
||||
const media = (status, quote = false) => {
|
||||
@ -251,7 +258,8 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||
);
|
||||
}
|
||||
} else if (status.get('spoiler_text').length === 0) {
|
||||
return <Card sensitive={status.get('sensitive')} onOpenMedia={this.props.onOpenMedia} card={status.get('card', null)} quote={quote} />;
|
||||
return (<Card sensitive={status.get('sensitive')} onOpenMedia={this.props.onOpenMedia}
|
||||
card={status.get('card', null)} quote={quote} />);
|
||||
}
|
||||
|
||||
}
|
||||
@ -331,10 +339,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||
<FormattedMessage id='status.direct_indicator' defaultMessage='Private mention' />
|
||||
</div>
|
||||
)}
|
||||
<a href={`/@${status.getIn(['account', 'acct'])}`} onClick={this.handleAccountClick} className='detailed-status__display-name'>
|
||||
<div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={46} /></div>
|
||||
<DisplayName account={status.get('account')} localDomain={this.props.domain} />
|
||||
</a>
|
||||
{identity(status, null, false)}
|
||||
|
||||
<StatusContent
|
||||
status={status}
|
||||
@ -362,4 +367,4 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||
|
||||
}
|
||||
|
||||
export default withRouter(DetailedStatus);
|
||||
export default connect(mapStateToProps)(withRouter(injectIntl(DetailedStatus)));
|
||||
|
@ -255,7 +255,7 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
handleToggleQuoteMediaVisibility = () => {
|
||||
this.setState({ showQuoteMedia: !this.state.showQuoteMedia });
|
||||
}
|
||||
};
|
||||
|
||||
handleFavouriteClick = (status) => {
|
||||
const { dispatch } = this.props;
|
||||
@ -327,7 +327,7 @@ class Status extends ImmutablePureComponent {
|
||||
} else {
|
||||
dispatch(quoteCompose(status, this.props.history));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleModalReblog = (status, privacy) => {
|
||||
this.props.dispatch(reblog(status, privacy));
|
||||
@ -450,7 +450,7 @@ class Status extends ImmutablePureComponent {
|
||||
} else {
|
||||
this.props.dispatch(hideQuote(status.get('id')));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleToggleAll = () => {
|
||||
const { status, ancestorsIds, descendantsIds } = this.props;
|
||||
|
@ -683,6 +683,7 @@
|
||||
"status.translate": "翻訳",
|
||||
"status.translated_from_with": "{provider}を使って{lang}から翻訳",
|
||||
"status.uncached_media_warning": "プレビューは使用できません",
|
||||
"status.unlisted_quote": "未収載の引用",
|
||||
"status.unmute_conversation": "会話のミュートを解除",
|
||||
"status.unpin": "プロフィールへの固定を解除",
|
||||
"subscribed_languages.lead": "選択した言語のトゥートだけがホームとリストのタイムラインに表示されます。全ての言語のトゥートを受け取る場合は全てのチェックを外して下さい。",
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
COMPOSE_REPLY,
|
||||
COMPOSE_REPLY_CANCEL,
|
||||
COMPOSE_QUOTE,
|
||||
COMPOSE_QUOTE_CANCEL,
|
||||
COMPOSE_DIRECT,
|
||||
COMPOSE_MENTION,
|
||||
COMPOSE_SUBMIT_REQUEST,
|
||||
@ -397,7 +396,6 @@ export default function compose(state = initialState, action) {
|
||||
case COMPOSE_UPLOAD_CHANGE_REQUEST:
|
||||
return state.set('is_changing_upload', true);
|
||||
case COMPOSE_REPLY_CANCEL:
|
||||
case COMPOSE_QUOTE_CANCEL:
|
||||
case COMPOSE_RESET:
|
||||
case COMPOSE_SUBMIT_SUCCESS:
|
||||
return clearAll(state);
|
||||
@ -530,6 +528,8 @@ export default function compose(state = initialState, action) {
|
||||
map.set('id', action.status.get('id'));
|
||||
map.set('text', action.text);
|
||||
map.set('in_reply_to', action.status.get('in_reply_to_id'));
|
||||
map.set('quote_from', action.status.getIn(['quote', 'id']));
|
||||
map.set('quote_from_url', action.status.getIn(['quote', 'url']));
|
||||
map.set('privacy', action.status.get('visibility'));
|
||||
map.set('media_attachments', action.status.get('media_attachments'));
|
||||
map.set('focusDate', new Date());
|
||||
|
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m228-240 92-160q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 23-5.5 42.5T458-480L320-240h-92Zm360 0 92-160q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 23-5.5 42.5T818-480L680-240h-92Z"/></svg>
|
After Width: | Height: | Size: 322 B |
1
app/javascript/material-icons/400-24px/format_quote.svg
Normal file
1
app/javascript/material-icons/400-24px/format_quote.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m228-240 92-160q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 23-5.5 42.5T458-480L320-240h-92Zm360 0 92-160q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 23-5.5 42.5T818-480L680-240h-92ZM320-500q25 0 42.5-17.5T380-560q0-25-17.5-42.5T320-620q-25 0-42.5 17.5T260-560q0 25 17.5 42.5T320-500Zm360 0q25 0 42.5-17.5T740-560q0-25-17.5-42.5T680-620q-25 0-42.5 17.5T620-560q0 25 17.5 42.5T680-500Zm0-60Zm-360 0Z"/></svg>
|
After Width: | Height: | Size: 538 B |
@ -1105,6 +1105,10 @@ body > [data-popper-placement] {
|
||||
grid-template-rows: 46px max-content;
|
||||
gap: 0 10px;
|
||||
|
||||
&.quote-indicator {
|
||||
background: $success-green;
|
||||
}
|
||||
|
||||
.detailed-status__display-name {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
@ -1422,17 +1426,6 @@ body > [data-popper-placement] {
|
||||
appearance: none;
|
||||
}
|
||||
|
||||
.status__avatar,
|
||||
.detailed-status__display-avatar {
|
||||
position: absolute;
|
||||
top: 5px !important;
|
||||
left: 5px !important;
|
||||
}
|
||||
|
||||
.display-name {
|
||||
padding-left: 56px;
|
||||
}
|
||||
|
||||
.detailed-status__display-name {
|
||||
margin-bottom: 0;
|
||||
line-height: unset;
|
||||
@ -1630,7 +1623,8 @@ body > [data-popper-placement] {
|
||||
color: $dark-text-color;
|
||||
}
|
||||
|
||||
.status__info .status__display-name {
|
||||
.status__info .status__display-name,
|
||||
.edit-indicator .status__display-name {
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
font-size: 15px;
|
||||
|
@ -13,7 +13,7 @@
|
||||
%span.display-name
|
||||
%bdi
|
||||
%strong.display-name__html.p-name.emojify= display_name(author, custom_emojify: true, autoplay: prefers_autoplay?)
|
||||
|
||||
|
||||
%span.display-name__account
|
||||
= acct(author)
|
||||
- if !inline && author.locked?
|
||||
|
@ -12,21 +12,8 @@
|
||||
*
|
||||
%data.dt-published{ value: status.created_at.to_time.iso8601 }
|
||||
|
||||
.p-author.h-card
|
||||
= link_to ActivityPub::TagManager.instance.url_for(status.account), class: 'status__display-name u-url', target: stream_link_target, rel: 'noopener noreferrer' do
|
||||
.status__avatar
|
||||
%div
|
||||
- if prefers_autoplay?
|
||||
= image_tag status.account.avatar_original_url, alt: '', class: 'u-photo account__avatar'
|
||||
- else
|
||||
= image_tag status.account.avatar_static_url, alt: '', class: 'u-photo account__avatar'
|
||||
%span.display-name
|
||||
%bdi
|
||||
%strong.display-name__html.p-name.emojify= display_name(status.account, custom_emojify: true, autoplay: prefers_autoplay?)
|
||||
|
||||
%span.display-name__account
|
||||
= acct(status.account)
|
||||
= fa_icon('lock') if status.account.locked?
|
||||
= render 'statuses/author', author: status.account
|
||||
|
||||
.status__content.emojify{ data: ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
|
||||
- if status.spoiler_text?
|
||||
%p<
|
||||
|
Loading…
Reference in New Issue
Block a user