Add REST compact mode for statuses

This commit is contained in:
noellabo 2022-08-27 19:57:05 +09:00
parent 91d6b018df
commit 73df565bde
28 changed files with 559 additions and 257 deletions

View file

@ -7,9 +7,15 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) } after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) }
def index def index
@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) if compact?
render json: CompactStatusesPresenter.new(statuses: @statuses), serializer: REST::CompactStatusesSerializer
else
account_ids = @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(account_ids, current_user&.account_id)
end
end end
private private
@ -42,6 +48,10 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
@account.permitted_statuses(current_account) @account.permitted_statuses(current_account)
end end
def compact?
truthy_param?(:compact)
end
def only_media_scope def only_media_scope
Status.include_expired.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id) Status.include_expired.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id)
end end
@ -71,7 +81,7 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
end end
def pagination_params(core_params) def pagination_params(core_params)
params.slice(:limit, :only_media, :exclude_replies).permit(:limit, :only_media, :exclude_replies).merge(core_params) params.slice(:limit, :only_media, :exclude_replies, :compact).permit(:limit, :only_media, :exclude_replies, :compact).merge(core_params)
end end
def insert_pagination_headers def insert_pagination_headers

View file

@ -6,9 +6,15 @@ class Api::V1::BookmarksController < Api::BaseController
after_action :insert_pagination_headers after_action :insert_pagination_headers
def index def index
@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) if compact?
render json: CompactStatusesPresenter.new(statuses: @statuses), serializer: REST::CompactStatusesSerializer
else
account_ids = @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(account_ids, current_user&.account_id)
end
end end
private private
@ -18,11 +24,11 @@ class Api::V1::BookmarksController < Api::BaseController
end end
def cached_bookmarks def cached_bookmarks
cache_collection(Status.include_expired.where(id: results.pluck(:status_id)), Status) cache_collection(results.map(&:status), Status)
end end
def results def results
@_results ||= account_bookmarks.joins('INNER JOIN statuses ON statuses.deleted_at IS NULL AND statuses.id = bookmarks.status_id').to_a_paginated_by_id( @_results ||= account_bookmarks.joins(:status).eager_load(:status).to_a_paginated_by_id(
limit_param(DEFAULT_STATUSES_LIMIT), limit_param(DEFAULT_STATUSES_LIMIT),
params_slice(:max_id, :since_id, :min_id) params_slice(:max_id, :since_id, :min_id)
) )
@ -32,6 +38,10 @@ class Api::V1::BookmarksController < Api::BaseController
current_account.bookmarks current_account.bookmarks
end end
def compact?
truthy_param?(:compact)
end
def insert_pagination_headers def insert_pagination_headers
set_pagination_headers(next_path, prev_path) set_pagination_headers(next_path, prev_path)
end end
@ -57,6 +67,6 @@ class Api::V1::BookmarksController < Api::BaseController
end end
def pagination_params(core_params) def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params) params.slice(:limit, :compact).permit(:limi, :compact).merge(core_params)
end end
end end

View file

@ -6,9 +6,15 @@ class Api::V1::EmojiReactionsController < Api::BaseController
after_action :insert_pagination_headers after_action :insert_pagination_headers
def index def index
@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) if compact?
render json: CompactStatusesPresenter.new(statuses: @statuses), serializer: REST::CompactStatusesSerializer
else
account_ids = @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(account_ids, current_user&.account_id)
end
end end
private private
@ -18,11 +24,11 @@ class Api::V1::EmojiReactionsController < Api::BaseController
end end
def cached_emoji_reactions def cached_emoji_reactions
cache_collection(Status.include_expired.where(id: results.pluck(:status_id)), Status) cache_collection(results.map(&:status), Status)
end end
def results def results
@_results ||= filtered_emoji_reactions.joins('INNER JOIN statuses ON statuses.deleted_at IS NULL AND statuses.id = emoji_reactions.status_id').to_a_paginated_by_id( @_results ||= filtered_emoji_reactions.joins(:status).eager_load(:status).to_a_paginated_by_id(
limit_param(DEFAULT_STATUSES_LIMIT), limit_param(DEFAULT_STATUSES_LIMIT),
params_slice(:max_id, :since_id, :min_id) params_slice(:max_id, :since_id, :min_id)
) )
@ -38,6 +44,10 @@ class Api::V1::EmojiReactionsController < Api::BaseController
current_account.emoji_reactions current_account.emoji_reactions
end end
def compact?
truthy_param?(:compact)
end
def insert_pagination_headers def insert_pagination_headers
set_pagination_headers(next_path, prev_path) set_pagination_headers(next_path, prev_path)
end end
@ -70,7 +80,7 @@ class Api::V1::EmojiReactionsController < Api::BaseController
emoji_reactions = EmojiReaction.none emoji_reactions = EmojiReaction.none
emoji_reactions_params[:emojis].each do |emoji| emoji_reactions_params[:emojis].each do |emoji|
shortcode, domain = emoji.split("@") shortcode, domain = emoji.split('@')
custom_emoji = CustomEmoji.find_by(shortcode: shortcode, domain: domain) custom_emoji = CustomEmoji.find_by(shortcode: shortcode, domain: domain)
emoji_reactions = emoji_reactions.or(EmojiReaction.where(name: shortcode, custom_emoji: custom_emoji)) emoji_reactions = emoji_reactions.or(EmojiReaction.where(name: shortcode, custom_emoji: custom_emoji))
@ -80,7 +90,7 @@ class Api::V1::EmojiReactionsController < Api::BaseController
end end
def pagination_params(core_params) def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params) params.slice(:limit, :compact).permit(:limit, :compact).merge(core_params)
end end
def emoji_reactions_params def emoji_reactions_params

View file

@ -6,9 +6,15 @@ class Api::V1::FavouritesController < Api::BaseController
after_action :insert_pagination_headers after_action :insert_pagination_headers
def index def index
@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) if compact?
render json: CompactStatusesPresenter.new(statuses: @statuses), serializer: REST::CompactStatusesSerializer
else
account_ids = @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(account_ids, current_user&.account_id)
end
end end
private private
@ -18,11 +24,11 @@ class Api::V1::FavouritesController < Api::BaseController
end end
def cached_favourites def cached_favourites
cache_collection(Status.include_expired.where(id: results.pluck(:status_id)), Status) cache_collection(results.map(&:status), Status)
end end
def results def results
@_results ||= account_favourites.joins('INNER JOIN statuses ON statuses.deleted_at IS NULL AND statuses.id = favourites.status_id').to_a_paginated_by_id( @_results ||= account_favourites.joins(:status).eager_load(:status).to_a_paginated_by_id(
limit_param(DEFAULT_STATUSES_LIMIT), limit_param(DEFAULT_STATUSES_LIMIT),
params_slice(:max_id, :since_id, :min_id) params_slice(:max_id, :since_id, :min_id)
) )
@ -32,6 +38,10 @@ class Api::V1::FavouritesController < Api::BaseController
current_account.favourites current_account.favourites
end end
def compact?
truthy_param?(:compact)
end
def insert_pagination_headers def insert_pagination_headers
set_pagination_headers(next_path, prev_path) set_pagination_headers(next_path, prev_path)
end end
@ -61,6 +71,6 @@ class Api::V1::FavouritesController < Api::BaseController
end end
def pagination_params(core_params) def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params) params.slice(:limit, :compact).permit(:limit, :compact).merge(core_params)
end end
end end

