Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
- app/models/form/admin_settings.rb
- config/locales/ja.yml
staging
Thibaut Girka 2019-04-01 21:28:31 +02:00
commit 12dae9d583
33 changed files with 259 additions and 116 deletions

View File

@ -32,7 +32,10 @@ class ActivityPub::InboxesController < Api::BaseController
end end
def body def body
@body ||= request.body.read.force_encoding('UTF-8') return @body if defined?(@body)
@body = request.body.read.force_encoding('UTF-8')
request.body.rewind if request.body.respond_to?(:rewind)
@body
end end
def upgrade_account def upgrade_account

View File

@ -25,7 +25,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
end end
def default_accounts def default_accounts
Account.includes(:active_relationships, :account_stat).references(:active_relationships) Account.without_blocking(current_account).includes(:active_relationships, :account_stat).references(:active_relationships)
end end
def paginated_follows def paginated_follows

View File

@ -25,7 +25,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
end end
def default_accounts def default_accounts
Account.includes(:passive_relationships, :account_stat).references(:passive_relationships) Account.without_blocking(current_account).includes(:passive_relationships, :account_stat).references(:passive_relationships)
end end
def paginated_follows def paginated_follows

View File

@ -3,6 +3,8 @@
class Api::V1::Accounts::StatusesController < Api::BaseController class Api::V1::Accounts::StatusesController < Api::BaseController
before_action -> { authorize_if_got_token! :read, :'read:statuses' } before_action -> { authorize_if_got_token! :read, :'read:statuses' }
before_action :set_account before_action :set_account
before_action :check_account_suspension
before_action :check_account_block
after_action :insert_pagination_headers after_action :insert_pagination_headers
respond_to :json respond_to :json
@ -18,6 +20,14 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
@account = Account.find(params[:account_id]) @account = Account.find(params[:account_id])
end end
def check_account_suspension
gone if @account.suspended?
end
def check_account_block
gone if current_account.present? && @account.blocking?(current_account)
end
def load_statuses def load_statuses
cached_account_statuses cached_account_statuses
end end

View File

@ -10,6 +10,7 @@ class Api::V1::AccountsController < Api::BaseController
before_action :require_user!, except: [:show, :create] before_action :require_user!, except: [:show, :create]
before_action :set_account, except: [:create] before_action :set_account, except: [:create]
before_action :check_account_suspension, only: [:show] before_action :check_account_suspension, only: [:show]
before_action :check_account_block, only: [:show]
before_action :check_enabled_registrations, only: [:create] before_action :check_enabled_registrations, only: [:create]
respond_to :json respond_to :json
@ -75,6 +76,10 @@ class Api::V1::AccountsController < Api::BaseController
gone if @account.suspended? gone if @account.suspended?
end end
def check_account_block
gone if current_account.present? && @account.blocking?(current_account)
end
def account_params def account_params
params.permit(:username, :email, :password, :agreement, :locale) params.permit(:username, :email, :password, :agreement, :locale)
end end

View File

@ -22,6 +22,7 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
def default_accounts def default_accounts
Account Account
.without_blocking(current_account)
.includes(:favourites, :account_stat) .includes(:favourites, :account_stat)
.references(:favourites) .references(:favourites)
.where(favourites: { status_id: @status.id }) .where(favourites: { status_id: @status.id })

View File

@ -21,7 +21,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
end end
def default_accounts def default_accounts
Account.includes(:statuses, :account_stat).references(:statuses) Account.without_blocking(current_account).includes(:statuses, :account_stat).references(:statuses)
end end
def paginated_statuses def paginated_statuses

View File

@ -111,7 +111,7 @@ class Header extends ImmutablePureComponent {
} else if (account.getIn(['relationship', 'requested'])) { } else if (account.getIn(['relationship', 'requested'])) {
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />; actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
} else if (!account.getIn(['relationship', 'blocking'])) { } else if (!account.getIn(['relationship', 'blocking'])) {
actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />; actionBtn = <Button className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />;
} else if (account.getIn(['relationship', 'blocking'])) { } else if (account.getIn(['relationship', 'blocking'])) {
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />; actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
} }

View File

