Remove: kmyblue独自機能の引用

This commit is contained in:
KMY 2025-04-24 08:48:28 +09:00
parent 620a895184
commit d845d1e9fb
70 changed files with 812 additions and 1788 deletions

View file

@ -56,11 +56,11 @@ class Api::V1::FiltersController < Api::BaseController
end end
def resource_params def resource_params
params.permit(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :with_quote, :with_profile, :whole_word, context: []) params.permit(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :with_profile, :whole_word, context: [])
end end
def filter_params def filter_params
resource_params.slice(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :with_quote, :with_profile, :context) resource_params.slice(:phrase, :expires_in, :irreversible, :exclude_follows, :exclude_localusers, :with_profile, :context)
end end
def keyword_params def keyword_params

View file

@ -43,6 +43,6 @@ class Api::V2::FiltersController < Api::BaseController
end end
def resource_params def resource_params
params.permit(:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, :with_quote, :with_profile, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy]) params.permit(:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, :with_profile, context: [], keywords_attributes: [:id, :keyword, :whole_word, :_destroy])
end end
end end

View file

@ -47,6 +47,6 @@ class FiltersController < ApplicationController
end end
def resource_params def resource_params
params.expect(custom_filter: [:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, :exclude_quote, :exclude_profile, context: [], keywords_attributes: [[:id, :keyword, :whole_word, :_destroy]]]) params.expect(custom_filter: [:title, :expires_in, :filter_action, :exclude_follows, :exclude_localusers, :exclude_profile, context: [], keywords_attributes: [[:id, :keyword, :whole_word, :_destroy]]])
end end
end end

View file

@ -29,7 +29,6 @@ module ContextHelper
limited_scope: { 'kmyblue' => 'http://kmy.blue/ns#', 'limitedScope' => 'kmyblue:limitedScope' }, limited_scope: { 'kmyblue' => 'http://kmy.blue/ns#', 'limitedScope' => 'kmyblue:limitedScope' },
other_setting: { 'fedibird' => 'http://fedibird.com/ns#', 'otherSetting' => 'fedibird:otherSetting' }, other_setting: { 'fedibird' => 'http://fedibird.com/ns#', 'otherSetting' => 'fedibird:otherSetting' },
references: { 'fedibird' => 'http://fedibird.com/ns#', 'references' => { '@id' => 'fedibird:references', '@type' => '@id' } }, references: { 'fedibird' => 'http://fedibird.com/ns#', 'references' => { '@id' => 'fedibird:references', '@type' => '@id' } },
quote_uri: { 'fedibird' => 'http://fedibird.com/ns#', 'quoteUri' => 'fedibird:quoteUri' },
keywords: { 'schema' => 'http://schema.org#', 'keywords' => 'schema:keywords' }, keywords: { 'schema' => 'http://schema.org#', 'keywords' => 'schema:keywords' },
license: { 'schema' => 'http://schema.org#', 'license' => 'schema:license' }, license: { 'schema' => 'http://schema.org#', 'license' => 'schema:license' },
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' }, suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },

View file

@ -15,7 +15,6 @@ module KmyblueCapabilitiesHelper
kmyblue_limited_scope kmyblue_limited_scope
kmyblue_antenna kmyblue_antenna
kmyblue_bookmark_category kmyblue_bookmark_category
kmyblue_quote
kmyblue_searchability_limited kmyblue_searchability_limited
kmyblue_circle_history kmyblue_circle_history
kmyblue_list_notification kmyblue_list_notification
@ -41,7 +40,6 @@ module KmyblueCapabilitiesHelper
capabilities = %i( capabilities = %i(
enable_wide_emoji enable_wide_emoji
status_reference status_reference
quote
emoji_keywords emoji_keywords
circle circle
) )

View file

@ -70,10 +70,6 @@ export function importFetchedStatuses(statuses) {
processStatus(status.reblog); processStatus(status.reblog);
} }
if (status.quote?.id && !getState().getIn(['statuses', status.id])) {
processStatus(status.quote);
}
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]));
} }

View file

@ -66,11 +66,6 @@ export function normalizeStatus(status, normalOldStatus) {
normalStatus.spoiler_text = normalOldStatus.get('spoiler_text'); normalStatus.spoiler_text = normalOldStatus.get('spoiler_text');
normalStatus.hidden = normalOldStatus.get('hidden'); normalStatus.hidden = normalOldStatus.get('hidden');
// for quoted post
if (!normalStatus.filtered && normalOldStatus.get('filtered')) {
normalStatus.filtered = normalOldStatus.get('filtered');
}
if (normalOldStatus.get('translation')) { if (normalOldStatus.get('translation')) {
normalStatus.translation = normalOldStatus.get('translation'); normalStatus.translation = normalOldStatus.get('translation');
} }

View file

@ -33,7 +33,7 @@ const messages = defineMessages({
message_poll: { id: 'notification.poll', defaultMessage: 'A poll you voted in has ended' }, message_poll: { id: 'notification.poll', defaultMessage: 'A poll you voted in has ended' },
message_reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your post' }, message_reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your post' },
message_status: { id: 'notification.status', defaultMessage: '{name} just posted' }, message_status: { id: 'notification.status', defaultMessage: '{name} just posted' },
message_status_reference: { id: 'notification.status_reference', defaultMessage: '{name} quoted your post' }, message_status_reference: { id: 'notification.status_reference', defaultMessage: '{name} linked your post' },
message_update: { id: 'notification.update', defaultMessage: '{name} edited a post' }, message_update: { id: 'notification.update', defaultMessage: '{name} edited a post' },
}); });

View file