View file

@ -8,9 +8,15 @@ class Api::V1::Statuses::ReferredByStatusesController < Api::BaseController
after_action :insert_pagination_headers after_action :insert_pagination_headers
def index def index
@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) if compact?
render json: CompactStatusesPresenter.new(statuses: @statuses), serializer: REST::CompactStatusesSerializer
else
account_ids = @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(account_ids, current_user&.account_id)
end
end end
private private
@ -20,7 +26,7 @@ class Api::V1::Statuses::ReferredByStatusesController < Api::BaseController
end end
def cached_referred_by_statuses def cached_referred_by_statuses
cache_collection(Status.where(id: results.pluck(:id)), Status) cache_collection(results, Status)
end end
def results def results
@ -34,6 +40,10 @@ class Api::V1::Statuses::ReferredByStatusesController < Api::BaseController
@status.referred_by_statuses(current_user&.account) @status.referred_by_statuses(current_user&.account)
end end
def compact?
truthy_param?(:compact)
end
def insert_pagination_headers def insert_pagination_headers
set_pagination_headers(next_path, prev_path) set_pagination_headers(next_path, prev_path)
end end
@ -66,6 +76,6 @@ class Api::V1::Statuses::ReferredByStatusesController < Api::BaseController
end end
def pagination_params(core_params) def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params) params.slice(:limit, :compact).permit(:limit, :compact).merge(core_params)
end end
end end

View file

@ -6,7 +6,14 @@ class Api::V1::Timelines::GroupController < Api::BaseController
def show def show
@statuses = load_statuses @statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
if compact?
render json: CompactStatusesPresenter.new(statuses: @statuses), serializer: REST::CompactStatusesSerializer
else
account_ids = @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(account_ids, current_user&.account_id)
end
end end
private private
@ -48,6 +55,10 @@ class Api::V1::Timelines::GroupController < Api::BaseController
true & (params[:without_bot].nil? && current_user&.setting_hide_bot_on_public_timeline || truthy_param?(:without_bot)) true & (params[:without_bot].nil? && current_user&.setting_hide_bot_on_public_timeline || truthy_param?(:without_bot))
end end
def compact?
truthy_param?(:compact)
end
def insert_pagination_headers def insert_pagination_headers
set_pagination_headers(next_path, prev_path) set_pagination_headers(next_path, prev_path)
end end

View file

@ -6,14 +6,15 @@ class Api::V1::Timelines::HomeController < Api::BaseController
after_action :insert_pagination_headers, unless: -> { @statuses.empty? } after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
def show def show
@statuses = load_statuses @statuses = load_statuses
accountIds = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
render json: @statuses, if compact?
each_serializer: REST::StatusSerializer, render json: CompactStatusesPresenter.new(statuses: @statuses), serializer: REST::CompactStatusesSerializer, status: account_home_feed.regenerating? ? 206 : 200
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), else
account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id), account_ids = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
status: account_home_feed.regenerating? ? 206 : 200
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), account_relationships: AccountRelationshipsPresenter.new(account_ids, current_user&.account_id), status: account_home_feed.regenerating? ? 206 : 200
end
end end
private private
@ -40,6 +41,10 @@ class Api::V1::Timelines::HomeController < Api::BaseController
HomeFeed.new(current_account) HomeFeed.new(current_account)
end end
def compact?
truthy_param?(:compact)
end
def insert_pagination_headers def insert_pagination_headers
set_pagination_headers(next_path, prev_path) set_pagination_headers(next_path, prev_path)
end end

View file

@ -9,12 +9,13 @@ class Api::V1::Timelines::ListController < Api::BaseController
after_action :insert_pagination_headers, unless: -> { @statuses.empty? } after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
def show def show
accountIds = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq if compact?
render json: CompactStatusesPresenter.new(statuses: @statuses), serializer: REST::CompactStatusesSerializer
else
account_ids = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
render json: @statuses, render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), account_relationships: AccountRelationshipsPresenter.new(account_ids, current_user&.account_id)
each_serializer: REST::StatusSerializer, end
relationships: StatusRelationshipsPresenter.new(@statuses, current_user.account_id),
account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id)
end end
private private
@ -44,6 +45,10 @@ class Api::V1::Timelines::ListController < Api::BaseController
ListFeed.new(@list) ListFeed.new(@list)
end end
def compact?
truthy_param?(:compact)
end
def insert_pagination_headers def insert_pagination_headers
set_pagination_headers(next_path, prev_path) set_pagination_headers(next_path, prev_path)
end end

View file

@ -5,10 +5,15 @@ class Api::V1::Timelines::PublicController < Api::BaseController
after_action :insert_pagination_headers, unless: -> { @statuses.empty? } after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
def show 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) if compact?
render json: CompactStatusesPresenter.new(statuses: @statuses), serializer: REST::CompactStatusesSerializer
else
account_ids = @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(account_ids, current_user&.account_id)
end
end end
private private
@ -51,6 +56,10 @@ class Api::V1::Timelines::PublicController < Api::BaseController
true & (params[:without_bot].nil? && current_user&.setting_hide_bot_on_public_timeline || truthy_param?(:without_bot)) true & (params[:without_bot].nil? && current_user&.setting_hide_bot_on_public_timeline || truthy_param?(:without_bot))
end end
def compact?
truthy_param?(:compact)
end
def insert_pagination_headers def insert_pagination_headers
set_pagination_headers(next_path, prev_path) set_pagination_headers(next_path, prev_path)
end end

View file

@ -5,9 +5,15 @@ class Api::V1::Timelines::TagController < Api::BaseController
after_action :insert_pagination_headers, unless: -> { @statuses.empty? } after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
def show 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) if compact?
render json: CompactStatusesPresenter.new(statuses: @statuses), serializer: REST::CompactStatusesSerializer
else
account_ids = @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(account_ids, current_user&.account_id)
end
end end
private private
@ -52,6 +58,10 @@ class Api::V1::Timelines::TagController < Api::BaseController
true & (params[:without_bot].nil? && current_user&.setting_hide_bot_on_public_timeline || truthy_param?(:without_bot)) true & (params[:without_bot].nil? && current_user&.setting_hide_bot_on_public_timeline || truthy_param?(:without_bot))
end end
def compact?
truthy_param?(:compact)
end
def insert_pagination_headers def insert_pagination_headers
set_pagination_headers(next_path, prev_path) set_pagination_headers(next_path, prev_path)
end end

View file