@ -14,17 +14,14 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import { fetchAccountIdentityProofs } from '../../actions/identity_proofs'; import { fetchAccountIdentityProofs } from '../../actions/identity_proofs';
const emptyList = ImmutableList();
const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => { const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => {
const path = withReplies ? `${accountId}:with_replies` : accountId; const path = withReplies ? `${accountId}:with_replies` : accountId;
return { return {
statusIds: state.getIn(['timelines', `account:${path}`, 'items'], emptyList), statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()),
featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], emptyList), featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()),
isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']), isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']),
hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']), hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']),
blockedBy: state.getIn(['relationships', accountId, 'blocked_by'], false),
}; };
}; };
@ -40,7 +37,6 @@ class AccountTimeline extends ImmutablePureComponent {
isLoading: PropTypes.bool, isLoading: PropTypes.bool,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
withReplies: PropTypes.bool, withReplies: PropTypes.bool,
blockedBy: PropTypes.bool,
}; };
componentWillMount () { componentWillMount () {
@ -48,11 +44,9 @@ class AccountTimeline extends ImmutablePureComponent {
this.props.dispatch(fetchAccount(accountId)); this.props.dispatch(fetchAccount(accountId));
this.props.dispatch(fetchAccountIdentityProofs(accountId)); this.props.dispatch(fetchAccountIdentityProofs(accountId));
if (!withReplies) { if (!withReplies) {
this.props.dispatch(expandAccountFeaturedTimeline(accountId)); this.props.dispatch(expandAccountFeaturedTimeline(accountId));
} }
this.props.dispatch(expandAccountTimeline(accountId, { withReplies })); this.props.dispatch(expandAccountTimeline(accountId, { withReplies }));
} }
@ -60,11 +54,9 @@ class AccountTimeline extends ImmutablePureComponent {
if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) { if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) {
this.props.dispatch(fetchAccount(nextProps.params.accountId)); this.props.dispatch(fetchAccount(nextProps.params.accountId));
this.props.dispatch(fetchAccountIdentityProofs(nextProps.params.accountId)); this.props.dispatch(fetchAccountIdentityProofs(nextProps.params.accountId));
if (!nextProps.withReplies) { if (!nextProps.withReplies) {
this.props.dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId)); this.props.dispatch(expandAccountFeaturedTimeline(nextProps.params.accountId));
} }
this.props.dispatch(expandAccountTimeline(nextProps.params.accountId, { withReplies: nextProps.params.withReplies })); this.props.dispatch(expandAccountTimeline(nextProps.params.accountId, { withReplies: nextProps.params.withReplies }));
} }
} }
@ -74,7 +66,7 @@ class AccountTimeline extends ImmutablePureComponent {
} }
render () { render () {
const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore, blockedBy } = this.props; const { shouldUpdateScroll, statusIds, featuredStatusIds, isLoading, hasMore } = this.props;
if (!statusIds && isLoading) { if (!statusIds && isLoading) {
return ( return (
@ -84,8 +76,6 @@ class AccountTimeline extends ImmutablePureComponent {
); );
} }
const emptyMessage = blockedBy ? <FormattedMessage id='empty_column.account_timeline_blocked' defaultMessage='You are blocked' /> : <FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />;
return ( return (
<Column> <Column>
<ColumnBackButton /> <ColumnBackButton />
@ -94,13 +84,13 @@ class AccountTimeline extends ImmutablePureComponent {
prepend={<HeaderContainer accountId={this.props.params.accountId} />} prepend={<HeaderContainer accountId={this.props.params.accountId} />}
alwaysPrepend alwaysPrepend
scrollKey='account_timeline' scrollKey='account_timeline'
statusIds={blockedBy ? emptyList : statusIds} statusIds={statusIds}
featuredStatusIds={featuredStatusIds} featuredStatusIds={featuredStatusIds}
isLoading={isLoading} isLoading={isLoading}
hasMore={hasMore} hasMore={hasMore}
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
shouldUpdateScroll={shouldUpdateScroll} shouldUpdateScroll={shouldUpdateScroll}
emptyMessage={emptyMessage} emptyMessage={<FormattedMessage id='empty_column.account_timeline' defaultMessage='No toots here!' />}
/> />
</Column> </Column>
); );

View File

@ -20,7 +20,6 @@ import ScrollableList from '../../components/scrollable_list';
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']), accountIds: state.getIn(['user_lists', 'followers', props.params.accountId, 'items']),
hasMore: !!state.getIn(['user_lists', 'followers', props.params.accountId, 'next']), hasMore: !!state.getIn(['user_lists', 'followers', props.params.accountId, 'next']),
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
}); });
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@ -32,7 +31,6 @@ class Followers extends ImmutablePureComponent {
shouldUpdateScroll: PropTypes.func, shouldUpdateScroll: PropTypes.func,
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
blockedBy: PropTypes.bool,
}; };
componentWillMount () { componentWillMount () {
@ -52,7 +50,7 @@ class Followers extends ImmutablePureComponent {
}, 300, { leading: true }); }, 300, { leading: true });
render () { render () {
const { shouldUpdateScroll, accountIds, hasMore, blockedBy } = this.props; const { shouldUpdateScroll, accountIds, hasMore } = this.props;
if (!accountIds) { if (!accountIds) {
return ( return (
@ -62,7 +60,7 @@ class Followers extends ImmutablePureComponent {
); );
} }
const emptyMessage = blockedBy ? <FormattedMessage id='empty_column.account_timeline_blocked' defaultMessage='You are blocked' /> : <FormattedMessage id='account.followers.empty' defaultMessage='No one follows this user yet.' />; const emptyMessage = <FormattedMessage id='account.followers.empty' defaultMessage='No one follows this user yet.' />;
return ( return (
<Column> <Column>
@ -77,7 +75,7 @@ class Followers extends ImmutablePureComponent {
alwaysPrepend alwaysPrepend
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
> >
{blockedBy ? [] : accountIds.map(id => {accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} /> <AccountContainer key={id} id={id} withNote={false} />
)} )}
</ScrollableList> </ScrollableList>

View File

@ -20,7 +20,6 @@ import ScrollableList from '../../components/scrollable_list';
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, props) => ({
accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']), accountIds: state.getIn(['user_lists', 'following', props.params.accountId, 'items']),
hasMore: !!state.getIn(['user_lists', 'following', props.params.accountId, 'next']), hasMore: !!state.getIn(['user_lists', 'following', props.params.accountId, 'next']),
blockedBy: state.getIn(['relationships', props.params.accountId, 'blocked_by'], false),
}); });
export default @connect(mapStateToProps) export default @connect(mapStateToProps)
@ -32,7 +31,6 @@ class Following extends ImmutablePureComponent {
shouldUpdateScroll: PropTypes.func, shouldUpdateScroll: PropTypes.func,
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
blockedBy: PropTypes.bool,
}; };
componentWillMount () { componentWillMount () {
@ -52,7 +50,7 @@ class Following extends ImmutablePureComponent {
}, 300, { leading: true }); }, 300, { leading: true });
render () { render () {
const { shouldUpdateScroll, accountIds, hasMore, blockedBy } = this.props; const { shouldUpdateScroll, accountIds, hasMore } = this.props;
if (!accountIds) { if (!accountIds) {
return ( return (
@ -62,7 +60,7 @@ class Following extends ImmutablePureComponent {
); );
} }
const emptyMessage = blockedBy ? <FormattedMessage id='empty_column.account_timeline_blocked' defaultMessage='You are blocked' /> : <FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />; const emptyMessage = <FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />;
return ( return (
<Column> <Column>
@ -77,7 +75,7 @@ class Following extends ImmutablePureComponent {
alwaysPrepend alwaysPrepend
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
> >
{blockedBy ? [] : accountIds.map(id => {accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} /> <AccountContainer key={id} id={id} withNote={false} />
)} )}
</ScrollableList> </ScrollableList>

View File

@ -551,6 +551,10 @@
}, },
{ {
"descriptors": [ "descriptors": [
{
"defaultMessage": "You are blocked",
"id": "empty_column.account_timeline_blocked"
},
{ {
"defaultMessage": "No toots here!", "defaultMessage": "No toots here!",
"id": "empty_column.account_timeline" "id": "empty_column.account_timeline"
@ -1251,6 +1255,10 @@
}, },
{ {
"descriptors": [ "descriptors": [
{
"defaultMessage": "You are blocked",
"id": "empty_column.account_timeline_blocked"
},
{ {
"defaultMessage": "No one follows this user yet.", "defaultMessage": "No one follows this user yet.",
"id": "account.followers.empty" "id": "account.followers.empty"
@ -1260,6 +1268,10 @@
}, },
{ {
"descriptors": [ "descriptors": [
{
"defaultMessage": "You are blocked",
"id": "empty_column.account_timeline_blocked"
},
{ {
"defaultMessage": "This user doesn't follow anyone yet.", "defaultMessage": "This user doesn't follow anyone yet.",
"id": "account.follows.empty" "id": "account.follows.empty"

View File

@ -121,6 +121,7 @@
"emoji_button.symbols": "Symbols", "emoji_button.symbols": "Symbols",
"emoji_button.travel": "Travel & Places", "emoji_button.travel": "Travel & Places",
"empty_column.account_timeline": "No toots here!", "empty_column.account_timeline": "No toots here!",
"empty_column.account_timeline_blocked": "You are blocked",
"empty_column.blocks": "You haven't blocked any users yet.", "empty_column.blocks": "You haven't blocked any users yet.",
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!", "empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.", "empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",

View File

@ -1,12 +1,12 @@
{ {
"account.add_or_remove_from_list": "リスト追加または外す", "account.add_or_remove_from_list": "リストから追加または外す",
"account.badges.bot": "Bot", "account.badges.bot": "Bot",
"account.block": "@{name}さんをブロック", "account.block": "@{name}さんをブロック",
"account.block_domain": "{domain}全体を非表示", "account.block_domain": "{domain}全体を非表示",
"account.blocked": "ブロック済み", "account.blocked": "ブロック済み",
"account.direct": "@{name}さんにダイレクトメッセージ", "account.direct": "@{name}さんにダイレクトメッセージ",
"account.domain_blocked": "ドメイン非表示中", "account.domain_blocked": "ドメイン非表示中",
"account.edit_profile": "プロフィール編集", "account.edit_profile": "プロフィール編集",
"account.endorse": "プロフィールで紹介する", "account.endorse": "プロフィールで紹介する",
"account.follow": "フォロー", "account.follow": "フォロー",
"account.followers": "フォロワー", "account.followers": "フォロワー",
@ -16,7 +16,7 @@
"account.follows_you": "フォローされています", "account.follows_you": "フォローされています",
"account.hide_reblogs": "@{name}さんからのブーストを非表示", "account.hide_reblogs": "@{name}さんからのブーストを非表示",
"account.link_verified_on": "このリンクの所有権は{date}に確認されました", "account.link_verified_on": "このリンクの所有権は{date}に確認されました",
"account.locked_info": "このアカウントは承認制アカウントです。相手が認するまでフォローは完了しません。", "account.locked_info": "このアカウントは承認制アカウントです。相手が認するまでフォローは完了しません。",
"account.media": "メディア", "account.media": "メディア",
"account.mention": "@{name}さんにトゥート", "account.mention": "@{name}さんにトゥート",
"account.moved_to": "{name}さんは引っ越しました:", "account.moved_to": "{name}さんは引っ越しました:",
@ -30,7 +30,7 @@
"account.share": "@{name}さんのプロフィールを共有する", "account.share": "@{name}さんのプロフィールを共有する",
"account.show_reblogs": "@{name}さんからのブーストを表示", "account.show_reblogs": "@{name}さんからのブーストを表示",
"account.unblock": "@{name}さんのブロックを解除", "account.unblock": "@{name}さんのブロックを解除",
"account.unblock_domain": "{domain}を表示", "account.unblock_domain": "{domain}の非表示を解除",
"account.unendorse": "プロフィールから外す", "account.unendorse": "プロフィールから外す",
"account.unfollow": "フォロー解除", "account.unfollow": "フォロー解除",
"account.unmute": "@{name}さんのミュートを解除", "account.unmute": "@{name}さんのミュートを解除",
@ -71,14 +71,14 @@
"community.column_settings.media_only": "メディアのみ表示", "community.column_settings.media_only": "メディアのみ表示",
"compose_form.direct_message_warning": "このトゥートはメンションされた人にのみ送信されます。", "compose_form.direct_message_warning": "このトゥートはメンションされた人にのみ送信されます。",
"compose_form.direct_message_warning_learn_more": "もっと詳しく", "compose_form.direct_message_warning_learn_more": "もっと詳しく",
"compose_form.hashtag_warning": "このトゥートは未収載なのでハッシュタグの一覧に表示されません。公開トゥートだけがハッシュタグで検索できます。", "compose_form.hashtag_warning": "このトゥートは公開設定ではないのでハッシュタグの一覧に表示されません。公開トゥートだけがハッシュタグで検索できます。",
"compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。", "compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。",
"compose_form.lock_disclaimer.lock": "承認制", "compose_form.lock_disclaimer.lock": "承認制",
"compose_form.placeholder": "今なにしてる?", "compose_form.placeholder": "今なにしてる?",
"compose_form.poll.add_option": "Add a choice", "compose_form.poll.add_option": "追加",
"compose_form.poll.duration": "投票期間", "compose_form.poll.duration": "アンケート期間",
"compose_form.poll.option_placeholder": "Choice {number}", "compose_form.poll.option_placeholder": "選択肢 {number}",
"compose_form.poll.remove_option": "Remove this choice", "compose_form.poll.remove_option": "この選択肢を削除",
"compose_form.publish": "トゥート", "compose_form.publish": "トゥート",
"compose_form.publish_loud": "{publish}", "compose_form.publish_loud": "{publish}",
"compose_form.sensitive.marked": "メディアに閲覧注意が設定されています", "compose_form.sensitive.marked": "メディアに閲覧注意が設定されています",
@ -87,7 +87,7 @@
"compose_form.spoiler.unmarked": "閲覧注意が設定されていません", "compose_form.spoiler.unmarked": "閲覧注意が設定されていません",
"compose_form.spoiler_placeholder": "ここに警告を書いてください", "compose_form.spoiler_placeholder": "ここに警告を書いてください",
"confirmation_modal.cancel": "キャンセル", "confirmation_modal.cancel": "キャンセル",
"confirmations.block.block_and_report": "Block & Report", "confirmations.block.block_and_report": "ブロックし通報",
"confirmations.block.confirm": "ブロック", "confirmations.block.confirm": "ブロック",
"confirmations.block.message": "本当に{name}さんをブロックしますか?", "confirmations.block.message": "本当に{name}さんをブロックしますか?",
"confirmations.delete.confirm": "削除", "confirmations.delete.confirm": "削除",
@ -121,6 +121,7 @@
"emoji_button.symbols": "記号", "emoji_button.symbols": "記号",
"emoji_button.travel": "旅行と場所", "emoji_button.travel": "旅行と場所",
"empty_column.account_timeline": "トゥートがありません!", "empty_column.account_timeline": "トゥートがありません!",
"empty_column.account_timeline_blocked": "ブロックされています",
"empty_column.blocks": "まだ誰もブロックしていません。", "empty_column.blocks": "まだ誰もブロックしていません。",
"empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!", "empty_column.community": "ローカルタイムラインはまだ使われていません。何か書いてみましょう!",
"empty_column.direct": "ダイレクトメッセージはまだありません。ダイレクトメッセージをやりとりすると、ここに表示されます。", "empty_column.direct": "ダイレクトメッセージはまだありません。ダイレクトメッセージをやりとりすると、ここに表示されます。",
@ -158,8 +159,8 @@
"home.column_settings.basic": "基本設定", "home.column_settings.basic": "基本設定",
"home.column_settings.show_reblogs": "ブースト表示", "home.column_settings.show_reblogs": "ブースト表示",
"home.column_settings.show_replies": "返信表示", "home.column_settings.show_replies": "返信表示",
"intervals.full.days": "{number, plural, one {# day} other {# days}}", "intervals.full.days": "{number}日",
"intervals.full.hours": "{number, plural, one {# hour} other {# hours}}", "intervals.full.hours": "{number}時間",
"intervals.full.minutes": "{number}分", "intervals.full.minutes": "{number}分",
"introduction.federation.action": "次へ", "introduction.federation.action": "次へ",
"introduction.federation.federated.headline": "連合タイムライン", "introduction.federation.federated.headline": "連合タイムライン",
@ -177,7 +178,7 @@
"introduction.interactions.reply.text": "自身や人々のトゥートに返信することで、一連の会話に繋げることができます。", "introduction.interactions.reply.text": "自身や人々のトゥートに返信することで、一連の会話に繋げることができます。",
"introduction.welcome.action": "はじめる!", "introduction.welcome.action": "はじめる!",
"introduction.welcome.headline": "はじめに", "introduction.welcome.headline": "はじめに",
"introduction.welcome.text": "Fediverseの世界へようこそあと少しでメッセージを配信したり、さまざまなサーバーを越えた友達と話せるようになります。ところでここ{domain}は特別なサーバーです…あなたのプロフィールを持つ主体のサーバーですので、名前を覚えておきましょう。", "introduction.welcome.text": "Fediverseの世界へようこそあと少しでメッセージを配信したり、さまざまなサーバーを越えた友達と話せるようになります。ところでここ{domain}は特別なサーバーです…あなたのプロフィールを持つ主体のサーバーですので、名前を覚えておきましょう。",
"keyboard_shortcuts.back": "戻る", "keyboard_shortcuts.back": "戻る",
"keyboard_shortcuts.blocked": "ブロックしたユーザーのリストを開く", "keyboard_shortcuts.blocked": "ブロックしたユーザーのリストを開く",
"keyboard_shortcuts.boost": "ブースト", "keyboard_shortcuts.boost": "ブースト",
@ -251,7 +252,7 @@
"notification.favourite": "{name}さんがあなたのトゥートをお気に入りに登録しました", "notification.favourite": "{name}さんがあなたのトゥートをお気に入りに登録しました",
"notification.follow": "{name}さんにフォローされました", "notification.follow": "{name}さんにフォローされました",
"notification.mention": "{name}さんがあなたに返信しました", "notification.mention": "{name}さんがあなたに返信しました",
"notification.poll": "A poll you have voted in has ended", "notification.poll": "アンケートが終了しました",
"notification.reblog": "{name}さんがあなたのトゥートをブーストしました", "notification.reblog": "{name}さんがあなたのトゥートをブーストしました",
"notifications.clear": "通知を消去", "notifications.clear": "通知を消去",
"notifications.clear_confirmation": "本当に通知を消去しますか?", "notifications.clear_confirmation": "本当に通知を消去しますか?",
@ -262,7 +263,7 @@
"notifications.column_settings.filter_bar.show": "表示", "notifications.column_settings.filter_bar.show": "表示",
"notifications.column_settings.follow": "新しいフォロワー:", "notifications.column_settings.follow": "新しいフォロワー:",
"notifications.column_settings.mention": "返信:", "notifications.column_settings.mention": "返信:",
"notifications.column_settings.poll": "Poll results:", "notifications.column_settings.poll": "アンケート結果:",
"notifications.column_settings.push": "プッシュ通知", "notifications.column_settings.push": "プッシュ通知",
"notifications.column_settings.reblog": "ブースト:", "notifications.column_settings.reblog": "ブースト:",
"notifications.column_settings.show": "カラムに表示", "notifications.column_settings.show": "カラムに表示",
@ -272,14 +273,14 @@
"notifications.filter.favourites": "お気に入り", "notifications.filter.favourites": "お気に入り",
"notifications.filter.follows": "フォロー", "notifications.filter.follows": "フォロー",
"notifications.filter.mentions": "返信", "notifications.filter.mentions": "返信",
"notifications.filter.polls": "Poll results", "notifications.filter.polls": "アンケート結果",
"notifications.group": "{count} 件の通知", "notifications.group": "{count} 件の通知",
"poll.closed": "終了", "poll.closed": "終了",
"poll.refresh": "更新", "poll.refresh": "更新",
"poll.total_votes": "{count}票", "poll.total_votes": "{count}票",
"poll.vote": "Vote", "poll.vote": "投票",
"poll_button.add_poll": "Add a poll", "poll_button.add_poll": "アンケートを追加",
"poll_button.remove_poll": "投票を削除", "poll_button.remove_poll": "アンケートを削除",
"privacy.change": "公開範囲を変更", "privacy.change": "公開範囲を変更",
"privacy.direct.long": "メンションしたユーザーだけに公開", "privacy.direct.long": "メンションしたユーザーだけに公開",
"privacy.direct.short": "ダイレクト", "privacy.direct.short": "ダイレクト",
@ -371,7 +372,7 @@
"upload_area.title": "ドラッグ&ドロップでアップロード", "upload_area.title": "ドラッグ&ドロップでアップロード",
"upload_button.label": "メディアを追加 (JPEG, PNG, GIF, WebM, MP4, MOV)", "upload_button.label": "メディアを追加 (JPEG, PNG, GIF, WebM, MP4, MOV)",
"upload_error.limit": "アップロードできる上限を超えています。", "upload_error.limit": "アップロードできる上限を超えています。",
"upload_error.poll": "File upload not allowed with polls.", "upload_error.poll": "アンケートではファイルをアップロードできません。",
"upload_form.description": "視覚障害者のための説明", "upload_form.description": "視覚障害者のための説明",
"upload_form.focus": "プレビューを変更", "upload_form.focus": "プレビューを変更",
"upload_form.undo": "削除", "upload_form.undo": "削除",

View File

@ -99,9 +99,9 @@
} }
} }
&:active:not(:disabled), &:active,
&:focus:not(:disabled), &:focus,
&:hover:not(:disabled) { &:hover {
background: lighten($ui-highlight-color, 10%); background: lighten($ui-highlight-color, 10%);
svg path:last-child { svg path:last-child {

View File

@ -102,6 +102,7 @@ class Account < ApplicationRecord
scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) } scope :tagged_with, ->(tag) { joins(:accounts_tags).where(accounts_tags: { tag_id: tag }) }
scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc')) } scope :by_recent_status, -> { order(Arel.sql('(case when account_stats.last_status_at is null then 1 else 0 end) asc, account_stats.last_status_at desc')) }
scope :popular, -> { order('account_stats.followers_count desc') } scope :popular, -> { order('account_stats.followers_count desc') }
scope :without_blocking, ->(account) { account.nil? ? all : where.not(id: Block.where(target_account_id: account.id).pluck(:account_id)) }
delegate :email, delegate :email,
:unconfirmed_email, :unconfirmed_email,

View File

@ -28,6 +28,9 @@ class Form::AdminSettings
profile_directory profile_directory
hide_followers_count hide_followers_count
flavour_and_skin flavour_and_skin
thumbnail
hero
mascot
).freeze ).freeze
BOOLEAN_KEYS = %i( BOOLEAN_KEYS = %i(
@ -73,7 +76,7 @@ class Form::AdminSettings
next if PSEUDO_KEYS.include?(key) next if PSEUDO_KEYS.include?(key)
value = instance_variable_get("@#{key}") value = instance_variable_get("@#{key}")
if UPLOAD_KEYS.include?(key) if UPLOAD_KEYS.include?(key) && !value.nil?
upload = SiteUpload.where(var: key).first_or_initialize(var: key) upload = SiteUpload.where(var: key).first_or_initialize(var: key)
upload.update(file: value) upload.update(file: value)
else else

View File

@ -18,6 +18,7 @@ class SiteUpload < ApplicationRecord
has_attached_file :file has_attached_file :file
validates_attachment_content_type :file, content_type: /\Aimage\/.*\z/ validates_attachment_content_type :file, content_type: /\Aimage\/.*\z/
validates :file, presence: true
validates :var, presence: true, uniqueness: true validates :var, presence: true, uniqueness: true
before_save :set_meta before_save :set_meta

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class AccountRelationshipsPresenter class AccountRelationshipsPresenter
attr_reader :following, :followed_by, :blocking, :blocked_by, attr_reader :following, :followed_by, :blocking,
:muting, :requested, :domain_blocking, :muting, :requested, :domain_blocking,
:endorsed :endorsed
@ -12,7 +12,6 @@ class AccountRelationshipsPresenter
@following = cached[:following].merge(Account.following_map(@uncached_account_ids, @current_account_id)) @following = cached[:following].merge(Account.following_map(@uncached_account_ids, @current_account_id))
@followed_by = cached[:followed_by].merge(Account.followed_by_map(@uncached_account_ids, @current_account_id)) @followed_by = cached[:followed_by].merge(Account.followed_by_map(@uncached_account_ids, @current_account_id))
@blocking = cached[:blocking].merge(Account.blocking_map(@uncached_account_ids, @current_account_id)) @blocking = cached[:blocking].merge(Account.blocking_map(@uncached_account_ids, @current_account_id))
@blocked_by = cached[:blocked_by].merge(Account.blocked_by_map(@uncached_account_ids, @current_account_id))
@muting = cached[:muting].merge(Account.muting_map(@uncached_account_ids, @current_account_id)) @muting = cached[:muting].merge(Account.muting_map(@uncached_account_ids, @current_account_id))
@requested = cached[:requested].merge(Account.requested_map(@uncached_account_ids, @current_account_id)) @requested = cached[:requested].merge(Account.requested_map(@uncached_account_ids, @current_account_id))
@domain_blocking = cached[:domain_blocking].merge(Account.domain_blocking_map(@uncached_account_ids, @current_account_id)) @domain_blocking = cached[:domain_blocking].merge(Account.domain_blocking_map(@uncached_account_ids, @current_account_id))
@ -23,7 +22,6 @@ class AccountRelationshipsPresenter
@following.merge!(options[:following_map] || {}) @following.merge!(options[:following_map] || {})
@followed_by.merge!(options[:followed_by_map] || {}) @followed_by.merge!(options[:followed_by_map] || {})
@blocking.merge!(options[:blocking_map] || {}) @blocking.merge!(options[:blocking_map] || {})
@blocked_by.merge!(options[:blocked_by_map] || {})
@muting.merge!(options[:muting_map] || {}) @muting.merge!(options[:muting_map] || {})
@requested.merge!(options[:requested_map] || {}) @requested.merge!(options[:requested_map] || {})
@domain_blocking.merge!(options[:domain_blocking_map] || {}) @domain_blocking.merge!(options[:domain_blocking_map] || {})
@ -39,7 +37,6 @@ class AccountRelationshipsPresenter
following: {}, following: {},
followed_by: {}, followed_by: {},
blocking: {}, blocking: {},
blocked_by: {},
muting: {}, muting: {},
requested: {}, requested: {},
domain_blocking: {}, domain_blocking: {},
@ -67,7 +64,6 @@ class AccountRelationshipsPresenter
following: { account_id => following[account_id] }, following: { account_id => following[account_id] },
followed_by: { account_id => followed_by[account_id] }, followed_by: { account_id => followed_by[account_id] },
blocking: { account_id => blocking[account_id] }, blocking: { account_id => blocking[account_id] },
blocked_by: { account_id => blocked_by[account_id] },
muting: { account_id => muting[account_id] }, muting: { account_id => muting[account_id] },
requested: { account_id => requested[account_id] }, requested: { account_id => requested[account_id] },
domain_blocking: { account_id => domain_blocking[account_id] }, domain_blocking: { account_id => domain_blocking[account_id] },

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class REST::RelationshipSerializer < ActiveModel::Serializer class REST::RelationshipSerializer < ActiveModel::Serializer
attributes :id, :following, :showing_reblogs, :followed_by, :blocking, :blocked_by, attributes :id, :following, :showing_reblogs, :followed_by, :blocking,
:muting, :muting_notifications, :requested, :domain_blocking, :muting, :muting_notifications, :requested, :domain_blocking,
:endorsed :endorsed
@ -27,10 +27,6 @@ class REST::RelationshipSerializer < ActiveModel::Serializer
instance_options[:relationships].blocking[object.id] || false instance_options[:relationships].blocking[object.id] || false
end end
def blocked_by
instance_options[:relationships].blocked_by[object.id] || false
end
def muting def muting
instance_options[:relationships].muting[object.id] ? true : false instance_options[:relationships].muting[object.id] ? true : false
end end

View File

@ -10,7 +10,15 @@ class AccountSearchService < BaseService
@options = options @options = options
@account = account @account = account
search_service_results results = search_service_results
unless account.nil?
account_ids = results.map(&:id)
blocked_by_map = Account.blocked_by_map(account_ids, account.id)
results.reject! { |item| blocked_by_map[item.id] }
end
results
end end
private private

View File

@ -12,6 +12,8 @@ class SearchService < BaseService
default_results.tap do |results| default_results.tap do |results|
if url_query? if url_query?
results.merge!(url_resource_results) unless url_resource.nil? results.merge!(url_resource_results) unless url_resource.nil?
results[:accounts].reject! { |item| item.blocking?(@account) }
results[:statuses].reject! { |status| StatusFilter.new(status, @account).filtered? }
elsif @query.present? elsif @query.present?
results[:accounts] = perform_accounts_search! if account_searchable? results[:accounts] = perform_accounts_search! if account_searchable?
results[:statuses] = perform_statuses_search! if full_text_searchable? results[:statuses] = perform_statuses_search! if full_text_searchable?

View File

@ -2,6 +2,9 @@
ja: ja:
activerecord: activerecord:
attributes: attributes:
poll:
expires_at: 期限
options: 選択肢
user: user:
email: メールアドレス email: メールアドレス
errors: errors:
@ -9,8 +12,8 @@ ja:
account: account:
attributes: attributes:
username: username:
invalid: アルファベット・数値・アンダーバー(_)で入力してください invalid: アルファベット・数字・アンダーバーの組み合わせで入力してください
status: status:
attributes: attributes:
reblog: reblog:
taken: のブーストはすでに存在します taken: は既にブーストされています

View File

@ -636,6 +636,7 @@ en:
all: All all: All
changes_saved_msg: Changes successfully saved! changes_saved_msg: Changes successfully saved!
copy: Copy copy: Copy
order_by: Order by
save_changes: Save changes save_changes: Save changes
use_this: Use this use_this: Use this
validation_errors: validation_errors:

View File

@ -163,8 +163,8 @@ ja:
search: 検索 search: 検索
shared_inbox_url: Shared inbox URL shared_inbox_url: Shared inbox URL
show: show:
created_reports: このアカウントで作られたレポート created_reports: このアカウントで作られた通報
targeted_reports: このアカウントについてのレポート targeted_reports: このアカウントについての通報
silence: サイレンス silence: サイレンス
silenced: サイレンス済み silenced: サイレンス済み
statuses: トゥート数 statuses: トゥート数
@ -180,7 +180,7 @@ ja:
web: Web web: Web
action_logs: action_logs:
actions: actions:
assigned_to_self_report: "%{name} さんがレポート %{target} を自身の担当に割り当てました" assigned_to_self_report: "%{name} さんが通報 %{target} を自身の担当に割り当てました"
change_email_user: "%{name} さんが %{target} さんのメールアドレスを変更しました" change_email_user: "%{name} さんが %{target} さんのメールアドレスを変更しました"
confirm_user: "%{name} さんが %{target} さんのメールアドレスを確認済みにしました" confirm_user: "%{name} さんが %{target} さんのメールアドレスを確認済みにしました"
create_account_warning: "%{name} さんが %{target} さんに警告メールを送信しました" create_account_warning: "%{name} さんが %{target} さんに警告メールを送信しました"
@ -200,12 +200,12 @@ ja:
memorialize_account: "%{name} さんが %{target} さんを追悼アカウントページに登録しました" memorialize_account: "%{name} さんが %{target} さんを追悼アカウントページに登録しました"
promote_user: "%{name} さんが %{target} さんを昇格しました" promote_user: "%{name} さんが %{target} さんを昇格しました"
remove_avatar_user: "%{name} さんが %{target} さんのアイコンを削除しました" remove_avatar_user: "%{name} さんが %{target} さんのアイコンを削除しました"
reopen_report: "%{name} さんがレポート %{target} を再び開きました" reopen_report: "%{name} さんが通報 %{target} を再び開きました"
reset_password_user: "%{name} さんが %{target} さんのパスワードをリセットしました" reset_password_user: "%{name} さんが %{target} さんのパスワードをリセットしました"
resolve_report: "%{name} さんがレポート %{target} を解決済みにしました" resolve_report: "%{name} さんが通報 %{target} を解決済みにしました"
silence_account: "%{name} さんが %{target} さんをサイレンスにしました" silence_account: "%{name} さんが %{target} さんをサイレンスにしました"
suspend_account: "%{name} さんが %{target} さんを停止しました" suspend_account: "%{name} さんが %{target} さんを停止しました"
unassigned_report: "%{name} さんがレポート %{target} の担当を外しました" unassigned_report: "%{name} さんが通報 %{target} の担当を外しました"
unsilence_account: "%{name} さんが %{target} さんのサイレンスを解除しました" unsilence_account: "%{name} さんが %{target} さんのサイレンスを解除しました"
unsuspend_account: "%{name} さんが %{target} さんの停止を解除しました" unsuspend_account: "%{name} さんが %{target} さんの停止を解除しました"
update_custom_emoji: "%{name} さんがカスタム絵文字 %{target} を更新しました" update_custom_emoji: "%{name} さんがカスタム絵文字 %{target} を更新しました"
@ -245,9 +245,10 @@ ja:
feature_profile_directory: ディレクトリ feature_profile_directory: ディレクトリ
feature_registrations: 新規登録 feature_registrations: 新規登録
feature_relay: 連合リレー feature_relay: 連合リレー
feature_timeline_preview: タイムラインプレビュー
features: 機能 features: 機能
hidden_service: 秘匿サービスとの連合 hidden_service: 秘匿サービスとの連合
open_reports: 未解決のレポート open_reports: 未解決の通報
recent_users: 最近登録したユーザー recent_users: 最近登録したユーザー
search: 全文検索 search: 全文検索
single_user_mode: シングルユーザーモード single_user_mode: シングルユーザーモード
@ -275,10 +276,10 @@ ja:
title: 新規ドメインブロック title: 新規ドメインブロック
reject_media: メディアファイルを拒否 reject_media: メディアファイルを拒否
reject_media_hint: ローカルに保存されたメディアファイルを削除し、今後のダウンロードを拒否します。停止とは無関係です reject_media_hint: ローカルに保存されたメディアファイルを削除し、今後のダウンロードを拒否します。停止とは無関係です
reject_reports: レポートを拒否 reject_reports: 通報を拒否
reject_reports_hint: このドメインからのレポートをすべて無視します。停止とは無関係です reject_reports_hint: このドメインからの通報をすべて無視します。停止とは無関係です
rejecting_media: メディアファイルを拒否中 rejecting_media: メディアファイルを拒否中
rejecting_reports: レポートを拒否中 rejecting_reports: 通報を拒否中
severity: severity:
silence: サイレンス中 silence: サイレンス中
suspend: 停止中 suspend: 停止中
@ -319,7 +320,7 @@ ja:
total_blocked_by_us: ブロック合計 total_blocked_by_us: ブロック合計
total_followed_by_them: 被フォロー合計 total_followed_by_them: 被フォロー合計
total_followed_by_us: フォロー合計 total_followed_by_us: フォロー合計
total_reported: レポート合計 total_reported: 通報合計
total_storage: 添付されたメディア total_storage: 添付されたメディア
invites: invites:
deactivate_all: すべて無効化 deactivate_all: すべて無効化
@ -345,19 +346,19 @@ ja:
status: ステータス status: ステータス
title: リレー title: リレー
report_notes: report_notes:
created_msg: レポートメモを書き込みました! created_msg: 通報メモを書き込みました!
destroyed_msg: レポートメモを削除しました! destroyed_msg: 通報メモを削除しました!
reports: reports:
account: account:
note: メモ note: メモ
report: レポート report: 通報
action_taken_by: レポート処理者 action_taken_by: 通報処理者
are_you_sure: 本当に実行しますか? are_you_sure: 本当に実行しますか?
assign_to_self: 担当になる assign_to_self: 担当になる
assigned: 担当者 assigned: 担当者
comment: comment:
none: なし none: なし
created_at: レポート日時 created_at: 通報日時
mark_as_resolved: 解決済みとしてマーク mark_as_resolved: 解決済みとしてマーク
mark_as_unresolved: 未解決として再び開く mark_as_unresolved: 未解決として再び開く
notes: notes:
@ -367,13 +368,13 @@ ja:
delete: 削除 delete: 削除
placeholder: どのような措置が取られたか、または関連する更新を記述してください… placeholder: どのような措置が取られたか、または関連する更新を記述してください…
reopen: 再び開く reopen: 再び開く
report: レポート#%{id} report: 通報#%{id}
reported_account: 報告対象アカウント reported_account: 報告対象アカウント
reported_by: 報告者 reported_by: 報告者
resolved: 解決済み resolved: 解決済み
resolved_msg: レポートを解決済みにしました! resolved_msg: 通報を解決済みにしました!
status: ステータス status: ステータス
title: レポート title: 通報
unassign: 担当を外す unassign: 担当を外す
unresolved: 未解決 unresolved: 未解決
updated_at: 更新日時 updated_at: 更新日時
@ -386,7 +387,7 @@ ja:
title: 新規ユーザーが自動フォローするアカウント title: 新規ユーザーが自動フォローするアカウント
contact_information: contact_information:
email: ビジネスメールアドレス email: ビジネスメールアドレス
username: 連絡先ユーザー名 username: 連絡先ユーザー名
custom_css: custom_css:
desc_html: 全ページに適用されるCSSの編集 desc_html: 全ページに適用されるCSSの編集
title: カスタムCSS title: カスタムCSS
@ -586,6 +587,9 @@ ja:
content: もうしわけありませんが、なにかが間違っています。 content: もうしわけありませんが、なにかが間違っています。
title: このページは正しくありません title: このページは正しくありません
noscript_html: Mastodonのウェブアプリケーションを利用する場合はJavaScriptを有効にしてください。またはあなたのプラットフォーム向けの<a href="%{apps_path}">Mastodonネイティブアプリ</a>を探すことができます。 noscript_html: Mastodonのウェブアプリケーションを利用する場合はJavaScriptを有効にしてください。またはあなたのプラットフォーム向けの<a href="%{apps_path}">Mastodonネイティブアプリ</a>を探すことができます。
existing_username_validator:
not_found: そのようなユーザー名はローカルに見つかりませんでした
not_found_multiple: "%{usernames} は見つかりませんでした"
exports: exports:
archive_takeout: archive_takeout:
date: 日時 date: 日時
@ -629,11 +633,32 @@ ja:
all: すべて all: すべて
changes_saved_msg: 正常に変更されました! changes_saved_msg: 正常に変更されました!
copy: コピー copy: コピー
order_by: 並び順
save_changes: 変更を保存 save_changes: 変更を保存
use_this: これを使う use_this: これを使う
validation_errors: validation_errors:
one: エラーが発生しました! 以下のエラーを確認してください one: エラーが発生しました! 以下のエラーを確認してください
other: エラーが発生しました! 以下の%{count}個のエラーを確認してください other: エラーが発生しました! 以下の%{count}個のエラーを確認してください
html_validator:
invalid_markup: '無効なHTMLマークアップが含まれています: %{error}'
identity_proofs:
active: アクティブ
authorize: 許可する
authorize_connection_prompt: この暗号化接続を許可しますか?
errors:
failed: 暗号化接続に失敗しました。%{provider}からもう一度やり直してください。
keybase:
invalid_token: Keybaseトークンは16進数で66文字のハッシュである必要があります
verification_failed: KeybaseはこのトークンをKeybaseユーザー%{kb_username}の署名として認識しませんでした。Keybaseから再試行してください。
wrong_user: "%{current}としてログインしている間%{proving}の証明を作成することはできません。%{proving}としてログインし、もう一度やり直してください。"
explanation_html: ここではKeybaseのような他のサービスのアカウントと暗号化し関連づけることができます。これにより他の人が暗号化されたメッセージを送信したり、その内容を信用できるようになります。
i_am_html: I am %{username} on %{service}.
identity: Identity
inactive: 非アクティブ
publicize_checkbox: 'そしてこれをトゥートしてください:'
publicize_toot: 'It is proven! I am %{username} on %{service}: %{url}'
status: 認証状態
view_proof: 証明を表示
imports: imports:
modes: modes:
merge: 統合 merge: 統合
@ -737,10 +762,11 @@ ja:
truncate: "&hellip;" truncate: "&hellip;"
polls: polls:
errors: errors:
already_voted: このアンケートには投票済みです
duplicate_options: に同じものがあります duplicate_options: に同じものがあります
duration_too_long: が長過ぎます duration_too_long: が長過ぎます
duration_too_short: が短過ぎます duration_too_short: が短過ぎます
expired: 既に終了している投票です expired: アンケートは既に終了しました
over_character_limit: は%{max}文字より長くすることはできません over_character_limit: は%{max}文字より長くすることはできません
too_few_options: は複数必要です too_few_options: は複数必要です
too_many_options: は%{max}個までです too_many_options: は%{max}個までです
@ -749,6 +775,19 @@ ja:
other: その他 other: その他
publishing: 投稿 publishing: 投稿
web: ウェブ web: ウェブ
relationships:
activity: 活動
dormant: 非アクティブ
last_active: 最後の活動
most_recent: 新着
moved: 引っ越し済み
mutual: 相互
primary: 標準
relationship: 関係性
remove_selected_domains: 選択したドメインのフォロワーを全て解除
remove_selected_followers: 選択したフォロワーを解除
remove_selected_follows: 選択したユーザーをフォロー解除
status: 状態
remote_follow: remote_follow:
acct: あなたの ユーザー名@ドメイン を入力してください acct: あなたの ユーザー名@ドメイン を入力してください
missing_resource: リダイレクト先が見つかりませんでした missing_resource: リダイレクト先が見つかりませんでした
@ -824,10 +863,12 @@ ja:
export: データのエクスポート export: データのエクスポート
featured_tags: 注目のハッシュタグ featured_tags: 注目のハッシュタグ
flavours: フレーバー flavours: フレーバー
identity_proofs: Identity proofs
import: データのインポート import: データのインポート
migrate: アカウントの引っ越し migrate: アカウントの引っ越し
notifications: 通知 notifications: 通知
preferences: ユーザー設定 preferences: ユーザー設定
relationships: フォロー・フォロワー
settings: 設定 settings: 設定
two_factor_authentication: 二段階認証 two_factor_authentication: 二段階認証
your_apps: アプリ your_apps: アプリ
@ -852,12 +893,12 @@ ja:
limit: 固定できるトゥート数の上限に達しました limit: 固定できるトゥート数の上限に達しました
ownership: 他人のトゥートを固定することはできません ownership: 他人のトゥートを固定することはできません
private: 非公開のトゥートを固定することはできません private: 非公開のトゥートを固定することはできません
reblog: ブーストされたトゥートを固定することはできません reblog: ブーストを固定することはできません
poll: poll:
total_votes: total_votes:
one: "%{count} vote" one: "%{count}"
other: "%{count} votes" other: "%{count}"
vote: Vote vote: 投票
show_more: もっと見る show_more: もっと見る
sign_in_to_participate: ログインして会話に参加 sign_in_to_participate: ログインして会話に参加
title: '%{name}: "%{quote}"' title: '%{name}: "%{quote}"'

View File

@ -236,11 +236,7 @@ module Mastodon
end end
if [404, 410].include?(code) if [404, 410].include?(code)
unless options[:dry_run] SuspendAccountService.new.call(account, destroy: true) unless options[:dry_run]
SuspendAccountService.new.call(account)
account.destroy
end
culled += 1 culled += 1
say('+', :green, false) say('+', :green, false)
else else

View File

@ -7,15 +7,40 @@ describe Api::V1::Accounts::FollowerAccountsController do
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
before do before do
Fabricate(:follow, target_account: user.account)
allow(controller).to receive(:doorkeeper_token) { token } allow(controller).to receive(:doorkeeper_token) { token }
end end
describe 'GET #index' do describe 'GET #index' do
let(:simon) { Fabricate(:account, username: 'simon') }
let(:lewis) { Fabricate(:account, username: 'lewis') }
before do
simon.follow!(lewis)
end
it 'returns http success' do it 'returns http success' do
get :index, params: { account_id: user.account.id, limit: 1 } get :index, params: { account_id: lewis.id, limit: 1 }
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
it 'returns JSON with correct data' do
get :index, params: { account_id: lewis.id, limit: 1 }
json = body_as_json
expect(json).to be_a Enumerable
expect(json.first[:username]).to eq 'simon'
end
it 'does not return accounts blocking you' do
simon.block!(user.account)
get :index, params: { account_id: lewis.id, limit: 1 }
json = body_as_json
expect(json).to be_a Enumerable
expect(json.size).to eq 0
end
end end
end end

View File

@ -7,15 +7,40 @@ describe Api::V1::Accounts::FollowingAccountsController do
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') } let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
before do before do
Fabricate(:follow, account: user.account)
allow(controller).to receive(:doorkeeper_token) { token } allow(controller).to receive(:doorkeeper_token) { token }
end end
describe 'GET #index' do describe 'GET #index' do
let(:simon) { Fabricate(:account, username: 'simon') }
let(:lewis) { Fabricate(:account, username: 'lewis') }
before do
lewis.follow!(simon)
end
it 'returns http success' do it 'returns http success' do
get :index, params: { account_id: user.account.id, limit: 1 } get :index, params: { account_id: lewis.id, limit: 1 }
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
it 'returns JSON with correct data' do
get :index, params: { account_id: lewis.id, limit: 1 }
json = body_as_json
expect(json).to be_a Enumerable
expect(json.first[:username]).to eq 'simon'
end
it 'does not return accounts blocking you' do
simon.block!(user.account)
get :index, params: { account_id: lewis.id, limit: 1 }
json = body_as_json
expect(json).to be_a Enumerable
expect(json.size).to eq 0
end
end end
end end

View File

@ -0,0 +1,6 @@
This "Utah teapot" photograph is licensed under the Creative Commons
Attribution-Share Alike 3.0 Unported license:
https://creativecommons.org/licenses/by-sa/3.0/deed.en
Original source of work:
https://commons.wikimedia.org/wiki/File:Utah_teapot_simple_2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

View File

@ -1,2 +1,3 @@
Fabricator(:site_upload) do Fabricator(:site_upload) do
file { File.open(File.join(Rails.root, 'spec', 'fabricators', 'assets', 'utah_teapot.png')) }
end end

View File

@ -156,5 +156,22 @@ describe AccountSearchService, type: :service do
expect(results).to eq [] expect(results).to eq []
end end
end end
describe 'should not include accounts blocking the requester' do
let!(:blocked) { Fabricate(:account) }
let!(:blocker) { Fabricate(:account, username: 'exact') }
before do
blocker.block!(blocked)
end
it 'returns the fuzzy match first, and does not return suspended exacts' do
partial = Fabricate(:account, username: 'exactness')
results = subject.call('exact', blocked, limit: 10)
expect(results.size).to eq 1
expect(results).to eq [partial]
end
end
end end
end end

View File

@ -3,6 +3,8 @@
require 'rails_helper' require 'rails_helper'
describe SearchService, type: :service do describe SearchService, type: :service do
let(:current_account) { Fabricate(:user).account }
subject { described_class.new } subject { described_class.new }
describe '#call' do describe '#call' do
@ -10,7 +12,7 @@ describe SearchService, type: :service do
it 'returns empty results without searching' do it 'returns empty results without searching' do
allow(AccountSearchService).to receive(:new) allow(AccountSearchService).to receive(:new)
allow(Tag).to receive(:search_for) allow(Tag).to receive(:search_for)
results = subject.call('', nil, 10) results = subject.call('', current_account, 10)
expect(results).to eq(empty_results) expect(results).to eq(empty_results)
expect(AccountSearchService).not_to have_received(:new) expect(AccountSearchService).not_to have_received(:new)
@ -27,33 +29,33 @@ describe SearchService, type: :service do
it 'returns the empty results' do it 'returns the empty results' do
service = double(call: nil) service = double(call: nil)
allow(ResolveURLService).to receive(:new).and_return(service) allow(ResolveURLService).to receive(:new).and_return(service)
results = subject.call(@query, nil, 10) results = subject.call(@query, current_account, 10)
expect(service).to have_received(:call).with(@query, on_behalf_of: nil) expect(service).to have_received(:call).with(@query, on_behalf_of: current_account)
expect(results).to eq empty_results expect(results).to eq empty_results
end end
end end
context 'that finds an account' do context 'that finds an account' do
it 'includes the account in the results' do it 'includes the account in the results' do
account = Account.new account = Fabricate(:account)
service = double(call: account) service = double(call: account)
allow(ResolveURLService).to receive(:new).and_return(service) allow(ResolveURLService).to receive(:new).and_return(service)
results = subject.call(@query, nil, 10) results = subject.call(@query, current_account, 10)
expect(service).to have_received(:call).with(@query, on_behalf_of: nil) expect(service).to have_received(:call).with(@query, on_behalf_of: current_account)
expect(results).to eq empty_results.merge(accounts: [account]) expect(results).to eq empty_results.merge(accounts: [account])
end end
end end
context 'that finds a status' do context 'that finds a status' do
it 'includes the status in the results' do it 'includes the status in the results' do
status = Status.new status = Fabricate(:status)
service = double(call: status) service = double(call: status)
allow(ResolveURLService).to receive(:new).and_return(service) allow(ResolveURLService).to receive(:new).and_return(service)
results = subject.call(@query, nil, 10) results = subject.call(@query, current_account, 10)
expect(service).to have_received(:call).with(@query, on_behalf_of: nil) expect(service).to have_received(:call).with(@query, on_behalf_of: current_account)
expect(results).to eq empty_results.merge(statuses: [status]) expect(results).to eq empty_results.merge(statuses: [status])
end end
end end
@ -63,12 +65,12 @@ describe SearchService, type: :service do
context 'that matches an account' do context 'that matches an account' do
it 'includes the account in the results' do it 'includes the account in the results' do
query = 'username' query = 'username'
account = Account.new account = Fabricate(:account)
service = double(call: [account]) service = double(call: [account])
allow(AccountSearchService).to receive(:new).and_return(service) allow(AccountSearchService).to receive(:new).and_return(service)
results = subject.call(query, nil, 10) results = subject.call(query, current_account, 10)
expect(service).to have_received(:call).with(query, nil, limit: 10, offset: 0, resolve: false) expect(service).to have_received(:call).with(query, current_account, limit: 10, offset: 0, resolve: false)
expect(results).to eq empty_results.merge(accounts: [account]) expect(results).to eq empty_results.merge(accounts: [account])
end end
end end
@ -79,7 +81,7 @@ describe SearchService, type: :service do
tag = Tag.new tag = Tag.new
allow(Tag).to receive(:search_for).with('tag', 10, 0).and_return([tag]) allow(Tag).to receive(:search_for).with('tag', 10, 0).and_return([tag])
results = subject.call(query, nil, 10) results = subject.call(query, current_account, 10)
expect(Tag).to have_received(:search_for).with('tag', 10, 0) expect(Tag).to have_received(:search_for).with('tag', 10, 0)
expect(results).to eq empty_results.merge(hashtags: [tag]) expect(results).to eq empty_results.merge(hashtags: [tag])
end end
@ -87,7 +89,7 @@ describe SearchService, type: :service do
query = '@username' query = '@username'
allow(Tag).to receive(:search_for) allow(Tag).to receive(:search_for)
results = subject.call(query, nil, 10) results = subject.call(query, current_account, 10)
expect(Tag).not_to have_received(:search_for) expect(Tag).not_to have_received(:search_for)
expect(results).to eq empty_results expect(results).to eq empty_results
end end