diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index 58e176364..c4cf4a77b 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -59,14 +59,20 @@ class AccountsController < ApplicationController private def show_pinned_statuses? - [replies_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none? + [reblogs_requested?, replies_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none? end def filtered_statuses - default_statuses.tap do |statuses| - statuses.merge!(hashtag_scope) if tag_requested? - statuses.merge!(only_media_scope) if media_requested? - statuses.merge!(no_replies_scope) unless @account.replies && replies_requested? + if reblogs_requested? + default_statuses.reblogs + elsif replies_requested? + @account.replies ? default_statuses : default_statuses.without_replies + elsif media_requested? + default_statuses.where(id: account_media_status_ids) + elsif tag_requested? + default_statuses.hashtag_scope + else + default_statuses.without_replies.without_reblogs end end @@ -74,18 +80,6 @@ class AccountsController < ApplicationController @account.statuses.not_local_only.where(visibility: [:public, :unlisted]) end - def only_media_scope - Status.where(id: account_media_status_ids) - end - - def account_media_status_ids - @account.media_attachments.attached.reorder(nil).select(:status_id).distinct - end - - def no_replies_scope - Status.without_replies - end - def hashtag_scope tag = Tag.find_normalized(params[:tag]) @@ -115,6 +109,8 @@ class AccountsController < ApplicationController short_account_media_url(@account, max_id: max_id, min_id: min_id) elsif replies_requested? short_account_with_replies_url(@account, max_id: max_id, min_id: min_id) + elsif reblogs_requested? + short_account_reblogs_url(@account, max_id: max_id, min_id: min_id) else short_account_url(@account, max_id: max_id, min_id: min_id) end @@ -128,6 +124,10 @@ class AccountsController < ApplicationController request.path.ends_with?('/with_replies') end + def reblogs_requested? + request.path.ends_with?('/reblogs') + end + def tag_requested? request.path.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize) end diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index 8cd8f8e79..8a216ce0d 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -28,14 +28,14 @@ class Api::V1::Accounts::StatusesController < Api::BaseController def account_statuses statuses = truthy_param?(:pinned) ? pinned_scope : permitted_account_statuses - statuses = statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id)) - statuses.merge!(only_media_scope) if truthy_param?(:only_media) - statuses.merge!(no_replies_scope) if truthy_param?(:exclude_replies) - statuses.merge!(no_reblogs_scope) if truthy_param?(:exclude_reblogs) - statuses.merge!(hashtag_scope) if params[:tagged].present? + statuses = statuses.without_replies if !@account.replies || truthy_param?(:exclude_replies) + statuses = statuses.without_reblogs if truthy_param?(:exclude_reblogs) + statuses = statuses.reblogs if truthy_param?(:reblogs) && !truthy_param?(:exclude_reblogs) + statuses = statuses.where(id: account_media_status_ids) if truthy_param?(:only_media) + statuses = statuses.hashtag_scope if params[:tagged].present? - statuses + statuses.paginate_by_id(limit_param(DEFAULT_STATUSES_LIMIT), params_slice(:max_id, :since_id, :min_id)) end def permitted_account_statuses diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js index cca571583..07d50d163 100644 --- a/app/javascript/flavours/glitch/actions/timelines.js +++ b/app/javascript/flavours/glitch/actions/timelines.js @@ -95,7 +95,7 @@ export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => ex export const expandPublicTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`public${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { max_id: maxId, only_media: !!onlyMedia }, done); export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done); export const expandDirectTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('direct', '/api/v1/timelines/direct', { max_id: maxId }, done); -export const expandAccountTimeline = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId }); +export const expandAccountTimeline = (accountId, { maxId, withReplies, reblogs } = {}) => expandTimeline(`account:${accountId}${reblogs ? ':reblogs' : withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, reblogs: reblogs, max_id: maxId }); export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true }); export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 }); export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done); diff --git a/app/javascript/flavours/glitch/features/account_timeline/components/header.js b/app/javascript/flavours/glitch/features/account_timeline/components/header.js index 0faa8a424..299a8c9e5 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/components/header.js +++ b/app/javascript/flavours/glitch/features/account_timeline/components/header.js @@ -120,6 +120,7 @@ export default class Header extends ImmutablePureComponent {
+
)} diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js index 93d8fc9ec..2e3bd5ce5 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/index.js +++ b/app/javascript/flavours/glitch/features/account_timeline/index.js @@ -15,13 +15,13 @@ import { FormattedMessage } from 'react-intl'; import { fetchAccountIdentityProofs } from '../../actions/identity_proofs'; import MissingIndicator from 'flavours/glitch/components/missing_indicator'; -const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => { - const path = withReplies ? `${accountId}:with_replies` : accountId; +const mapStateToProps = (state, { params: { accountId }, withReplies = false, reblogs = false }) => { + const path = reblogs ? `${accountId}:reblogs` : withReplies ? `${accountId}:with_replies` : accountId; return { isAccount: !!state.getIn(['accounts', accountId]), statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()), - featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()), + featuredStatusIds: (withReplies || reblogs) ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()), isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']), hasMore: state.getIn(['timelines', `account:${path}`, 'hasMore']), }; @@ -38,28 +38,29 @@ export default class AccountTimeline extends ImmutablePureComponent { isLoading: PropTypes.bool, hasMore: PropTypes.bool, withReplies: PropTypes.bool, + reblogs: PropTypes.bool, isAccount: PropTypes.bool, }; componentWillMount () { - const { params: { accountId }, withReplies } = this.props; + const { params: { accountId }, withReplies, reblogs } = this.props; this.props.dispatch(fetchAccount(accountId)); this.props.dispatch(fetchAccountIdentityProofs(accountId)); - if (!withReplies) { + if (!withReplies && !reblogs) { this.props.dispatch(expandAccountFeaturedTimeline(accountId)); } - this.props.dispatch(expandAccountTimeline(accountId, { withReplies })); + this.props.dispatch(expandAccountTimeline(accountId, { withReplies: withReplies, reblogs: reblogs })); } componentWillReceiveProps (nextProps) { - 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 || nextProps.reblogs !== this.props.reblogs) { this.props.dispatch(fetchAccount(nextProps.params.accountId)); this.props.dispatch(fetchAccountIdentityProofs(nextProps.params.accountId)); - if (!nextProps.withReplies) { + if (!nextProps.withReplies && !nextProps.reblogs) { 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, reblogs: nextProps.params.reblogs })); } } @@ -68,7 +69,7 @@ export default class AccountTimeline extends ImmutablePureComponent { } handleLoadMore = maxId => { - this.props.dispatch(expandAccountTimeline(this.props.params.accountId, { maxId, withReplies: this.props.withReplies })); + this.props.dispatch(expandAccountTimeline(this.props.params.accountId, { maxId, withReplies: this.props.withReplies, reblogs: this.props.reblogs })); } setRef = c => { diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js index 13c71337a..1d8d906b0 100644 --- a/app/javascript/flavours/glitch/features/ui/index.js +++ b/app/javascript/flavours/glitch/features/ui/index.js @@ -500,6 +500,7 @@ export default class UI extends React.Component { + diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index a21f0049f..9417a745d 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -24,6 +24,7 @@ "account.mute_notifications": "Mute notifications from @{name}", "account.muted": "Muted", "account.posts": "Roars", + "account.reblogs": "Repeats", "account.posts_with_replies": "Roars & growls", "account.report": "Report @{name}", "account.requested": "Awaiting approval. Click to cancel join request", diff --git a/app/views/accounts/show.html.haml b/app/views/accounts/show.html.haml index fc9eb04e6..aa7903304 100644 --- a/app/views/accounts/show.html.haml +++ b/app/views/accounts/show.html.haml @@ -32,6 +32,7 @@ = active_link_to t('accounts.posts_tab_heading'), short_account_url(@account) - if @account.replies = active_link_to t('accounts.posts_with_replies'), short_account_with_replies_url(@account) + = active_link_to t('accounts.reblogs_tab_heading'), short_account_reblogs_url(@account) = active_link_to t('accounts.media'), short_account_media_url(@account) - if user_signed_in? && @account.blocking?(current_account) diff --git a/config/locales/en.yml b/config/locales/en.yml index 72c9379c3..e7d596885 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -61,7 +61,11 @@ en: posts: one: Roar other: Roars + reblogs: + one: Repeat + other: Repeats posts_tab_heading: Roars + reblogs_tab_heading: Repeats posts_with_replies: Roars & growls reserved_username: The creaturename is reserved roles: diff --git a/config/routes.rb b/config/routes.rb index 5614a7cdc..83ef69f15 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -77,6 +77,7 @@ Rails.application.routes.draw do get '/@:username', to: 'accounts#show', as: :short_account get '/@:username/with_replies', to: 'accounts#show', as: :short_account_with_replies get '/@:username/media', to: 'accounts#show', as: :short_account_media + get '/@:username/reblogs', to: 'accounts#show', as: :short_account_reblogs get '/@:username/tagged/:tag', to: 'accounts#show', as: :short_account_tag get '/@:account_username/:id', to: 'statuses#show', as: :short_account_status get '/@:account_username/:id/embed', to: 'statuses#embed', as: :embed_short_account_status