@ -10,12 +10,18 @@ class Api::V2::SearchController < Api::BaseController
def index def index
@search = Search.new(search_results) @search = Search.new(search_results)
render json: @search, serializer: REST::SearchSerializer render json: @search, serializer: compact? ? REST::CompactSearchSerializer : REST::SearchSerializer
end end
private private
def search_results def search_results
search_service_results.tap do |results|
results[:statuses] = CompactStatusesPresenter.new(statuses: results[:statuses]) if compact?
end
end
def search_service_results
SearchService.new.call( SearchService.new.call(
params[:q], params[:q],
current_account, current_account,
@ -24,7 +30,11 @@ class Api::V2::SearchController < Api::BaseController
) )
end end
def compact?
truthy_param?(:compact)
end
def search_params def search_params
params.permit(:type, :offset, :min_id, :max_id, :account_id, :with_profiles, :searchability) params.permit(:type, :offset, :min_id, :max_id, :account_id, :with_profiles, :searchability, :compact)
end end
end end

View file

@ -138,14 +138,11 @@ export function fetchAccountsFromStatuses(statuses) {
return fetchAccounts( return fetchAccounts(
uniq( uniq(
statuses statuses
.flatMap(status => status.reblog ? status.reblog.emoji_reactions : status.emoji_reactions) .map(status => status.reblog ? status.reblog : status)
.concat( .flatMap(status => [status.emoji_reactions, status.quote?.emoji_reactions])
statuses .flatMap(emoji_reaction => emoji_reaction?.account_ids)
.flatMap(status => status.quote ? status.quote.emoji_reactions : null) .filter(e => !!e),
) ),
.flatMap(emoji_reaction => emoji_reaction?.account_ids)
.filter(e => !!e)
)
); );
}; };
@ -671,7 +668,7 @@ export function fetchSubscribing(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(fetchSubscribeRequest(id)); dispatch(fetchSubscribeRequest(id));
api(getState).get(`/api/v1/accounts/subscribing`).then(response => { api(getState).get('/api/v1/accounts/subscribing').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data));
@ -761,13 +758,10 @@ export function fetchRelationshipsFromStatuses(statuses) {
return fetchRelationships( return fetchRelationships(
uniq( uniq(
statuses statuses
.map(status => status.reblog ? status.reblog.account.id : status.account.id) .map(status => status.reblog ? status.reblog : status)
.concat( .flatMap(status => [status.account.id, status.quote?.account?.id])
statuses .filter(e => !!e),
.map(status => status.quote ? status.quote.account.id : null) ),
)
.filter(e => !!e)
)
); );
}; };

View file

@ -1,7 +1,6 @@
import { fetchRelationshipsFromStatuses, fetchAccountsFromStatuses } from './accounts'; import { fetchRelationshipsSuccess, fetchRelationshipsFromStatuses, fetchAccountsFromStatuses } from './accounts';
import api, { getLinks } from '../api'; import api, { getLinks } from '../api';
import { importFetchedStatuses } from './importer'; import { importFetchedStatuses, importFetchedAccounts } from './importer';
import { uniq } from '../utils/uniq';
export const BOOKMARKED_STATUSES_FETCH_REQUEST = 'BOOKMARKED_STATUSES_FETCH_REQUEST'; export const BOOKMARKED_STATUSES_FETCH_REQUEST = 'BOOKMARKED_STATUSES_FETCH_REQUEST';
export const BOOKMARKED_STATUSES_FETCH_SUCCESS = 'BOOKMARKED_STATUSES_FETCH_SUCCESS'; export const BOOKMARKED_STATUSES_FETCH_SUCCESS = 'BOOKMARKED_STATUSES_FETCH_SUCCESS';
@ -19,13 +18,21 @@ export function fetchBookmarkedStatuses() {
dispatch(fetchBookmarkedStatusesRequest()); dispatch(fetchBookmarkedStatusesRequest());
api(getState).get('/api/v1/bookmarks').then(response => { api(getState).get('/api/v1/bookmarks?compact=true').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
const statuses = response.data; if ('statuses' in response.data && 'accounts' in response.data) {
dispatch(importFetchedStatuses(statuses)); const { statuses, referenced_statuses, accounts, relationships } = response.data;
dispatch(fetchRelationshipsFromStatuses(statuses)); dispatch(importFetchedStatuses(statuses.concat(referenced_statuses)));
dispatch(fetchAccountsFromStatuses(statuses)); dispatch(importFetchedAccounts(accounts));
dispatch(fetchBookmarkedStatusesSuccess(statuses, next ? next.uri : null)); dispatch(fetchRelationshipsSuccess(relationships));
dispatch(fetchBookmarkedStatusesSuccess(statuses, next ? next.uri : null));
} else {
const statuses = response.data;
dispatch(importFetchedStatuses(statuses));
dispatch(fetchRelationshipsFromStatuses(statuses));
dispatch(fetchAccountsFromStatuses(statuses));
dispatch(fetchBookmarkedStatusesSuccess(statuses, next ? next.uri : null));
}
}).catch(error => { }).catch(error => {
dispatch(fetchBookmarkedStatusesFail(error)); dispatch(fetchBookmarkedStatusesFail(error));
}); });
@ -65,11 +72,19 @@ export function expandBookmarkedStatuses() {
api(getState).get(url).then(response => { api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
const statuses = response.data; if ('statuses' in response.data && 'accounts' in response.data) {
dispatch(importFetchedStatuses(statuses)); const { statuses, referenced_statuses, accounts, relationships } = response.data;
dispatch(fetchRelationshipsFromStatuses(statuses)); dispatch(importFetchedStatuses(statuses.concat(referenced_statuses)));
dispatch(fetchAccountsFromStatuses(statuses)); dispatch(importFetchedAccounts(accounts));
dispatch(expandBookmarkedStatusesSuccess(statuses, next ? next.uri : null)); dispatch(fetchRelationshipsSuccess(relationships));
dispatch(expandBookmarkedStatusesSuccess(statuses, next ? next.uri : null));
} else {
const statuses = response.data;
dispatch(importFetchedStatuses(statuses));
dispatch(fetchRelationshipsFromStatuses(statuses));
dispatch(fetchAccountsFromStatuses(statuses));
dispatch(expandBookmarkedStatusesSuccess(statuses, next ? next.uri : null));
}
}).catch(error => { }).catch(error => {
dispatch(expandBookmarkedStatusesFail(error)); dispatch(expandBookmarkedStatusesFail(error));
}); });

View file