@ -20,7 +20,6 @@ export interface ApiAccountOtherSettingsJSON {
hide_followers_count: boolean; hide_followers_count: boolean;
translatable_private: boolean; translatable_private: boolean;
link_preview: boolean; link_preview: boolean;
allow_quote: boolean;
emoji_reaction_policy: emoji_reaction_policy:
| 'allow' | 'allow'
| 'outside_only' | 'outside_only'
@ -34,7 +33,6 @@ export interface ApiAccountOtherSettingsJSON {
export interface ApiServerFeaturesJSON { export interface ApiServerFeaturesJSON {
circle: boolean; circle: boolean;
emoji_reaction: boolean; emoji_reaction: boolean;
quote: boolean;
status_reference: boolean; status_reference: boolean;
} }

View file

@ -1,580 +0,0 @@
import PropTypes from 'prop-types';
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';
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 ReplyIcon from '@/material-icons/400-24px/reply.svg?react';
import AttachmentList from 'mastodon/components/attachment_list';
import { ContentWarning } from 'mastodon/components/content_warning';
import { Icon } from 'mastodon/components/icon';
import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
import { withOptionalRouter, WithOptionalRouterPropTypes } from 'mastodon/utils/react_router';
import Card from '../features/status/components/card';
// We use the component (and not the container) since we do not want
// to use the progress bar to show download progress
import Bundle from '../features/ui/components/bundle';
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
import { SensitiveMediaContext } from '../features/ui/util/sensitive_media_context';
import { displayMedia } from '../initial_state';
import { Avatar } from './avatar';
import { DisplayName } from './display_name';
import { getHashtagBarForStatus } from './hashtag_bar';
import { RelativeTimestamp } from './relative_timestamp';
import StatusContent from './status_content';
import { VisibilityIcon } from './visibility_icon';
const domParser = new DOMParser();
export const textForScreenReader = (intl, status, rebloggedByText = false) => {
const displayName = status.getIn(['account', 'display_name']);
const spoilerText = status.getIn(['translation', 'spoiler_text']) || status.get('spoiler_text');
const contentHtml = status.getIn(['translation', 'contentHtml']) || status.get('contentHtml');
const contentText = domParser.parseFromString(contentHtml, 'text/html').documentElement.textContent;
const values = [
displayName.length === 0 ? status.getIn(['account', 'acct']).split('@')[0] : displayName,
spoilerText && status.get('hidden') ? spoilerText : contentText,
intl.formatDate(status.get('created_at'), { hour: '2-digit', minute: '2-digit', month: 'short', day: 'numeric' }),
status.getIn(['account', 'acct']),
];
if (rebloggedByText) {
values.push(rebloggedByText);
}
return values.join(', ');
};
export const defaultMediaVisibility = (status) => {
if (!status) {
return undefined;
}
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
status = status.get('reblog');
}
return (displayMedia !== 'hide_all' && !status.get('sensitive') || displayMedia === 'show_all');
};
const messages = defineMessages({
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited' },
edited: { id: 'status.edited', defaultMessage: 'Edited {date}' },
});
class CompactedStatus extends ImmutablePureComponent {
static contextType = SensitiveMediaContext;
static propTypes = {
status: ImmutablePropTypes.map,
account: ImmutablePropTypes.record,
previousId: PropTypes.string,
nextInReplyToId: PropTypes.string,
rootId: PropTypes.string,
onClick: PropTypes.func,
onReply: PropTypes.func,
onFavourite: PropTypes.func,
onEmojiReact: PropTypes.func,
onUnEmojiReact: PropTypes.func,
onReblog: PropTypes.func,
onReblogForceModal: PropTypes.func,
onDelete: PropTypes.func,
onDirect: PropTypes.func,
onMention: PropTypes.func,
onPin: PropTypes.func,
onOpenMedia: PropTypes.func,
onOpenVideo: PropTypes.func,
onBlock: PropTypes.func,
onAddFilter: PropTypes.func,
onEmbed: PropTypes.func,
onHeightChange: PropTypes.func,
onToggleHidden: PropTypes.func,
onToggleCollapsed: PropTypes.func,
onTranslate: PropTypes.func,
onInteractionModal: PropTypes.func,
muted: PropTypes.bool,
hidden: PropTypes.bool,
unread: PropTypes.bool,
onMoveUp: PropTypes.func,
onMoveDown: PropTypes.func,
showThread: PropTypes.bool,
getScrollPosition: PropTypes.func,
updateScrollBottom: PropTypes.func,
cacheMediaWidth: PropTypes.func,
cachedMediaWidth: PropTypes.number,
scrollKey: PropTypes.string,
skipPrepend: PropTypes.bool,
deployPictureInPicture: PropTypes.func,
unfocusable: PropTypes.bool,
pictureInPicture: ImmutablePropTypes.contains({
inUse: PropTypes.bool,
available: PropTypes.bool,
}),
withoutEmojiReactions: PropTypes.bool,
...WithOptionalRouterPropTypes,
};
// Avoid checking props that are functions (and whose equality will always
// evaluate to false. See react-immutable-pure-component for usage.
updateOnProps = [
'status',
'account',
'muted',
'hidden',
'unread',
'pictureInPicture',
];
state = {
showMedia: defaultMediaVisibility(this.props.status) && !(this.context?.hideMediaByDefault),
};
componentDidUpdate (prevProps) {
// This will potentially cause a wasteful redraw, but in most cases `Status` components are used
// with a `key` directly depending on their `id`, preventing re-use of the component across
// different IDs.
// But just in case this does change, reset the state on status change.
if (this.props.status?.get('id') !== prevProps.status?.get('id')) {
this.setState({
showMedia: defaultMediaVisibility(this.props.status) && !(this.context?.hideMediaByDefault),
});
}
}
handleToggleMediaVisibility = () => {
this.setState({ showMedia: !this.state.showMedia });
};
handleClick = e => {
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
return;
}
if (e) {
e.preventDefault();
}
this.handleHotkeyOpen();
};
handlePrependAccountClick = e => {
this.handleAccountClick(e, false);
};
handleAccountClick = (e, proper = true) => {
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
return;
}
if (e) {
e.preventDefault();
e.stopPropagation();
}
this._openProfile(proper);
};
handleExpandedToggle = () => {
this.props.onToggleHidden(this._properStatus());
};
handleCollapsedToggle = isCollapsed => {
this.props.onToggleCollapsed(this._properStatus(), isCollapsed);
};
handleTranslate = () => {
this.props.onTranslate(this._properStatus());
};
getAttachmentAspectRatio () {
const attachments = this._properStatus().get('media_attachments');
if (attachments.getIn([0, 'type']) === 'video') {
return `${attachments.getIn([0, 'meta', 'original', 'width'])} / ${attachments.getIn([0, 'meta', 'original', 'height'])}`;
} else if (attachments.getIn([0, 'type']) === 'audio') {
return '16 / 9';
} else {
return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2';
}
}
renderLoadingMediaGallery = () => {
return (
<div className='media-gallery' style={{ aspectRatio: this.getAttachmentAspectRatio() }} />
);
};
renderLoadingVideoPlayer = () => {
return (
<div className='video-player' style={{ aspectRatio: this.getAttachmentAspectRatio() }} />
);
};
renderLoadingAudioPlayer = () => {
return (
<div className='audio-player' style={{ aspectRatio: this.getAttachmentAspectRatio() }} />
);
};
handleOpenVideo = (options) => {
const status = this._properStatus();
const lang = status.getIn(['translation', 'language']) || status.get('language');
this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), lang, options);
};
handleOpenMedia = (media, index) => {
const status = this._properStatus();
const lang = status.getIn(['translation', 'language']) || status.get('language');
this.props.onOpenMedia(status.get('id'), media, index, lang);
};
handleHotkeyOpenMedia = e => {
const { onOpenMedia, onOpenVideo } = this.props;
const status = this._properStatus();
e.preventDefault();
if (status.get('media_attachments').size > 0) {
const lang = status.getIn(['translation', 'language']) || status.get('language');
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), lang, { startTime: 0 });
} else {
onOpenMedia(status.get('id'), status.get('media_attachments'), 0, lang);
}
}
};
handleDeployPictureInPicture = (type, mediaProps) => {
const { deployPictureInPicture } = this.props;
const status = this._properStatus();
deployPictureInPicture(status, type, mediaProps);
};
handleHotkeyReply = e => {
e.preventDefault();
this.props.onReply(this._properStatus());
};
handleHotkeyFavourite = () => {
this.props.onFavourite(this._properStatus());
};
handleHotkeyBoost = e => {
this.props.onReblog(this._properStatus(), e);
};
handleHotkeyMention = e => {
e.preventDefault();
this.props.onMention(this._properStatus().get('account'));
};
handleHotkeyOpen = () => {
if (this.props.onClick) {
this.props.onClick();
return;
}
const { history } = this.props;
const status = this._properStatus();
if (!history) {
return;
}
history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`);
};
handleHotkeyOpenProfile = () => {
this._openProfile();
};
_openProfile = (proper = true) => {
const { history } = this.props;
const status = proper ? this._properStatus() : this.props.status;
if (!history) {
return;
}
history.push(`/@${status.getIn(['account', 'acct'])}`);
};
handleHotkeyMoveUp = e => {
this.props.onMoveUp(this.props.status.get('id'), e.target.getAttribute('data-featured'));
};
handleHotkeyMoveDown = e => {
this.props.onMoveDown(this.props.status.get('id'), e.target.getAttribute('data-featured'));
};
handleHotkeyToggleHidden = () => {
const { onToggleHidden } = this.props;
const status = this._properStatus();
onToggleHidden(status);
};
handleHotkeyToggleSensitive = () => {
this.handleToggleMediaVisibility();
};
_properStatus () {
const { status } = this.props;
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
return status.get('reblog');
} else {
return status;
}
}
handleRef = c => {
this.node = c;
};
render () {
const { intl, hidden, featured, unfocusable, unread, showThread, pictureInPicture, previousId, nextInReplyToId, rootId, skipPrepend } = this.props;
let { status } = this.props;
if (status === null) {
return null;
}
const handlers = this.props.muted ? {} : {
reply: this.handleHotkeyReply,
favourite: this.handleHotkeyFavourite,
boost: this.handleHotkeyBoost,
mention: this.handleHotkeyMention,
open: this.handleHotkeyOpen,
openProfile: this.handleHotkeyOpenProfile,
moveUp: this.handleHotkeyMoveUp,
moveDown: this.handleHotkeyMoveDown,
toggleHidden: this.handleHotkeyToggleHidden,
toggleSensitive: this.handleHotkeyToggleSensitive,
openMedia: this.handleHotkeyOpenMedia,
};
let media, prepend, rebloggedByText;
if (hidden) {
return (
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
<div ref={this.handleRef} className={classNames('status__wrapper', { focusable: !this.props.muted })} tabIndex={unfocusable ? null : 0}>
<span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
<span>{status.get('content')}</span>
</div>
</HotKeys>
);
}
const connectUp = previousId && previousId === status.get('in_reply_to_id');
const connectToRoot = rootId && rootId === status.get('in_reply_to_id');
const connectReply = nextInReplyToId && nextInReplyToId === status.get('id');
let visibilityName = status.get('limited_scope') || status.get('visibility_ex') || status.get('visibility');
if (featured) {
prepend = (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'><Icon id='thumb-tack' icon={PushPinIcon} className='status__prepend-icon' /></div>
<FormattedMessage id='status.pinned' defaultMessage='Pinned post' />
</div>
);
} else if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
prepend = (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'><Icon id='retweet' icon={RepeatIcon} className='status__prepend-icon' /></div>
<div className='status__prepend-icon-wrapper'><VisibilityIcon visibility={visibilityName} className='status__prepend-icon' /></div>
<FormattedMessage id='status.reblogged_by' defaultMessage='{name} boosted' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} data-hover-card-account={status.getIn(['account', 'id'])} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
</div>
);
rebloggedByText = intl.formatMessage({ id: 'status.reblogged_by', defaultMessage: '{name} boosted' }, { name: status.getIn(['account', 'acct']) });
status = status.get('reblog');
} else if (status.get('visibility') === 'direct') {
prepend = (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'><Icon id='at' icon={AlternateEmailIcon} className='status__prepend-icon' /></div>
<FormattedMessage id='status.direct_indicator' defaultMessage='Private mention' />
</div>
);
} else if (showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id'])) {
const display_name_html = { __html: status.getIn(['account', 'display_name_html']) };
prepend = (
<div className='status__prepend'>
<div className='status__prepend-icon-wrapper'><Icon id='reply' icon={ReplyIcon} className='status__prepend-icon' /></div>
<FormattedMessage id='status.replied_to' defaultMessage='Replied to {name}' values={{ name: <a onClick={this.handlePrependAccountClick} data-id={status.getIn(['account', 'id'])} data-hover-card-account={status.getIn(['account', 'id'])} href={`/@${status.getIn(['account', 'acct'])}`} className='status__display-name muted'><bdi><strong dangerouslySetInnerHTML={display_name_html} /></bdi></a> }} />
</div>
);
}
if (pictureInPicture.get('inUse')) {
media = <PictureInPicturePlaceholder aspectRatio={this.getAttachmentAspectRatio()} />;
} else if (status.get('media_attachments').size > 0) {
const language = status.getIn(['translation', 'language']) || status.get('language');
if (this.props.muted) {
media = (
<AttachmentList
compact
media={status.get('media_attachments')}
/>
);
} else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
const attachment = status.getIn(['media_attachments', 0]);
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
media = (
<Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
{Component => (
<Component
src={attachment.get('url')}
alt={description}
lang={language}
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
accentColor={attachment.getIn(['meta', 'colors', 'accent'])}
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
width={this.props.cachedMediaWidth}
height={110}
cacheWidth={this.props.cacheMediaWidth}
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
sensitive={status.get('sensitive')}
blurhash={attachment.get('blurhash')}
visible={this.state.showMedia}
onToggleVisibility={this.handleToggleMediaVisibility}
/>
)}
</Bundle>
);
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
const attachment = status.getIn(['media_attachments', 0]);
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
media = (
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
{Component => (
<Component
preview={attachment.get('preview_url')}
frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
aspectRatio={`${attachment.getIn(['meta', 'original', 'width'])} / ${attachment.getIn(['meta', 'original', 'height'])}`}
blurhash={attachment.get('blurhash')}
src={attachment.get('url')}
alt={description}
lang={language}
sensitive={status.get('sensitive')}
onOpenVideo={this.handleOpenVideo}
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
visible={this.state.showMedia}
onToggleVisibility={this.handleToggleMediaVisibility}
/>
)}
</Bundle>
);
} else {
media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
{Component => (
<Component
media={status.get('media_attachments')}
lang={language}
sensitive={status.get('sensitive')}
height={110}
onOpenMedia={this.handleOpenMedia}
cacheWidth={this.props.cacheMediaWidth}
defaultWidth={this.props.cachedMediaWidth}
visible={this.state.showMedia}
onToggleVisibility={this.handleToggleMediaVisibility}
/>
)}
</Bundle>
);
}
} else if (status.get('card') && !this.props.muted) {
media = (
<Card
onOpenMedia={this.handleOpenMedia}
card={status.get('card')}
compact
sensitive={status.get('sensitive') && !status.get('spoiler_text')}
/>
);
}
visibilityName = status.get('limited_scope') || status.get('visibility_ex') || status.get('visibility');
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
const expanded = !status.get('hidden') || status.get('spoiler_text').length === 0;
return (
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
<div className={classNames('status__wrapper', 'status__wrapper__compact', 'content-warning', 'content-warning--compacted-status', `status__wrapper-${status.get('visibility_ex')}`, { '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}
<div className={classNames('status', `status-${status.get('visibility_ex')}`, { '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')}>
{(connectReply || connectUp || connectToRoot) && <div className={classNames('status__line', { 'status__line--full': connectReply, 'status__line--first': !status.get('in_reply_to_id') && !connectToRoot })} />}
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div onClick={this.handleClick} className='status__info'>
<a href={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time' target='_blank' rel='noopener noreferrer'>
<span className='status__visibility-icon'><VisibilityIcon visibility={visibilityName} /></span>
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
</a>
<a onClick={this.handleAccountClick} href={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} data-hover-card-account={status.getIn(['account', 'id'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
<div className='status__avatar__compact'>
<Avatar account={status.get('account')} size={32} inline />
</div>
<DisplayName account={status.get('account')} />
</a>
</div>
{status.get('spoiler_text').length > 0 && <ContentWarning text={status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml')} expanded={expanded} onClick={this.handleExpandedToggle} />}
{expanded && (
<>
<StatusContent
status={status}
onClick={this.handleClick}
onTranslate={this.handleTranslate}
collapsible
onCollapsedToggle={this.handleCollapsedToggle}
{...statusContentProps}
/>
{media}
{hashtagBar}
</>
)}
</div>
</div>
</HotKeys>
);
}
}
export default withOptionalRouter(injectIntl(CompactedStatus));

View file

@ -12,7 +12,6 @@ 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 QuoteIcon from '@/material-icons/400-24px/format_quote.svg?react';
import ReferenceIcon from '@/material-icons/400-24px/link.svg?react'; import ReferenceIcon from '@/material-icons/400-24px/link.svg?react';
import PushPinIcon from '@/material-icons/400-24px/push_pin.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';
@ -25,7 +24,6 @@ import { Icon } from 'mastodon/components/icon';
import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder'; import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
import { withOptionalRouter, WithOptionalRouterPropTypes } from 'mastodon/utils/react_router'; import { withOptionalRouter, WithOptionalRouterPropTypes } from 'mastodon/utils/react_router';
import CompactedStatusContainer from '../containers/compacted_status_container';
import Card from '../features/status/components/card'; import Card from '../features/status/components/card';
// We use the component (and not the container) since we do not want // We use the component (and not the container) since we do not want
// to use the progress bar to show download progress // to use the progress bar to show download progress
@ -136,7 +134,6 @@ class Status extends ImmutablePureComponent {
available: PropTypes.bool, available: PropTypes.bool,
}), }),
withoutEmojiReactions: PropTypes.bool, withoutEmojiReactions: PropTypes.bool,
withoutQuote: PropTypes.bool,
...WithOptionalRouterPropTypes, ...WithOptionalRouterPropTypes,
}; };
@ -382,7 +379,7 @@ class Status extends ImmutablePureComponent {
}; };
render () { render () {
const { intl, hidden, featured, unfocusable, unread, showThread, scrollKey, pictureInPicture, previousId, rootId, withoutQuote, skipPrepend, avatarSize = 46 } = this.props; const { intl, hidden, featured, unfocusable, unread, showThread, scrollKey, pictureInPicture, previousId, rootId, skipPrepend, avatarSize = 46 } = this.props;
let { status, account, ...other } = this.props; let { status, account, ...other } = this.props;
@ -582,12 +579,9 @@ class Status extends ImmutablePureComponent {
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status); const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
const withLimited = status.get('visibility_ex') === 'limited' && status.get('limited_scope') ? <span className='status__visibility-icon'><Icon id='get-pocket' icon={LimitedIcon} title={intl.formatMessage(messages.limited_short)} /></span> : null; const withLimited = status.get('visibility_ex') === 'limited' && status.get('limited_scope') ? <span className='status__visibility-icon'><Icon id='get-pocket' icon={LimitedIcon} title={intl.formatMessage(messages.limited_short)} /></span> : null;
const withQuote = status.get('quote_id') ? <span className='status__visibility-icon'><Icon id='quote-right' icon={QuoteIcon} title='Quote' /></span> : null; const withReference = status.get('status_references_count') > 0 ? <span className='status__visibility-icon'><Icon id='link' icon={ReferenceIcon} title='Link' /></span> : null;
const withReference = (!withQuote && status.get('status_references_count') > 0) ? <span className='status__visibility-icon'><Icon id='link' icon={ReferenceIcon} title='Quiet quote' /></span> : null;
const withExpiration = status.get('expires_at') ? <span className='status__visibility-icon'><Icon id='clock-o' icon={TimerIcon} title='Expiration' /></span> : null; const withExpiration = status.get('expires_at') ? <span className='status__visibility-icon'><Icon id='clock-o' icon={TimerIcon} title='Expiration' /></span> : null;
const quote = !this.props.muted && !withoutQuote && status.get('quote_id') && (['public', 'community'].includes(contextType) ? isShowItem('quote_in_public') : isShowItem('quote_in_home')) && <CompactedStatusContainer id={status.get('quote_id')} history={this.props.history} />;
return ( return (
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}> <HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility_ex')}`, { '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_ex')}`, { '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}>
@ -598,7 +592,6 @@ class Status extends ImmutablePureComponent {
{(!matchedFilters || expanded || isShowItem('avatar_on_filter')) && ( {(!matchedFilters || expanded || isShowItem('avatar_on_filter')) && (
<div onMouseUp={this.handleMouseUp} className='status__info'> <div onMouseUp={this.handleMouseUp} className='status__info'>
<Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time'> <Link to={`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`} className='status__relative-time'>
{withQuote}
{withReference} {withReference}
{withExpiration} {withExpiration}
{withLimited} {withLimited}
@ -633,7 +626,6 @@ class Status extends ImmutablePureComponent {
{media} {media}
{hashtagBar} {hashtagBar}
{quote}
{emojiReactionsBar} {emojiReactionsBar}
</> </>
)} )}

View file

@ -68,8 +68,7 @@ const messages = defineMessages({
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' }, admin_status: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' },
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' }, admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
copy: { id: 'status.copy', defaultMessage: 'Copy link to post' }, copy: { id: 'status.copy', defaultMessage: 'Copy link to post' },
reference: { id: 'status.reference', defaultMessage: 'Quiet quote' }, reference: { id: 'status.reference', defaultMessage: 'Link' },
quote: { id: 'status.quote', defaultMessage: 'Quote' },
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' }, blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' }, unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
@ -110,7 +109,6 @@ class StatusActionBar extends ImmutablePureComponent {
onFilter: PropTypes.func, onFilter: PropTypes.func,
onAddFilter: PropTypes.func, onAddFilter: PropTypes.func,
onReference: PropTypes.func, onReference: PropTypes.func,
onQuote: PropTypes.func,
onInteractionModal: PropTypes.func, onInteractionModal: PropTypes.func,
withDismiss: PropTypes.bool, withDismiss: PropTypes.bool,
withCounters: PropTypes.bool, withCounters: PropTypes.bool,
@ -288,10 +286,6 @@ class StatusActionBar extends ImmutablePureComponent {
this.props.onReference(this.props.status, this.props.history); this.props.onReference(this.props.status, this.props.history);
}; };
handleQuote = () => {
this.props.onQuote(this.props.status, this.props.history);
};
render () { render () {
const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props; const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props;
const { signedIn, permissions } = this.props.identity; const { signedIn, permissions } = this.props.identity;
@ -303,7 +297,6 @@ class StatusActionBar extends ImmutablePureComponent {
const account = status.get('account'); const account = status.get('account');
const writtenByMe = status.getIn(['account', 'id']) === me; const writtenByMe = status.getIn(['account', 'id']) === me;
const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']); const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']);
const allowQuote = status.getIn(['account', 'other_settings', 'allow_quote']);
let menu = []; let menu = [];
@ -335,10 +328,6 @@ class StatusActionBar extends ImmutablePureComponent {
} }
if (!boostMenu) { if (!boostMenu) {
if (publicStatus && allowQuote && (account.getIn(['server_features', 'quote']) || !isHideItem('quote_unavailable_server'))) {
menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote, tag: 'reblog' });
}
if (account.getIn(['server_features', 'status_reference']) || !isHideItem('status_reference_unavailable_server')) { if (account.getIn(['server_features', 'status_reference']) || !isHideItem('status_reference_unavailable_server')) {
menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference, tag: 'reblog' }); menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference, tag: 'reblog' });
} }
@ -423,10 +412,6 @@ class StatusActionBar extends ImmutablePureComponent {
} }
if (publicStatus) { if (publicStatus) {
if (allowQuote && (account.getIn(['server_features', 'quote']) || !isHideItem('quote_unavailable_server'))) {
reblogMenu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote });
}
if (account.getIn(['server_features', 'status_reference']) || !isHideItem('status_reference_unavailable_server')) { if (account.getIn(['server_features', 'status_reference']) || !isHideItem('status_reference_unavailable_server')) {
reblogMenu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference }); reblogMenu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
} }

View file

@ -1,78 +0,0 @@
import { injectIntl } from 'react-intl';
import { connect } from 'react-redux';
import { openModal } from '../actions/modal';
import {
hideStatus,
revealStatus,
toggleStatusCollapse,
translateStatus,
undoStatusTranslation,
} from '../actions/statuses';
import CompactedStatus from '../components/compacted_status';
import { makeGetStatus, makeGetPictureInPicture } from '../selectors';
const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
const getPictureInPicture = makeGetPictureInPicture();
const mapStateToProps = (state, props) => ({
status: getStatus(state, props),
nextInReplyToId: props.nextId ? state.getIn(['statuses', props.nextId, 'in_reply_to_id']) : null,
pictureInPicture: getPictureInPicture(state, props),
});
return mapStateToProps;
};
const mapDispatchToProps = (dispatch) => ({
onTranslate (status) {
if (status.get('translation')) {
dispatch(undoStatusTranslation(status.get('id'), status.get('poll')));
} else {
dispatch(translateStatus(status.get('id')));
}
},
onOpenMedia (statusId, media, index, lang) {
dispatch(openModal({
modalType: 'MEDIA',
modalProps: { statusId, media, index, lang },
}));
},
onOpenVideo (statusId, media, lang, options) {
dispatch(openModal({
modalType: 'VIDEO',
modalProps: { statusId, media, lang, options },
}));
},
onToggleHidden (status) {
if (status.get('hidden')) {
dispatch(revealStatus(status.get('id')));
} else {
dispatch(hideStatus(status.get('id')));
}
},
onToggleCollapsed (status, isCollapsed) {
dispatch(toggleStatusCollapse(status.get('id'), isCollapsed));
},
onInteractionModal (type, status) {
dispatch(openModal({
modalType: 'INTERACTION',
modalProps: {
type,
accountId: status.getIn(['account', 'id']),
url: status.get('uri'),
},
}));
},
});
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(CompactedStatus));

View file

@ -152,10 +152,6 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({
dispatch(insertReferenceCompose(0, status.get('url'), 'BT', router)); dispatch(insertReferenceCompose(0, status.get('url'), 'BT', router));
}, },
onQuote (status, router) {
dispatch(insertReferenceCompose(0, status.get('url'), 'QT', router));
},
onTranslate (status) { onTranslate (status) {
if (status.get('translation')) { if (status.get('translation')) {
dispatch(undoStatusTranslation(status.get('id'), status.get('poll'))); dispatch(undoStatusTranslation(status.get('id'), status.get('poll')));

View file

@ -164,7 +164,7 @@ class ColumnSettings extends PureComponent {
<section> <section>
<div role='group' aria-labelledby='notifications-status_reference'> <div role='group' aria-labelledby='notifications-status_reference'>
<h3 id='notifications-status_reference'><FormattedMessage id='notifications.column_settings.status_reference' defaultMessage='Quotes:' /></h3> <h3 id='notifications-status_reference'><FormattedMessage id='notifications.column_settings.status_reference' defaultMessage='Links:' /></h3>
<div className='column-settings__row'> <div className='column-settings__row'>
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'status_reference']} onChange={onChange} label={alertStr} /> <SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'status_reference']} onChange={onChange} label={alertStr} />

View file

@ -42,7 +42,7 @@ const messages = defineMessages({
reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your post' }, reblog: { id: 'notification.reblog', defaultMessage: '{name} boosted your post' },
status: { id: 'notification.status', defaultMessage: '{name} just posted' }, status: { id: 'notification.status', defaultMessage: '{name} just posted' },
listStatus: { id: 'notification.list_status', defaultMessage: '{name} post is added to {listName}' }, listStatus: { id: 'notification.list_status', defaultMessage: '{name} post is added to {listName}' },
statusReference: { id: 'notification.status_reference', defaultMessage: '{name} quoted your post' }, statusReference: { id: 'notification.status_reference', defaultMessage: '{name} linked your post' },
update: { id: 'notification.update', defaultMessage: '{name} edited a post' }, update: { id: 'notification.update', defaultMessage: '{name} edited a post' },
adminSignUp: { id: 'notification.admin.sign_up', defaultMessage: '{name} signed up' }, adminSignUp: { id: 'notification.admin.sign_up', defaultMessage: '{name} signed up' },
adminReport: { id: 'notification.admin.report', defaultMessage: '{name} reported {target}' }, adminReport: { id: 'notification.admin.report', defaultMessage: '{name} reported {target}' },
@ -305,7 +305,7 @@ class Notification extends ImmutablePureComponent {
</div> </div>
<span title={notification.get('created_at')}> <span title={notification.get('created_at')}>
<FormattedMessage id='notification.status_reference' defaultMessage='{name} quoted your post' values={{ name: link }} /> <FormattedMessage id='notification.status_reference' defaultMessage='{name} linked your post' values={{ name: link }} />
</span> </span>
</div> </div>

View file

@ -9,7 +9,7 @@ import { NotificationWithStatus } from './notification_with_status';
const labelRenderer: LabelRenderer = (displayedName) => ( const labelRenderer: LabelRenderer = (displayedName) => (
<FormattedMessage <FormattedMessage
id='notification.status_reference' id='notification.status_reference'
defaultMessage='{name} quoted your post' defaultMessage='{name} linked your post'
values={{ name: displayedName }} values={{ name: displayedName }}
/> />
); );

View file

@ -64,8 +64,7 @@ const messages = defineMessages({
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' }, admin_status: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' },
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' }, admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
copy: { id: 'status.copy', defaultMessage: 'Copy link to post' }, copy: { id: 'status.copy', defaultMessage: 'Copy link to post' },
reference: { id: 'status.reference', defaultMessage: 'Quiet quote' }, reference: { id: 'status.reference', defaultMessage: 'Link' },
quote: { id: 'status.quote', defaultMessage: 'Quote' },
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' }, blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' }, unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
@ -89,7 +88,6 @@ class ActionBar extends PureComponent {
onFavourite: PropTypes.func.isRequired, onFavourite: PropTypes.func.isRequired,
onEmojiReact: PropTypes.func.isRequired, onEmojiReact: PropTypes.func.isRequired,
onReference: PropTypes.func.isRequired, onReference: PropTypes.func.isRequired,
onQuote: PropTypes.func.isRequired,
onBookmark: PropTypes.func.isRequired, onBookmark: PropTypes.func.isRequired,
onBookmarkCategoryAdder: PropTypes.func.isRequired, onBookmarkCategoryAdder: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,
@ -225,10 +223,6 @@ class ActionBar extends PureComponent {
this.props.onReference(this.props.status, this.props.history); this.props.onReference(this.props.status, this.props.history);
}; };
handleQuote = () => {
this.props.onQuote(this.props.status, this.props.history);
};
handleEmojiPick = (data) => { handleEmojiPick = (data) => {
this.props.onEmojiReact(this.props.status, data); this.props.onEmojiReact(this.props.status, data);
}; };
@ -244,7 +238,6 @@ class ActionBar extends PureComponent {
const account = status.get('account'); const account = status.get('account');
const writtenByMe = status.getIn(['account', 'id']) === me; const writtenByMe = status.getIn(['account', 'id']) === me;
const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']); const isRemote = status.getIn(['account', 'username']) !== status.getIn(['account', 'acct']);
const allowQuote = status.getIn(['account', 'other_settings', 'allow_quote']);
let menu = []; let menu = [];
@ -269,10 +262,6 @@ class ActionBar extends PureComponent {
menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog : messages.reblog), action: this.handleReblogForceModalClick, tag: 'reblog' }); menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog : messages.reblog), action: this.handleReblogForceModalClick, tag: 'reblog' });
if (publicStatus) { if (publicStatus) {
if (allowQuote && (account.getIn(['server_features', 'quote']) || !isHideItem('quote_unavailable_server'))) {
menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote, tag: 'reblog' });
}
if (account.getIn(['server_features', 'status_reference']) || !isHideItem('status_reference_unavailable_server')) { if (account.getIn(['server_features', 'status_reference']) || !isHideItem('status_reference_unavailable_server')) {
menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference, tag: 'reblog' }); menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference, tag: 'reblog' });
} }
@ -350,10 +339,6 @@ class ActionBar extends PureComponent {
} }
if (publicStatus) { if (publicStatus) {
if (allowQuote && (account.getIn(['server_features', 'quote']) || !isHideItem('quote_unavailable_server'))) {
reblogMenu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote });
}
if (account.getIn(['server_features', 'status_reference']) || !isHideItem('status_reference_unavailable_server')) { if (account.getIn(['server_features', 'status_reference']) || !isHideItem('status_reference_unavailable_server')) {
reblogMenu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference }); reblogMenu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
} }

View file

@ -32,7 +32,6 @@ import { DisplayName } from '../../../components/display_name';
import MediaGallery from '../../../components/media_gallery'; import MediaGallery from '../../../components/media_gallery';
import StatusContent from '../../../components/status_content'; import StatusContent from '../../../components/status_content';
import StatusEmojiReactionsBar from '../../../components/status_emoji_reactions_bar'; import StatusEmojiReactionsBar from '../../../components/status_emoji_reactions_bar';
import CompactedStatusContainer from '../../../containers/compacted_status_container';
import Audio from '../../audio'; import Audio from '../../audio';
import scheduleIdleTask from '../../ui/util/schedule_idle_task'; import scheduleIdleTask from '../../ui/util/schedule_idle_task';
@ -77,7 +76,6 @@ export const DetailedStatus: React.FC<{
onToggleHidden, onToggleHidden,
onEmojiReact, onEmojiReact,
onUnEmojiReact, onUnEmojiReact,
muted,
}) => { }) => {
const properStatus = status?.get('reblog') ?? status; const properStatus = status?.get('reblog') ?? status;
const [height, setHeight] = useState(0); const [height, setHeight] = useState(0);
@ -330,7 +328,7 @@ export const DetailedStatus: React.FC<{
<AnimatedNumber value={status.get('status_referred_by_count')} /> <AnimatedNumber value={status.get('status_referred_by_count')} />
</span> </span>
<FormattedMessage <FormattedMessage
id='status.quotes' id='status.references'
defaultMessage='{count, plural, one {boost} other {boosts}}' defaultMessage='{count, plural, one {boost} other {boosts}}'
values={{ count: status.get('status_referred_by_count') }} values={{ count: status.get('status_referred_by_count') }}
/> />
@ -383,13 +381,6 @@ export const DetailedStatus: React.FC<{
(!matchedFilters || showDespiteFilter) && (!matchedFilters || showDespiteFilter) &&
(!status.get('hidden') || status.get('spoiler_text').length === 0); (!status.get('hidden') || status.get('spoiler_text').length === 0);
const quote = !muted && status.get('quote_id') && (
<>
{/* @ts-expect-error: CompactedStatusContainer class is not typescript still. */}
<CompactedStatusContainer id={status.get('quote_id')} history={history} />
</>
);
return ( return (
<div style={outerStyle}> <div style={outerStyle}>
<div ref={handleRef} className={classNames('detailed-status')}> <div ref={handleRef} className={classNames('detailed-status')}>
@ -459,7 +450,6 @@ export const DetailedStatus: React.FC<{
{media} {media}
{hashtagBar} {hashtagBar}
{quote}
{emojiReactionsBar} {emojiReactionsBar}
</> </>
)} )}

View file

@ -156,7 +156,7 @@ const makeMapStateToProps = () => {
if (status) { if (status) {
ancestorsIds = getAncestorsIds(state, { id: status.get('in_reply_to_id') }); ancestorsIds = getAncestorsIds(state, { id: status.get('in_reply_to_id') });
descendantsIds = getDescendantsIds(state, { id: status.get('id') }); descendantsIds = getDescendantsIds(state, { id: status.get('id') });
referenceIds = getReferenceIds(state, { id: status.get('id') }).filter((id) => id !== status.get('quote_id')); referenceIds = getReferenceIds(state, { id: status.get('id') });
} }
return { return {
@ -341,10 +341,6 @@ class Status extends ImmutablePureComponent {
this.props.dispatch(insertReferenceCompose(0, status.get('url'), 'BT', router)); this.props.dispatch(insertReferenceCompose(0, status.get('url'), 'BT', router));
}; };
handleQuote = (status, router) => {
this.props.dispatch(insertReferenceCompose(0, status.get('url'), 'QT', router));
};
handleBookmarkClick = (status) => { handleBookmarkClick = (status) => {
if (bookmarkCategoryNeeded) { if (bookmarkCategoryNeeded) {
this.handleBookmarkCategoryAdderClick(status); this.handleBookmarkCategoryAdderClick(status);
@ -774,7 +770,6 @@ class Status extends ImmutablePureComponent {
onReblog={this.handleReblogClick} onReblog={this.handleReblogClick}
onReblogForceModal={this.handleReblogForceModalClick} onReblogForceModal={this.handleReblogForceModalClick}
onReference={this.handleReference} onReference={this.handleReference}
onQuote={this.handleQuote}
onBookmark={this.handleBookmarkClick} onBookmark={this.handleBookmarkClick}
onBookmarkCategoryAdder={this.handleBookmarkCategoryAdderClick} onBookmarkCategoryAdder={this.handleBookmarkCategoryAdderClick}
onDelete={this.handleDeleteClick} onDelete={this.handleDeleteClick}

View file

@ -81,7 +81,7 @@ class StatusReferences extends ImmutablePureComponent {
bindToDocument={!multiColumn} bindToDocument={!multiColumn}
> >
{accountIds.map(id => {accountIds.map(id =>
<StatusContainer key={id} id={id} withNote={false} withoutQuote />, <StatusContainer key={id} id={id} withNote={false} />,
)} )}
</ScrollableList> </ScrollableList>

View file

@ -2,14 +2,10 @@
/** /**
* @typedef { 'blocking_quote' * @typedef { 'emoji_reaction_on_timeline'
* | 'emoji_reaction_on_timeline'
* | 'emoji_reaction_unavailable_server' * | 'emoji_reaction_unavailable_server'
* | 'emoji_reaction_count' * | 'emoji_reaction_count'
* | 'favourite_menu' * | 'favourite_menu'
* | 'quote_in_home'
* | 'quote_in_public'
* | 'quote_unavailable_server'
* | 'recent_emojis' * | 'recent_emojis'
* | 'relationships' * | 'relationships'
* | 'status_reference_unavailable_server' * | 'status_reference_unavailable_server'

View file

@ -462,7 +462,7 @@
"empty_column.notification_requests": "All clear! There is nothing here. When you receive new notifications, they will appear here according to your settings.", "empty_column.notification_requests": "All clear! There is nothing here. When you receive new notifications, they will appear here according to your settings.",
"empty_column.notifications": "You don't have any notifications yet. When other people interact with you, you will see it here.", "empty_column.notifications": "You don't have any notifications yet. When other people interact with you, you will see it here.",
"empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up", "empty_column.public": "There is nothing here! Write something publicly, or manually follow users from other servers to fill it up",
"empty_column.status_references": "No one has quotes this post yet. When someone does, they will show up here.", "empty_column.status_references": "No one has links this post yet. When someone does, they will show up here.",
"error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.", "error.unexpected_crash.explanation": "Due to a bug in our code or a browser compatibility issue, this page could not be displayed correctly.",
"error.unexpected_crash.explanation_addons": "This page could not be displayed correctly. This error is likely caused by a browser add-on or automatic translation tools.", "error.unexpected_crash.explanation_addons": "This page could not be displayed correctly. This error is likely caused by a browser add-on or automatic translation tools.",
"error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.", "error.unexpected_crash.next_steps": "Try refreshing the page. If that does not help, you may still be able to use Mastodon through a different browser or native app.",
@ -762,7 +762,7 @@
"notification.relationships_severance_event.learn_more": "Learn more", "notification.relationships_severance_event.learn_more": "Learn more",
"notification.relationships_severance_event.user_domain_block": "You have blocked {target}, removing {followersCount} of your followers and {followingCount, plural, one {# account} other {# accounts}} you follow.", "notification.relationships_severance_event.user_domain_block": "You have blocked {target}, removing {followersCount} of your followers and {followingCount, plural, one {# account} other {# accounts}} you follow.",
"notification.status": "{name} just posted", "notification.status": "{name} just posted",
"notification.status_reference": "{name} quoted your post", "notification.status_reference": "{name} linked your post",
"notification.update": "{name} edited a post", "notification.update": "{name} edited a post",
"notification_requests.accept": "Accept", "notification_requests.accept": "Accept",
"notification_requests.accept_multiple": "{count, plural, one {Accept # request…} other {Accept # requests…}}", "notification_requests.accept_multiple": "{count, plural, one {Accept # request…} other {Accept # requests…}}",
@ -804,7 +804,7 @@
"notifications.column_settings.show": "Show in column", "notifications.column_settings.show": "Show in column",
"notifications.column_settings.sound": "Play sound", "notifications.column_settings.sound": "Play sound",
"notifications.column_settings.status": "New posts:", "notifications.column_settings.status": "New posts:",
"notifications.column_settings.status_reference": "Quotes:", "notifications.column_settings.status_reference": "Links:",
"notifications.column_settings.unread_notifications.category": "Unread notifications", "notifications.column_settings.unread_notifications.category": "Unread notifications",
"notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications", "notifications.column_settings.unread_notifications.highlight": "Highlight unread notifications",
"notifications.column_settings.update": "Edits:", "notifications.column_settings.update": "Edits:",
@ -815,7 +815,7 @@
"notifications.filter.follows": "Follows", "notifications.filter.follows": "Follows",
"notifications.filter.mentions": "Mentions", "notifications.filter.mentions": "Mentions",
"notifications.filter.polls": "Poll results", "notifications.filter.polls": "Poll results",
"notifications.filter.status_references": "Quotes", "notifications.filter.status_references": "Links",
"notifications.filter.statuses": "Updates from people you follow", "notifications.filter.statuses": "Updates from people you follow",
"notifications.grant_permission": "Grant permission.", "notifications.grant_permission": "Grant permission.",
"notifications.group": "{count} notifications", "notifications.group": "{count} notifications",
@ -1061,8 +1061,6 @@
"status.open": "Expand this post", "status.open": "Expand this post",
"status.pin": "Pin on profile", "status.pin": "Pin on profile",
"status.pinned": "Pinned post", "status.pinned": "Pinned post",
"status.quote": "Quote",
"status.quotes": "{count, plural, one {quote} other {quotes}}",
"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",
@ -1071,7 +1069,8 @@
"status.reblogs": "{count, plural, one {boost} other {boosts}}", "status.reblogs": "{count, plural, one {boost} other {boosts}}",
"status.reblogs.empty": "No one has boosted this post yet. When someone does, they will show up here.", "status.reblogs.empty": "No one has boosted this post yet. When someone does, they will show up here.",
"status.redraft": "Delete & re-draft", "status.redraft": "Delete & re-draft",
"status.reference": "Quiet quote", "status.reference": "Link",
"status.references": "{count, plural, one {link} other {links}}",
"status.remove_bookmark": "Remove bookmark", "status.remove_bookmark": "Remove bookmark",
"status.remove_favourite": "Remove from favorites", "status.remove_favourite": "Remove from favorites",
"status.replied_in_thread": "Replied in thread", "status.replied_in_thread": "Replied in thread",

View file

@ -438,7 +438,7 @@
"empty_column.notification_requests": "ここに表示するものはありません。新しい通知を受け取ったとき、フィルタリング設定で通知がブロックされたアカウントがある場合はここに表示されます。", "empty_column.notification_requests": "ここに表示するものはありません。新しい通知を受け取ったとき、フィルタリング設定で通知がブロックされたアカウントがある場合はここに表示されます。",
"empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。", "empty_column.notifications": "まだ通知がありません。他の人とふれ合って会話を始めましょう。",
"empty_column.public": "ここにはまだ何もありません! 公開で何かを投稿したり、他のサーバーのユーザーをフォローしたりしていっぱいにしましょう", "empty_column.public": "ここにはまだ何もありません! 公開で何かを投稿したり、他のサーバーのユーザーをフォローしたりしていっぱいにしましょう",
"empty_column.status_references": "まだ誰も引用していません。引用されるとここに表示されます。", "empty_column.status_references": "まだ誰もリンクしていません。リンクされるとここに表示されます。",
"error.unexpected_crash.explanation": "不具合かブラウザの互換性問題のため、このページを正しく表示できませんでした。", "error.unexpected_crash.explanation": "不具合かブラウザの互換性問題のため、このページを正しく表示できませんでした。",
"error.unexpected_crash.explanation_addons": "このページは正しく表示できませんでした。このエラーはブラウザのアドオンや自動翻訳ツールによって引き起こされることがあります。", "error.unexpected_crash.explanation_addons": "このページは正しく表示できませんでした。このエラーはブラウザのアドオンや自動翻訳ツールによって引き起こされることがあります。",
"error.unexpected_crash.next_steps": "ページの再読み込みをお試しください。それでも解決しない場合、別のブラウザかアプリを使えば使用できることがあります。", "error.unexpected_crash.next_steps": "ページの再読み込みをお試しください。それでも解決しない場合、別のブラウザかアプリを使えば使用できることがあります。",
@ -733,7 +733,7 @@
"notification.relationships_severance_event.learn_more": "詳細を確認", "notification.relationships_severance_event.learn_more": "詳細を確認",
"notification.relationships_severance_event.user_domain_block": "{target} のブロックにより{followersCount}フォロワーと{followingCount, plural, other {#フォロー}}が解除されました。", "notification.relationships_severance_event.user_domain_block": "{target} のブロックにより{followersCount}フォロワーと{followingCount, plural, other {#フォロー}}が解除されました。",
"notification.status": "{name}さんが投稿しました", "notification.status": "{name}さんが投稿しました",
"notification.status_reference": "{name}さんがあなたの投稿を引用しました", "notification.status_reference": "{name}さんがあなたの投稿をリンクしました",
"notification.update": "{name}さんが投稿を編集しました", "notification.update": "{name}さんが投稿を編集しました",
"notification_requests.accept": "受け入れる", "notification_requests.accept": "受け入れる",
"notification_requests.accept_multiple": "{count, plural, other {選択中の#件を受け入れる}}", "notification_requests.accept_multiple": "{count, plural, other {選択中の#件を受け入れる}}",
@ -774,7 +774,7 @@
"notifications.column_settings.show": "カラムに表示", "notifications.column_settings.show": "カラムに表示",
"notifications.column_settings.sound": "通知音を再生", "notifications.column_settings.sound": "通知音を再生",
"notifications.column_settings.status": "新しい投稿:", "notifications.column_settings.status": "新しい投稿:",
"notifications.column_settings.status_reference": "引用", "notifications.column_settings.status_reference": "リンク",
"notifications.column_settings.unread_notifications.category": "未読の通知:", "notifications.column_settings.unread_notifications.category": "未読の通知:",
"notifications.column_settings.unread_notifications.highlight": "未読の通知を強調表示", "notifications.column_settings.unread_notifications.highlight": "未読の通知を強調表示",
"notifications.column_settings.update": "編集:", "notifications.column_settings.update": "編集:",
@ -786,7 +786,7 @@
"notifications.filter.mentions": "返信", "notifications.filter.mentions": "返信",
"notifications.filter.polls": "アンケート結果", "notifications.filter.polls": "アンケート結果",
"notifications.filter.statuses": "フォローしている人の新着情報", "notifications.filter.statuses": "フォローしている人の新着情報",
"notifications.filter.status_references": "引用", "notifications.filter.status_references": "リンク",
"notifications.grant_permission": "権限の付与", "notifications.grant_permission": "権限の付与",
"notifications.group": "{count}件の通知", "notifications.group": "{count}件の通知",
"notifications.mark_as_read": "すべて既読にする", "notifications.mark_as_read": "すべて既読にする",
@ -1031,8 +1031,7 @@
"status.open": "詳細を表示", "status.open": "詳細を表示",
"status.pin": "プロフィールに固定表示", "status.pin": "プロフィールに固定表示",
"status.pinned": "固定された投稿", "status.pinned": "固定された投稿",
"status.quote": "引用", "status.quote": "リンク",
"status.quotes": "{count, plural, one {引用} other {引用}}",
"status.read_more": "もっと見る", "status.read_more": "もっと見る",
"status.reblog": "ブースト", "status.reblog": "ブースト",
"status.reblog_private": "ブースト", "status.reblog_private": "ブースト",
@ -1041,7 +1040,8 @@
"status.reblogs": "{count, plural, one {ブースト} other {ブースト}}", "status.reblogs": "{count, plural, one {ブースト} other {ブースト}}",
"status.reblogs.empty": "まだ誰もブーストしていません。ブーストされるとここに表示されます。", "status.reblogs.empty": "まだ誰もブーストしていません。ブーストされるとここに表示されます。",
"status.redraft": "削除して下書きに戻す", "status.redraft": "削除して下書きに戻す",
"status.reference": "ひかえめな引用", "status.reference": "リンク",
"status.references": "{count, plural, one {リンク} other {リンク}}",
"status.remove_bookmark": "ブックマークを削除", "status.remove_bookmark": "ブックマークを削除",
"status.remove_favourite": "お気に入りから削除", "status.remove_favourite": "お気に入りから削除",
"status.replied_in_thread": "ほかのユーザーへ", "status.replied_in_thread": "ほかのユーザーへ",

View file

@ -56,7 +56,6 @@ const AccountOtherSettingsFactory = ImmutableRecord<AccountOtherSettingsShape>({
hide_statuses_count: false, hide_statuses_count: false,
translatable_private: false, translatable_private: false,
link_preview: true, link_preview: true,
allow_quote: true,
emoji_reaction_policy: 'allow', emoji_reaction_policy: 'allow',
subscription_policy: 'allow', subscription_policy: 'allow',
}); });
@ -69,7 +68,6 @@ const AccountServerFeaturesFactory =
ImmutableRecord<AccountServerFeaturesShape>({ ImmutableRecord<AccountServerFeaturesShape>({
circle: false, circle: false,
emoji_reaction: false, emoji_reaction: false,
quote: false,
status_reference: false, status_reference: false,
}); });

View file

@ -11,7 +11,6 @@ const normalizeFilter = (state, filter) => {
filter_action: filter.filter_action, filter_action: filter.filter_action,
keywords: filter.keywords, keywords: filter.keywords,
expires_at: filter.expires_at ? Date.parse(filter.expires_at) : null, expires_at: filter.expires_at ? Date.parse(filter.expires_at) : null,
with_quote: filter.with_quote,
}); });
if (is(state.get(filter.id), normalizedFilter)) { if (is(state.get(filter.id), normalizedFilter)) {

View file

@ -13,41 +13,27 @@ export const makeGetStatus = () => {
[ [
(state, { id }) => state.getIn(['statuses', id]), (state, { id }) => state.getIn(['statuses', id]),
(state, { id }) => state.getIn(['statuses', state.getIn(['statuses', id, 'reblog'])]), (state, { id }) => state.getIn(['statuses', state.getIn(['statuses', id, 'reblog'])]),
(state, { id }) => state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id'])]),
(state, { id }) => state.getIn(['statuses', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'quote_id'])]),
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]), (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]),
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]), (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]),
getFilters, getFilters,
(_, { contextType }) => ['detailed', 'bookmarks', 'favourites'].includes(contextType), (_, { contextType }) => ['detailed', 'bookmarks', 'favourites'].includes(contextType),
], ],
(statusBase, statusReblog, statusQuote, statusReblogQuote, accountBase, accountReblog, filters, warnInsteadOfHide) => { (statusBase, statusReblog, accountBase, accountReblog, filters, warnInsteadOfHide) => {
if (!statusBase || statusBase.get('isLoading')) { if (!statusBase || statusBase.get('isLoading')) {
return null; return null;
} }
if (statusReblog) { if (statusReblog) {
statusReblog = statusReblog.set('account', accountReblog); statusReblog = statusReblog.set('account', accountReblog);
statusQuote = statusReblogQuote;
} else { } else {
statusReblog = null; statusReblog = null;
} }
if (isHideItem('blocking_quote') && (statusReblog || statusBase).getIn(['quote', 'quote_muted'])) {
return null;
}
let filtered = false; let filtered = false;
let mediaFiltered = false; let mediaFiltered = false;
if ((accountReblog || accountBase).get('id') !== me && filters) { if ((accountReblog || accountBase).get('id') !== me && filters) {
let filterResults = statusReblog?.get('filtered') || statusBase.get('filtered') || ImmutableList(); let filterResults = statusReblog?.get('filtered') || statusBase.get('filtered') || ImmutableList();
const quoteFilterResults = statusQuote?.get('filtered');
if (quoteFilterResults) {
const filterWithQuote = quoteFilterResults.some((result) => filters.getIn([result.get('filter'), 'with_quote']));
if (filterWithQuote) {
filterResults = filterResults.concat(quoteFilterResults);
}
}
if (!warnInsteadOfHide && filterResults.some((result) => filters.getIn([result.get('filter'), 'filter_action']) === 'hide')) { if (!warnInsteadOfHide && filterResults.some((result) => filters.getIn([result.get('filter'), 'filter_action']) === 'hide')) {
return null; return null;
@ -66,7 +52,6 @@ export const makeGetStatus = () => {
return statusBase.withMutations(map => { return statusBase.withMutations(map => {
map.set('reblog', statusReblog); map.set('reblog', statusReblog);
map.set('quote', statusQuote);
map.set('account', accountBase); map.set('account', accountBase);
map.set('matched_filters', filtered); map.set('matched_filters', filtered);
map.set('matched_media_filters', mediaFiltered); map.set('matched_media_filters', mediaFiltered);

View file

@ -555,7 +555,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
def related_to_local_activity? def related_to_local_activity?
fetch? || followed_by_local_accounts? || requested_through_relay? || fetch? || followed_by_local_accounts? || requested_through_relay? ||
responds_to_followed_account? || addresses_local_accounts? || quote_local? || free_friend_domain? responds_to_followed_account? || addresses_local_accounts? || free_friend_domain?
end end
def responds_to_followed_account? def responds_to_followed_account?
@ -621,16 +621,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
ProcessReferencesService.call_service_without_error(@status, [], reference_uris, [quote].compact) ProcessReferencesService.call_service_without_error(@status, [], reference_uris, [quote].compact)
end end
def quote_local?
url = quote
if url.present?
ActivityPub::TagManager.instance.uri_to_resource(url, Status)&.local?
else
false
end
end
def free_friend_domain? def free_friend_domain?
FriendDomain.free_receivings.exists?(domain: @account.domain) FriendDomain.free_receivings.exists?(domain: @account.domain)
end end
@ -640,15 +630,6 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
end end
def quote def quote
@quote ||= quote_from_tags || @object['quote'] || @object['quoteUrl'] || @object['quoteURL'] || @object['_misskey_quote'] @quote ||= nil # TODO: quote
end
def quote_from_tags
return @quote_from_tags if defined?(@quote_from_tags)
hit_tag = as_array(@object['tag']).detect do |tag|
equals_or_includes?(tag['type'], 'Link') && LINK_MEDIA_TYPES.include?(tag['mediaType']) && tag['href'].present?
end
@quote_from_tags = hit_tag && hit_tag['href']
end end
end end

View file

@ -5,7 +5,6 @@ module ActivityPub::CaseTransform
NO_CONVERT_VALUES = %w( NO_CONVERT_VALUES = %w(
_misskey_content _misskey_content
_misskey_license _misskey_license
_misskey_quote
).freeze ).freeze
def camel_lower_cache def camel_lower_cache

View file

@ -78,7 +78,6 @@ class StatusReachFinder
reblogs_account_ids, reblogs_account_ids,
favourites_account_ids, favourites_account_ids,
replies_account_ids, replies_account_ids,
quoted_account_id,
].tap do |arr| ].tap do |arr|
arr.flatten! arr.flatten!
arr.compact! arr.compact!
@ -114,10 +113,6 @@ class StatusReachFinder
@status.replies.pluck(:account_id) if distributable? || unsafe? @status.replies.pluck(:account_id) if distributable? || unsafe?
end end
def quoted_account_id
@status.quote.account_id if @status.quote?
end
def followers_inboxes def followers_inboxes
scope = followers_scope scope = followers_scope
inboxes_without_suspended_for(scope) inboxes_without_suspended_for(scope)

View file

@ -15,13 +15,6 @@ module Account::OtherSettings
false false
end end
def allow_quote?
return user.setting_allow_quote if local? && user.present?
return settings['allow_quote'] if settings.present? && settings.key?('allow_quote')
true
end
def hide_statuses_count? def hide_statuses_count?
return user&.setting_hide_statuses_count if local? && user.present? return user&.setting_hide_statuses_count if local? && user.present?
return settings['hide_statuses_count'] if settings.present? && settings.key?('hide_statuses_count') return settings['hide_statuses_count'] if settings.present? && settings.key?('hide_statuses_count')
@ -88,7 +81,6 @@ module Account::OtherSettings
'hide_following_count' => hide_following_count?, 'hide_following_count' => hide_following_count?,
'hide_followers_count' => hide_followers_count?, 'hide_followers_count' => hide_followers_count?,
'translatable_private' => translatable_private?, 'translatable_private' => translatable_private?,
'allow_quote' => allow_quote?,
'emoji_reaction_policy' => Setting.enable_emoji_reaction ? emoji_reaction_policy.to_s : 'block', 'emoji_reaction_policy' => Setting.enable_emoji_reaction ? emoji_reaction_policy.to_s : 'block',
} }
end end

View file

@ -107,18 +107,6 @@ module User::HasSettings
settings['web.content_font_size'] settings['web.content_font_size']
end end
def setting_show_quote_in_home
settings['web.show_quote_in_home']
end
def setting_show_quote_in_public
settings['web.show_quote_in_public']
end
def setting_hide_blocking_quote
settings['web.hide_blocking_quote']
end
def setting_show_relationships def setting_show_relationships
settings['web.show_relationships'] settings['web.show_relationships']
end end
@ -127,10 +115,6 @@ module User::HasSettings
settings['web.show_avatar_on_filter'] settings['web.show_avatar_on_filter']
end end
def setting_allow_quote
settings['allow_quote']
end
def setting_reject_send_limited_to_suspects def setting_reject_send_limited_to_suspects
settings['reject_send_limited_to_suspects'] settings['reject_send_limited_to_suspects']
end end
@ -259,10 +243,6 @@ module User::HasSettings
settings['use_public_index'] settings['use_public_index']
end end
def setting_reverse_search_quote
settings['reverse_search_quote']
end
def setting_disallow_unlisted_public_searchability def setting_disallow_unlisted_public_searchability
settings['disallow_unlisted_public_searchability'] settings['disallow_unlisted_public_searchability']
end end
@ -275,10 +255,6 @@ module User::HasSettings
settings['web.hide_emoji_reaction_unavailable_server'] settings['web.hide_emoji_reaction_unavailable_server']
end end
def setting_hide_quote_unavailable_server
settings['web.hide_quote_unavailable_server']
end
def setting_hide_status_reference_unavailable_server def setting_hide_status_reference_unavailable_server
settings['web.hide_status_reference_unavailable_server'] settings['web.hide_status_reference_unavailable_server']
end end

View file

@ -12,7 +12,6 @@
# expires_at :datetime # expires_at :datetime
# phrase :text default(""), not null # phrase :text default(""), not null
# with_profile :boolean default(FALSE), not null # with_profile :boolean default(FALSE), not null
# with_quote :boolean default(TRUE), not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# account_id :bigint(8) not null # account_id :bigint(8) not null
@ -69,14 +68,6 @@ class CustomFilter < ApplicationRecord
hide_action? hide_action?
end end
def exclude_quote=(value)
self.with_quote = !ActiveModel::Type::Boolean.new.cast(value)
end
def exclude_quote
!with_quote
end
def exclude_profile=(value) def exclude_profile=(value)
self.with_profile = !ActiveModel::Type::Boolean.new.cast(value) self.with_profile = !ActiveModel::Type::Boolean.new.cast(value)
end end
@ -111,9 +102,6 @@ class CustomFilter < ApplicationRecord
end end
def self.apply_cached_filters(cached_filters, status, following: false) def self.apply_cached_filters(cached_filters, status, following: false)
references_text_cache = nil
references_spoiler_text_cache = nil
cached_filters.filter_map do |filter, rules| cached_filters.filter_map do |filter, rules|
next if filter.exclude_follows && following next if filter.exclude_follows && following
next if filter.exclude_localusers && status.account.local? next if filter.exclude_localusers && status.account.local?
@ -121,17 +109,10 @@ class CustomFilter < ApplicationRecord
if rules[:keywords].present? if rules[:keywords].present?
match = rules[:keywords].match(status.proper.searchable_text) match = rules[:keywords].match(status.proper.searchable_text)
match = rules[:keywords].match([status.account.display_name, status.account.note].join("\n\n")) if !match && filter.with_profile match = rules[:keywords].match([status.account.display_name, status.account.note].join("\n\n")) if !match && filter.with_profile
if match.nil? && filter.with_quote && status.proper.reference_objects.exists?
references_text_cache = status.proper.references.pluck(:text).join("\n\n") if references_text_cache.nil?
references_spoiler_text_cache = status.proper.references.pluck(:spoiler_text).join("\n\n") if references_spoiler_text_cache.nil?
match = rules[:keywords].match(references_text_cache)
match = rules[:keywords].match(references_spoiler_text_cache) if match.nil?
end
end end
keyword_matches = [match.to_s] unless match.nil? keyword_matches = [match.to_s] unless match.nil?
reference_ids = filter.with_quote ? status.proper.reference_objects.pluck(:target_status_id) : [] status_matches = [status.id, status.reblog_of_id].compact & rules[:status_ids] if rules[:status_ids].present?
status_matches = ([status.id, status.reblog_of_id] + reference_ids).compact & rules[:status_ids] if rules[:status_ids].present?
next if keyword_matches.blank? && status_matches.blank? next if keyword_matches.blank? && status_matches.blank?

View file

@ -35,8 +35,6 @@ class InstanceInfo < ApplicationRecord
yojo-art yojo-art
).freeze ).freeze
QUOTE_AVAILABLE_SOFTWARES = EMOJI_REACTION_AVAILABLE_SOFTWARES + %w(bridgy-fed).freeze
STATUS_REFERENCE_AVAILABLE_SOFTWARES = %w(fedibird).freeze STATUS_REFERENCE_AVAILABLE_SOFTWARES = %w(fedibird).freeze
CIRCLE_AVAILABLE_SOFTWARES = %w(fedibird).freeze CIRCLE_AVAILABLE_SOFTWARES = %w(fedibird).freeze
@ -87,7 +85,6 @@ class InstanceInfo < ApplicationRecord
{ {
emoji_reaction: feature_available?(info, EMOJI_REACTION_AVAILABLE_SOFTWARES, 'emoji_reaction'), emoji_reaction: feature_available?(info, EMOJI_REACTION_AVAILABLE_SOFTWARES, 'emoji_reaction'),
quote: feature_available?(info, QUOTE_AVAILABLE_SOFTWARES, 'quote'),
status_reference: feature_available?(info, STATUS_REFERENCE_AVAILABLE_SOFTWARES, 'status_reference'), status_reference: feature_available?(info, STATUS_REFERENCE_AVAILABLE_SOFTWARES, 'status_reference'),
circle: feature_available?(info, CIRCLE_AVAILABLE_SOFTWARES, 'circle'), circle: feature_available?(info, CIRCLE_AVAILABLE_SOFTWARES, 'circle'),
} }
@ -96,7 +93,6 @@ class InstanceInfo < ApplicationRecord
def local_features def local_features
{ {
emoji_reaction: Setting.enable_emoji_reaction, emoji_reaction: Setting.enable_emoji_reaction,
quote: true,
status_reference: true, status_reference: true,
circle: true, circle: true,
} }

View file

@ -205,19 +205,6 @@ class Status < ApplicationRecord
account: [:account_stat, user: :role], account: [:account_stat, user: :role],
active_mentions: { account: :account_stat }, active_mentions: { account: :account_stat },
], ],
quote: [
:application,
:tags,
:media_attachments,
:conversation,
:status_stat,
:preloadable_poll,
:reference_objects,
:scheduled_expiration_status,
preview_cards_status: { preview_card: { author_account: [:account_stat, user: :role] } },
account: [:account_stat, user: :role],
active_mentions: :account,
],
thread: :account thread: :account
delegate :domain, to: :account, prefix: true delegate :domain, to: :account, prefix: true
@ -252,10 +239,6 @@ class Status < ApplicationRecord
!reblog_of_id.nil? !reblog_of_id.nil?
end end
def quote?
!quote_of_id.nil? && !quote.nil?
end
def expires? def expires?
scheduled_expiration_status.present? scheduled_expiration_status.present?
end end

View file

@ -5,12 +5,11 @@
# Table name: status_references # Table name: status_references
# #
# id :bigint(8) not null, primary key # id :bigint(8) not null, primary key
# status_id :bigint(8) not null # attribute_type :string
# target_status_id :bigint(8) not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# attribute_type :string # status_id :bigint(8) not null
# quote :boolean default(FALSE), not null # target_status_id :bigint(8) not null
# #
class StatusReference < ApplicationRecord class StatusReference < ApplicationRecord
@ -22,8 +21,6 @@ class StatusReference < ApplicationRecord
has_one :notification, as: :activity, dependent: :destroy has_one :notification, as: :activity, dependent: :destroy
after_commit :reset_parent_cache after_commit :reset_parent_cache
after_create_commit :set_quote
after_destroy_commit :remove_quote
private private
@ -31,18 +28,4 @@ class StatusReference < ApplicationRecord
Rails.cache.delete("statuses/#{status_id}") Rails.cache.delete("statuses/#{status_id}")
Rails.cache.delete("statuses/#{target_status_id}") Rails.cache.delete("statuses/#{target_status_id}")
end end
def set_quote
return unless quote
return if status.quote_of_id.present?
status.quote_of_id = target_status_id
end
def remove_quote
return unless quote
return unless status.quote_of_id == target_status_id
status.quote_of_id = nil
end
end end

View file

@ -27,7 +27,6 @@ class UserSettings
setting :default_searchability, default: :direct, in: %w(public private direct limited public_unlisted) setting :default_searchability, default: :direct, in: %w(public private direct limited public_unlisted)
setting :default_searchability_of_search, default: :public, in: %w(public private direct limited) setting :default_searchability_of_search, default: :public, in: %w(public private direct limited)
setting :use_public_index, default: true setting :use_public_index, default: true
setting :reverse_search_quote, default: false
setting :disallow_unlisted_public_searchability, default: false setting :disallow_unlisted_public_searchability, default: false
setting :public_post_to_unlisted, default: false setting :public_post_to_unlisted, default: false
setting :reject_public_unlisted_subscription, default: false setting :reject_public_unlisted_subscription, default: false
@ -40,7 +39,6 @@ class UserSettings
setting :dtl_force_visibility, default: :unchange, in: %w(unchange public public_unlisted unlisted) setting :dtl_force_visibility, default: :unchange, in: %w(unchange public public_unlisted unlisted)
setting :dtl_force_searchability, default: :unchange, in: %w(unchange public public_unlisted) setting :dtl_force_searchability, default: :unchange, in: %w(unchange public public_unlisted)
setting :lock_follow_from_bot, default: false setting :lock_follow_from_bot, default: false
setting :allow_quote, default: true
setting :reject_send_limited_to_suspects, default: false setting :reject_send_limited_to_suspects, default: false
setting_inverse_alias :indexable, :noindex setting_inverse_alias :indexable, :noindex
@ -75,18 +73,12 @@ class UserSettings
setting :auto_play, default: true setting :auto_play, default: true
setting :simple_timeline_menu, default: false setting :simple_timeline_menu, default: false
setting :boost_menu, default: false setting :boost_menu, default: false
setting :show_quote_in_home, default: true
setting :show_quote_in_public, default: false
setting :show_relationships, default: true setting :show_relationships, default: true
setting :hide_blocking_quote, default: true
setting :hide_emoji_reaction_unavailable_server, default: false setting :hide_emoji_reaction_unavailable_server, default: false
setting :hide_quote_unavailable_server, default: false
setting :hide_status_reference_unavailable_server, default: false
setting :hide_favourite_menu, default: false setting :hide_favourite_menu, default: false
setting :hide_emoji_reaction_count, default: false setting :hide_emoji_reaction_count, default: false
setting :show_avatar_on_filter, default: true setting :show_avatar_on_filter, default: true
setting_inverse_alias :'web.show_blocking_quote', :'web.hide_blocking_quote'
setting_inverse_alias :'web.show_emoji_reaction_count', :'web.hide_emoji_reaction_count' setting_inverse_alias :'web.show_emoji_reaction_count', :'web.hide_emoji_reaction_count'
setting_inverse_alias :'web.show_favourite_menu', :'web.hide_favourite_menu' setting_inverse_alias :'web.show_favourite_menu', :'web.hide_favourite_menu'
setting_inverse_alias :'web.show_recent_emojis', :'web.hide_recent_emojis' setting_inverse_alias :'web.show_recent_emojis', :'web.hide_recent_emojis'

View file

@ -47,10 +47,6 @@ class StatusPolicy < ApplicationPolicy
show? && !blocking_author? show? && !blocking_author?
end end
def quote?
%i(public public_unlisted unlisted).include?(record.visibility.to_sym) && show? && !blocking_author?
end
def destroy? def destroy?
owned? owned?
end end

View file

@ -21,7 +21,6 @@ class StatusRelationshipsPresenter
@emoji_reaction_allows_map = nil @emoji_reaction_allows_map = nil
else else
statuses = statuses.compact statuses = statuses.compact
statuses += statuses.filter_map(&:quote)
status_ids = statuses.flat_map { |s| [s.id, s.reblog_of_id] }.uniq.compact status_ids = statuses.flat_map { |s| [s.id, s.reblog_of_id] }.uniq.compact
conversation_ids = statuses.filter_map(&:conversation_id).uniq conversation_ids = statuses.filter_map(&:conversation_id).uniq
pinnable_status_ids = statuses.map(&:proper).filter_map { |s| s.id if s.account_id == current_account_id && PINNABLE_VISIBILITIES.include?(s.visibility) } pinnable_status_ids = statuses.map(&:proper).filter_map { |s| s.id if s.account_id == current_account_id && PINNABLE_VISIBILITIES.include?(s.visibility) }

View file

@ -3,7 +3,7 @@
class ActivityPub::NoteSerializer < ActivityPub::Serializer class ActivityPub::NoteSerializer < ActivityPub::Serializer
include FormattingHelper include FormattingHelper
context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :searchable_by, :references, :limited_scope, :quote_uri context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :searchable_by, :references, :limited_scope
attributes :id, :type, :summary, attributes :id, :type, :summary,
:in_reply_to, :published, :url, :in_reply_to, :published, :url,
@ -16,9 +16,6 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
attribute :updated, if: :edited? attribute :updated, if: :edited?
attribute :limited_scope, if: :limited_visibility? attribute :limited_scope, if: :limited_visibility?
attribute :quote_uri, if: :quote?
attribute :misskey_quote, key: :_misskey_quote, if: :quote?
has_many :virtual_attachments, key: :attachment has_many :virtual_attachments, key: :attachment
has_many :virtual_tags, key: :tag has_many :virtual_tags, key: :tag
@ -158,30 +155,7 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
end end
def virtual_tags def virtual_tags
object.active_mentions.to_a.sort_by(&:id) + object.tags + object.emojis + virtual_tags_of_quote object.active_mentions.to_a.sort_by(&:id) + object.tags + object.emojis
end
class NoteLink < ActiveModelSerializers::Model
attributes :href
end
class NoteLinkSerializer < ActivityPub::Serializer
attributes :type, :href
attribute :media_type, key: :mediaType
def type
'Link'
end
def media_type
'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
end
end
def virtual_tags_of_quote
return [] unless object.quote?
[NoteLink.new(href: quote_uri)]
end end
def atom_uri def atom_uri
@ -218,20 +192,6 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
object.account.local? object.account.local?
end end
delegate :quote?, to: :object
def quote_post
@quote_post ||= object.quote
end
def quote_uri
ActivityPub::TagManager.instance.uri_for(quote_post)
end
def misskey_quote
quote_uri
end
def poll_options def poll_options
object.preloadable_poll.loaded_options object.preloadable_poll.loaded_options
end end

View file

@ -40,14 +40,10 @@ class InitialStateSerializer < ActiveModel::Serializer
store[:hide_items] = [ store[:hide_items] = [
object_account_user.setting_hide_favourite_menu ? 'favourite_menu' : nil, object_account_user.setting_hide_favourite_menu ? 'favourite_menu' : nil,
object_account_user.setting_hide_recent_emojis ? 'recent_emojis' : nil, object_account_user.setting_hide_recent_emojis ? 'recent_emojis' : nil,
object_account_user.setting_hide_blocking_quote ? 'blocking_quote' : nil,
object_account_user.setting_hide_emoji_reaction_unavailable_server ? 'emoji_reaction_unavailable_server' : nil, object_account_user.setting_hide_emoji_reaction_unavailable_server ? 'emoji_reaction_unavailable_server' : nil,
object_account_user.setting_hide_quote_unavailable_server ? 'quote_unavailable_server' : nil,
object_account_user.setting_hide_status_reference_unavailable_server ? 'status_reference_unavailable_server' : nil, object_account_user.setting_hide_status_reference_unavailable_server ? 'status_reference_unavailable_server' : nil,
object_account_user.setting_hide_emoji_reaction_count ? 'emoji_reaction_count' : nil, object_account_user.setting_hide_emoji_reaction_count ? 'emoji_reaction_count' : nil,
object_account_user.setting_show_emoji_reaction_on_timeline ? nil : 'emoji_reaction_on_timeline', object_account_user.setting_show_emoji_reaction_on_timeline ? nil : 'emoji_reaction_on_timeline',
object_account_user.setting_show_quote_in_home ? nil : 'quote_in_home',
object_account_user.setting_show_quote_in_public ? nil : 'quote_in_public',
object_account_user.setting_show_relationships ? nil : 'relationships', object_account_user.setting_show_relationships ? nil : 'relationships',
object_account_user.setting_show_avatar_on_filter ? nil : 'avatar_on_filter', object_account_user.setting_show_avatar_on_filter ? nil : 'avatar_on_filter',
].compact ].compact

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class REST::FilterSerializer < ActiveModel::Serializer class REST::FilterSerializer < ActiveModel::Serializer
attributes :id, :title, :exclude_follows, :exclude_localusers, :with_quote, :with_profile, :context, :expires_at, :filter_action attributes :id, :title, :exclude_follows, :exclude_localusers, :with_profile, :context, :expires_at, :filter_action
has_many :keywords, serializer: REST::FilterKeywordSerializer, if: :rules_requested? has_many :keywords, serializer: REST::FilterKeywordSerializer, if: :rules_requested?
has_many :statuses, serializer: REST::FilterStatusSerializer, if: :rules_requested? has_many :statuses, serializer: REST::FilterStatusSerializer, if: :rules_requested?

View file

@ -18,7 +18,6 @@ class REST::StatusSerializer < ActiveModel::Serializer
attribute :pinned, if: :pinnable? attribute :pinned, if: :pinnable?
attribute :reactions, if: :reactions? attribute :reactions, if: :reactions?
attribute :expires_at, if: :will_expire? attribute :expires_at, if: :will_expire?
attribute :quote_id, if: :quote?
attribute :markdown_opt, key: :markdown attribute :markdown_opt, key: :markdown
has_many :filtered, serializer: REST::FilterResultSerializer, if: :current_user? has_many :filtered, serializer: REST::FilterResultSerializer, if: :current_user?
@ -37,23 +36,6 @@ class REST::StatusSerializer < ActiveModel::Serializer
has_one :preview_card, key: :card, serializer: REST::PreviewCardSerializer has_one :preview_card, key: :card, serializer: REST::PreviewCardSerializer
has_one :preloadable_poll, key: :poll, serializer: REST::PollSerializer has_one :preloadable_poll, key: :poll, serializer: REST::PollSerializer
class QuotedStatusSerializer < REST::StatusSerializer
attribute :quote_muted, if: :current_user?
def quote
nil
end
def quote_muted
if relationships
muted || relationships.blocks_map[object.account_id] || relationships.domain_blocks_map[object.account.domain] || false
else
muted || current_user.account.blocking?(object.account_id) || current_user.account.domain_blocking?(object.account.domain)
end
end
end
belongs_to :quote, if: :quote?, serializer: QuotedStatusSerializer, relationships: -> { relationships }
def id def id
object.id.to_s object.id.to_s
end end
@ -182,12 +164,6 @@ class REST::StatusSerializer < ActiveModel::Serializer
end end
end end
def quote_id
object.quote_of_id.to_s
end
delegate :quote?, to: :object
def reblogged def reblogged
if relationships if relationships
relationships.reblogs_map[object.id] || false relationships.reblogs_map[object.id] || false

View file

@ -351,7 +351,8 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
end end
def quote def quote
@json['quote'] || @json['quoteUrl'] || @json['quoteURL'] || @json['_misskey_quote'] # TODO: quote
nil
end end
def local_referred_accounts def local_referred_accounts

View file

@ -115,7 +115,7 @@ class FanOutOnWriteService < BaseService
end end
def notify_about_update! def notify_about_update!
@status.reblogged_by_accounts.or(@status.quoted_by_accounts).merge(Account.local).select(:id).reorder(nil).find_in_batches do |accounts| @status.reblogged_by_accounts.merge(Account.local).select(:id).reorder(nil).find_in_batches do |accounts|
LocalNotificationWorker.push_bulk(accounts) do |account| LocalNotificationWorker.push_bulk(accounts) do |account|
[account.id, @status.id, 'Status', 'update'] [account.id, @status.id, 'Status', 'update']
end end

View file

@ -277,6 +277,7 @@ class PostStatusService < BaseService
def quote_url def quote_url
ProcessReferencesService.extract_quote(@text) ProcessReferencesService.extract_quote(@text)
# TODO: quote
end end
def reference_urls def reference_urls

View file

@ -188,7 +188,8 @@ class ProcessReferencesService < BaseService
quote = quote_attribute?(attribute_type) quote = quote_attribute?(attribute_type)
@added_objects << @status.reference_objects.new(target_status: status, attribute_type: attribute_type, quote: quote) @added_objects << @status.reference_objects.new(target_status: status, attribute_type: attribute_type, quote: quote)
@status.update!(quote_of_id: status_id) if quote # TODO: quote
# @status.update!(quote_of_id: status_id) if quote
status.increment_count!(:status_referred_by_count) status.increment_count!(:status_referred_by_count)
@references_count += 1 @references_count += 1
@ -214,7 +215,9 @@ class ProcessReferencesService < BaseService
@removed_objects = [] @removed_objects = []
@status.reference_objects.where(target_status: @removed_items.keys).destroy_all @status.reference_objects.where(target_status: @removed_items.keys).destroy_all
@status.update!(quote_of_id: nil) if @status.quote_of_id.present? && @removed_items.key?(@status.quote_of_id)
# TODO: quote
# @status.update!(quote_of_id: nil) if @status.quote_of_id.present? && @removed_items.key?(@status.quote_of_id)
statuses = Status.where(id: @added_items.keys).to_a statuses = Status.where(id: @added_items.keys).to_a
@removed_items.each_key do |status_id| @removed_items.each_key do |status_id|
@ -240,11 +243,12 @@ class ProcessReferencesService < BaseService
next unless quote_change next unless quote_change
if quote # TODO: quote
ref.status.update!(quote_of_id: ref.target_status.id) # if quote
else # ref.status.update!(quote_of_id: ref.target_status.id)
ref.status.update!(quote_of_id: nil) # else
end # ref.status.update!(quote_of_id: nil)
# end
end end
end end

View file

@ -139,6 +139,7 @@ class UpdateStatusService < BaseService
def quote_url def quote_url
ProcessReferencesService.extract_quote(text) ProcessReferencesService.extract_quote(text)
# TODO: quote
end end
def reference_urls def reference_urls

View file

@ -38,7 +38,6 @@
= f.input :exclude_localusers, wrapper: :with_label, kmyblue: true, label: t('simple_form.labels.filters.options.exclude_localusers') = f.input :exclude_localusers, wrapper: :with_label, kmyblue: true, label: t('simple_form.labels.filters.options.exclude_localusers')
.fields-group .fields-group
= f.input :exclude_quote, wrapper: :with_label, as: :boolean, kmyblue: true, label: t('simple_form.labels.filters.options.exclude_quote')
= f.input :exclude_profile, wrapper: :with_label, as: :boolean, kmyblue: true, label: t('simple_form.labels.filters.options.exclude_profile') = f.input :exclude_profile, wrapper: :with_label, as: :boolean, kmyblue: true, label: t('simple_form.labels.filters.options.exclude_profile')
%hr.spacer/ %hr.spacer/

View file

@ -86,7 +86,6 @@
.fields-group .fields-group
- if Setting.enable_emoji_reaction - if Setting.enable_emoji_reaction
= ff.input :'web.hide_emoji_reaction_unavailable_server', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_hide_emoji_reaction_unavailable_server') = ff.input :'web.hide_emoji_reaction_unavailable_server', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_hide_emoji_reaction_unavailable_server')
= ff.input :'web.hide_quote_unavailable_server', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_hide_quote_unavailable_server')
= ff.input :'web.hide_status_reference_unavailable_server', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_hide_status_reference_unavailable_server') = ff.input :'web.hide_status_reference_unavailable_server', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_hide_status_reference_unavailable_server')
%h4= t 'appearance.saved_posts' %h4= t 'appearance.saved_posts'
@ -95,13 +94,6 @@
= ff.input :'web.bookmark_category_needed', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_bookmark_category_needed'), hint: I18n.t('simple_form.hints.defaults.setting_bookmark_category_needed') = ff.input :'web.bookmark_category_needed', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_bookmark_category_needed'), hint: I18n.t('simple_form.hints.defaults.setting_bookmark_category_needed')
= ff.input :'web.show_favourite_menu', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_show_favourite_menu') = ff.input :'web.show_favourite_menu', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_show_favourite_menu')
%h4= t 'appearance.quotes'
.fields-group
= ff.input :'web.show_quote_in_home', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_show_quote_in_home'), hint: false
= ff.input :'web.show_quote_in_public', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_show_quote_in_public'), hint: false
= ff.input :'web.show_blocking_quote', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_show_blocking_quote'), hint: false
%h4= t 'appearance.status_action_bar' %h4= t 'appearance.status_action_bar'
.fields-group .fields-group

View file

@ -18,9 +18,6 @@
.fields-group .fields-group
= ff.input :translatable_private, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_translatable_private') = ff.input :translatable_private, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_translatable_private')
.fields-group
= ff.input :allow_quote, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_allow_quote'), hint: I18n.t('simple_form.hints.defaults.setting_allow_quote')
- if Setting.enable_emoji_reaction - if Setting.enable_emoji_reaction
%h4= t 'preferences.emoji_reaction_permitting' %h4= t 'preferences.emoji_reaction_permitting'

View file

@ -484,7 +484,7 @@ en:
reject_media_hint: Removes locally stored media files and refuses to download any in the future. Irrelevant for suspensions reject_media_hint: Removes locally stored media files and refuses to download any in the future. Irrelevant for suspensions
reject_new_follow: Reject follows reject_new_follow: Reject follows
reject_new_follow_hint: Reject follows in the future reject_new_follow_hint: Reject follows in the future
reject_reply_exclude_followers: Reject mentions/quotes exclude followers reject_reply_exclude_followers: Reject mentions exclude followers
reject_reply_exclude_followers_hint: Reject replies exclude followers in the future reject_reply_exclude_followers_hint: Reject replies exclude followers in the future
reject_reports: Reject reports reject_reports: Reject reports
reject_reports_hint: Ignore all reports coming from this domain. Irrelevant for suspensions reject_reports_hint: Ignore all reports coming from this domain. Irrelevant for suspensions
@ -629,7 +629,7 @@ en:
reject_hashtag: Reject hashtags reject_hashtag: Reject hashtags
reject_media: Reject media reject_media: Reject media
reject_new_follow: Reject follows reject_new_follow: Reject follows
reject_reply_exclude_followers: Reject reply/quote exclude followers reject_reply_exclude_followers: Reject reply exclude followers
reject_reports: Reject reports reject_reports: Reject reports
reject_send_sensitive: No Sensitive Submission Delivery reject_send_sensitive: No Sensitive Submission Delivery
reject_straight_follow: Reject straight follow reject_straight_follow: Reject straight follow
@ -781,7 +781,7 @@ en:
needed: have needed: have
no_needed: Should not have no_needed: Should not have
optional: Optional optional: Optional
status_allow_follower_mention: Check posts only if they contain mentions/quotes to non-followers status_allow_follower_mention: Check posts only if they contain mentions to non-followers
status_allow_follower_mention_hint: If enabled, mentions between other servers are unconditionally allowed status_allow_follower_mention_hint: If enabled, mentions between other servers are unconditionally allowed
status_cw_state: Has warning or not status_cw_state: Has warning or not
status_media_state: Has media or not status_media_state: Has media or not
@ -791,8 +791,8 @@ en:
status_poll_state: Has poll or not status_poll_state: Has poll or not
status_poll_threshold: Poll items limit status_poll_threshold: Poll items limit
status_quote_state: Has quote or not status_quote_state: Has quote or not
status_reference_state: Has quiet quote or not status_reference_state: Has link or not
status_reference_threshold: Has quiet quote or not status_reference_threshold: Has link or not
status_reply_state: Is reply or not status_reply_state: Is reply or not
status_searchability: Searchability status_searchability: Searchability
status_sensitive_state: Is sensitive or not status_sensitive_state: Is sensitive or not
@ -811,7 +811,7 @@ en:
phrases: phrases:
regexp_html: "<strong>Reg</strong> - If the <strong>Reg</strong> checkbox is checked, the comparison is performed using regular expressions." regexp_html: "<strong>Reg</strong> - If the <strong>Reg</strong> checkbox is checked, the comparison is performed using regular expressions."
regexp_short: Reg regexp_short: Reg
stranger_html: "<strong>UE</strong> - Items checked under <strong>Uem</strong> apply only to mentions, replies, quotes, etc. from accounts with which you have no follow relationship." stranger_html: "<strong>UE</strong> - Items checked under <strong>Uem</strong> apply only to mentions, replies, links, etc. from accounts with which you have no follow relationship."
stranger_short: Uem stranger_short: Uem
post_hash_tags_max: Hash tags limit of a post post_hash_tags_max: Hash tags limit of a post
post_mentions_max: Mentions limit of a post post_mentions_max: Mentions limit of a post
@ -1106,7 +1106,7 @@ en:
special_domains: special_domains:
stop_fetch_activity_domains: stop_fetch_activity_domains:
preamble: On servers with different domains for Web and Activity, you may not be able to retrieve Activity even if you access Web. In citations, this setting suppresses fetching when such a URL is specified in the body of a post. In most cases, this setting is rarely used. preamble: On servers with different domains for Web and Activity, you may not be able to retrieve Activity even if you access Web. In citations, this setting suppresses fetching when such a URL is specified in the body of a post. In most cases, this setting is rarely used.
title: Domains do not fetch for quotes title: Domains do not fetch for links
stop_link_preview_domains: stop_link_preview_domains:
preamble: If a site temporarily restricts connections from IP addresses that have received many hits, your server may be treated unfavorably. This setting suppresses the process of accessing such sites and generating link previews. In most cases, this setting is rarely used. preamble: If a site temporarily restricts connections from IP addresses that have received many hits, your server may be treated unfavorably. This setting suppresses the process of accessing such sites and generating link previews. In most cases, this setting is rarely used.
title: Domains do not fetch for link previews title: Domains do not fetch for link previews
@ -1435,7 +1435,6 @@ en:
body: Mastodon is translated by volunteers. body: Mastodon is translated by volunteers.
guide_link: https://crowdin.com/project/mastodon guide_link: https://crowdin.com/project/mastodon
guide_link_text: Everyone can contribute. guide_link_text: Everyone can contribute.
quotes: Quotes
remote_server_features: Other server features remote_server_features: Other server features
saved_posts: Saving posts saved_posts: Saving posts
sensitive_content: Sensitive content sensitive_content: Sensitive content

View file

@ -1389,7 +1389,6 @@ ja:
body: Mastodonは有志によって翻訳されています。 body: Mastodonは有志によって翻訳されています。
guide_link: https://ja.crowdin.com/project/mastodon guide_link: https://ja.crowdin.com/project/mastodon
guide_link_text: 誰でも参加することができます。 guide_link_text: 誰でも参加することができます。
quotes: 引用
remote_server_features: 他のサーバーの機能 remote_server_features: 他のサーバーの機能
saved_posts: 投稿の記録 saved_posts: 投稿の記録
sensitive_content: 閲覧注意コンテンツ sensitive_content: 閲覧注意コンテンツ

View file

@ -60,7 +60,6 @@ en:
phrase: Will be matched regardless of casing in text or content warning of a post phrase: Will be matched regardless of casing in text or content warning of a post
scopes: Which APIs the application will be allowed to access. If you select a top-level scope, you don't need to select individual ones. scopes: Which APIs the application will be allowed to access. If you select a top-level scope, you don't need to select individual ones.
setting_aggregate_reblogs: Do not show new boosts for posts that have been recently boosted (only affects newly-received boosts) setting_aggregate_reblogs: Do not show new boosts for posts that have been recently boosted (only affects newly-received boosts)
setting_allow_quote: Subdued quotes are allowed regardless of this setting; you can quote freely from any source except kmyblue!
setting_always_send_emails: Normally e-mail notifications won't be sent when you are actively using Mastodon setting_always_send_emails: Normally e-mail notifications won't be sent when you are actively using Mastodon
setting_bookmark_category_needed: When removing from all category, unbookmarked automatically setting_bookmark_category_needed: When removing from all category, unbookmarked automatically
setting_custom_css_lead: 'Be sure to remember: In the unlikely event that you make a mistake in entering your custom CSS and the screen does not display properly, you can disable your custom CSS from the link at the bottom of the sign-in screen. Open the sign-in screen in private mode of your browser, for example, and disable it.' setting_custom_css_lead: 'Be sure to remember: In the unlikely event that you make a mistake in entering your custom CSS and the screen does not display properly, you can disable your custom CSS from the link at the bottom of the sign-in screen. Open the sign-in screen in private mode of your browser, for example, and disable it.'
@ -80,7 +79,6 @@ en:
setting_public_post_to_unlisted: 未対応のサードパーティアプリからもローカル公開で投稿できますが、公開投稿はWeb以外できなくなります setting_public_post_to_unlisted: 未対応のサードパーティアプリからもローカル公開で投稿できますが、公開投稿はWeb以外できなくなります
setting_reject_send_limited_to_suspects: This applies to "Mutual Only" posts. Circle posts will be delivered without exception. Some Misskey servers have independently supported limited posting, but this is a setting for those who are concerned about it, as mutual-only posting exposes some of the users you are mutual with to Misskey users! setting_reject_send_limited_to_suspects: This applies to "Mutual Only" posts. Circle posts will be delivered without exception. Some Misskey servers have independently supported limited posting, but this is a setting for those who are concerned about it, as mutual-only posting exposes some of the users you are mutual with to Misskey users!
setting_reject_unlisted_subscription: Misskey and its forks can **subscribe and search** for "non-following" posts from accounts they do not follow. This differs from kmyblue's behavior. It delivers posts in the specified public range to such servers as "followers only". Please understand, however, that due to its structure, it is difficult to handle perfectly and will occasionally be delivered as non-subscribed. setting_reject_unlisted_subscription: Misskey and its forks can **subscribe and search** for "non-following" posts from accounts they do not follow. This differs from kmyblue's behavior. It delivers posts in the specified public range to such servers as "followers only". Please understand, however, that due to its structure, it is difficult to handle perfectly and will occasionally be delivered as non-subscribed.
setting_reverse_search_quote: Double-quotes will result in a search with a wider range of notation, which is the opposite of Mastodon's default behavior.
setting_show_application: 投稿するのに使用したアプリが投稿の詳細ビューに表示されるようになります setting_show_application: 投稿するのに使用したアプリが投稿の詳細ビューに表示されるようになります
setting_stay_privacy: If you choose to enable this setting, please consider manually setting the visibility of the boost setting_stay_privacy: If you choose to enable this setting, please consider manually setting the visibility of the boost
setting_stop_emoji_reaction_streaming: Helps to save communication capacity. setting_stop_emoji_reaction_streaming: Helps to save communication capacity.
@ -270,7 +268,6 @@ en:
phrase: Keyword or phrase phrase: Keyword or phrase
setting_advanced_layout: Enable advanced web interface setting_advanced_layout: Enable advanced web interface
setting_aggregate_reblogs: Group boosts in timelines setting_aggregate_reblogs: Group boosts in timelines
setting_allow_quote: Allow quote your posts
setting_always_send_emails: Always send e-mail notifications setting_always_send_emails: Always send e-mail notifications
setting_auto_play_gif: Auto-play animated GIFs setting_auto_play_gif: Auto-play animated GIFs
setting_bio_markdown: Enable profile markdown setting_bio_markdown: Enable profile markdown
@ -315,8 +312,7 @@ en:
setting_expand_spoilers: Always expand posts marked with content warnings setting_expand_spoilers: Always expand posts marked with content warnings
setting_hide_emoji_reaction_unavailable_server: Hide emoji reaction button from unavailable server setting_hide_emoji_reaction_unavailable_server: Hide emoji reaction button from unavailable server
setting_hide_network: Hide your social graph setting_hide_network: Hide your social graph
setting_hide_quote_unavailable_server: Hide quote menu from unavailable server setting_hide_status_reference_unavailable_server: Hide link (reference named by Fedibird) menu from unavailable server
setting_hide_status_reference_unavailable_server: Hide quiet quote (reference named by Fedibird) menu from unavailable server
setting_lock_follow_from_bot: Request approval about bot follow setting_lock_follow_from_bot: Request approval about bot follow
setting_missing_alt_text_modal: Show confirmation dialog before posting media without alt text setting_missing_alt_text_modal: Show confirmation dialog before posting media without alt text
setting_public_post_to_unlisted: Convert public post to public unlisted if not using Web app setting_public_post_to_unlisted: Convert public post to public unlisted if not using Web app
@ -327,14 +323,11 @@ en:
setting_reverse_search_quote: Perform word-by-word search when search keywords are not enclosed in double quotes setting_reverse_search_quote: Perform word-by-word search when search keywords are not enclosed in double quotes
setting_show_application: Disclose application used to send posts setting_show_application: Disclose application used to send posts
setting_show_avatar_on_filter: Show filtered posts with avatar and user profile setting_show_avatar_on_filter: Show filtered posts with avatar and user profile
setting_show_blocking_quote: Show posts which have a quote written by the user you are blocking
setting_show_emoji_reaction_count: Show emoji reaction number setting_show_emoji_reaction_count: Show emoji reaction number
setting_show_emoji_reaction_on_timeline: Show all emoji reactions on timeline setting_show_emoji_reaction_on_timeline: Show all emoji reactions on timeline
setting_show_favourite_menu: Show favourite menu setting_show_favourite_menu: Show favourite menu
setting_show_followers_count: Show followers count setting_show_followers_count: Show followers count
setting_show_following_count: Show following count setting_show_following_count: Show following count
setting_show_quote_in_home: Show quotes in home, list or antenna timelines
setting_show_quote_in_public: Show quotes in public timelines
setting_show_recent_emojis: Show recent emojis setting_show_recent_emojis: Show recent emojis
setting_show_relationships: Show relationships on account page setting_show_relationships: Show relationships on account page
setting_show_statuses_count: Show statuses count setting_show_statuses_count: Show statuses count
@ -386,7 +379,6 @@ en:
exclude_follows: Exclude following users exclude_follows: Exclude following users
exclude_localusers: Exclude local users exclude_localusers: Exclude local users
exclude_profile: Exclude account name and bio exclude_profile: Exclude account name and bio
exclude_quote: Exclude quote or references
form_admin_settings: form_admin_settings:
activity_api_enabled: Publish aggregate statistics about user activity in the API activity_api_enabled: Publish aggregate statistics about user activity in the API
app_icon: App icon app_icon: App icon

View file

@ -60,7 +60,6 @@ ja:
phrase: 投稿内容の大文字小文字や閲覧注意に関係なく一致 phrase: 投稿内容の大文字小文字や閲覧注意に関係なく一致
scopes: アプリの API に許可するアクセス権を選択してください。最上位のスコープを選択する場合、個々のスコープを選択する必要はありません。 scopes: アプリの API に許可するアクセス権を選択してください。最上位のスコープを選択する場合、個々のスコープを選択する必要はありません。
setting_aggregate_reblogs: 最近ブーストされた投稿が新たにブーストされても表示しません (設定後受信したものにのみ影響) setting_aggregate_reblogs: 最近ブーストされた投稿が新たにブーストされても表示しません (設定後受信したものにのみ影響)
setting_allow_quote: ひかえめな引用はこの設定に関わらず可能です。kmyblue以外からは自由に引用できます
setting_always_send_emails: 通常、Mastodon からメール通知は行われません。 setting_always_send_emails: 通常、Mastodon からメール通知は行われません。
setting_bookmark_category_needed: すべてのカテゴリから削除したとき、ブックマークが自動で外れるようになります setting_bookmark_category_needed: すべてのカテゴリから削除したとき、ブックマークが自動で外れるようになります
setting_custom_css_lead: '必ず覚えてください: 万が一カスタムCSSの入力を誤り、画面が正常に表示されなくなった場合は、サインイン画面の下にあるリンクよりカスタムCSSを無効化することができます。ブラウザのプライベートモードなどでサインイン画面を開き、無効化してください。' setting_custom_css_lead: '必ず覚えてください: 万が一カスタムCSSの入力を誤り、画面が正常に表示されなくなった場合は、サインイン画面の下にあるリンクよりカスタムCSSを無効化することができます。ブラウザのプライベートモードなどでサインイン画面を開き、無効化してください。'
@ -261,7 +260,6 @@ ja:
phrase: キーワードまたはフレーズ phrase: キーワードまたはフレーズ
setting_advanced_layout: 上級者向けUIを有効にする setting_advanced_layout: 上級者向けUIを有効にする
setting_aggregate_reblogs: ブーストをまとめる setting_aggregate_reblogs: ブーストをまとめる
setting_allow_quote: 引用を許可する
setting_always_send_emails: 常にメール通知を送信する setting_always_send_emails: 常にメール通知を送信する
setting_auto_play_gif: アニメーションGIFを自動再生する setting_auto_play_gif: アニメーションGIFを自動再生する
setting_bio_markdown: プロフィールのMarkdownを有効にする setting_bio_markdown: プロフィールのMarkdownを有効にする
@ -306,7 +304,6 @@ ja:
setting_expand_spoilers: 閲覧注意としてマークされた投稿を常に展開する setting_expand_spoilers: 閲覧注意としてマークされた投稿を常に展開する
setting_hide_emoji_reaction_unavailable_server: 絵文字リアクションに対応していないと思われるサーバーの投稿から絵文字リアクション機能を隠す setting_hide_emoji_reaction_unavailable_server: 絵文字リアクションに対応していないと思われるサーバーの投稿から絵文字リアクション機能を隠す
setting_hide_network: 繋がりを隠す setting_hide_network: 繋がりを隠す
setting_hide_quote_unavailable_server: 引用に対応していないと思われるサーバーの投稿からメニューを隠す
setting_hide_status_reference_unavailable_server: ひかえめな引用Fedibirdの参照に対応していないと思われるサーバーの投稿からメニューを隠す setting_hide_status_reference_unavailable_server: ひかえめな引用Fedibirdの参照に対応していないと思われるサーバーの投稿からメニューを隠す
setting_lock_follow_from_bot: botからのフォローを承認制にする setting_lock_follow_from_bot: botからのフォローを承認制にする
setting_missing_alt_text_modal: 代替テキストなしでメディアを投稿する前に確認ダイアログを表示する setting_missing_alt_text_modal: 代替テキストなしでメディアを投稿する前に確認ダイアログを表示する
@ -318,14 +315,11 @@ ja:
setting_reverse_search_quote: ダブルクオートで囲まず検索した時、単語単位で検索する setting_reverse_search_quote: ダブルクオートで囲まず検索した時、単語単位で検索する
setting_show_application: 送信したアプリを開示する setting_show_application: 送信したアプリを開示する
setting_show_avatar_on_filter: フィルター対象投稿の投稿者名やアイコンを表示する setting_show_avatar_on_filter: フィルター対象投稿の投稿者名やアイコンを表示する
setting_show_blocking_quote: ブロックしたユーザーの投稿を引用した投稿を表示する
setting_show_emoji_reaction_count: 投稿につけられた各絵文字の数を表示する setting_show_emoji_reaction_count: 投稿につけられた各絵文字の数を表示する
setting_show_emoji_reaction_on_timeline: タイムライン上の投稿に他の人のつけた絵文字を表示する setting_show_emoji_reaction_on_timeline: タイムライン上の投稿に他の人のつけた絵文字を表示する
setting_show_favourite_menu: 右サイドメニューに「お気に入り」を表示する setting_show_favourite_menu: 右サイドメニューに「お気に入り」を表示する
setting_show_followers_count: フォロワー数を公開する setting_show_followers_count: フォロワー数を公開する
setting_show_following_count: フォロー数を公開する setting_show_following_count: フォロー数を公開する
setting_show_quote_in_home: ホーム・リスト・アンテナなどで引用された投稿を表示する
setting_show_quote_in_public: 公開タイムライン(ローカル・連合)で引用された投稿を表示する
setting_show_recent_emojis: 絵文字ピッカーで絵文字デッキと一緒に、絵文字の使用履歴も表示する setting_show_recent_emojis: 絵文字ピッカーで絵文字デッキと一緒に、絵文字の使用履歴も表示する
setting_show_relationships: アカウント詳細ベージで、相手からのフォロー状況を表示する setting_show_relationships: アカウント詳細ベージで、相手からのフォロー状況を表示する
setting_show_statuses_count: 投稿数を公開する setting_show_statuses_count: 投稿数を公開する
@ -376,7 +370,6 @@ ja:
exclude_follows: フォロー中のユーザーをフィルターの対象にしない exclude_follows: フォロー中のユーザーをフィルターの対象にしない
exclude_localusers: ローカルユーザーをフィルターの対象にしない exclude_localusers: ローカルユーザーをフィルターの対象にしない
exclude_profile: アカウント名および紹介文をフィルターの対象にしない exclude_profile: アカウント名および紹介文をフィルターの対象にしない
exclude_quote: 引用の内容をフィルターの対象にしない
form_admin_settings: form_admin_settings:
activity_api_enabled: APIでユーザーアクティビティに関する集計統計を公開する activity_api_enabled: APIでユーザーアクティビティに関する集計統計を公開する
app_icon: アプリアイコン app_icon: アプリアイコン

View file

@ -0,0 +1,10 @@
# frozen_string_literal: true
class RemoveKmyblueQuoteAccessories < ActiveRecord::Migration[8.0]
def change
safety_assured do
remove_column :custom_filters, :with_quote, :boolean, default: true, null: false
remove_column :status_references, :quote, :boolean, default: false, null: false
end
end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do ActiveRecord::Schema[8.0].define(version: 2025_04_23_224935) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql" enable_extension "pg_catalog.plpgsql"
@ -552,7 +552,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
t.integer "action", default: 0, null: false t.integer "action", default: 0, null: false
t.boolean "exclude_follows", default: false, null: false t.boolean "exclude_follows", default: false, null: false
t.boolean "exclude_localusers", default: false, null: false t.boolean "exclude_localusers", default: false, null: false
t.boolean "with_quote", default: true, null: false
t.boolean "with_profile", default: false, null: false t.boolean "with_profile", default: false, null: false
t.index ["account_id"], name: "index_custom_filters_on_account_id" t.index ["account_id"], name: "index_custom_filters_on_account_id"
end end
@ -1374,7 +1373,6 @@ ActiveRecord::Schema[8.0].define(version: 2025_04_10_144908) do
t.datetime "created_at", precision: nil, null: false t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.string "attribute_type" t.string "attribute_type"
t.boolean "quote", default: false, null: false
t.index ["status_id"], name: "index_status_references_on_status_id" t.index ["status_id"], name: "index_status_references_on_status_id"
t.index ["target_status_id"], name: "index_status_references_on_target_status_id" t.index ["target_status_id"], name: "index_status_references_on_target_status_id"
end end

View file

@ -158,7 +158,7 @@ namespace :dangerous do
%w(custom_filters exclude_follows), %w(custom_filters exclude_follows),
%w(custom_filters exclude_localusers), %w(custom_filters exclude_localusers),
%w(custom_filters with_profile), %w(custom_filters with_profile),
%w(custom_filters with_quote), # Removed: custom_filters with_quote,
%w(domain_blocks block_trends), %w(domain_blocks block_trends),
%w(domain_blocks detect_invalid_subscription), %w(domain_blocks detect_invalid_subscription),
%w(domain_blocks hidden), %w(domain_blocks hidden),

View file

@ -4,5 +4,4 @@ Fabricator(:status_reference) do
status { Fabricate.build(:status) } status { Fabricate.build(:status) }
target_status { Fabricate.build(:status) } target_status { Fabricate.build(:status) }
attribute_type 'BT' attribute_type 'BT'
quote false
end end

View file

@ -1711,104 +1711,10 @@ RSpec.describe ActivityPub::Activity::Create do
status = sender.statuses.first status = sender.statuses.first
expect(status).to_not be_nil expect(status).to_not be_nil
expect(status.quote).to be_nil
expect(status.references.pluck(:id)).to eq [target_status.id] expect(status.references.pluck(:id)).to eq [target_status.id]
end end
end end
context 'with quote' do
let(:recipient) { Fabricate(:account) }
let!(:target_status) { Fabricate(:status, account: Fabricate(:account, domain: nil)) }
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
quote: ActivityPub::TagManager.instance.uri_for(target_status),
}
end
it 'creates status' do
expect { subject.perform }.to change(sender.statuses, :count).by(1)
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.references.pluck(:id)).to eq [target_status.id]
expect(status.quote).to_not be_nil
expect(status.quote.id).to eq target_status.id
end
end
context 'with quote as feb-e232 object links' do
let(:recipient) { Fabricate(:account) }
let!(:target_status) { Fabricate(:status, account: Fabricate(:account, domain: nil)) }
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
tag: [
{
type: 'Link',
mediaType: 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
href: ActivityPub::TagManager.instance.uri_for(target_status),
},
],
}
end
it 'creates status' do
expect { subject.perform }.to change(sender.statuses, :count).by(1)
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.references.pluck(:id)).to eq [target_status.id]
expect(status.quote).to_not be_nil
expect(status.quote.id).to eq target_status.id
end
end
context 'with references and quote' do
let(:recipient) { Fabricate(:account) }
let!(:target_status) { Fabricate(:status, account: Fabricate(:account, domain: nil)) }
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
quote: ActivityPub::TagManager.instance.uri_for(target_status),
references: {
id: 'target_status',
type: 'Collection',
first: {
type: 'CollectionPage',
next: nil,
partOf: 'target_status',
items: [
ActivityPub::TagManager.instance.uri_for(target_status),
],
},
},
}
end
it 'creates status' do
expect { subject.perform }.to change(sender.statuses, :count).by(1)
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.references.pluck(:id)).to eq [target_status.id]
expect(status.quote).to_not be_nil
expect(status.quote.id).to eq target_status.id
end
end
context 'with language' do context 'with language' do
let(:to) { 'https://www.w3.org/ns/activitystreams#Public' } let(:to) { 'https://www.w3.org/ns/activitystreams#Public' }
let(:object_json) do let(:object_json) do
@ -2556,53 +2462,6 @@ RSpec.describe ActivityPub::Activity::Create do
end end
end end
context 'when sender quotes to local status' do
subject { described_class.new(json, sender, delivery: true) }
let!(:local_status) { Fabricate(:status) }
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
quote: ActivityPub::TagManager.instance.uri_for(local_status),
}
end
before do
subject.perform
end
it 'creates status' do
status = sender.statuses.first
expect(status).to_not be_nil
expect(status.text).to eq 'Lorem ipsum'
end
end
context 'when sender quotes to non-local status' do
subject { described_class.new(json, sender, delivery: true) }
let!(:remote_status) { Fabricate(:status, uri: 'https://foo.bar/among', account: Fabricate(:account, domain: 'foo.bar', uri: 'https://foo.bar/account')) }
let(:object_json) do
{
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
type: 'Note',
content: 'Lorem ipsum',
quote: ActivityPub::TagManager.instance.uri_for(remote_status),
}
end
before do
subject.perform
end
it 'creates status' do
expect(sender.statuses.count).to eq 0
end
end
context 'when sender targets a local user' do context 'when sender targets a local user' do
subject { described_class.new(json, sender, delivery: true) } subject { described_class.new(json, sender, delivery: true) }

View file

@ -8,11 +8,10 @@ RSpec.describe StatusReachFinder do
subject { described_class.new(status) } subject { described_class.new(status) }
let(:parent_status) { nil } let(:parent_status) { nil }
let(:quoted_status) { nil }
let(:visibility) { :public } let(:visibility) { :public }
let(:searchability) { :public } let(:searchability) { :public }
let(:alice) { Fabricate(:account, username: 'alice') } let(:alice) { Fabricate(:account, username: 'alice') }
let(:status) { Fabricate(:status, account: alice, thread: parent_status, quote_of_id: quoted_status&.id, visibility: visibility, searchability: searchability) } let(:status) { Fabricate(:status, account: alice, thread: parent_status, visibility: visibility, searchability: searchability) }
context 'with a simple case' do context 'with a simple case' do
let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') } let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') }
@ -353,15 +352,6 @@ RSpec.describe StatusReachFinder do
end end
end end
end end
context 'when it is a quote to a remote account' do
let(:bob) { Fabricate(:account, username: 'bob', domain: 'foo.bar', protocol: :activitypub, inbox_url: 'https://foo.bar/inbox') }
let(:quoted_status) { Fabricate(:status, account: bob) }
it 'includes the inbox of the quoted-to account' do
expect(subject.inboxes).to include 'https://foo.bar/inbox'
end
end
end end
context 'with extended domain block' do context 'with extended domain block' do

View file

@ -397,13 +397,9 @@ RSpec.describe Account do
describe '#public_settings_for_local' do describe '#public_settings_for_local' do
subject { account.public_settings_for_local } subject { account.public_settings_for_local }
let(:account) { Fabricate(:user, settings: { allow_quote: true, hide_statuses_count: true, emoji_reaction_policy: :followers_only }).account } let(:account) { Fabricate(:user, settings: { hide_statuses_count: true, emoji_reaction_policy: :followers_only }).account }
shared_examples 'some settings' do |permitted, emoji_reaction_policy| shared_examples 'some settings' do |permitted, emoji_reaction_policy|
it 'allow_quote is allowed' do
expect(subject['allow_quote']).to be permitted.include?(:allow_quote)
end
it 'hide_statuses_count is allowed' do it 'hide_statuses_count is allowed' do
expect(subject['hide_statuses_count']).to be permitted.include?(:hide_statuses_count) expect(subject['hide_statuses_count']).to be permitted.include?(:hide_statuses_count)
end end
@ -417,24 +413,18 @@ RSpec.describe Account do
end end
end end
it_behaves_like 'some settings', %i(allow_quote hide_statuses_count), 'followers_only' it_behaves_like 'some settings', %i(hide_statuses_count), 'followers_only'
context 'when default true setting is set false' do context 'when default true setting is set false' do
let(:account) { Fabricate(:user, settings: { allow_quote: false, hide_statuses_count: true, emoji_reaction_policy: :followers_only }).account } let(:account) { Fabricate(:user, settings: { hide_statuses_count: true, emoji_reaction_policy: :followers_only }).account }
it_behaves_like 'some settings', %i(hide_statuses_count), 'followers_only' it_behaves_like 'some settings', %i(hide_statuses_count), 'followers_only'
end end
context 'when remote user' do context 'when remote user' do
let(:account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/actor', settings: { 'allow_quote' => true, 'hide_statuses_count' => true, 'emoji_reaction_policy' => 'followers_only' }) } let(:account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/actor', settings: { 'hide_statuses_count' => true, 'emoji_reaction_policy' => 'followers_only' }) }
it_behaves_like 'some settings', %i(allow_quote hide_statuses_count), 'followers_only' it_behaves_like 'some settings', %i(hide_statuses_count), 'followers_only'
end
context 'when remote user by server other_settings is not supported' do
let(:account) { Fabricate(:account, domain: 'example.com', uri: 'https://example.com/actor') }
it_behaves_like 'some settings', %i(allow_quote), 'allow'
end end
end end

View file

@ -86,39 +86,6 @@ RSpec.describe Status do
end end
end end
describe '#quote' do
let(:target_status) { Fabricate(:status) }
let(:quote) { true }
before do
Fabricate(:status_reference, status: subject, target_status: target_status, quote: quote)
end
context 'when quoting single' do
it 'get quote' do
expect(subject.quote).to_not be_nil
expect(subject.quote.id).to eq target_status.id
end
end
context 'when multiple quotes' do
it 'get quote' do
target2 = Fabricate(:status)
Fabricate(:status_reference, status: subject, quote: quote)
expect(subject.quote).to_not be_nil
expect([target_status.id, target2.id].include?(subject.quote.id)).to be true
end
end
context 'when no quote but reference' do
let(:quote) { false }
it 'get quote' do
expect(subject.quote).to be_nil
end
end
end
describe '#content' do describe '#content' do
it 'returns the text of the status if it is not a reblog' do it 'returns the text of the status if it is not a reblog' do
expect(subject.content).to eql subject.text expect(subject.content).to eql subject.text

View file

@ -251,30 +251,6 @@ RSpec.describe StatusPolicy, type: :model do
end end
end end
context 'with the permission of quote?' do
permissions :quote? do
it 'grants access when viewer is not blocked' do
follow = Fabricate(:follow)
status.account = follow.target_account
expect(subject).to permit(follow.account, status)
end
it 'denies when viewer is blocked' do
block = Fabricate(:block)
status.account = block.target_account
expect(subject).to_not permit(block.account, status)
end
it 'denies when private visibility' do
status.visibility = :private
expect(subject).to_not permit(Fabricate(:account), status)
end
end
end
context 'with the permission of update?' do context 'with the permission of update?' do
permissions :update? do permissions :update? do
it 'grants access if owner' do it 'grants access if owner' do

View file

@ -170,11 +170,9 @@ RSpec.describe 'Public' do
let(:exclude_follows) { false } let(:exclude_follows) { false }
let(:exclude_localusers) { false } let(:exclude_localusers) { false }
let(:include_quotes) { false }
let(:account) { user.account } let(:account) { user.account }
let(:remote_account) { remote_status.account } let(:remote_account) { remote_status.account }
let!(:filter) { Fabricate(:custom_filter, account: account, exclude_follows: exclude_follows, exclude_localusers: exclude_localusers, with_quote: include_quotes) } let!(:filter) { Fabricate(:custom_filter, account: account, exclude_follows: exclude_follows, exclude_localusers: exclude_localusers) }
let!(:quote_status) { Fabricate(:status, quote: Fabricate(:status, text: 'ohagi')) }
it 'load statuses', :aggregate_failures do it 'load statuses', :aggregate_failures do
ids = subject ids = subject
@ -201,16 +199,6 @@ RSpec.describe 'Public' do
expect(ids).to_not include(remote_status.id) expect(ids).to_not include(remote_status.id)
end end
end end
context 'when include_quotes' do
let(:with_quote) { true }
it 'load statuses', :aggregate_failures do
ids = subject
expect(ids).to_not include(local_status.id)
expect(ids).to include(quote_status.id)
end
end
end end
end end
end end

View file

@ -16,14 +16,9 @@ RSpec.describe ActivityPub::NoteSerializer do
let!(:reply_by_account_third) { Fabricate(:status, account: account, thread: parent, visibility: :public) } let!(:reply_by_account_third) { Fabricate(:status, account: account, thread: parent, visibility: :public) }
let!(:reply_by_account_visibility_direct) { Fabricate(:status, account: account, thread: parent, visibility: :direct) } let!(:reply_by_account_visibility_direct) { Fabricate(:status, account: account, thread: parent, visibility: :direct) }
let!(:referred) { nil } let!(:referred) { nil }
let!(:quote) { nil }
before do before do
parent.references << referred if referred.present? parent.references << referred if referred.present?
if quote.present?
parent.references << quote
parent.quote = quote
end
end end
it 'has the expected shape and replies collection' do it 'has the expected shape and replies collection' do
@ -98,23 +93,7 @@ RSpec.describe ActivityPub::NoteSerializer do
end end
it 'has as reference' do it 'has as reference' do
expect(subject['quoteUri']).to be_nil
expect(subject['references']['first']['items']).to include referred.uri expect(subject['references']['first']['items']).to include referred.uri
end end
end end
context 'when has a quote' do
let(:quote) { Fabricate(:status) }
it 'has a quote as transitional type' do
expect(subject['quoteUri']).to eq ActivityPub::TagManager.instance.uri_for(quote)
end
it 'has a quote as feb-e232 object link' do
tag = subject['tag'].detect { |t| t['type'] == 'Link' }
expect(tag).to_not be_nil
expect(tag['mediaType']).to eq 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
expect(tag['href']).to eq ActivityPub::TagManager.instance.uri_for(quote)
end
end
end end

View file

@ -30,7 +30,6 @@ RSpec.describe NodeInfo::Serializer do
end end
it 'returns nodeinfo own features' do it 'returns nodeinfo own features' do
expect(serialization['metadata']['features']).to include 'quote'
expect(serialization['metadata']['features']).to_not include 'kmyblue_markdown' expect(serialization['metadata']['features']).to_not include 'kmyblue_markdown'
end end
end end

View file

@ -678,7 +678,7 @@ RSpec.describe FanOutOnWriteService do
end end
end end
context 'when updated status is already boosted or quoted' do context 'when updated status is already boosted' do
let(:custom_before) { true } let(:custom_before) { true }
before do before do
@ -695,13 +695,6 @@ RSpec.describe FanOutOnWriteService do
expect(notification.activity_id).to eq status.id expect(notification.activity_id).to eq status.id
end end
it 'notified to quoted account', :inline_jobs do
notification = Notification.find_by(account: tom, type: 'update')
expect(notification).to_not be_nil
expect(notification.activity_id).to eq status.id
end
it 'notified not to non-boosted account', :inline_jobs do it 'notified not to non-boosted account', :inline_jobs do
notification = Notification.find_by(account: ohagi, type: 'update') notification = Notification.find_by(account: ohagi, type: 'update')

View file

@ -11,7 +11,6 @@ RSpec.describe ProcessReferencesService, type: :service do
let(:target_status) { Fabricate(:status, account: Fabricate(:user).account, visibility: target_status_visibility) } let(:target_status) { Fabricate(:status, account: Fabricate(:user).account, visibility: target_status_visibility) }
let(:target_status_uri) { ActivityPub::TagManager.instance.uri_for(target_status) } let(:target_status_uri) { ActivityPub::TagManager.instance.uri_for(target_status) }
let(:quote_urls) { nil } let(:quote_urls) { nil }
let(:allow_quote) { true }
def notify?(target_status_id = nil) def notify?(target_status_id = nil)
target_status_id ||= target_status.id target_status_id ||= target_status.id
@ -20,7 +19,6 @@ RSpec.describe ProcessReferencesService, type: :service do
describe 'posting new status' do describe 'posting new status' do
subject do subject do
target_status.account.user.settings['allow_quote'] = false unless allow_quote
target_status.account.user&.save target_status.account.user&.save
described_class.new.call(status, reference_parameters, urls: urls, fetch_remote: fetch_remote, quote_urls: quote_urls) described_class.new.call(status, reference_parameters, urls: urls, fetch_remote: fetch_remote, quote_urls: quote_urls)
@ -42,7 +40,9 @@ RSpec.describe ProcessReferencesService, type: :service do
end end
it 'not quote', :inline_jobs do it 'not quote', :inline_jobs do
expect(status.quote).to be_nil # TODO: quote
expect(status).to_not be_nil # Remove
# expect(status.quote).to be_nil
end end
end end
@ -89,8 +89,9 @@ RSpec.describe ProcessReferencesService, type: :service do
expect(subject.size).to eq 1 expect(subject.size).to eq 1
expect(subject.pluck(0)).to include target_status.id expect(subject.pluck(0)).to include target_status.id
expect(subject.pluck(1)).to include 'QT' expect(subject.pluck(1)).to include 'QT'
expect(status.quote).to_not be_nil # TODO: quote
expect(status.quote.id).to eq target_status.id # expect(status.quote).to_not be_nil
# expect(status.quote.id).to eq target_status.id
expect(notify?).to be true expect(notify?).to be true
end end
end end
@ -103,8 +104,9 @@ RSpec.describe ProcessReferencesService, type: :service do
expect(subject.size).to eq 1 expect(subject.size).to eq 1
expect(subject.pluck(0)).to include target_status.id expect(subject.pluck(0)).to include target_status.id
expect(subject.pluck(1)).to include 'QT' expect(subject.pluck(1)).to include 'QT'
expect(status.quote).to_not be_nil # TODO: quote
expect(status.quote.id).to eq target_status.id # expect(status.quote).to_not be_nil
# expect(status.quote.id).to eq target_status.id
expect(notify?).to be true expect(notify?).to be true
end end
end end
@ -117,8 +119,9 @@ RSpec.describe ProcessReferencesService, type: :service do
expect(subject.size).to eq 1 expect(subject.size).to eq 1
expect(subject.pluck(0)).to include target_status.id expect(subject.pluck(0)).to include target_status.id
expect(subject.pluck(1)).to include 'QT' expect(subject.pluck(1)).to include 'QT'
expect(status.quote).to_not be_nil # TODO: quote
expect(status.quote.id).to eq target_status.id # expect(status.quote).to_not be_nil
# expect(status.quote.id).to eq target_status.id
expect(notify?).to be true expect(notify?).to be true
end end
end end
@ -131,21 +134,23 @@ RSpec.describe ProcessReferencesService, type: :service do
expect(subject.size).to eq 1 expect(subject.size).to eq 1
expect(subject.pluck(0)).to include target_status.id expect(subject.pluck(0)).to include target_status.id
expect(subject.pluck(1)).to include 'QT' expect(subject.pluck(1)).to include 'QT'
expect(status.quote).to_not be_nil # TODO: quote
expect(status.quote.id).to eq target_status.id # expect(status.quote).to_not be_nil
# expect(status.quote.id).to eq target_status.id
expect(notify?).to be true expect(notify?).to be true
end end
end end
context 'when quote is rejected' do context 'when quote is rejected' do
let(:text) { "Hello QT #{target_status_uri}" } let(:text) { "Hello QT #{target_status_uri}" }
let(:allow_quote) { false } # let(:allow_quote) { false }
it 'post status', :inline_jobs do it 'post status', :inline_jobs do
expect(subject.size).to eq 1 expect(subject.size).to eq 1
expect(subject.pluck(0)).to include target_status.id expect(subject.pluck(0)).to include target_status.id
expect(subject.pluck(1)).to include 'BT' expect(subject.pluck(1)).to include 'BT'
expect(status.quote).to be_nil # TODO: quote
# expect(status.quote).to be_nil
expect(notify?).to be true expect(notify?).to be true
end end
end end
@ -159,8 +164,9 @@ RSpec.describe ProcessReferencesService, type: :service do
expect(subject.size).to eq 2 expect(subject.size).to eq 2
expect(subject).to include [target_status.id, 'QT'] expect(subject).to include [target_status.id, 'QT']
expect(subject).to include [target_status2.id, 'BT'] expect(subject).to include [target_status2.id, 'BT']
expect(status.quote).to_not be_nil # TODO: quote
expect(status.quote.id).to eq target_status.id # expect(status.quote).to_not be_nil
# expect(status.quote.id).to eq target_status.id
expect(notify?).to be true expect(notify?).to be true
expect(notify?(target_status2.id)).to be true expect(notify?(target_status2.id)).to be true
end end
@ -407,7 +413,8 @@ RSpec.describe ProcessReferencesService, type: :service do
it 'post status', :inline_jobs do it 'post status', :inline_jobs do
expect(subject.size).to eq 0 expect(subject.size).to eq 0
expect(status.quote).to be_nil # TODO: quote
# expect(status.quote).to be_nil
expect(notify?).to be false expect(notify?).to be false
end end
end end
@ -430,8 +437,9 @@ RSpec.describe ProcessReferencesService, type: :service do
it 'post status', :inline_jobs do it 'post status', :inline_jobs do
expect(subject.size).to eq 1 expect(subject.size).to eq 1
expect(subject).to include target_status2.id expect(subject).to include target_status2.id
expect(status.quote).to_not be_nil # TODO: quote
expect(status.quote.id).to eq target_status2.id # expect(status.quote).to_not be_nil
# expect(status.quote.id).to eq target_status2.id
expect(notify?(target_status2.id)).to be true expect(notify?(target_status2.id)).to be true
end end
end end
@ -443,7 +451,8 @@ RSpec.describe ProcessReferencesService, type: :service do
it 'post status', :inline_jobs do it 'post status', :inline_jobs do
expect(subject.size).to eq 1 expect(subject.size).to eq 1
expect(subject).to include target_status.id expect(subject).to include target_status.id
expect(status.quote).to be_nil # TODO: quote
# expect(status.quote).to be_nil
expect(notify?(target_status.id)).to be true expect(notify?(target_status.id)).to be true
end end
end end
@ -455,8 +464,9 @@ RSpec.describe ProcessReferencesService, type: :service do
it 'post status', :inline_jobs do it 'post status', :inline_jobs do
expect(subject.size).to eq 1 expect(subject.size).to eq 1
expect(subject).to include target_status.id expect(subject).to include target_status.id
expect(status.quote).to_not be_nil # TODO: quote
expect(status.quote.id).to eq target_status.id # expect(status.quote).to_not be_nil
# expect(status.quote.id).to eq target_status.id
expect(notify?(target_status.id)).to be true expect(notify?(target_status.id)).to be true
end end
end end

File diff suppressed because it is too large Load diff