From 17e0ba29e442c8c57d01c00266e0e0d61d35b81d Mon Sep 17 00:00:00 2001 From: noellabo Date: Sat, 6 Jun 2020 12:59:45 +0900 Subject: [PATCH] Fix to hide mute and blocked quotes --- .../api/v1/accounts/statuses_controller.rb | 5 +- .../api/v1/bookmarks_controller.rb | 5 +- .../api/v1/favourites_controller.rb | 5 +- app/controllers/api/v1/statuses_controller.rb | 3 +- .../api/v1/timelines/home_controller.rb | 4 +- .../api/v1/timelines/list_controller.rb | 5 +- .../api/v1/timelines/public_controller.rb | 6 +- .../api/v1/timelines/tag_controller.rb | 5 +- app/javascript/mastodon/actions/bookmarks.js | 4 + app/javascript/mastodon/actions/favourites.js | 4 + .../mastodon/actions/importer/index.js | 4 + .../mastodon/actions/notifications.js | 2 +- app/javascript/mastodon/actions/timelines.js | 4 + app/javascript/mastodon/components/status.js | 56 ++++++++++-- .../status/components/detailed_status.js | 65 +++++++++++-- .../mastodon/locales/defaultMessages.json | 4 + app/javascript/mastodon/locales/en.json | 4 +- app/javascript/mastodon/locales/ja.json | 5 +- app/javascript/mastodon/selectors/index.js | 30 +++++- app/javascript/mastodon/utils/uniq.js | 3 + .../styles/mastodon/components.scss | 91 ++++--------------- app/serializers/rest/status_serializer.rb | 10 ++ 22 files changed, 219 insertions(+), 105 deletions(-) create mode 100644 app/javascript/mastodon/utils/uniq.js diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index 92ccb8061..f301666db 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -7,8 +7,9 @@ class Api::V1::Accounts::StatusesController < Api::BaseController after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) } def index - @statuses = load_statuses - render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + @statuses = load_statuses + accountIds = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq + render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id) end private diff --git a/app/controllers/api/v1/bookmarks_controller.rb b/app/controllers/api/v1/bookmarks_controller.rb index aa3fb88f0..f666a1d8d 100644 --- a/app/controllers/api/v1/bookmarks_controller.rb +++ b/app/controllers/api/v1/bookmarks_controller.rb @@ -6,8 +6,9 @@ class Api::V1::BookmarksController < Api::BaseController after_action :insert_pagination_headers def index - @statuses = load_statuses - render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + @statuses = load_statuses + accountIds = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq + render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id) end private diff --git a/app/controllers/api/v1/favourites_controller.rb b/app/controllers/api/v1/favourites_controller.rb index 21836bc17..651a057d5 100644 --- a/app/controllers/api/v1/favourites_controller.rb +++ b/app/controllers/api/v1/favourites_controller.rb @@ -6,8 +6,9 @@ class Api::V1::FavouritesController < Api::BaseController after_action :insert_pagination_headers def index - @statuses = load_statuses - render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + @statuses = load_statuses + accountIds = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq + render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id) end private diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 1e23cf300..d8f4db42f 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -30,8 +30,9 @@ class Api::V1::StatusesController < Api::BaseController @context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants) statuses = [@status] + @context.ancestors + @context.descendants + accountIds = statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq - render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id) + render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id), account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id) end def create diff --git a/app/controllers/api/v1/timelines/home_controller.rb b/app/controllers/api/v1/timelines/home_controller.rb index ae6dbcb8b..c07e1a820 100644 --- a/app/controllers/api/v1/timelines/home_controller.rb +++ b/app/controllers/api/v1/timelines/home_controller.rb @@ -6,11 +6,13 @@ class Api::V1::Timelines::HomeController < Api::BaseController after_action :insert_pagination_headers, unless: -> { @statuses.empty? } def show - @statuses = load_statuses + @statuses = load_statuses + accountIds = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), + account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id), status: account_home_feed.regenerating? ? 206 : 200 end diff --git a/app/controllers/api/v1/timelines/list_controller.rb b/app/controllers/api/v1/timelines/list_controller.rb index a15eae468..bc13ed5ec 100644 --- a/app/controllers/api/v1/timelines/list_controller.rb +++ b/app/controllers/api/v1/timelines/list_controller.rb @@ -9,9 +9,12 @@ class Api::V1::Timelines::ListController < Api::BaseController after_action :insert_pagination_headers, unless: -> { @statuses.empty? } def show + accountIds = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq + render json: @statuses, each_serializer: REST::StatusSerializer, - relationships: StatusRelationshipsPresenter.new(@statuses, current_user.account_id) + relationships: StatusRelationshipsPresenter.new(@statuses, current_user.account_id), + account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id) end private diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb index d253b744f..1a778012b 100644 --- a/app/controllers/api/v1/timelines/public_controller.rb +++ b/app/controllers/api/v1/timelines/public_controller.rb @@ -5,8 +5,10 @@ class Api::V1::Timelines::PublicController < Api::BaseController after_action :insert_pagination_headers, unless: -> { @statuses.empty? } def show - @statuses = load_statuses - render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + @statuses = load_statuses + accountIds = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq + + render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id) end private diff --git a/app/controllers/api/v1/timelines/tag_controller.rb b/app/controllers/api/v1/timelines/tag_controller.rb index 64a1db58d..c7afd4580 100644 --- a/app/controllers/api/v1/timelines/tag_controller.rb +++ b/app/controllers/api/v1/timelines/tag_controller.rb @@ -5,8 +5,9 @@ class Api::V1::Timelines::TagController < Api::BaseController after_action :insert_pagination_headers, unless: -> { @statuses.empty? } def show - @statuses = load_statuses - render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + @statuses = load_statuses + accountIds = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq + render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id) end private diff --git a/app/javascript/mastodon/actions/bookmarks.js b/app/javascript/mastodon/actions/bookmarks.js index 544ed2ff2..b3e0a2ab4 100644 --- a/app/javascript/mastodon/actions/bookmarks.js +++ b/app/javascript/mastodon/actions/bookmarks.js @@ -1,5 +1,7 @@ +import { fetchRelationships } from './accounts'; import api, { getLinks } from '../api'; import { importFetchedStatuses } from './importer'; +import { uniq } from '../utils/uniq'; export const BOOKMARKED_STATUSES_FETCH_REQUEST = 'BOOKMARKED_STATUSES_FETCH_REQUEST'; export const BOOKMARKED_STATUSES_FETCH_SUCCESS = 'BOOKMARKED_STATUSES_FETCH_SUCCESS'; @@ -20,6 +22,7 @@ export function fetchBookmarkedStatuses() { api(getState).get('/api/v1/bookmarks').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); + dispatch(fetchRelationships(uniq(response.data.map(item => item.reblog ? item.reblog.account.id : item.account.id)))); dispatch(fetchBookmarkedStatusesSuccess(response.data, next ? next.uri : null)); }).catch(error => { dispatch(fetchBookmarkedStatusesFail(error)); @@ -61,6 +64,7 @@ export function expandBookmarkedStatuses() { api(getState).get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); + dispatch(fetchRelationships(uniq(response.data.map(item => item.reblog ? item.reblog.account.id : item.account.id)))); dispatch(expandBookmarkedStatusesSuccess(response.data, next ? next.uri : null)); }).catch(error => { dispatch(expandBookmarkedStatusesFail(error)); diff --git a/app/javascript/mastodon/actions/favourites.js b/app/javascript/mastodon/actions/favourites.js index 9448b1efe..9b28ac4c4 100644 --- a/app/javascript/mastodon/actions/favourites.js +++ b/app/javascript/mastodon/actions/favourites.js @@ -1,5 +1,7 @@ +import { fetchRelationships } from './accounts'; import api, { getLinks } from '../api'; import { importFetchedStatuses } from './importer'; +import { uniq } from '../utils/uniq'; export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST'; export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS'; @@ -20,6 +22,7 @@ export function fetchFavouritedStatuses() { api(getState).get('/api/v1/favourites').then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); + dispatch(fetchRelationships(uniq(response.data.map(item => item.reblog ? item.reblog.account.id : item.account.id)))); dispatch(fetchFavouritedStatusesSuccess(response.data, next ? next.uri : null)); }).catch(error => { dispatch(fetchFavouritedStatusesFail(error)); @@ -64,6 +67,7 @@ export function expandFavouritedStatuses() { api(getState).get(url).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); + dispatch(fetchRelationships(uniq(response.data.map(item => item.reblog ? item.reblog.account.id : item.account.id)))); dispatch(expandFavouritedStatusesSuccess(response.data, next ? next.uri : null)); }).catch(error => { dispatch(expandFavouritedStatusesFail(error)); diff --git a/app/javascript/mastodon/actions/importer/index.js b/app/javascript/mastodon/actions/importer/index.js index f4372fb31..35032fc70 100644 --- a/app/javascript/mastodon/actions/importer/index.js +++ b/app/javascript/mastodon/actions/importer/index.js @@ -70,6 +70,10 @@ export function importFetchedStatuses(statuses) { processStatus(status.reblog); } + if (status.quote && status.quote.id) { + processStatus(status.quote); + } + if (status.poll && status.poll.id) { pushUnique(polls, normalizePoll(status.poll)); } diff --git a/app/javascript/mastodon/actions/notifications.js b/app/javascript/mastodon/actions/notifications.js index 3464ac995..22f1e0cbb 100644 --- a/app/javascript/mastodon/actions/notifications.js +++ b/app/javascript/mastodon/actions/notifications.js @@ -46,7 +46,7 @@ defineMessages({ }); const fetchRelatedRelationships = (dispatch, notifications) => { - const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id); + const accountIds = notifications.map(item => item.account.id); if (accountIds.length > 0) { dispatch(fetchRelationships(accountIds)); diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index 31ae09e4a..d82bf1d0d 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -1,9 +1,11 @@ +import { fetchRelationships } from './accounts'; import { importFetchedStatus, importFetchedStatuses } from './importer'; import { submitMarkers } from './markers'; import api, { getLinks } from 'mastodon/api'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import compareId from 'mastodon/compare_id'; import { usePendingItems as preferPendingItems } from 'mastodon/initial_state'; +import { uniq } from '../utils/uniq'; export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'; export const TIMELINE_DELETE = 'TIMELINE_DELETE'; @@ -39,6 +41,7 @@ export function updateTimeline(timeline, status, accept) { } dispatch(importFetchedStatus(status)); + dispatch(fetchRelationships([status.reblog ? status.reblog.account.id : status.account.id, status.quote ? status.quote.account.id : null].filter(function(e){return e}))); dispatch({ type: TIMELINE_UPDATE, @@ -111,6 +114,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) { api(getState).get(path, { params }).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(importFetchedStatuses(response.data)); + dispatch(fetchRelationships(uniq(response.data.map(item => item.reblog ? item.reblog.account.id : item.account.id).concat(response.data.map(item => item.quote ? item.quote.account.id : null)).filter(function(e){return e})))); dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems)); if (timelineId === 'home') { diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js index 39ca49693..f4d80c00e 100644 --- a/app/javascript/mastodon/components/status.js +++ b/app/javascript/mastodon/components/status.js @@ -1,4 +1,5 @@ import React from 'react'; +import { connect } from 'react-redux'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import Avatar from './avatar'; @@ -23,6 +24,29 @@ import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_ // to use the progress bar to show download progress import Bundle from '../features/ui/components/bundle'; +const mapStateToProps = (state, props) => { + let status = props.status; + + if (status === null) { + return null; + } + + if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') { + status = status.get('reblog'); + } + + if (status.get('quote', null) === null) { + return { + quote_muted: status.get('quote_id', null) ? true : false, + }; + } + const id = status.getIn(['quote', 'account', 'id'], null); + + return { + quote_muted: id !== null && (state.getIn(['relationships', id, 'muting']) || state.getIn(['relationships', id, 'blocking']) || state.getIn(['relationships', id, 'blocked_by']) || state.getIn(['relationships', id, 'domain_blocking'])) || status.getIn(['quote', 'quote_muted']), + }; +}; + export const textForScreenReader = (intl, status, rebloggedByText = false) => { const displayName = status.getIn(['account', 'display_name']); @@ -59,7 +83,8 @@ const messages = defineMessages({ direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, }); -export default @injectIntl +export default @connect(mapStateToProps) +@injectIntl class Status extends ImmutablePureComponent { static contextTypes = { @@ -70,6 +95,7 @@ class Status extends ImmutablePureComponent { status: ImmutablePropTypes.map, account: ImmutablePropTypes.map, otherAccounts: ImmutablePropTypes.list, + quote_muted: PropTypes.bool, onClick: PropTypes.func, onReply: PropTypes.func, onFavourite: PropTypes.func, @@ -114,6 +140,7 @@ class Status extends ImmutablePureComponent { 'hidden', 'unread', 'pictureInPicture', + 'quote_muted', ]; state = { @@ -308,9 +335,9 @@ class Status extends ImmutablePureComponent { render () { let media = null; - let statusAvatar, prepend, rebloggedByText, unlistedQuoteText; + let statusAvatar, prepend, rebloggedByText; - const { intl, hidden, featured, otherAccounts, unread, showThread, scrollKey, pictureInPicture, contextType } = this.props; + const { intl, hidden, featured, otherAccounts, unread, showThread, scrollKey, pictureInPicture, contextType, quote_muted } = this.props; let { status, account, ...other } = this.props; @@ -569,12 +596,21 @@ class Status extends ImmutablePureComponent { } } - if (quote_status.get('visibility') === 'unlisted' && contextType !== 'home') { - unlistedQuoteText = intl.formatMessage({ id: 'status.unlisted_quote', defaultMessage: 'Unlisted quote' }); + if (quote_muted) { + quote = ( +
+
+ +
+
+ ); + } else if (quote_status.get('visibility') === 'unlisted' && contextType !== 'home') { quote = (
- +
); @@ -592,6 +628,14 @@ class Status extends ImmutablePureComponent { ); } + } else if (quote_muted) { + quote = ( +
+
+ +
+
+ ); } return ( diff --git a/app/javascript/mastodon/features/status/components/detailed_status.js b/app/javascript/mastodon/features/status/components/detailed_status.js index 242b4aee9..2344d8019 100644 --- a/app/javascript/mastodon/features/status/components/detailed_status.js +++ b/app/javascript/mastodon/features/status/components/detailed_status.js @@ -1,4 +1,5 @@ import React from 'react'; +import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import Avatar from '../../../components/avatar'; @@ -6,7 +7,7 @@ import DisplayName from '../../../components/display_name'; import StatusContent from '../../../components/status_content'; import MediaGallery from '../../../components/media_gallery'; import { Link } from 'react-router-dom'; -import { injectIntl, defineMessages, FormattedDate } from 'react-intl'; +import { injectIntl, defineMessages, FormattedDate, FormattedMessage } from 'react-intl'; import Card from './card'; import ImmutablePureComponent from 'react-immutable-pure-component'; import Video from '../../video'; @@ -24,7 +25,31 @@ const messages = defineMessages({ direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, }); -export default @injectIntl +const mapStateToProps = (state, props) => { + let status = props.status; + + if (status === null) { + return null; + } + + if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') { + status = status.get('reblog'); + } + + if (status.get('quote', null) === null) { + return { + quote_muted: status.get('quote_id', null) ? true : false, + }; + } + const id = status.getIn(['quote', 'account', 'id'], null); + + return { + quote_muted: id !== null && (state.getIn(['relationships', id, 'muting']) || state.getIn(['relationships', id, 'blocking']) || state.getIn(['relationships', id, 'blocked_by']) || state.getIn(['relationships', id, 'domain_blocking'])) || status.getIn(['quote', 'quote_muted']), + }; +}; + +export default @connect(mapStateToProps) +@injectIntl class DetailedStatus extends ImmutablePureComponent { static contextTypes = { @@ -33,6 +58,7 @@ class DetailedStatus extends ImmutablePureComponent { static propTypes = { status: ImmutablePropTypes.map, + quote_muted: PropTypes.bool, onOpenMedia: PropTypes.func.isRequired, onOpenVideo: PropTypes.func.isRequired, onToggleHidden: PropTypes.func.isRequired, @@ -120,6 +146,7 @@ class DetailedStatus extends ImmutablePureComponent { render () { const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status; + const quote_muted = this.props.quote_muted const outerStyle = { boxSizing: 'border-box' }; const { intl, compact, pictureInPicture } = this.props; @@ -194,15 +221,33 @@ class DetailedStatus extends ImmutablePureComponent { } } - quote = ( -
- -
- -
+ if (quote_muted) { + quote = ( +
+
+ +
+
+ ); + } else { + quote = ( +
+ +
+ +
- - {quote_media} + + {quote_media} +
+ ); + } + } else if (quote_muted) { + quote = ( +
+
+ +
); } diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 7cbb11dc6..321d7507d 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -457,6 +457,10 @@ "defaultMessage": "Unlisted quote", "id": "status.unlisted_quote" }, + { + "defaultMessage": "Muted quote", + "id": "status.muted_quote" + }, { "defaultMessage": "Favourite", "id": "status.favourite" diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index e4d97fc34..8e9eeff8f 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -401,11 +401,12 @@ "status.more": "More", "status.mute": "Mute @{name}", "status.mute_conversation": "Mute conversation", + "status.muted_quote": "Muted quote", "status.open": "Expand this post", "status.pin": "Pin on profile", "status.pinned": "Pinned post", - "status.read_more": "Read more", "status.quote": "Quote", + "status.read_more": "Read more", "status.reblog": "Boost", "status.reblog_private": "Boost with original visibility", "status.reblogged_by": "{name} boosted", @@ -424,6 +425,7 @@ "status.show_poll": "Show poll", "status.show_thread": "Show thread", "status.uncached_media_warning": "Not available", + "status.unlisted_quote": "Unlisted quote", "status.unmute_conversation": "Unmute conversation", "status.unpin": "Unpin from profile", "suggestions.dismiss": "Dismiss suggestion", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index 53597ac85..45ddd4ef5 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -401,12 +401,12 @@ "status.more": "もっと見る", "status.mute": "@{name}さんをミュート", "status.mute_conversation": "会話をミュート", + "status.muted_quote": "ミュートされた引用", "status.open": "詳細を表示", "status.pin": "プロフィールに固定表示", "status.pinned": "固定された投稿", - "status.read_more": "もっと見る", "status.quote": "引用", - "status.unlisted_quote": "未収載の引用", + "status.read_more": "もっと見る", "status.reblog": "ブースト", "status.reblog_private": "ブースト", "status.reblogged_by": "{name}さんがブースト", @@ -425,6 +425,7 @@ "status.show_poll": "アンケートを表示", "status.show_thread": "スレッドを表示", "status.uncached_media_warning": "利用できません", + "status.unlisted_quote": "未収載の引用", "status.unmute_conversation": "会話のミュートを解除", "status.unpin": "プロフィールへの固定を解除", "suggestions.dismiss": "隠す", diff --git a/app/javascript/mastodon/selectors/index.js b/app/javascript/mastodon/selectors/index.js index 1e19db65d..f84152199 100644 --- a/app/javascript/mastodon/selectors/index.js +++ b/app/javascript/mastodon/selectors/index.js @@ -88,22 +88,49 @@ export const makeGetStatus = () => { [ (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, 'quote_id'])]), (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, 'quote_id']), 'account'])]), + (state, { id }) => state.getIn(['relationships', state.getIn(['statuses', id, 'account'])]), + (state, { id }) => state.getIn(['relationships', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]), + (state, { id }) => state.getIn(['relationships', state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id']), 'account'])]), + (state, { id }) => state.getIn(['accounts', state.getIn(['accounts', state.getIn(['statuses', id, 'account']), 'moved'])]), + (state, { id }) => state.getIn(['accounts', state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account']), 'moved'])]), + (state, { id }) => state.getIn(['accounts', state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id']), 'account']), 'moved'])]), getFiltersRegex, ], - (statusBase, statusReblog, accountBase, accountReblog, filtersRegex) => { + (statusBase, statusReblog, statusQuote, accountBase, accountReblog, accountQuote, relationship, reblogRelationship, quoteRelationship, moved, reblogMoved, quoteMoved, filtersRegex) => { if (!statusBase) { return null; } + accountBase = accountBase.withMutations(map => { + map.set('relationship', relationship); + map.set('moved', moved); + }); + if (statusReblog) { + accountReblog = accountReblog.withMutations(map => { + map.set('relationship', reblogRelationship); + map.set('moved', reblogMoved); + }); statusReblog = statusReblog.set('account', accountReblog); } else { statusReblog = null; } + if (statusQuote) { + accountQuote = accountQuote.withMutations(map => { + map.set('relationship', quoteRelationship); + map.set('moved', quoteMoved); + }); + statusQuote = statusQuote.set('account', accountQuote); + } else { + statusQuote = null; + } + const dropRegex = (accountReblog || accountBase).get('id') !== me && filtersRegex[0]; if (dropRegex && dropRegex.test(statusBase.get('reblog') ? statusReblog.get('search_index') : statusBase.get('search_index'))) { return null; @@ -114,6 +141,7 @@ export const makeGetStatus = () => { return statusBase.withMutations(map => { map.set('reblog', statusReblog); + map.set('quote', statusQuote); map.set('account', accountBase); map.set('filtered', filtered); }); diff --git a/app/javascript/mastodon/utils/uniq.js b/app/javascript/mastodon/utils/uniq.js new file mode 100644 index 000000000..00f1804a1 --- /dev/null +++ b/app/javascript/mastodon/utils/uniq.js @@ -0,0 +1,3 @@ +export const uniq = array => { + return array.filter((x, i, self) => self.indexOf(x) === i) +}; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 8e965d080..cba90481d 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1036,6 +1036,12 @@ } } + & > .muted-quote { + color: $dark-text-color; + font-weight: 500; + font-size: 100%; + } + .status__avatar, .detailed-status__display-avatar { height: 18px; @@ -1531,77 +1537,6 @@ a .account__avatar { } } -.account__action-bar { - border-top: 1px solid lighten($ui-base-color, 8%); - border-bottom: 1px solid lighten($ui-base-color, 8%); - line-height: 36px; - overflow: hidden; - flex: 0 0 auto; - display: flex; -} - -.account__action-bar-dropdown { - padding: 10px; - - .icon-button { - vertical-align: middle; - } - - .dropdown--active { - .dropdown__content.dropdown__right { - left: 6px; - right: initial; - } - - &::after { - bottom: initial; - margin-left: 11px; - margin-top: -7px; - right: initial; - } - } -} - -.account__action-bar-links { - display: flex; - flex: 1 1 auto; - line-height: 18px; - text-align: center; -} - -.account__action-bar__tab { - text-decoration: none; - overflow: hidden; - flex: 0 1 100%; - border-right: 1px solid lighten($ui-base-color, 8%); - padding: 10px 0; - border-bottom: 4px solid transparent; - - &.active { - border-bottom: 4px solid $ui-highlight-color; - } - - & > span { - display: block; - text-transform: uppercase; - font-size: 11px; - color: $darker-text-color; - } - - strong { - display: block; - font-size: 15px; - font-weight: 500; - color: $primary-text-color; - - @each $lang in $cjk-langs { - &:lang(#{$lang}) { - font-weight: 700; - } - } - } -} - .account-authorize { padding: 14px 10px; @@ -1688,6 +1623,15 @@ a.account__display-name { margin-right: 10px; } +.account__action-bar { + position: absolute; + height: 24px; + width: 48px; + top: 60px; + left: 10px; + z-index: 1; +} + .status__avatar { height: 48px; left: 10px; @@ -2456,6 +2400,11 @@ a.account__display-name { margin-right: 15px; } } + + .account__action-bar { + top: 67px; + left: 15px; + } } } diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index 12c386600..f49b365bf 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -178,6 +178,16 @@ class REST::NestedQuoteSerializer < REST::StatusSerializer attribute :quote do nil end + attribute :quote_muted, if: :current_user? + + def quote_muted + if instance_options && instance_options[:account_relationships] + instance_options[:account_relationships].muting[object.account_id] ? true : false || instance_options[:account_relationships].blocking[object.account_id] || instance_options[:account_relationships].blocked_by[object.account_id] || instance_options[:account_relationships].domain_blocking[object.account_id] || false + else + current_user.account.muting?(object.account) || object.account.blocking?(current_user.account) || current_user.account.blocking?(object.account) || current_user.account.domain_blocking?(object.account.domain) + end + end + end class REST::StatusSerializer < ActiveModel::Serializer