@ -1,7 +1,6 @@
import { fetchRelationshipsFromStatuses, fetchAccountsFromStatuses } from './accounts'; import { fetchRelationshipsSuccess, fetchRelationshipsFromStatuses, fetchAccountsFromStatuses } from './accounts';
import api, { getLinks } from '../api'; import api, { getLinks } from '../api';
import { importFetchedStatuses } from './importer'; import { importFetchedStatuses, importFetchedAccounts } from './importer';
import { uniq } from '../utils/uniq';
export const EMOJI_REACTIONED_STATUSES_FETCH_REQUEST = 'EMOJI_REACTIONED_STATUSES_FETCH_REQUEST'; export const EMOJI_REACTIONED_STATUSES_FETCH_REQUEST = 'EMOJI_REACTIONED_STATUSES_FETCH_REQUEST';
export const EMOJI_REACTIONED_STATUSES_FETCH_SUCCESS = 'EMOJI_REACTIONED_STATUSES_FETCH_SUCCESS'; export const EMOJI_REACTIONED_STATUSES_FETCH_SUCCESS = 'EMOJI_REACTIONED_STATUSES_FETCH_SUCCESS';
@ -19,13 +18,21 @@ export function fetchEmojiReactionedStatuses() {
dispatch(fetchEmojiReactionedStatusesRequest()); dispatch(fetchEmojiReactionedStatusesRequest());
api(getState).get('/api/v1/emoji_reactions').then(response => { api(getState).get('/api/v1/emoji_reactions?compact=true').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
const statuses = response.data; if ('statuses' in response.data && 'accounts' in response.data) {
dispatch(importFetchedStatuses(statuses)); const { statuses, referenced_statuses, accounts, relationships } = response.data;
dispatch(fetchRelationshipsFromStatuses(statuses)); dispatch(importFetchedStatuses(statuses.concat(referenced_statuses)));
dispatch(fetchAccountsFromStatuses(statuses)); dispatch(importFetchedAccounts(accounts));
dispatch(fetchEmojiReactionedStatusesSuccess(statuses, next ? next.uri : null)); dispatch(fetchRelationshipsSuccess(relationships));
dispatch(fetchEmojiReactionedStatusesSuccess(statuses, next ? next.uri : null));
} else {
const statuses = response.data;
dispatch(importFetchedStatuses(statuses));
dispatch(fetchRelationshipsFromStatuses(statuses));
dispatch(fetchAccountsFromStatuses(statuses));
dispatch(fetchEmojiReactionedStatusesSuccess(statuses, next ? next.uri : null));
}
}).catch(error => { }).catch(error => {
dispatch(fetchEmojiReactionedStatusesFail(error)); dispatch(fetchEmojiReactionedStatusesFail(error));
}); });
@ -65,11 +72,19 @@ export function expandEmojiReactionedStatuses() {
api(getState).get(url).then(response => { api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
const statuses = response.data; if ('statuses' in response.data && 'accounts' in response.data) {
dispatch(importFetchedStatuses(statuses)); const { statuses, referenced_statuses, accounts, relationships } = response.data;
dispatch(fetchRelationshipsFromStatuses(statuses)); dispatch(importFetchedStatuses(statuses.concat(referenced_statuses)));
dispatch(fetchAccountsFromStatuses(statuses)); dispatch(importFetchedAccounts(accounts));
dispatch(expandEmojiReactionedStatusesSuccess(statuses, next ? next.uri : null)); dispatch(fetchRelationshipsSuccess(relationships));
dispatch(expandEmojiReactionedStatusesSuccess(statuses, next ? next.uri : null));
} else {
const statuses = response.data;
dispatch(importFetchedStatuses(statuses));
dispatch(fetchRelationshipsFromStatuses(statuses));
dispatch(fetchAccountsFromStatuses(statuses));
dispatch(expandEmojiReactionedStatusesSuccess(statuses, next ? next.uri : null));
}
}).catch(error => { }).catch(error => {
dispatch(expandEmojiReactionedStatusesFail(error)); dispatch(expandEmojiReactionedStatusesFail(error));
}); });

View file

@ -1,7 +1,6 @@
import { fetchRelationshipsFromStatuses, fetchAccountsFromStatuses } from './accounts'; import { fetchRelationshipsSuccess, fetchRelationshipsFromStatuses, fetchAccountsFromStatuses } from './accounts';
import api, { getLinks } from '../api'; import api, { getLinks } from '../api';
import { importFetchedStatuses } from './importer'; import { importFetchedStatuses, importFetchedAccounts } from './importer';
import { uniq } from '../utils/uniq';
export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST'; export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST';
export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS'; export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS';
@ -19,13 +18,21 @@ export function fetchFavouritedStatuses() {
dispatch(fetchFavouritedStatusesRequest()); dispatch(fetchFavouritedStatusesRequest());
api(getState).get('/api/v1/favourites').then(response => { api(getState).get('/api/v1/favourites?compact=true').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
const statuses = response.data; if ('statuses' in response.data && 'accounts' in response.data) {
dispatch(importFetchedStatuses(statuses)); const { statuses, referenced_statuses, accounts, relationships } = response.data;
dispatch(fetchRelationshipsFromStatuses(statuses)); dispatch(importFetchedStatuses(statuses.concat(referenced_statuses)));
dispatch(fetchAccountsFromStatuses(statuses)); dispatch(importFetchedAccounts(accounts));
dispatch(fetchFavouritedStatusesSuccess(statuses, next ? next.uri : null)); dispatch(fetchRelationshipsSuccess(relationships));
dispatch(fetchFavouritedStatusesSuccess(statuses, next ? next.uri : null));
} else {
const statuses = response.data;
dispatch(importFetchedStatuses(statuses));
dispatch(fetchRelationshipsFromStatuses(statuses));
dispatch(fetchAccountsFromStatuses(statuses));
dispatch(fetchFavouritedStatusesSuccess(statuses, next ? next.uri : null));
}
}).catch(error => { }).catch(error => {
dispatch(fetchFavouritedStatusesFail(error)); dispatch(fetchFavouritedStatusesFail(error));
}); });
@ -68,11 +75,19 @@ export function expandFavouritedStatuses() {
api(getState).get(url).then(response => { api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
const statuses = response.data; if ('statuses' in response.data && 'accounts' in response.data) {
dispatch(importFetchedStatuses(statuses)); const { statuses, referenced_statuses, accounts, relationships } = response.data;
dispatch(fetchRelationshipsFromStatuses(statuses)); dispatch(importFetchedStatuses(statuses.concat(referenced_statuses)));
dispatch(fetchAccountsFromStatuses(statuses)); dispatch(importFetchedAccounts(accounts));
dispatch(expandFavouritedStatusesSuccess(statuses, next ? next.uri : null)); dispatch(fetchRelationshipsSuccess(relationships));
dispatch(expandFavouritedStatusesSuccess(statuses, next ? next.uri : null));
} else {
const statuses = response.data;
dispatch(importFetchedStatuses(statuses));
dispatch(fetchRelationshipsFromStatuses(statuses));
dispatch(fetchAccountsFromStatuses(statuses));
dispatch(expandFavouritedStatusesSuccess(statuses, next ? next.uri : null));
}
}).catch(error => { }).catch(error => {
dispatch(expandFavouritedStatusesFail(error)); dispatch(expandFavouritedStatusesFail(error));
}); });

View file

@ -63,20 +63,21 @@ export function importFetchedStatuses(statuses) {
const polls = []; const polls = [];
function processStatus(status) { function processStatus(status) {
pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id])));
pushUnique(accounts, status.account);
if (status.reblog && status.reblog.id) {
processStatus(status.reblog);
}
if (status.quote && status.quote.id) {
processStatus(status.quote);
}
if (status.poll && status.poll.id) { if (status.poll && status.poll.id) {
pushUnique(polls, normalizePoll(status.poll)); pushUnique(polls, normalizePoll(status.poll));
} }
if (typeof status.account === 'object') {
pushUnique(accounts, status.account);
}
if (status.reblog && status.reblog.id) {
processStatus(status.reblog);
} else if (status.quote && status.quote.id) {
processStatus(status.quote);
}
pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id])));
} }
statuses.forEach(processStatus); statuses.forEach(processStatus);

View file

@ -44,7 +44,9 @@ export function normalizeAccount(account) {
export function normalizeStatus(status, normalOldStatus) { export function normalizeStatus(status, normalOldStatus) {
const normalStatus = { ...status }; const normalStatus = { ...status };
normalStatus.account = status.account.id; if (typeof status.account === 'object') {
normalStatus.account = status.account.id;
}
if (status.reblog && status.reblog.id) { if (status.reblog && status.reblog.id) {
normalStatus.reblog = status.reblog.id; normalStatus.reblog = status.reblog.id;
@ -64,7 +66,6 @@ export function normalizeStatus(status, normalOldStatus) {
normalStatus.spoiler_text = normalOldStatus.get('spoiler_text'); normalStatus.spoiler_text = normalOldStatus.get('spoiler_text');
normalStatus.hidden = normalOldStatus.get('hidden'); normalStatus.hidden = normalOldStatus.get('hidden');
normalStatus.visibility = normalOldStatus.get('visibility'); normalStatus.visibility = normalOldStatus.get('visibility');
normalStatus.quote = normalOldStatus.get('quote');
normalStatus.quote_hidden = normalOldStatus.get('quote_hidden'); normalStatus.quote_hidden = normalOldStatus.get('quote_hidden');
} else { } else {
// If the status has a CW but no contents, treat the CW as if it were the // If the status has a CW but no contents, treat the CW as if it were the
@ -88,29 +89,7 @@ export function normalizeStatus(status, normalOldStatus) {
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap); normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive; normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive;
normalStatus.visibility = normalStatus.visibility_ex ? normalStatus.visibility_ex : normalStatus.visibility; normalStatus.visibility = normalStatus.visibility_ex ? normalStatus.visibility_ex : normalStatus.visibility;
normalStatus.quote = null;
if (status.quote && status.quote.id) {
const quote_spoilerText = status.quote.spoiler_text || '';
const quote_searchContent = [quote_spoilerText, status.quote.content].join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
const quote_emojiMap = makeEmojiMap(normalStatus.quote);
const quote_account_emojiMap = makeEmojiMap(status.quote.account);
const displayName = normalStatus.quote.account.display_name.length === 0 ? normalStatus.quote.account.username : normalStatus.quote.account.display_name;
normalStatus.quote.account.display_name_html = emojify(escapeTextContentForBrowser(displayName), quote_account_emojiMap);
normalStatus.quote.search_index = domParser.parseFromString(quote_searchContent, 'text/html').documentElement.textContent;
let docElem = domParser.parseFromString(normalStatus.quote.content, 'text/html').documentElement;
Array.from(docElem.querySelectorAll('p,br'), line => {
let parentNode = line.parentNode;
if (line.nextSibling) {
parentNode.insertBefore(document.createTextNode(' '), line.nextSibling);
}
});
let _contentHtml = docElem.textContent;
normalStatus.quote.contentHtml = '<p>'+emojify(_contentHtml.substr(0, 150), quote_emojiMap) + (_contentHtml.substr(150) ? '...' : '')+'</p>';
normalStatus.quote.spoilerHtml = emojify(escapeTextContentForBrowser(quote_spoilerText), quote_emojiMap);
normalStatus.quote_hidden = expandSpoilers ? false : quote_spoilerText.length > 0 || normalStatus.quote.sensitive;
}
} }
return normalStatus; return normalStatus;

View file

@ -1,6 +1,6 @@
import api, { getLinks } from '../api'; import api, { getLinks } from '../api';
import { importFetchedAccounts, importFetchedStatus, importFetchedStatuses } from './importer'; import { importFetchedAccounts, importFetchedStatus, importFetchedStatuses } from './importer';
import { fetchRelationships, fetchRelationshipsFromStatuses, fetchAccountsFromStatuses } from './accounts'; import { fetchRelationshipsSuccess, fetchRelationships, fetchRelationshipsFromStatuses, fetchAccountsFromStatuses } from './accounts';
import { me } from '../initial_state'; import { me } from '../initial_state';
export const REBLOG_REQUEST = 'REBLOG_REQUEST'; export const REBLOG_REQUEST = 'REBLOG_REQUEST';
@ -567,13 +567,21 @@ export function fetchReferredByStatuses(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(fetchReferredByStatusesRequest(id)); dispatch(fetchReferredByStatusesRequest(id));
api(getState).get(`/api/v1/statuses/${id}/referred_by`).then(response => { api(getState).get(`/api/v1/statuses/${id}/referred_by?compact=true`).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
const statuses = response.data; if ('statuses' in response.data && 'accounts' in response.data) {
dispatch(importFetchedStatuses(statuses)); const { statuses, referenced_statuses, accounts, relationships } = response.data;
dispatch(fetchRelationshipsFromStatuses(statuses)); dispatch(importFetchedStatuses(statuses.concat(referenced_statuses)));
dispatch(fetchAccountsFromStatuses(statuses)); dispatch(importFetchedAccounts(accounts));
dispatch(fetchReferredByStatusesSuccess(id, statuses, next ? next.uri : null)); dispatch(fetchRelationshipsSuccess(relationships));
dispatch(fetchReferredByStatusesSuccess(id, statuses, next ? next.uri : null));
} else {
const statuses = response.data;
dispatch(importFetchedStatuses(statuses));
dispatch(fetchRelationshipsFromStatuses(statuses));
dispatch(fetchAccountsFromStatuses(statuses));
dispatch(fetchReferredByStatusesSuccess(id, statuses, next ? next.uri : null));
}
}).catch(error => { }).catch(error => {
dispatch(fetchReferredByStatusesFail(id, error)); dispatch(fetchReferredByStatusesFail(id, error));
}); });
@ -616,11 +624,19 @@ export function expandReferredByStatuses(id) {
api(getState).get(url).then(response => { api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
const statuses = response.data; if ('statuses' in response.data && 'accounts' in response.data) {
dispatch(importFetchedStatuses(statuses)); const { statuses, referenced_statuses, accounts, relationships } = response.data;
dispatch(fetchRelationshipsFromStatuses(statuses)); dispatch(importFetchedStatuses(statuses.concat(referenced_statuses)));
dispatch(fetchAccountsFromStatuses(statuses)); dispatch(importFetchedAccounts(accounts));
dispatch(expandReferredByStatusesSuccess(id, statuses, next ? next.uri : null)); dispatch(fetchRelationshipsSuccess(relationships));
dispatch(expandReferredByStatusesSuccess(id, statuses, next ? next.uri : null));
} else {
const statuses = response.data;
dispatch(importFetchedStatuses(statuses));
dispatch(fetchRelationshipsFromStatuses(statuses));
dispatch(fetchAccountsFromStatuses(statuses));
dispatch(expandReferredByStatusesSuccess(id, statuses, next ? next.uri : null));
}
}).catch(error => { }).catch(error => {
dispatch(expandReferredByStatusesFail(id, error)); dispatch(expandReferredByStatusesFail(id, error));
}); });

View file

@ -1,5 +1,5 @@
import api from '../api'; import api from '../api';
import { fetchRelationships, fetchAccountsFromStatuses } from './accounts'; import { fetchRelationshipsSuccess, fetchRelationships, fetchAccountsFromStatuses } from './accounts';
import { importFetchedAccounts, importFetchedStatuses } from './importer'; import { importFetchedAccounts, importFetchedStatuses } from './importer';
export const SEARCH_CHANGE = 'SEARCH_CHANGE'; export const SEARCH_CHANGE = 'SEARCH_CHANGE';
@ -44,6 +44,7 @@ export function submitSearch() {
resolve: true, resolve: true,
limit: 5, limit: 5,
with_profiles: true, with_profiles: true,
compact: true,
}, },
}).then(response => { }).then(response => {
if (response.data.accounts) { if (response.data.accounts) {
@ -55,8 +56,16 @@ export function submitSearch() {
} }
if (response.data.statuses) { if (response.data.statuses) {
dispatch(importFetchedStatuses(response.data.statuses)); if (response.data.statuses.statuses && response.data.statuses.accounts) {
dispatch(fetchAccountsFromStatuses(response.data.statuses)); const { statuses, referenced_statuses, accounts, relationships } = response.data.statuses;
response.data.statuses = statuses;
dispatch(importFetchedStatuses(statuses.concat(referenced_statuses)));
dispatch(importFetchedAccounts(accounts));
dispatch(fetchRelationshipsSuccess(relationships));
} else {
dispatch(importFetchedStatuses(response.data.statuses));
dispatch(fetchAccountsFromStatuses(response.data.statuses));
}
} }
dispatch(fetchSearchSuccess(response.data, value)); dispatch(fetchSearchSuccess(response.data, value));
@ -101,6 +110,7 @@ export const expandSearch = type => (dispatch, getState) => {
type, type,
offset, offset,
with_profiles: true, with_profiles: true,
compact: true,
}, },
}).then(({ data }) => { }).then(({ data }) => {
if (data.accounts) { if (data.accounts) {
@ -112,8 +122,16 @@ export const expandSearch = type => (dispatch, getState) => {
} }
if (data.statuses) { if (data.statuses) {
dispatch(importFetchedStatuses(data.statuses)); if (data.statuses.statuses && data.statuses.accounts) {
dispatch(fetchAccountsFromStatuses(data.statuses)); const { statuses, referenced_statuses, accounts, relationships } = data.statuses;
data.statuses = statuses;
dispatch(importFetchedStatuses(statuses.concat(referenced_statuses)));
dispatch(importFetchedAccounts(accounts));
dispatch(fetchRelationshipsSuccess(relationships));
} else {
dispatch(importFetchedStatuses(data.statuses));
dispatch(fetchAccountsFromStatuses(data.statuses));
}
} }
dispatch(expandSearchSuccess(data, value, type)); dispatch(expandSearchSuccess(data, value, type));

View file

@ -1,10 +1,10 @@
import { fetchRelationshipsFromStatus, fetchAccountsFromStatus, fetchRelationshipsFromStatuses, fetchAccountsFromStatuses } from './accounts'; import { fetchRelationshipsSuccess, fetchRelationshipsFromStatus, fetchAccountsFromStatus, fetchRelationshipsFromStatuses, fetchAccountsFromStatuses } from './accounts';
import { importFetchedStatus, importFetchedStatuses } from './importer'; import { importFetchedStatus, importFetchedStatuses, importFetchedAccounts } from './importer';
import { submitMarkers } from './markers'; import { submitMarkers } from './markers';
import api, { getLinks } from 'mastodon/api'; import api, { getLinks } from 'mastodon/api';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import compareId from 'mastodon/compare_id'; import compareId from 'mastodon/compare_id';
import { usePendingItems as preferPendingItems } from 'mastodon/initial_state'; import { usePendingItems as preferPendingItems, show_follow_button_on_timeline, show_subscribe_button_on_timeline } from 'mastodon/initial_state';
import { getHomeVisibilities, getLimitedVisibilities } from 'mastodon/selectors'; import { getHomeVisibilities, getLimitedVisibilities } from 'mastodon/selectors';
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'; export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
@ -42,8 +42,12 @@ export function updateTimeline(timeline, status, accept) {
} }
dispatch(importFetchedStatus(status)); dispatch(importFetchedStatus(status));
dispatch(fetchRelationshipsFromStatus(status)); if (show_follow_button_on_timeline || show_subscribe_button_on_timeline || status.quote_id) {
dispatch(fetchAccountsFromStatus(status)); dispatch(fetchRelationshipsFromStatus(status));
}
if (status.emoji_reactions.length) {
dispatch(fetchAccountsFromStatus(status));
}
const insertTimeline = timeline => { const insertTimeline = timeline => {
dispatch({ dispatch({
@ -140,17 +144,27 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
} }
} }
params.compact = true;
const isLoadingRecent = !!params.since_id; const isLoadingRecent = !!params.since_id;
dispatch(expandTimelineRequest(timelineId, isLoadingMore)); dispatch(expandTimelineRequest(timelineId, isLoadingMore));
api(getState).get(path, { params }).then(response => { api(getState).get(path, { params }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next'); const next = getLinks(response).refs.find(link => link.rel === 'next');
const statuses = response.data; if ('statuses' in response.data && 'accounts' in response.data) {
dispatch(importFetchedStatuses(statuses)); const { statuses, referenced_statuses, accounts, relationships } = response.data;
dispatch(fetchRelationshipsFromStatuses(statuses)); dispatch(importFetchedStatuses(statuses.concat(referenced_statuses)));
dispatch(fetchAccountsFromStatuses(statuses)); dispatch(importFetchedAccounts(accounts));
dispatch(expandTimelineSuccess(timelineId, statuses, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems)); dispatch(fetchRelationshipsSuccess(relationships));
dispatch(expandTimelineSuccess(timelineId, statuses, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
} else {
const statuses = response.data;
dispatch(importFetchedStatuses(statuses));
dispatch(fetchRelationshipsFromStatuses(statuses));
dispatch(fetchAccountsFromStatuses(statuses));
dispatch(expandTimelineSuccess(timelineId, statuses, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
}
if (timelineId === 'home') { if (timelineId === 'home') {
dispatch(submitMarkers()); dispatch(submitMarkers());

View file

@ -37,12 +37,6 @@ import {
UNPIN_SUCCESS, UNPIN_SUCCESS,
} from '../actions/interactions'; } from '../actions/interactions';
const initialListState = ImmutableMap({
next: null,
isLoading: false,
items: ImmutableList(),
});
const initialState = ImmutableMap({ const initialState = ImmutableMap({
favourites: ImmutableMap({ favourites: ImmutableMap({
next: null, next: null,

View file

@ -86,62 +86,56 @@ export const getFiltersRegex = makeGetFiltersRegex();
export const makeGetStatus = () => { export const makeGetStatus = () => {
return createSelector( return createSelector(
[ [
(state, { id }) => state.getIn(['statuses', id]), (state, { id }) => state.getIn(['statuses', id], null),
(state, { id }) => state.getIn(['statuses', state.getIn(['statuses', id, 'reblog'])]), (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])], null),
(state, { id }) => state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id'])]), (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', id, 'account']), 'moved'], null),
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]), (state, { id }) => state.getIn(['relationships', state.getIn(['statuses', id, 'account'])], null),
(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(['statuses', state.getIn(['statuses', id, 'reblog'])], null),
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'quote', 'account'])]), (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])], null),
(state, { id }) => state.getIn(['relationships', state.getIn(['statuses', id, 'account'])]), (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account']), 'moved'], null),
(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, 'reblog']), 'account'])], null),
(state, { id }) => state.getIn(['relationships', state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id']), 'account'])]),
(state, { id }) => state.getIn(['relationships', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'quote', 'account'])]), (state, { id }) => state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id'])], null),
(state, { id }) => state.getIn(['accounts', state.getIn(['accounts', state.getIn(['statuses', id, 'account']), 'moved'])]), (state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id']), 'account'])], null),
(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(['statuses', state.getIn(['statuses', id, 'quote_id']), 'account']), 'moved'], null),
(state, { id }) => state.getIn(['accounts', state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id']), 'account']), 'moved'])]), (state, { id }) => state.getIn(['relationships', state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id']), 'account'])], null),
(state, { id }) => state.getIn(['accounts', state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'quote', 'account']), 'moved'])]),
(state, { id }) => state.getIn(['statuses', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'quote_id'])], null),
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'quote_id']), 'account'])], null),
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'quote_id']), 'account']), 'moved'], null),
(state, { id }) => state.getIn(['relationships', state.getIn(['statuses', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'quote_id']), 'account'])], null),
getFiltersRegex, getFiltersRegex,
], ],
(statusBase, statusReblog, statusQuote, accountBase, accountReblog, accountQuote, accountReblogQuote, relationship, reblogRelationship, quoteRelationship, reblogQuoteRelationship, moved, reblogMoved, quoteMoved, reblogQuoteMoved, filtersRegex) => { (
if (!statusBase) { statusBase,
accountBase,
moved,
relationship,
statusReblog,
accountReblog,
reblogMoved,
reblogRelationship,
statusQuote,
accountQuote,
quoteMoved,
quoteRelationship,
statusReblogQuote,
accountReblogQuote,
reblogQuoteMoved,
reblogQuoteRelationship,
filtersRegex,
) => {
if (!statusBase || !accountBase) {
return null; 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;
}
if (statusReblog && accountReblogQuote) {
accountReblogQuote = accountReblog.withMutations(map => {
map.set('relationship', reblogQuoteRelationship);
map.set('moved', reblogQuoteMoved);
});
statusReblog = statusReblog.setIn(['quote', 'account'], accountReblogQuote);
}
const dropRegex = (accountReblog || accountBase).get('id') !== me && filtersRegex[0]; const dropRegex = (accountReblog || accountBase).get('id') !== me && filtersRegex[0];
if (dropRegex && dropRegex.test(statusBase.get('reblog') ? statusReblog.get('search_index') : statusBase.get('search_index'))) { if (dropRegex && dropRegex.test(statusBase.get('reblog') ? statusReblog.get('search_index') : statusBase.get('search_index'))) {
return null; return null;
@ -150,12 +144,50 @@ export const makeGetStatus = () => {
const regex = (accountReblog || accountBase).get('id') !== me && filtersRegex[1]; const regex = (accountReblog || accountBase).get('id') !== me && filtersRegex[1];
const filtered = regex && regex.test(statusBase.get('reblog') ? statusReblog.get('search_index') : statusBase.get('search_index')); const filtered = regex && regex.test(statusBase.get('reblog') ? statusReblog.get('search_index') : statusBase.get('search_index'));
return statusBase.withMutations(map => { if (statusReblogQuote && accountReblogQuote) {
accountReblogQuote = accountReblogQuote.withMutations(map => {
map.set('relationship', reblogQuoteRelationship);
map.set('moved', reblogQuoteMoved);
});
statusReblogQuote = statusReblogQuote.withMutations(map => {
map.set('account', accountReblogQuote);
});
}
if (statusReblog && accountReblog) {
accountReblog = accountReblog.withMutations(map => {
map.set('relationship', reblogRelationship);
map.set('moved', reblogMoved);
});
statusReblog = statusReblog.withMutations(map => {
map.set('quote', statusReblogQuote);
map.set('account', accountReblog);
});
}
if (statusQuote && accountQuote) {
accountQuote = accountQuote.withMutations(map => {
map.set('relationship', quoteRelationship);
map.set('moved', quoteMoved);
});
statusQuote = statusQuote.withMutations(map => {
map.set('account', accountQuote);
});
}
accountBase = accountBase.withMutations(map => {
map.set('relationship', relationship);
map.set('moved', moved);
});
statusBase = statusBase.withMutations(map => {
map.set('reblog', statusReblog); map.set('reblog', statusReblog);
map.set('quote', statusQuote); map.set('quote', statusQuote);
map.set('account', accountBase); map.set('account', accountBase);
map.set('filtered', filtered); map.set('filtered', filtered);
}); });
return statusBase;
}, },
); );
}; };
@ -220,9 +252,9 @@ export const getHomeVisibilities = createSelector(
shows => { shows => {
return enable_limited_timeline ? ( return enable_limited_timeline ? (
['public', 'unlisted'] ['public', 'unlisted']
.concat(shows.get('private') ? ['private'] : []) .concat(shows.get('private') ? ['private'] : [])
.concat(shows.get('limited') ? ['limited'] : []) .concat(shows.get('limited') ? ['limited'] : [])
.concat(shows.get('direct') ? ['direct'] : []) .concat(shows.get('direct') ? ['direct'] : [])
) : []; ) : [];
}); });
@ -231,8 +263,8 @@ export const getLimitedVisibilities = createSelector(
shows => { shows => {
return enable_limited_timeline ? ( return enable_limited_timeline ? (
[] []
.concat(shows.get('private') ? ['private'] : []) .concat(shows.get('private') ? ['private'] : [])
.concat(shows.get('limited') ? ['limited'] : []) .concat(shows.get('limited') ? ['limited'] : [])
.concat(shows.get('direct') ? ['direct'] : []) .concat(shows.get('direct') ? ['direct'] : [])
) : []; ) : [];
}); });

View file

@ -44,10 +44,7 @@ class Status < ApplicationRecord
# If `override_timestamps` is set at creation time, Snowflake ID creation # If `override_timestamps` is set at creation time, Snowflake ID creation
# will be based on current time instead of `created_at` # will be based on current time instead of `created_at`
attr_accessor :override_timestamps attr_accessor :override_timestamps, :circle, :expires_at, :expires_action
attr_accessor :circle
attr_accessor :expires_at, :expires_action
update_index('statuses', :proper) update_index('statuses', :proper)
@ -335,6 +332,19 @@ class Status < ApplicationRecord
status_stat&.status_referred_by_count || 0 status_stat&.status_referred_by_count || 0
end end
def account_ids(recursive: true)
ids = (Oj.load(status_stat&.emoji_reactions_cache || '', mode: :strict) || []).flat_map { |emoji_reaction| emoji_reaction['account_ids'] }
ids << account_id.to_s
if recursive
ids.concat(quote.account_ids(recursive: false)) unless quote.nil?
ids.concat(reblog.account_ids(recursive: false)) unless reblog.nil?
ids.concat(reblog.quote.account_ids(recursive: false)) unless reblog&.quote.nil?
end
ids.uniq
end
def grouped_emoji_reactions(account = nil) def grouped_emoji_reactions(account = nil)
(Oj.load(status_stat&.emoji_reactions_cache || '', mode: :strict) || []).tap do |emoji_reactions| (Oj.load(status_stat&.emoji_reactions_cache || '', mode: :strict) || []).tap do |emoji_reactions|
if account.present? if account.present?
@ -513,13 +523,12 @@ class Status < ApplicationRecord
return [] if text.blank? return [] if text.blank?
text.scan(FetchLinkCardService::URL_PATTERN).map(&:first).uniq.filter_map do |url| text.scan(FetchLinkCardService::URL_PATTERN).map(&:first).uniq.filter_map do |url|
status = begin status =
if TagManager.instance.local_url?(url) if TagManager.instance.local_url?(url)
ActivityPub::TagManager.instance.uri_to_resource(url, Status) ActivityPub::TagManager.instance.uri_to_resource(url, Status)
else else
EntityCache.instance.status(url) EntityCache.instance.status(url)
end end
end
status&.distributable? ? status : nil status&.distributable? ? status : nil
end end
end end

View file

@ -0,0 +1,5 @@
# frozen_string_literal: true
class CompactStatusesPresenter < ActiveModelSerializers::Model
attributes :statuses
end

View file

@ -0,0 +1,8 @@
# frozen_string_literal: true
class REST::CompactSearchSerializer < ActiveModel::Serializer
has_many :accounts, serializer: REST::AccountSerializer
has_one :statuses, serializer: REST::CompactStatusesSerializer
has_many :hashtags, serializer: REST::TagSerializer
has_many :profiles, serializer: REST::AccountSerializer
end

View file

@ -0,0 +1,58 @@
# frozen_string_literal: true
class REST::CompactStatusesSerializer < ActiveModel::Serializer
attribute :statuses
attribute :referenced_statuses
attribute :accounts
attribute :relationships
def current_user?
!current_user.nil?
end
def statuses
object.statuses.map do |status|
REST::StatusSerializer.new(status, root: false, relationships: status_relationships, account_relationships: account_relationships, compact: true, scope: current_user, scope_name: :current_user)
end || []
end
def referenced_statuses
Status.where(id: referenced_status_ids).map do |status|
REST::StatusSerializer.new(status, root: false, relationships: status_relationships, account_relationships: account_relationships, compact: true, scope: current_user, scope_name: :current_user)
end || []
end
def accounts
source_accounts.map do |account|
REST::AccountSerializer.new(account, root: false, scope: current_user, scope_name: :current_user)
end || []
end
def relationships
return [] unless current_user?
source_accounts.map do |account|
REST::RelationshipSerializer.new(account, root: false, relationships: account_relationships, scope: current_user, scope_name: :current_user)
end || []
end
def referenced_status_ids
@referenced_status_ids ||= object.statuses.flat_map { |status| [status.reblog_of_id, status.quote_id, status.reblog&.quote_id] }.compact.uniq - object.statuses.pluck(:id)
end
def source_accounts
@source_accounts ||= Account.where(id: source_accounts_ids)
end
def source_accounts_ids
@source_accounts_ids ||= object.statuses.flat_map(&:account_ids).uniq
end
def status_relationships
@status_relationships ||= StatusRelationshipsPresenter.new(object.statuses, current_user&.account_id)
end
def account_relationships
@account_relationships ||= AccountRelationshipsPresenter.new(source_accounts_ids, current_user&.account_id)
end
end

View file

@ -135,6 +135,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
:misskey_location, :misskey_location,
:status_reference, :status_reference,
:searchability, :searchability,
:status_compact_mode,
] ]
capabilities << :profile_search unless Chewy.enabled? capabilities << :profile_search unless Chewy.enabled?

View file

@ -24,13 +24,14 @@ class REST::StatusSerializer < ActiveModel::Serializer
attribute :quote_id, if: :quote? attribute :quote_id, if: :quote?
attribute :expires_at, if: :has_expires? attribute :expires_at, if: :expires?
attribute :expires_action, if: :has_expires? attribute :expires_action, if: :expires?
attribute :visibility_ex, if: :visibility_ex? attribute :visibility_ex, if: :visibility_ex?
belongs_to :reblog, serializer: REST::StatusSerializer attribute :account
attribute :reblog
belongs_to :application, if: :show_application? belongs_to :application, if: :show_application?
belongs_to :account, serializer: REST::AccountSerializer
has_many :media_attachments, serializer: REST::MediaAttachmentSerializer has_many :media_attachments, serializer: REST::MediaAttachmentSerializer
has_many :ordered_mentions, key: :mentions has_many :ordered_mentions, key: :mentions
@ -40,6 +41,8 @@ class REST::StatusSerializer < ActiveModel::Serializer
has_one :preview_card, key: :card, serializer: REST::PreviewCardSerializer has_one :preview_card, key: :card, serializer: REST::PreviewCardSerializer
has_one :preloadable_poll, key: :poll, serializer: REST::PollSerializer has_one :preloadable_poll, key: :poll, serializer: REST::PollSerializer
delegate :quote?, to: :object
def id def id
object.id.to_s object.id.to_s
end end
@ -56,6 +59,24 @@ class REST::StatusSerializer < ActiveModel::Serializer
object.quote_id.to_s object.quote_id.to_s
end end
def account
if instance_options[:compact]
object.account.id.to_s
else
REST::AccountSerializer.new(object.account, root: false, scope: current_user, scope_name: :current_user)
end
end
def reblog
if object.reblog.nil?
nil
elsif instance_options[:compact]
object.reblog.id.to_s
else
REST::StatusSerializer.new(object.reblog, root: false, relationships: instance_options[:relationships], account_relationships: instance_options[:account_relationships], compact: false, scope: current_user, scope_name: :current_user)
end
end
def current_user? def current_user?
!current_user.nil? !current_user.nil?
end end
@ -68,11 +89,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
object.account.user_shows_application? || owned_status? object.account.user_shows_application? || owned_status?
end end
def quote? def expires?
object.quote?
end
def has_expires?
object.expires? || object.expired? object.expires? || object.expired?
end end
@ -265,9 +282,20 @@ class REST::NestedQuoteSerializer < REST::StatusSerializer
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) 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 end
end end
class REST::StatusSerializer < ActiveModel::Serializer class REST::StatusSerializer < ActiveModel::Serializer
belongs_to :quote, serializer: REST::NestedQuoteSerializer attribute :quote
def quote
if object.quote.nil?
nil
elsif instance_options[:compact]
object.quote.id.to_s
else
REST::NestedQuoteSerializer.new(object.quote, root: false, relationships: instance_options[:relationships], account_relationships: instance_options[:account_relationships], compact: false, scope: current_user, scope_name: :current_user)
end
end
end end