Merge commit '0caf334891e0a5f4e995f1d667eb83d3aad7e41e' into glitch-soc/merge-upstream
Conflicts: - `lib/mastodon/version.rb`: Upstream updated the API version, while we also have a custom glitch API version. Updated as upstream did.
This commit is contained in:
commit
ae2ff31f32
@ -1,3 +0,0 @@
|
||||
---
|
||||
ignore:
|
||||
- CVE-2026-45363
|
||||
4
Gemfile
4
Gemfile
@ -135,7 +135,7 @@ group :test do
|
||||
# Browser integration testing
|
||||
gem 'capybara', '~> 3.39'
|
||||
gem 'capybara-playwright-driver'
|
||||
gem 'playwright-ruby-client', '1.59.1', require: false # Pinning the exact version as it needs to be kept in sync with the installed npm package
|
||||
gem 'playwright-ruby-client', '1.60.0', require: false # Pinning the exact version as it needs to be kept in sync with the installed npm package
|
||||
|
||||
# Used to reset the database between system tests
|
||||
gem 'database_cleaner-active_record'
|
||||
@ -223,7 +223,7 @@ gem 'concurrent-ruby', require: false
|
||||
gem 'connection_pool', require: false
|
||||
gem 'xorcist', '~> 1.1'
|
||||
|
||||
gem 'net-http', '~> 0.6.0'
|
||||
gem 'net-http', '~> 0.9.0'
|
||||
gem 'rubyzip', '~> 3.0'
|
||||
|
||||
gem 'hcaptcha', '~> 7.1'
|
||||
|
||||
24
Gemfile.lock
24
Gemfile.lock
@ -99,8 +99,8 @@ GEM
|
||||
ast (2.4.3)
|
||||
attr_required (1.0.2)
|
||||
aws-eventstream (1.4.0)
|
||||
aws-partitions (1.1253.0)
|
||||
aws-sdk-core (3.249.0)
|
||||
aws-partitions (1.1254.0)
|
||||
aws-sdk-core (3.250.0)
|
||||
aws-eventstream (~> 1, >= 1.3.0)
|
||||
aws-partitions (~> 1, >= 1.992.0)
|
||||
aws-sigv4 (~> 1.9)
|
||||
@ -378,7 +378,7 @@ GEM
|
||||
addressable (~> 2.8)
|
||||
bigdecimal (>= 3.1, < 5)
|
||||
jsonapi-renderer (0.2.2)
|
||||
jwt (2.10.2)
|
||||
jwt (2.10.3)
|
||||
base64
|
||||
kaminari (1.2.2)
|
||||
activesupport (>= 4.1.0)
|
||||
@ -455,8 +455,8 @@ GEM
|
||||
msgpack (1.8.0)
|
||||
multi_json (1.20.1)
|
||||
mutex_m (0.3.0)
|
||||
net-http (0.6.0)
|
||||
uri
|
||||
net-http (0.9.1)
|
||||
uri (>= 0.11.1)
|
||||
net-imap (0.6.4)
|
||||
date
|
||||
net-protocol
|
||||
@ -539,7 +539,7 @@ GEM
|
||||
opentelemetry-instrumentation-active_support (~> 0.10)
|
||||
opentelemetry-instrumentation-active_support (0.12.0)
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
opentelemetry-instrumentation-base (0.26.0)
|
||||
opentelemetry-instrumentation-base (0.26.1)
|
||||
opentelemetry-api (~> 1.7)
|
||||
opentelemetry-common (~> 0.21)
|
||||
opentelemetry-registry (~> 0.1)
|
||||
@ -559,7 +559,7 @@ GEM
|
||||
opentelemetry-helpers-sql
|
||||
opentelemetry-helpers-sql-processor
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
opentelemetry-instrumentation-rack (0.31.0)
|
||||
opentelemetry-instrumentation-rack (0.31.1)
|
||||
opentelemetry-instrumentation-base (~> 0.25)
|
||||
opentelemetry-instrumentation-rails (0.42.0)
|
||||
opentelemetry-instrumentation-action_mailer (~> 0.7)
|
||||
@ -598,7 +598,7 @@ GEM
|
||||
pg (1.6.3)
|
||||
pghero (3.8.0)
|
||||
activerecord (>= 7.2)
|
||||
playwright-ruby-client (1.59.1)
|
||||
playwright-ruby-client (1.60.0)
|
||||
base64
|
||||
concurrent-ruby (>= 1.1.6)
|
||||
mime-types (>= 3.0)
|
||||
@ -816,7 +816,7 @@ GEM
|
||||
securerandom (0.4.1)
|
||||
shoulda-matchers (7.0.1)
|
||||
activesupport (>= 7.1)
|
||||
sidekiq (8.1.5)
|
||||
sidekiq (8.1.6)
|
||||
connection_pool (>= 3.0.0)
|
||||
json (>= 2.16.0)
|
||||
logger (>= 1.7.0)
|
||||
@ -827,7 +827,7 @@ GEM
|
||||
sidekiq-scheduler (6.0.2)
|
||||
rufus-scheduler (~> 3.2)
|
||||
sidekiq (>= 7.3, < 9)
|
||||
sidekiq-unique-jobs (8.0.13)
|
||||
sidekiq-unique-jobs (8.1.0)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||
sidekiq (>= 7.0.0, < 9.0.0)
|
||||
thor (>= 1.0, < 3.0)
|
||||
@ -1012,7 +1012,7 @@ DEPENDENCIES
|
||||
memory_profiler
|
||||
mime-types (~> 3.7.0)
|
||||
mutex_m
|
||||
net-http (~> 0.6.0)
|
||||
net-http (~> 0.9.0)
|
||||
net-ldap (~> 0.18)
|
||||
nokogiri (~> 1.15)
|
||||
omniauth (~> 2.0)
|
||||
@ -1040,7 +1040,7 @@ DEPENDENCIES
|
||||
parslet
|
||||
pg (~> 1.5)
|
||||
pghero
|
||||
playwright-ruby-client (= 1.59.1)
|
||||
playwright-ruby-client (= 1.60.0)
|
||||
premailer-rails
|
||||
prometheus_exporter (~> 2.2)
|
||||
propshaft
|
||||
|
||||
@ -9,7 +9,6 @@ class ActivityPub::FeaturedCollectionsController < ApplicationController
|
||||
|
||||
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
|
||||
|
||||
before_action :check_feature_enabled
|
||||
before_action :require_account_signature!, if: -> { authorized_fetch_mode? }
|
||||
before_action :set_collections
|
||||
|
||||
@ -72,8 +71,4 @@ class ActivityPub::FeaturedCollectionsController < ApplicationController
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def check_feature_enabled
|
||||
raise ActionController::RoutingError unless Mastodon::Feature.collections_enabled?
|
||||
end
|
||||
end
|
||||
|
||||
@ -25,6 +25,8 @@ module Admin
|
||||
@action_logs = @report.history.includes(:target)
|
||||
@form = Admin::StatusBatchAction.new
|
||||
@statuses = @report.statuses.with_includes
|
||||
@collections = @report.collections
|
||||
@collection_form = Admin::CollectionBatchAction.new
|
||||
|
||||
render 'admin/reports/show'
|
||||
end
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1Alpha::CollectionItemsController < Api::BaseController
|
||||
class Api::V1::CollectionItemsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
before_action :check_feature_enabled
|
||||
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:collections' }
|
||||
|
||||
before_action :require_user!
|
||||
@ -55,8 +53,4 @@ class Api::V1Alpha::CollectionItemsController < Api::BaseController
|
||||
def set_collection_item
|
||||
@collection_item = @collection.collection_items.find(params[:id])
|
||||
end
|
||||
|
||||
def check_feature_enabled
|
||||
raise ActionController::RoutingError unless Mastodon::Feature.collections_enabled?
|
||||
end
|
||||
end
|
||||
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1Alpha::CollectionsController < Api::BaseController
|
||||
class Api::V1::CollectionsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
DEFAULT_COLLECTIONS_LIMIT = 40
|
||||
@ -9,8 +9,6 @@ class Api::V1Alpha::CollectionsController < Api::BaseController
|
||||
render json: { error: ValidationErrorFormatter.new(e).as_json }, status: 422
|
||||
end
|
||||
|
||||
before_action :check_feature_enabled
|
||||
|
||||
before_action -> { authorize_if_got_token! :read, :'read:collections' }, only: [:index, :show]
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:collections' }, only: [:create, :update, :destroy]
|
||||
|
||||
@ -91,20 +89,16 @@ class Api::V1Alpha::CollectionsController < Api::BaseController
|
||||
params.permit(:name, :description, :language, :sensitive, :discoverable, :tag_name)
|
||||
end
|
||||
|
||||
def check_feature_enabled
|
||||
raise ActionController::RoutingError unless Mastodon::Feature.collections_enabled?
|
||||
end
|
||||
|
||||
def next_path
|
||||
return unless records_continue?
|
||||
|
||||
api_v1_alpha_account_collections_url(@account, pagination_params(offset: offset_param + limit_param(DEFAULT_COLLECTIONS_LIMIT)))
|
||||
api_v1_account_collections_url(@account, pagination_params(offset: offset_param + limit_param(DEFAULT_COLLECTIONS_LIMIT)))
|
||||
end
|
||||
|
||||
def prev_path
|
||||
return if offset_param.zero?
|
||||
|
||||
api_v1_alpha_account_collections_url(@account, pagination_params(offset: offset_param - limit_param(DEFAULT_COLLECTIONS_LIMIT)))
|
||||
api_v1_account_collections_url(@account, pagination_params(offset: offset_param - limit_param(DEFAULT_COLLECTIONS_LIMIT)))
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@ -1,12 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1Alpha::InCollectionsController < Api::BaseController
|
||||
class Api::V1::InCollectionsController < Api::BaseController
|
||||
include Authorization
|
||||
|
||||
DEFAULT_COLLECTIONS_LIMIT = 40
|
||||
|
||||
before_action :check_feature_enabled
|
||||
|
||||
before_action -> { authorize_if_got_token! :read, :'read:collections' }, only: [:index]
|
||||
|
||||
before_action :require_user!
|
||||
@ -37,20 +35,16 @@ class Api::V1Alpha::InCollectionsController < Api::BaseController
|
||||
.limit(limit_param(DEFAULT_COLLECTIONS_LIMIT))
|
||||
end
|
||||
|
||||
def check_feature_enabled
|
||||
raise ActionController::RoutingError unless Mastodon::Feature.collections_enabled?
|
||||
end
|
||||
|
||||
def next_path
|
||||
return unless records_continue?
|
||||
|
||||
api_v1_alpha_account_in_collections_url(@account, pagination_params(offset: offset_param + limit_param(DEFAULT_COLLECTIONS_LIMIT)))
|
||||
api_v1_account_in_collections_url(@account, pagination_params(offset: offset_param + limit_param(DEFAULT_COLLECTIONS_LIMIT)))
|
||||
end
|
||||
|
||||
def prev_path
|
||||
return if offset_param.zero?
|
||||
|
||||
api_v1_alpha_account_in_collections_url(@account, pagination_params(offset: offset_param - limit_param(DEFAULT_COLLECTIONS_LIMIT)))
|
||||
api_v1_account_in_collections_url(@account, pagination_params(offset: offset_param - limit_param(DEFAULT_COLLECTIONS_LIMIT)))
|
||||
end
|
||||
|
||||
def records_continue?
|
||||
@ -23,10 +23,6 @@ class Api::V1::ReportsController < Api::BaseController
|
||||
end
|
||||
|
||||
def report_params
|
||||
if Mastodon::Feature.collections_enabled?
|
||||
params.permit(:account_id, :comment, :category, :forward, forward_to_domains: [], status_ids: [], collection_ids: [], rule_ids: [])
|
||||
else
|
||||
params.permit(:account_id, :comment, :category, :forward, forward_to_domains: [], status_ids: [], rule_ids: [])
|
||||
end
|
||||
params.permit(:account_id, :comment, :category, :forward, forward_to_domains: [], status_ids: [], collection_ids: [], rule_ids: [])
|
||||
end
|
||||
end
|
||||
|
||||
@ -175,6 +175,6 @@ class Api::V1::StatusesController < Api::BaseController
|
||||
end
|
||||
|
||||
def serialized_accounts(accounts)
|
||||
ActiveModel::Serializer::CollectionSerializer.new(accounts, serializer: REST::AccountSerializer)
|
||||
ActiveModel::Serializer::CollectionSerializer.new(accounts, serializer: REST::AccountSerializer, scope_name: :current_user, scope: current_user)
|
||||
end
|
||||
end
|
||||
|
||||
@ -7,7 +7,6 @@ class CollectionItemsController < ApplicationController
|
||||
|
||||
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
|
||||
|
||||
before_action :check_feature_enabled
|
||||
before_action :require_account_signature!, if: -> { authorized_fetch_mode? }
|
||||
before_action :set_collection_item
|
||||
|
||||
@ -35,8 +34,4 @@ class CollectionItemsController < ApplicationController
|
||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
def check_feature_enabled
|
||||
raise ActionController::RoutingError unless Mastodon::Feature.collections_enabled?
|
||||
end
|
||||
end
|
||||
|
||||
@ -8,7 +8,6 @@ class CollectionsController < ApplicationController
|
||||
|
||||
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
|
||||
|
||||
before_action :check_feature_enabled
|
||||
before_action :require_account_signature!, only: :show, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_collection
|
||||
|
||||
@ -51,8 +50,4 @@ class CollectionsController < ApplicationController
|
||||
recently_updated = @collection.updated_at > 15.minutes.ago
|
||||
recently_updated ? 30.seconds : 5.minutes
|
||||
end
|
||||
|
||||
def check_feature_enabled
|
||||
raise ActionController::RoutingError unless Mastodon::Feature.collections_enabled?
|
||||
end
|
||||
end
|
||||
|
||||
@ -3,6 +3,7 @@ import { createAction } from '@reduxjs/toolkit';
|
||||
import {
|
||||
apiRemoveAccountFromFollowers,
|
||||
apiGetEndorsedAccounts,
|
||||
apiGetAccounts,
|
||||
} from 'mastodon/api/accounts';
|
||||
import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships';
|
||||
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
|
||||
@ -113,3 +114,12 @@ export const fetchEndorsedAccounts = createDataLoadingThunk(
|
||||
return data;
|
||||
},
|
||||
);
|
||||
|
||||
export const fetchAccounts = createDataLoadingThunk(
|
||||
'accounts/multi_accounts',
|
||||
({ accountIds }: { accountIds: string[] }) => apiGetAccounts(accountIds),
|
||||
(data, { dispatch }) => {
|
||||
dispatch(importFetchedAccounts(data));
|
||||
return data;
|
||||
},
|
||||
);
|
||||
|
||||
@ -36,9 +36,18 @@ function notificationTypeForFilter(type: NotificationType) {
|
||||
}
|
||||
|
||||
function notificationTypeForQuickFilter(type: NotificationType) {
|
||||
if (type === 'quoted_update') return 'update';
|
||||
else if (type === 'quote') return 'mention';
|
||||
else return type;
|
||||
switch (type) {
|
||||
case 'quoted_update':
|
||||
return 'update';
|
||||
case 'quote':
|
||||
return 'mention';
|
||||
case 'collection_update':
|
||||
return 'collection';
|
||||
case 'added_to_collection':
|
||||
return 'collection';
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
function excludeAllTypesExcept(filter: string) {
|
||||
|
||||
@ -86,6 +86,9 @@ export function setupBrowserNotifications() {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {(NotificationPermission) => void} callback
|
||||
*/
|
||||
export function requestBrowserPermission(callback = noOp) {
|
||||
return dispatch => {
|
||||
requestNotificationPermission((permission) => {
|
||||
|
||||
@ -19,6 +19,11 @@ import type {
|
||||
ApiProfileUpdateParams,
|
||||
} from '../api_types/profile';
|
||||
|
||||
export const apiGetAccounts = (ids: string[]) =>
|
||||
apiRequestGet<ApiAccountJSON[]>('v1/accounts', {
|
||||
id: ids,
|
||||
});
|
||||
|
||||
export const apiSubmitAccountNote = (id: string, value: string) =>
|
||||
apiRequestPost<ApiRelationshipJSON>(`v1/accounts/${id}/note`, {
|
||||
comment: value,
|
||||
|
||||
@ -15,48 +15,40 @@ import type {
|
||||
} from '../api_types/collections';
|
||||
|
||||
export const apiCreateCollection = (collection: ApiCreateCollectionPayload) =>
|
||||
apiRequestPost<ApiWrappedCollectionJSON>('v1_alpha/collections', collection);
|
||||
apiRequestPost<ApiWrappedCollectionJSON>('v1/collections', collection);
|
||||
|
||||
export const apiUpdateCollection = ({
|
||||
id,
|
||||
...collection
|
||||
}: ApiUpdateCollectionPayload) =>
|
||||
apiRequestPut<ApiWrappedCollectionJSON>(
|
||||
`v1_alpha/collections/${id}`,
|
||||
collection,
|
||||
);
|
||||
apiRequestPut<ApiWrappedCollectionJSON>(`v1/collections/${id}`, collection);
|
||||
|
||||
export const apiDeleteCollection = (collectionId: string) =>
|
||||
apiRequestDelete(`v1_alpha/collections/${collectionId}`);
|
||||
apiRequestDelete(`v1/collections/${collectionId}`);
|
||||
|
||||
export const apiGetCollection = (collectionId: string) =>
|
||||
apiRequestGet<ApiCollectionWithAccountsJSON>(
|
||||
`v1_alpha/collections/${collectionId}`,
|
||||
`v1/collections/${collectionId}`,
|
||||
);
|
||||
|
||||
export const apiGetCollectionsCreatedByAccount = (accountId: string) =>
|
||||
apiRequestGet<ApiCollectionsJSON>(
|
||||
`v1_alpha/accounts/${accountId}/collections`,
|
||||
);
|
||||
apiRequestGet<ApiCollectionsJSON>(`v1/accounts/${accountId}/collections`);
|
||||
|
||||
export const apiGetCollectionsFeaturingAccount = (accountId: string) =>
|
||||
apiRequestGet<ApiCollectionsJSON>(
|
||||
`v1_alpha/accounts/${accountId}/in_collections`,
|
||||
);
|
||||
apiRequestGet<ApiCollectionsJSON>(`v1/accounts/${accountId}/in_collections`);
|
||||
|
||||
export const apiAddCollectionItem = (collectionId: string, accountId: string) =>
|
||||
apiRequestPost<WrappedCollectionAccountItem>(
|
||||
`v1_alpha/collections/${collectionId}/items`,
|
||||
`v1/collections/${collectionId}/items`,
|
||||
{ account_id: accountId },
|
||||
);
|
||||
|
||||
export const apiRemoveCollectionItem = (collectionId: string, itemId: string) =>
|
||||
apiRequestDelete<WrappedCollectionAccountItem>(
|
||||
`v1_alpha/collections/${collectionId}/items/${itemId}`,
|
||||
`v1/collections/${collectionId}/items/${itemId}`,
|
||||
);
|
||||
|
||||
export const apiRevokeCollectionInclusion = (
|
||||
collectionId: string,
|
||||
itemId: string,
|
||||
) =>
|
||||
apiRequestPost(`v1_alpha/collections/${collectionId}/items/${itemId}/revoke`);
|
||||
) => apiRequestPost(`v1/collections/${collectionId}/items/${itemId}/revoke`);
|
||||
|
||||
@ -15,7 +15,7 @@ export const apiUpdate = (list: Partial<ApiListJSON>) =>
|
||||
|
||||
export const apiGetLists = () => apiRequestGet<ApiListJSON[]>('v1/lists');
|
||||
|
||||
export const apiGetAccounts = (listId: string) =>
|
||||
export const apiGetListAccounts = (listId: string) =>
|
||||
apiRequestGet<ApiAccountJSON[]>(`v1/lists/${listId}/accounts`, {
|
||||
limit: 0,
|
||||
});
|
||||
|
||||
@ -110,6 +110,9 @@
|
||||
word-break: break-all;
|
||||
text-align: left;
|
||||
|
||||
/* Allow the handle text to be selected */
|
||||
user-select: text;
|
||||
|
||||
> svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
@ -100,6 +100,10 @@ interface ComboboxProps<
|
||||
* Icon to be displayed in the text input
|
||||
*/
|
||||
icon?: TextInputProps['icon'] | null;
|
||||
/**
|
||||
* Set to true to open as soon as there is focus
|
||||
*/
|
||||
openOnFocus?: boolean;
|
||||
/**
|
||||
* Set to false to keep the menu open when an item is selected
|
||||
*/
|
||||
@ -217,8 +221,10 @@ const ComboboxWithRef = <Item extends ComboboxItem, GroupKey extends string>(
|
||||
renderGroupTitle,
|
||||
renderItem,
|
||||
onSelectItem,
|
||||
onFocus,
|
||||
onChange,
|
||||
onKeyDown,
|
||||
openOnFocus = false,
|
||||
closeOnSelect = true,
|
||||
suppressMenu = false,
|
||||
icon = SearchIcon,
|
||||
@ -288,6 +294,16 @@ const ComboboxWithRef = <Item extends ComboboxItem, GroupKey extends string>(
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleFocus: React.FocusEventHandler<HTMLInputElement> = useCallback(
|
||||
(e) => {
|
||||
if (openOnFocus) {
|
||||
setShouldMenuOpen(true);
|
||||
}
|
||||
onFocus?.(e);
|
||||
},
|
||||
[onFocus, openOnFocus],
|
||||
);
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onChange(e);
|
||||
@ -487,6 +503,7 @@ const ComboboxWithRef = <Item extends ComboboxItem, GroupKey extends string>(
|
||||
autoComplete='off'
|
||||
spellCheck='false'
|
||||
value={value}
|
||||
onFocus={handleFocus}
|
||||
onChange={handleInputChange}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
icon={icon ?? undefined}
|
||||
|
||||
@ -9,7 +9,6 @@ import { Button } from '@/mastodon/components/button';
|
||||
import { DisplayName } from '@/mastodon/components/display_name';
|
||||
import { EmptyState } from '@/mastodon/components/empty_state';
|
||||
import { LimitedAccountHint } from '@/mastodon/components/limited_account_hint';
|
||||
import { areCollectionsEnabled } from '@/mastodon/features/collections/utils';
|
||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
|
||||
import { useAppDispatch } from '@/mastodon/store';
|
||||
@ -50,56 +49,39 @@ export const EmptyMessage: React.FC<EmptyMessageProps> = ({
|
||||
let title: React.ReactNode = null;
|
||||
let message: React.ReactNode = null;
|
||||
|
||||
const hasCollections = areCollectionsEnabled();
|
||||
|
||||
if (me === accountId) {
|
||||
if (hasCollections) {
|
||||
// Return only here to insert the "Create a collection" button as the action for the empty state.
|
||||
return (
|
||||
<EmptyState
|
||||
title={
|
||||
// Return only here to insert the "Create a collection" button as the action for the empty state.
|
||||
return (
|
||||
<EmptyState
|
||||
title={
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.showcase_accounts'
|
||||
defaultMessage='Showcase your favorite accounts'
|
||||
/>
|
||||
}
|
||||
message={
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.showcase_accounts_desc'
|
||||
defaultMessage='Collections are curated lists of accounts to help others discover more of the Fediverse.'
|
||||
/>
|
||||
}
|
||||
>
|
||||
{!withoutAddCollectionButton && (
|
||||
<Link to='/collections/new' className='button'>
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.showcase_accounts'
|
||||
defaultMessage='Showcase your favorite accounts'
|
||||
id='empty_column.account_featured_self.no_collections_button'
|
||||
defaultMessage='Create a collection'
|
||||
/>
|
||||
}
|
||||
message={
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.showcase_accounts_desc'
|
||||
defaultMessage='Collections are curated lists of accounts to help others discover more of the Fediverse.'
|
||||
/>
|
||||
}
|
||||
>
|
||||
{!withoutAddCollectionButton && (
|
||||
<Link to='/collections/new' className='button'>
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.no_collections_button'
|
||||
defaultMessage='Create a collection'
|
||||
/>
|
||||
</Link>
|
||||
)}
|
||||
<Button secondary onClick={confirmHideFeaturedTab}>
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.no_collections_hide_tab'
|
||||
defaultMessage='Hide this tab instead'
|
||||
/>
|
||||
</Button>
|
||||
</EmptyState>
|
||||
);
|
||||
} else {
|
||||
title = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.pre_collections'
|
||||
defaultMessage='Stay tuned for Collections'
|
||||
/>
|
||||
);
|
||||
message = (
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.pre_collections_desc'
|
||||
defaultMessage='Collections (coming in Mastodon 4.6) allow you to create your own curated lists of accounts to recommend to others.'
|
||||
/>
|
||||
);
|
||||
}
|
||||
</Link>
|
||||
)}
|
||||
<Button secondary onClick={confirmHideFeaturedTab}>
|
||||
<FormattedMessage
|
||||
id='empty_column.account_featured_self.no_collections_hide_tab'
|
||||
defaultMessage='Hide this tab instead'
|
||||
/>
|
||||
</Button>
|
||||
</EmptyState>
|
||||
);
|
||||
} else if (suspended) {
|
||||
title = (
|
||||
<FormattedMessage
|
||||
|
||||
@ -30,13 +30,10 @@ import AddIcon from '@/material-icons/400-24px/add.svg?react';
|
||||
|
||||
import { CollectionListItem } from '../collections/components/collection_list_item';
|
||||
import { useCollectionsCreatedBy } from '../collections/overview/created_by_you';
|
||||
import { areCollectionsEnabled } from '../collections/utils';
|
||||
|
||||
import { EmptyMessage } from './components/empty_message';
|
||||
import { Subheading, SubheadingLink } from './components/subheading';
|
||||
|
||||
const collectionsEnabled = areCollectionsEnabled();
|
||||
|
||||
const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
multiColumn,
|
||||
}) => {
|
||||
@ -98,14 +95,11 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
);
|
||||
|
||||
const hasCollections =
|
||||
collectionsEnabled &&
|
||||
collectionsLoadStatus === 'idle' &&
|
||||
listedCollections.length > 0;
|
||||
collectionsLoadStatus === 'idle' && listedCollections.length > 0;
|
||||
|
||||
const hasFeaturedAccounts = !featuredAccountIds.isEmpty();
|
||||
|
||||
const isLoading =
|
||||
!accountId || (collectionsEnabled && collectionsLoadStatus !== 'idle');
|
||||
const isLoading = !accountId || collectionsLoadStatus !== 'idle';
|
||||
|
||||
if (accountId === null) {
|
||||
return <BundleColumnError multiColumn={multiColumn} errorType='routing' />;
|
||||
@ -165,57 +159,53 @@ const AccountFeatured: React.FC<{ multiColumn: boolean }> = ({
|
||||
</ItemList>
|
||||
</>
|
||||
)}
|
||||
{collectionsEnabled && (
|
||||
<>
|
||||
<Subheading as='header'>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id='account.featured.collections'
|
||||
defaultMessage='Collections'
|
||||
/>
|
||||
</h2>
|
||||
{accountId === me && (
|
||||
<SubheadingLink to='/collections/new' icon={AddIcon}>
|
||||
<FormattedMessage
|
||||
id='account.featured.new_collection'
|
||||
defaultMessage='New collection'
|
||||
/>
|
||||
</SubheadingLink>
|
||||
)}
|
||||
</Subheading>
|
||||
{hasCollections ? (
|
||||
<ItemList>
|
||||
<TruncatedListItems
|
||||
visibleItems={listedCollections}
|
||||
truncatedItems={unlistedCollections}
|
||||
toggleButton={{
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id='collections.unlisted_collections_with_count'
|
||||
defaultMessage='Unlisted collections ({count})'
|
||||
values={{ count: unlistedCollections.length }}
|
||||
/>
|
||||
),
|
||||
subtitle: (
|
||||
<FormattedMessage
|
||||
id='collections.unlisted_collections_description'
|
||||
defaultMessage='These don’t appear on your profile to others. Anyone with the link can discover them.'
|
||||
/>
|
||||
),
|
||||
}}
|
||||
renderListItem={renderListItem}
|
||||
/>
|
||||
</ItemList>
|
||||
) : (
|
||||
<EmptyMessage
|
||||
withoutAddCollectionButton
|
||||
blockedBy={blockedBy}
|
||||
hidden={hidden}
|
||||
suspended={suspended}
|
||||
accountId={accountId}
|
||||
<Subheading as='header'>
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id='account.featured.collections'
|
||||
defaultMessage='Collections'
|
||||
/>
|
||||
</h2>
|
||||
{accountId === me && (
|
||||
<SubheadingLink to='/collections/new' icon={AddIcon}>
|
||||
<FormattedMessage
|
||||
id='account.featured.new_collection'
|
||||
defaultMessage='New collection'
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
</SubheadingLink>
|
||||
)}
|
||||
</Subheading>
|
||||
{hasCollections ? (
|
||||
<ItemList>
|
||||
<TruncatedListItems
|
||||
visibleItems={listedCollections}
|
||||
truncatedItems={unlistedCollections}
|
||||
toggleButton={{
|
||||
title: (
|
||||
<FormattedMessage
|
||||
id='collections.unlisted_collections_with_count'
|
||||
defaultMessage='Unlisted collections ({count})'
|
||||
values={{ count: unlistedCollections.length }}
|
||||
/>
|
||||
),
|
||||
subtitle: (
|
||||
<FormattedMessage
|
||||
id='collections.unlisted_collections_description'
|
||||
defaultMessage='These don’t appear on your profile to others. Anyone with the link can discover them.'
|
||||
/>
|
||||
),
|
||||
}}
|
||||
renderListItem={renderListItem}
|
||||
/>
|
||||
</ItemList>
|
||||
) : (
|
||||
<EmptyMessage
|
||||
withoutAddCollectionButton
|
||||
blockedBy={blockedBy}
|
||||
hidden={hidden}
|
||||
suspended={suspended}
|
||||
accountId={accountId}
|
||||
/>
|
||||
)}
|
||||
<RemoteHint accountId={accountId} />
|
||||
</Scrollable>
|
||||
|
||||
@ -215,6 +215,7 @@ export const CollectionAccounts: React.FC<{
|
||||
resetAccounts,
|
||||
} = useSearchAccounts({
|
||||
withRelationships: true,
|
||||
withDefaultFollows: searchValue === '',
|
||||
// Don't suggest accounts that were already added
|
||||
filterResults: (account) =>
|
||||
!editorItems.find((item) => item.account_id === account.id),
|
||||
@ -363,6 +364,7 @@ export const CollectionAccounts: React.FC<{
|
||||
)}
|
||||
{hasPendingItems && <PendingNote />}
|
||||
<ComboboxField
|
||||
openOnFocus
|
||||
id={inputId}
|
||||
label={intl.formatMessage({
|
||||
id: 'collections.search_accounts_label',
|
||||
|
||||
@ -23,7 +23,6 @@ import {
|
||||
userCollectionLimit,
|
||||
} from '../editor';
|
||||
import classes from '../styles.module.scss';
|
||||
import { areCollectionsEnabled } from '../utils';
|
||||
|
||||
const CreateButton: React.FC = () => (
|
||||
<Link to='/collections/new' className='button button--compact'>
|
||||
@ -49,7 +48,7 @@ export function useCollectionsCreatedBy(accountId: string | null | undefined) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (accountId && areCollectionsEnabled()) {
|
||||
if (accountId) {
|
||||
void dispatch(fetchCollectionsCreatedByAccount({ accountId }));
|
||||
}
|
||||
}, [dispatch, accountId]);
|
||||
|
||||
@ -15,7 +15,6 @@ import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { CollectionListItem } from '../components/collection_list_item';
|
||||
import classes from '../styles.module.scss';
|
||||
import { areCollectionsEnabled } from '../utils';
|
||||
|
||||
import { CollectionListError } from './created_by_you';
|
||||
|
||||
@ -23,7 +22,7 @@ function useCollectionsFeaturing(accountId: string | null | undefined) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
if (accountId && areCollectionsEnabled()) {
|
||||
if (accountId) {
|
||||
void dispatch(fetchCollectionsFeaturingAccount({ accountId }));
|
||||
}
|
||||
}, [dispatch, accountId]);
|
||||
|
||||
@ -1,18 +1,13 @@
|
||||
import type { ApiMutedAccountJSON } from '@/mastodon/api_types/accounts';
|
||||
import type { ApiAccountJSON } from '@/mastodon/api_types/accounts';
|
||||
import type { Account } from '@/mastodon/models/account';
|
||||
import { isServerFeatureEnabled } from '@/mastodon/utils/environment';
|
||||
|
||||
export function areCollectionsEnabled() {
|
||||
return isServerFeatureEnabled('collections');
|
||||
}
|
||||
|
||||
export const getCollectionPath = (id: string) => `/collections/${id}`;
|
||||
|
||||
export const canAccountBeAdded = (account: ApiMutedAccountJSON | Account) =>
|
||||
export const canAccountBeAdded = (account: ApiAccountJSON | Account) =>
|
||||
['automatic', 'manual'].includes(account.feature_approval.current_user);
|
||||
|
||||
export const canAccountBeAddedByFollowers = (
|
||||
account: ApiMutedAccountJSON | Account,
|
||||
account: ApiAccountJSON | Account,
|
||||
) =>
|
||||
account.feature_approval.automatic.includes('followers') ||
|
||||
account.feature_approval.manual.includes('followers');
|
||||
|
||||
@ -15,7 +15,7 @@ import { fetchList } from 'mastodon/actions/lists';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { apiFollowAccount } from 'mastodon/api/accounts';
|
||||
import {
|
||||
apiGetAccounts,
|
||||
apiGetListAccounts,
|
||||
apiAddAccountToList,
|
||||
apiRemoveAccountFromList,
|
||||
} from 'mastodon/api/lists';
|
||||
@ -184,7 +184,7 @@ const ListMembers: React.FC<{
|
||||
if (id) {
|
||||
dispatch(fetchList(id));
|
||||
|
||||
void apiGetAccounts(id)
|
||||
void apiGetListAccounts(id)
|
||||
.then((data) => {
|
||||
dispatch(importFetchedAccounts(data));
|
||||
setAccountIds(data.map((a) => a.id));
|
||||
|
||||
@ -12,7 +12,7 @@ import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react'
|
||||
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
|
||||
import { fetchList } from 'mastodon/actions/lists';
|
||||
import { createList, updateList } from 'mastodon/actions/lists_typed';
|
||||
import { apiGetAccounts } from 'mastodon/api/lists';
|
||||
import { apiGetListAccounts } from 'mastodon/api/lists';
|
||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||
import type { RepliesPolicyType } from 'mastodon/api_types/lists';
|
||||
import { Avatar } from 'mastodon/components/avatar';
|
||||
@ -44,7 +44,7 @@ const MembersLink: React.FC<{
|
||||
const [avatarAccounts, setAvatarAccounts] = useState<ApiAccountJSON[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
void apiGetAccounts(id)
|
||||
void apiGetListAccounts(id)
|
||||
.then((data) => {
|
||||
setAvatarCount(data.length);
|
||||
setAvatarAccounts(data.slice(0, 3));
|
||||
|
||||
@ -51,7 +51,6 @@ import { selectUnreadNotificationGroupsCount } from 'mastodon/selectors/notifica
|
||||
import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { AnnualReportNavItem } from '../annual_report/nav_item';
|
||||
import { areCollectionsEnabled } from '../collections/utils';
|
||||
|
||||
import { DisabledAccountBanner } from './components/disabled_account_banner';
|
||||
import { FollowedTagsPanel } from './components/followed_tags_panel';
|
||||
@ -361,18 +360,16 @@ export const NavigationPanel: React.FC<{ multiColumn?: boolean }> = ({
|
||||
text={intl.formatMessage(messages.bookmarks)}
|
||||
/>
|
||||
</li>
|
||||
{areCollectionsEnabled() && (
|
||||
<li>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to={`/@${account?.acct}/collections`}
|
||||
icon='collections'
|
||||
iconComponent={CollectionsIcon}
|
||||
activeIconComponent={CollectionsActiveIcon}
|
||||
text={intl.formatMessage(messages.collections)}
|
||||
/>
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<ColumnLink
|
||||
transparent
|
||||
to={`/@${account?.acct}/collections`}
|
||||
icon='collections'
|
||||
iconComponent={CollectionsIcon}
|
||||
activeIconComponent={CollectionsActiveIcon}
|
||||
text={intl.formatMessage(messages.collections)}
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
<ColumnLink
|
||||
transparent
|
||||
|
||||
@ -12,6 +12,7 @@ import ClearColumnButton from './clear_column_button';
|
||||
import GrantPermissionButton from './grant_permission_button';
|
||||
import { PolicyControls } from './policy_controls';
|
||||
import SettingToggle from './setting_toggle';
|
||||
import { ColumnSettingsGroup } from './column_settings_group';
|
||||
|
||||
class ColumnSettings extends PureComponent {
|
||||
static propTypes = {
|
||||
@ -187,6 +188,11 @@ class ColumnSettings extends PureComponent {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ColumnSettingsGroup
|
||||
type="collections"
|
||||
label={<FormattedMessage id='notifications.column_settings.collections' defaultMessage='Collections:' />}
|
||||
/>
|
||||
|
||||
{((this.props.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && (
|
||||
<section role='group' aria-labelledby='notifications-admin-sign-up'>
|
||||
<h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.sign_up' defaultMessage='New sign-ups:' /></h3>
|
||||
|
||||
@ -0,0 +1,162 @@
|
||||
import { useCallback } from 'react';
|
||||
import type { FC, ReactNode } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { showAlert } from '@/mastodon/actions/alerts';
|
||||
import { requestBrowserPermission } from '@/mastodon/actions/notifications';
|
||||
import { changeAlerts } from '@/mastodon/actions/push_notifications';
|
||||
import { changeSetting } from '@/mastodon/actions/settings';
|
||||
import {
|
||||
createAppSelector,
|
||||
useAppDispatch,
|
||||
useAppSelector,
|
||||
} from '@/mastodon/store';
|
||||
|
||||
import SettingToggle from './setting_toggle';
|
||||
|
||||
const selectNotificationSettings = createAppSelector(
|
||||
[
|
||||
(state) =>
|
||||
(state.settings as Immutable.Map<string, unknown>).get(
|
||||
'notifications',
|
||||
) as Immutable.Map<string, Immutable.Map<string, unknown> | boolean>,
|
||||
(state) => state.notifications.get('browserPermission') as string,
|
||||
(state) => state.push_notifications,
|
||||
],
|
||||
(settings, browserPermission, pushSettings) => ({
|
||||
settings,
|
||||
browserPermission: browserPermission !== 'denied',
|
||||
pushSettings,
|
||||
showPushSettings:
|
||||
pushSettings.get('supported') && pushSettings.get('enabled'),
|
||||
}),
|
||||
);
|
||||
|
||||
const messages = defineMessages({
|
||||
permissionDenied: {
|
||||
id: 'notifications.permission_denied_alert',
|
||||
defaultMessage:
|
||||
"Desktop notifications can't be enabled, as browser permission has been denied before",
|
||||
},
|
||||
});
|
||||
|
||||
type SettingsType = 'alerts' | 'shows' | 'sounds';
|
||||
|
||||
export const ColumnSettingsGroup: FC<{ label: ReactNode; type: string }> = ({
|
||||
label,
|
||||
type,
|
||||
}) => {
|
||||
const { settings, browserPermission, pushSettings, showPushSettings } =
|
||||
useAppSelector(selectNotificationSettings);
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
const handleChange = useCallback(
|
||||
(path: [SettingsType, string], checked: boolean) => {
|
||||
if (
|
||||
path[0] === 'alerts' &&
|
||||
checked &&
|
||||
typeof window.Notification !== 'undefined' &&
|
||||
Notification.permission !== 'granted'
|
||||
) {
|
||||
dispatch(
|
||||
requestBrowserPermission((permission) => {
|
||||
if (permission === 'granted') {
|
||||
dispatch(changeSetting(['notifications', ...path], checked));
|
||||
} else {
|
||||
dispatch(showAlert({ message: messages.permissionDenied }));
|
||||
}
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
dispatch(changeSetting(['notifications', ...path], checked));
|
||||
}
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
const handlePushChange = useCallback(
|
||||
(path: string[], checked: boolean) => {
|
||||
if (
|
||||
checked &&
|
||||
typeof window.Notification !== 'undefined' &&
|
||||
Notification.permission !== 'granted'
|
||||
) {
|
||||
dispatch(
|
||||
requestBrowserPermission((permission: NotificationPermission) => {
|
||||
if (permission === 'granted') {
|
||||
dispatch(changeAlerts(path, checked));
|
||||
} else {
|
||||
dispatch(showAlert({ message: messages.permissionDenied }));
|
||||
}
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
dispatch(changeAlerts(path, checked));
|
||||
}
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
return (
|
||||
<section role='group' aria-labelledby={`notifications-${type}`}>
|
||||
<h3 id={`notifications-${type}`}>{label}</h3>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle
|
||||
disabled={!browserPermission}
|
||||
prefix='notifications_desktop'
|
||||
settings={settings}
|
||||
settingPath={['alerts', type]}
|
||||
onChange={handleChange}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='notifications.column_settings.alert'
|
||||
defaultMessage='Desktop notifications'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
{showPushSettings && (
|
||||
<SettingToggle
|
||||
prefix='notifications_push'
|
||||
settings={pushSettings}
|
||||
settingPath={['alerts', type]}
|
||||
onChange={handlePushChange}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='notifications.column_settings.push'
|
||||
defaultMessage='Push notifications'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
|
||||
<SettingToggle
|
||||
prefix='notifications'
|
||||
settings={settings}
|
||||
settingPath={['shows', type]}
|
||||
onChange={handleChange}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='notifications.column_settings.show'
|
||||
defaultMessage='Show in column'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
||||
<SettingToggle
|
||||
prefix='notifications'
|
||||
settings={settings}
|
||||
settingPath={['sounds', type]}
|
||||
onChange={handleChange}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='notifications.column_settings.sound'
|
||||
defaultMessage='Play sound'
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
@ -3,6 +3,7 @@ import { useCallback } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import CollectionsIcon from '@/material-icons/400-24px/category.svg?react';
|
||||
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
|
||||
import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react';
|
||||
import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
|
||||
@ -26,6 +27,10 @@ const tooltips = defineMessages({
|
||||
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' },
|
||||
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
|
||||
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
|
||||
collections: {
|
||||
id: 'notifications.filter.collections',
|
||||
defaultMessage: 'Collections',
|
||||
},
|
||||
statuses: {
|
||||
id: 'notifications.filter.statuses',
|
||||
defaultMessage: 'Updates from people you follow',
|
||||
@ -124,6 +129,14 @@ export const FilterBar: React.FC = () => {
|
||||
>
|
||||
<Icon id='user-plus' icon={PersonAddIcon} />
|
||||
</BarButton>
|
||||
<BarButton
|
||||
selectedFilter={selectedFilter}
|
||||
type='collection'
|
||||
key='collection'
|
||||
title={intl.formatMessage(tooltips.collections)}
|
||||
>
|
||||
<Icon id='collections' icon={CollectionsIcon} />
|
||||
</BarButton>
|
||||
</div>
|
||||
);
|
||||
else
|
||||
|
||||
@ -17,7 +17,6 @@ import { blockAccount } from 'mastodon/actions/accounts';
|
||||
import { closeModal } from 'mastodon/actions/modal';
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { areCollectionsEnabled } from '../../collections/utils';
|
||||
|
||||
export const BlockModal = ({ accountId, acct }) => {
|
||||
const dispatch = useDispatch();
|
||||
@ -73,12 +72,10 @@ export const BlockModal = ({ accountId, acct }) => {
|
||||
<div><FormattedMessage id='block_modal.they_cant_mention' defaultMessage="You can't mention, follow, or quote each other." /></div>
|
||||
</li>
|
||||
|
||||
{areCollectionsEnabled() &&
|
||||
<li>
|
||||
<div className='safety-action-modal__bullet-points__icon'><Icon icon={CollectionsIcon} /></div>
|
||||
<div><FormattedMessage id='block_modal.no_collections' defaultMessage="Neither of you can add each other to collections. You'll be automatically removed from each others' existing collections, if applicable." /></div>
|
||||
</li>
|
||||
}
|
||||
<li>
|
||||
<div className='safety-action-modal__bullet-points__icon'><Icon icon={CollectionsIcon} /></div>
|
||||
<div><FormattedMessage id='block_modal.no_collections' defaultMessage="Neither of you can add each other to collections. You'll be automatically removed from each others' existing collections, if applicable." /></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
@ -93,7 +93,6 @@ import { CustomHomepage } from 'mastodon/features/custom_homepage';
|
||||
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
||||
// Without this it ends up in ~8 very commonly used bundles.
|
||||
import '../../components/status';
|
||||
import { areCollectionsEnabled } from '../collections/utils';
|
||||
import { getNavigationSkipLinkId, SkipLinks } from './components/skip_links';
|
||||
|
||||
const messages = defineMessages({
|
||||
@ -235,13 +234,9 @@ class SwitchingColumnsArea extends PureComponent {
|
||||
|
||||
<WrappedRoute path={['/@:acct', '/accounts/:id']} exact component={AccountTimeline} content={children} />
|
||||
<WrappedRoute path={['/@:acct/featured', '/accounts/:id/featured']} component={AccountFeatured} content={children} />
|
||||
{areCollectionsEnabled() &&
|
||||
[
|
||||
<WrappedRoute path={['/@:acct/collections']} component={Collections} content={children} key='collections-list' />,
|
||||
<WrappedRoute path={['/collections/new', '/collections/:id/edit']} component={CollectionsEditor} content={children} key='collections-editor' />,
|
||||
<WrappedRoute path='/collections/:id' component={CollectionDetail} content={children} key='collections-detail' />,
|
||||
]
|
||||
}
|
||||
<WrappedRoute path={['/@:acct/collections']} component={Collections} content={children} key='collections-list' />
|
||||
<WrappedRoute path={['/collections/new', '/collections/:id/edit']} component={CollectionsEditor} content={children} key='collections-editor' />
|
||||
<WrappedRoute path='/collections/:id' component={CollectionDetail} content={children} key='collections-detail' />
|
||||
<WrappedRoute path='/@:acct/tagged/:tagged?' exact component={AccountTimeline} content={children} />
|
||||
<WrappedRoute path={['/@:acct/with_replies', '/accounts/:id/with_replies']} component={AccountTimeline} content={children} componentParams={{ withReplies: true }} />
|
||||
<WrappedRoute path={['/accounts/:id/followers', '/users/:acct/followers', '/@:acct/followers']} component={Followers} content={children} />
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useCallback, useRef, useState } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
@ -8,16 +8,20 @@ import { apiRequest } from 'mastodon/api';
|
||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { useCurrentAccountId } from './useAccountId';
|
||||
|
||||
export function useSearchAccounts({
|
||||
onSettled,
|
||||
filterResults,
|
||||
resetOnInputClear = true,
|
||||
withRelationships = false,
|
||||
withDefaultFollows = false,
|
||||
}: {
|
||||
onSettled?: (value: string) => void;
|
||||
filterResults?: (account: ApiAccountJSON) => boolean;
|
||||
resetOnInputClear?: boolean;
|
||||
withRelationships?: boolean;
|
||||
withDefaultFollows?: boolean;
|
||||
} = {}) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@ -29,7 +33,7 @@ export function useSearchAccounts({
|
||||
const searchRequestRef = useRef<AbortController | null>(null);
|
||||
|
||||
const searchAccounts = useDebouncedCallback(
|
||||
(value: string) => {
|
||||
async (value: string) => {
|
||||
if (searchRequestRef.current) {
|
||||
searchRequestRef.current.abort();
|
||||
}
|
||||
@ -46,41 +50,100 @@ export function useSearchAccounts({
|
||||
|
||||
searchRequestRef.current = new AbortController();
|
||||
|
||||
void apiRequest<ApiAccountJSON[]>('GET', 'v1/accounts/search', {
|
||||
signal: searchRequestRef.current.signal,
|
||||
params: {
|
||||
q: value,
|
||||
resolve: true,
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
const accounts = filterResults ? data.filter(filterResults) : data;
|
||||
const accountIds = accounts.map((a) => a.id);
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
if (withRelationships) {
|
||||
dispatch(fetchRelationships(accountIds));
|
||||
}
|
||||
setAccounts(accounts);
|
||||
setLoadingState('idle');
|
||||
onSettled?.(value);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoadingState('error');
|
||||
onSettled?.(value);
|
||||
});
|
||||
try {
|
||||
const data = await apiRequest<ApiAccountJSON[]>(
|
||||
'GET',
|
||||
'v1/accounts/search',
|
||||
{
|
||||
signal: searchRequestRef.current.signal,
|
||||
params: {
|
||||
q: value,
|
||||
resolve: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
const accounts = filterResults ? data.filter(filterResults) : data;
|
||||
const accountIds = accounts.map((a) => a.id);
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
if (withRelationships) {
|
||||
dispatch(fetchRelationships(accountIds));
|
||||
}
|
||||
setAccounts(accounts);
|
||||
setLoadingState('idle');
|
||||
onSettled?.(value);
|
||||
} catch {
|
||||
setLoadingState('error');
|
||||
onSettled?.(value);
|
||||
}
|
||||
},
|
||||
500,
|
||||
{ leading: true, trailing: true },
|
||||
);
|
||||
|
||||
const startSearch = useCallback(
|
||||
(value: string) => {
|
||||
void searchAccounts(value);
|
||||
},
|
||||
[searchAccounts],
|
||||
);
|
||||
|
||||
const resetAccounts = useCallback(() => {
|
||||
setAccounts([]);
|
||||
}, []);
|
||||
|
||||
return {
|
||||
searchAccounts,
|
||||
resetAccounts,
|
||||
const currentUserId = useCurrentAccountId();
|
||||
const [defaultAccounts, setDefaultAccounts] = useState<
|
||||
ApiAccountJSON[] | null
|
||||
>(null);
|
||||
useEffect(() => {
|
||||
if (
|
||||
!currentUserId ||
|
||||
loadingState !== 'idle' ||
|
||||
defaultAccounts !== null ||
|
||||
!withDefaultFollows
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
async function doRequest() {
|
||||
setLoadingState('loading');
|
||||
try {
|
||||
const data = await apiRequest<ApiAccountJSON[]>(
|
||||
'GET',
|
||||
`v1/accounts/${currentUserId}/following`,
|
||||
{ params: { limit: 40 } },
|
||||
);
|
||||
const accounts = filterResults ? data.filter(filterResults) : data;
|
||||
const accountIds = accounts.map((a) => a.id);
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
if (withRelationships) {
|
||||
dispatch(fetchRelationships(accountIds));
|
||||
}
|
||||
setDefaultAccounts(accounts);
|
||||
setLoadingState('idle');
|
||||
} catch {
|
||||
setLoadingState('error');
|
||||
}
|
||||
}
|
||||
void doRequest();
|
||||
}, [
|
||||
currentUserId,
|
||||
accounts,
|
||||
dispatch,
|
||||
filterResults,
|
||||
loadingState,
|
||||
withRelationships,
|
||||
defaultAccounts,
|
||||
withDefaultFollows,
|
||||
]);
|
||||
|
||||
return {
|
||||
searchAccounts: startSearch,
|
||||
resetAccounts,
|
||||
accounts:
|
||||
accounts.length === 0 && withDefaultFollows
|
||||
? (defaultAccounts ?? [])
|
||||
: accounts,
|
||||
isLoading: loadingState === 'loading',
|
||||
isError: loadingState === 'error',
|
||||
};
|
||||
|
||||
@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Новыя скаргі:",
|
||||
"notifications.column_settings.admin.sign_up": "Новыя рэгістрацыі:",
|
||||
"notifications.column_settings.alert": "Апавяшчэнні на працоўным стале",
|
||||
"notifications.column_settings.collections": "Калекцыі:",
|
||||
"notifications.column_settings.favourite": "Упадабанае:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Паказаць усе катэгорыі",
|
||||
"notifications.column_settings.filter_bar.category": "Панэль хуткай фільтрацыі",
|
||||
@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Праўкі:",
|
||||
"notifications.filter.all": "Усе",
|
||||
"notifications.filter.boosts": "Пашырэнні",
|
||||
"notifications.filter.collections": "Калекцыі",
|
||||
"notifications.filter.favourites": "Упадабанае",
|
||||
"notifications.filter.follows": "Падпісаны на",
|
||||
"notifications.filter.mentions": "Згадванні",
|
||||
|
||||
@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Nye rapporteringer:",
|
||||
"notifications.column_settings.admin.sign_up": "Nye tilmeldinger:",
|
||||
"notifications.column_settings.alert": "Computernotifikationer",
|
||||
"notifications.column_settings.collections": "Samlinger:",
|
||||
"notifications.column_settings.favourite": "Favoritter:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Vis alle kategorier",
|
||||
"notifications.column_settings.filter_bar.category": "Hurtigfiltreringsbjælke",
|
||||
@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Redigeringer:",
|
||||
"notifications.filter.all": "Alle",
|
||||
"notifications.filter.boosts": "Fremhævelser",
|
||||
"notifications.filter.collections": "Samlinger",
|
||||
"notifications.filter.favourites": "Favoritter",
|
||||
"notifications.filter.follows": "Følger",
|
||||
"notifications.filter.mentions": "Omtaler",
|
||||
|
||||
@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Neue Meldungen:",
|
||||
"notifications.column_settings.admin.sign_up": "Neue Registrierungen:",
|
||||
"notifications.column_settings.alert": "Desktop-Benachrichtigungen",
|
||||
"notifications.column_settings.collections": "Sammlungen:",
|
||||
"notifications.column_settings.favourite": "Favoriten:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Alle Filterkategorien anzeigen",
|
||||
"notifications.column_settings.filter_bar.category": "Filterleiste",
|
||||
@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Überarbeitete Beiträge:",
|
||||
"notifications.filter.all": "Alles",
|
||||
"notifications.filter.boosts": "Geteilte Beiträge",
|
||||
"notifications.filter.collections": "Sammlungen",
|
||||
"notifications.filter.favourites": "Favoriten",
|
||||
"notifications.filter.follows": "Neue Follower",
|
||||
"notifications.filter.mentions": "Erwähnungen",
|
||||
|
||||
@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Νέες αναφορές:",
|
||||
"notifications.column_settings.admin.sign_up": "Νέες εγγραφές:",
|
||||
"notifications.column_settings.alert": "Ειδοποιήσεις για υπολογιστή",
|
||||
"notifications.column_settings.collections": "Συλλογές:",
|
||||
"notifications.column_settings.favourite": "Αγαπημένα:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Εμφάνιση όλων των κατηγοριών",
|
||||
"notifications.column_settings.filter_bar.category": "Μπάρα γρήγορου φίλτρου",
|
||||
@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Επεξεργασίες:",
|
||||
"notifications.filter.all": "Όλες",
|
||||
"notifications.filter.boosts": "Προωθήσεις",
|
||||
"notifications.filter.collections": "Συλλογές",
|
||||
"notifications.filter.favourites": "Αγαπημένα",
|
||||
"notifications.filter.follows": "Ακολουθείς",
|
||||
"notifications.filter.mentions": "Επισημάνσεις",
|
||||
|
||||
@ -637,8 +637,6 @@
|
||||
"empty_column.account_featured.other": "{acct} has not featured anything yet.",
|
||||
"empty_column.account_featured_self.no_collections_button": "Create a collection",
|
||||
"empty_column.account_featured_self.no_collections_hide_tab": "Hide this tab instead",
|
||||
"empty_column.account_featured_self.pre_collections": "Stay tuned for Collections",
|
||||
"empty_column.account_featured_self.pre_collections_desc": "Collections (coming in Mastodon 4.6) allow you to create your own curated lists of accounts to recommend to others.",
|
||||
"empty_column.account_featured_self.showcase_accounts": "Showcase your favorite accounts",
|
||||
"empty_column.account_featured_self.showcase_accounts_desc": "Collections are curated lists of accounts to help others discover more of the Fediverse.",
|
||||
"empty_column.account_featured_unknown.other": "This account hasn’t featured anything yet.",
|
||||
@ -1000,6 +998,7 @@
|
||||
"notifications.column_settings.admin.report": "New reports:",
|
||||
"notifications.column_settings.admin.sign_up": "New sign-ups:",
|
||||
"notifications.column_settings.alert": "Desktop notifications",
|
||||
"notifications.column_settings.collections": "Collections:",
|
||||
"notifications.column_settings.favourite": "Favorites:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Display all categories",
|
||||
"notifications.column_settings.filter_bar.category": "Quick filter bar",
|
||||
@ -1019,6 +1018,7 @@
|
||||
"notifications.column_settings.update": "Edits:",
|
||||
"notifications.filter.all": "All",
|
||||
"notifications.filter.boosts": "Boosts",
|
||||
"notifications.filter.collections": "Collections",
|
||||
"notifications.filter.favourites": "Favorites",
|
||||
"notifications.filter.follows": "Follows",
|
||||
"notifications.filter.mentions": "Mentions",
|
||||
|
||||
@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Nuevas denuncias:",
|
||||
"notifications.column_settings.admin.sign_up": "Nuevos registros:",
|
||||
"notifications.column_settings.alert": "Notificaciones de escritorio",
|
||||
"notifications.column_settings.collections": "Colecciones:",
|
||||
"notifications.column_settings.favourite": "Favoritos:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Mostrar todas las categorías",
|
||||
"notifications.column_settings.filter_bar.category": "Barra de filtrado rápido",
|
||||
@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Ediciones:",
|
||||
"notifications.filter.all": "Todas",
|
||||
"notifications.filter.boosts": "Adhesiones",
|
||||
"notifications.filter.collections": "Colecciones",
|
||||
"notifications.filter.favourites": "Favoritos",
|
||||
"notifications.filter.follows": "Seguidores",
|
||||
"notifications.filter.mentions": "Menciones",
|
||||
|
||||
@ -584,6 +584,12 @@
|
||||
"copy_icon_button.copy_this_text": "Copiar enlace al portapapeles",
|
||||
"copypaste.copied": "Copiado",
|
||||
"copypaste.copy_to_clipboard": "Copiar al portapapeles",
|
||||
"custom_homepage.about": "Acerca de",
|
||||
"custom_homepage.about_this_server": "Acerca de este servidor",
|
||||
"custom_homepage.administered_by": "Administrado por",
|
||||
"custom_homepage.contact": "Contacto:",
|
||||
"custom_homepage.latest_activity": "Actividad más reciente",
|
||||
"custom_homepage.these_are_the_latest_posts": "Estas son las últimas 40 publicaciones de las cuentas de este servidor.",
|
||||
"directory.federated": "Desde el fediverso conocido",
|
||||
"directory.local": "Sólo de {domain}",
|
||||
"directory.new_arrivals": "Recién llegados",
|
||||
|
||||
@ -584,6 +584,12 @@
|
||||
"copy_icon_button.copy_this_text": "Copiar enlace al portapapeles",
|
||||
"copypaste.copied": "Copiado",
|
||||
"copypaste.copy_to_clipboard": "Copiar al portapapeles",
|
||||
"custom_homepage.about": "Acerca de",
|
||||
"custom_homepage.about_this_server": "Acerca de este servidor",
|
||||
"custom_homepage.administered_by": "Administrado por",
|
||||
"custom_homepage.contact": "Contacto:",
|
||||
"custom_homepage.latest_activity": "Actividad más reciente",
|
||||
"custom_homepage.these_are_the_latest_posts": "Estas son las últimas 40 publicaciones de las cuentas de este servidor.",
|
||||
"directory.federated": "Desde el fediverso conocido",
|
||||
"directory.local": "Solo desde {domain}",
|
||||
"directory.new_arrivals": "Recién llegados",
|
||||
|
||||
@ -584,6 +584,12 @@
|
||||
"copy_icon_button.copy_this_text": "Kopeeri link lõikelauale",
|
||||
"copypaste.copied": "Kopeeritud",
|
||||
"copypaste.copy_to_clipboard": "Kopeeri vahemällu",
|
||||
"custom_homepage.about": "Teave",
|
||||
"custom_homepage.about_this_server": "Teave koduserveri kohta",
|
||||
"custom_homepage.administered_by": "Seda haldab",
|
||||
"custom_homepage.contact": "Kontakt:",
|
||||
"custom_homepage.latest_activity": "Viimane tegevus",
|
||||
"custom_homepage.these_are_the_latest_posts": "Need on selle serveri kasutajate viimased 40 postitust.",
|
||||
"directory.federated": "Tuntud födiversumist",
|
||||
"directory.local": "Ainult domeenilt {domain}",
|
||||
"directory.new_arrivals": "Uustulijad",
|
||||
@ -775,17 +781,17 @@
|
||||
"home.pending_critical_update.link": "Vaata uuendusi",
|
||||
"home.pending_critical_update.title": "Saadaval kriitiline turvauuendus!",
|
||||
"home.show_announcements": "Kuva teadaandeid",
|
||||
"ignore_notifications_modal.disclaimer": "Mastodon ei saa teavitada kasutajaid, et ignoreerisid nende teavitusi. Teavituste ignoreerimine ei peata sõnumite endi saatmist.",
|
||||
"ignore_notifications_modal.disclaimer": "Mastodon ei saa teavitada kasutajaid, et eirasid nende teavitusi. Teavituste eiramine ei peata sõnumite endi saatmist.",
|
||||
"ignore_notifications_modal.filter_instead": "Selle asemel filtreeri",
|
||||
"ignore_notifications_modal.filter_to_act_users": "Saad endiselt kasutajaid vastu võtta, tagasi lükata või neist teatada",
|
||||
"ignore_notifications_modal.filter_to_avoid_confusion": "Filtreerimine aitab vältida võimalikke segaminiajamisi",
|
||||
"ignore_notifications_modal.filter_to_review_separately": "Saad filtreeritud teateid eraldi vaadata",
|
||||
"ignore_notifications_modal.ignore": "Ignoreeri teavitusi",
|
||||
"ignore_notifications_modal.limited_accounts_title": "Ignoreeri modereeritud kontode teavitusi?",
|
||||
"ignore_notifications_modal.new_accounts_title": "Ignoreeri uute kontode teavitusi?",
|
||||
"ignore_notifications_modal.not_followers_title": "Ignoreeri inimeste teavitusi, kes sind ei jälgi?",
|
||||
"ignore_notifications_modal.not_following_title": "Ignoreeri inimeste teavitusi, keda sa ei jälgi?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Ignoreeri soovimatute eraviisiliste mainimiste teateid?",
|
||||
"ignore_notifications_modal.limited_accounts_title": "Kas eirad modereeritud kontode teavitusi?",
|
||||
"ignore_notifications_modal.new_accounts_title": "Kas eirad uute kontode teavitusi?",
|
||||
"ignore_notifications_modal.not_followers_title": "Kas eirad teavitusi kasutajatelt, kes sind ei jälgi?",
|
||||
"ignore_notifications_modal.not_following_title": "Kas eirad teavitusi kasutajatelt, keda sa ei jälgi?",
|
||||
"ignore_notifications_modal.private_mentions_title": "Kas eirad soovimatute eraviisiliste mainimiste teavitusi?",
|
||||
"info_button.label": "Abi",
|
||||
"info_button.what_is_alt_text": "<h1>Mis on alt-tekst?</h1> <p>Alt-tekst pakub pildi kirjeldust nägemispuudega inimeste jaoks või neile, kel on aeglane internet või neile, kes otsivad lisaselgitust</p> <p>Saad parandada ligipääsetavust ja mõistmist kõigi jaoks, kirjutades selge, lühida ja objektiivse alt-teksti.</p> <ul> <li>Lisa tähtsad elemendid</li> <li>Tee pildil olevast tekstist kokkuvõte</li> <li>Kasuta reeglipärast lausestruktuuri</li> <li>Väldi ebaolulist infot</li> <li>Keskendu keerukate vaadete puhul (näiteks diagrammid ja kaardid) puhul trendidele ja põhiseostele</li> </ul>",
|
||||
"interaction_modal.action": "Suhestumaks kasutaja {name} postitusega palun logi sisse oma Mastodoni kasutajakontoga sõltumata serverist, mida kasutad.",
|
||||
@ -879,7 +885,7 @@
|
||||
"loading_indicator.label": "Laadimine…",
|
||||
"media_gallery.hide": "Peida",
|
||||
"moved_to_account_banner.text": "Kontot {disabledAccount} ei ole praegu võimalik kasutada, sest kolisid kontole {movedToAccount}.",
|
||||
"mute_modal.hide_from_notifications": "Peida teavituste hulgast",
|
||||
"mute_modal.hide_from_notifications": "Peida teavituste seast",
|
||||
"mute_modal.hide_options": "Peida valikud",
|
||||
"mute_modal.indefinite": "Kuni eemaldan neilt summutamise",
|
||||
"mute_modal.show_options": "Kuva valikud",
|
||||
@ -908,6 +914,7 @@
|
||||
"navigation_bar.live_feed_local": "Ajajoon reaalajas (sinu server)",
|
||||
"navigation_bar.live_feed_public": "Ajajoon reaalajas (Födiversum)",
|
||||
"navigation_bar.logout": "Logi välja",
|
||||
"navigation_bar.main": "Esileht",
|
||||
"navigation_bar.moderation": "Modereerimine",
|
||||
"navigation_bar.more": "Lisavalikud",
|
||||
"navigation_bar.mutes": "Summutatud kasutajad",
|
||||
@ -980,8 +987,8 @@
|
||||
"notification_requests.dismiss_multiple": "{count, plural, one {Loobuda # taotlusest…} other {Loobuda # taotlusest…}}",
|
||||
"notification_requests.edit_selection": "Muuda",
|
||||
"notification_requests.exit_selection": "Valmis",
|
||||
"notification_requests.explainer_for_limited_account": "Sellelt kontolt tulevad teavitused on filtreeritud, sest moderaator on seda kontot piiranud.",
|
||||
"notification_requests.explainer_for_limited_remote_account": "Sellelt kontolt tulevad teavitused on filtreeritud, sest moderaator on seda kontot või serverit piiranud.",
|
||||
"notification_requests.explainer_for_limited_account": "Selle kasutajakontoga seotud teavitused on filtreeritud, sest moderaator on selle konto tegevust piiranud.",
|
||||
"notification_requests.explainer_for_limited_remote_account": "Selle kasutajakontoga seotud teavitused on filtreeritud, sest moderaator on selle konto tegevust või suhtlust andtu koduserveriga piiranud.",
|
||||
"notification_requests.maximize": "Maksimeeri",
|
||||
"notification_requests.minimize_banner": "Minimeeri filtreeritud teavituste bänner",
|
||||
"notification_requests.notifications_from": "Teavitus kasutajalt {name}",
|
||||
@ -1305,6 +1312,7 @@
|
||||
"tabs_bar.menu": "Menüü",
|
||||
"tabs_bar.notifications": "Teated",
|
||||
"tabs_bar.publish": "Uus postitus",
|
||||
"tabs_bar.quick_links": "Kiirlingid",
|
||||
"tabs_bar.search": "Otsing",
|
||||
"tag.remove": "Eemalda",
|
||||
"terms_of_service.effective_as_of": "Kehtib alates {date}",
|
||||
|
||||
@ -584,6 +584,12 @@
|
||||
"copy_icon_button.copy_this_text": "Cóipeáil nasc chuig an ghearrthaisce",
|
||||
"copypaste.copied": "Cóipeáilte",
|
||||
"copypaste.copy_to_clipboard": "Cóipeáil chuig an ngearrthaisce",
|
||||
"custom_homepage.about": "Maidir",
|
||||
"custom_homepage.about_this_server": "Maidir leis an bhfreastalaí seo",
|
||||
"custom_homepage.administered_by": "Arna riar ag",
|
||||
"custom_homepage.contact": "Déan teagmháil le:",
|
||||
"custom_homepage.latest_activity": "An ghníomhaíocht is déanaí",
|
||||
"custom_homepage.these_are_the_latest_posts": "Seo iad na 40 postáil is déanaí ó chuntais ar an bhfreastalaí seo.",
|
||||
"directory.federated": "Ó chomhchruinne aitheanta",
|
||||
"directory.local": "Ó {domain} amháin",
|
||||
"directory.new_arrivals": "Daoine atá tar éis teacht",
|
||||
|
||||
@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Novas denuncias:",
|
||||
"notifications.column_settings.admin.sign_up": "Novas usuarias:",
|
||||
"notifications.column_settings.alert": "Notificacións de escritorio",
|
||||
"notifications.column_settings.collections": "Coleccións:",
|
||||
"notifications.column_settings.favourite": "Favoritas:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Mostrar todas as categorías",
|
||||
"notifications.column_settings.filter_bar.category": "Barra de filtrado rápido",
|
||||
@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Edicións:",
|
||||
"notifications.filter.all": "Todo",
|
||||
"notifications.filter.boosts": "Compartidos",
|
||||
"notifications.filter.collections": "Coleccións",
|
||||
"notifications.filter.favourites": "Favoritas",
|
||||
"notifications.filter.follows": "Seguimentos",
|
||||
"notifications.filter.mentions": "Mencións",
|
||||
|
||||
@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Nuove segnalazioni:",
|
||||
"notifications.column_settings.admin.sign_up": "Nuove iscrizioni:",
|
||||
"notifications.column_settings.alert": "Notifiche desktop",
|
||||
"notifications.column_settings.collections": "Collezioni:",
|
||||
"notifications.column_settings.favourite": "Preferiti:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Mostra tutte le categorie",
|
||||
"notifications.column_settings.filter_bar.category": "Barra del filtro veloce",
|
||||
@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Modifiche:",
|
||||
"notifications.filter.all": "Tutti",
|
||||
"notifications.filter.boosts": "Condivisioni",
|
||||
"notifications.filter.collections": "Collezioni",
|
||||
"notifications.filter.favourites": "Preferiti",
|
||||
"notifications.filter.follows": "Seguaci",
|
||||
"notifications.filter.mentions": "Menzioni",
|
||||
|
||||
@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Nieuwe rapportages:",
|
||||
"notifications.column_settings.admin.sign_up": "Nieuwe registraties:",
|
||||
"notifications.column_settings.alert": "Desktopmeldingen",
|
||||
"notifications.column_settings.collections": "Verzamelingen:",
|
||||
"notifications.column_settings.favourite": "Favorieten:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Alle categorieën tonen",
|
||||
"notifications.column_settings.filter_bar.category": "Snelle filterbalk",
|
||||
@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Bewerkingen:",
|
||||
"notifications.filter.all": "Alles",
|
||||
"notifications.filter.boosts": "Boosts",
|
||||
"notifications.filter.collections": "Verzamelingen",
|
||||
"notifications.filter.favourites": "Favorieten",
|
||||
"notifications.filter.follows": "Nieuwe volgers",
|
||||
"notifications.filter.mentions": "Vermeldingen",
|
||||
|
||||
@ -715,22 +715,22 @@
|
||||
"follow_suggestions.curated_suggestion": "Escolha da equipe",
|
||||
"follow_suggestions.dismiss": "Não mostrar novamente",
|
||||
"follow_suggestions.featured_longer": "Escolhido à mão pela equipe de {domain}",
|
||||
"follow_suggestions.friends_of_friends_longer": "Popular entre as pessoas que você segue",
|
||||
"follow_suggestions.hints.featured": "Este perfil foi escolhido a dedo pela equipe {domain}.",
|
||||
"follow_suggestions.hints.friends_of_friends": "Este perfil é popular entre as pessoas que você segue.",
|
||||
"follow_suggestions.friends_of_friends_longer": "Em alta entre as pessoas que você segue",
|
||||
"follow_suggestions.hints.featured": "Este perfil foi escolhido à mão pela equipe de {domain}.",
|
||||
"follow_suggestions.hints.friends_of_friends": "Este perfil está em alta entre as pessoas que você segue.",
|
||||
"follow_suggestions.hints.most_followed": "Este perfil é um dos mais seguidos em {domain}.",
|
||||
"follow_suggestions.hints.most_interactions": "Este perfil tem recebido recentemente muita atenção em {domain}.",
|
||||
"follow_suggestions.hints.most_interactions": "Este perfil recentemente ganhou bastante atenção em {domain}.",
|
||||
"follow_suggestions.hints.similar_to_recently_followed": "Este perfil é semelhante aos perfis que você seguiu recentemente.",
|
||||
"follow_suggestions.personalized_suggestion": "Sugestão personalizada",
|
||||
"follow_suggestions.popular_suggestion": "Sugestão popular",
|
||||
"follow_suggestions.popular_suggestion_longer": "Popular em {domain}",
|
||||
"follow_suggestions.similar_to_recently_followed_longer": "Similar a perfis que você seguiu recentemente",
|
||||
"follow_suggestions.view_all": "Visualizar tudo",
|
||||
"follow_suggestions.popular_suggestion": "Sugestão em alta",
|
||||
"follow_suggestions.popular_suggestion_longer": "Em alta em {domain}",
|
||||
"follow_suggestions.similar_to_recently_followed_longer": "Semelhante aos perfis que você seguiu recentemente",
|
||||
"follow_suggestions.view_all": "Ver tudo",
|
||||
"follow_suggestions.who_to_follow": "Quem seguir",
|
||||
"followed_tags": "Hashtags seguidas",
|
||||
"followers.hide_other_followers": "Este usuário escolheu não deixar visíveis seus seguidores",
|
||||
"followers.title": "Seguinte {name}",
|
||||
"following.hide_other_following": "Este usuário escolheu não deixar visíveis aqueles a quem segue",
|
||||
"followers.hide_other_followers": "O usuário decidiu não deixar seus seguidores visíveis",
|
||||
"followers.title": "Seguindo {name}",
|
||||
"following.hide_other_following": "O usuário decidiu não deixar quem eles seguem visível",
|
||||
"following.title": "Seguido por {name}",
|
||||
"footer.about": "Sobre",
|
||||
"footer.about_mastodon": "Sobre o Mastodon",
|
||||
@ -740,13 +740,13 @@
|
||||
"footer.get_app": "Baixe o app",
|
||||
"footer.keyboard_shortcuts": "Atalhos de teclado",
|
||||
"footer.privacy_policy": "Política de privacidade",
|
||||
"footer.source_code": "Exibir código-fonte",
|
||||
"footer.source_code": "Ver código-fonte",
|
||||
"footer.status": "Status",
|
||||
"footer.terms_of_service": "Termos de serviço",
|
||||
"form_error.blank": "O espaço não pode estar em branco.",
|
||||
"form_error.blank": "O campo não pode estar vazio.",
|
||||
"form_field.optional": "(opcional)",
|
||||
"getting_started.heading": "Primeiros passos",
|
||||
"hashtag.admin_moderation": "Abrir interface de moderação para #{name}",
|
||||
"hashtag.admin_moderation": "Abrir menu de moderação para #{name}",
|
||||
"hashtag.browse": "Buscar publicações em #{hashtag}",
|
||||
"hashtag.browse_from_account": "Buscar publicações de @{name} em #{hashtag}",
|
||||
"hashtag.column_header.tag_mode.all": "e {additional}",
|
||||
@ -764,27 +764,27 @@
|
||||
"hashtag.feature": "Destacar no perfil",
|
||||
"hashtag.follow": "Seguir hashtag",
|
||||
"hashtag.mute": "Silenciar #{hashtag}",
|
||||
"hashtag.unfeature": "Não destacar no perfil",
|
||||
"hashtag.unfeature": "Remover destaque",
|
||||
"hashtag.unfollow": "Parar de seguir hashtag",
|
||||
"hashtags.and_other": "…e {count, plural, one {}other {outros #}}",
|
||||
"hints.profiles.followers_may_be_missing": "Pode haver seguidores deste perfil faltando.",
|
||||
"hints.profiles.follows_may_be_missing": "Pode haver seguidos por este perfil faltando.",
|
||||
"hints.profiles.posts_may_be_missing": "É possível que algumas publicações deste perfil estejam faltando.",
|
||||
"hints.profiles.see_more_followers": "Ver mais seguidores no {domain}",
|
||||
"hints.profiles.see_more_follows": "Ver mais seguidos no {domain}",
|
||||
"hashtags.and_other": "…e {count, plural, other {mais #}}",
|
||||
"hints.profiles.followers_may_be_missing": "Alguns seguidores deste perfil podem estar faltando.",
|
||||
"hints.profiles.follows_may_be_missing": "Alguns seguidos deste perfil podem estar faltando.",
|
||||
"hints.profiles.posts_may_be_missing": "Algumas publicações deste perfil podem estar faltando.",
|
||||
"hints.profiles.see_more_followers": "Ver mais seguidores em {domain}",
|
||||
"hints.profiles.see_more_follows": "Ver mais perfis seguidos em {domain}",
|
||||
"hints.profiles.see_more_posts": "Ver mais publicações em {domain}",
|
||||
"home.column_settings.show_quotes": "Mostrar citações",
|
||||
"home.column_settings.show_quotes": "Exibir citações",
|
||||
"home.column_settings.show_reblogs": "Mostrar impulsos",
|
||||
"home.column_settings.show_replies": "Mostrar respostas",
|
||||
"home.hide_announcements": "Ocultar anúncios",
|
||||
"home.pending_critical_update.body": "Por favor, atualize o seu servidor Mastodon o mais rápido possível!",
|
||||
"home.pending_critical_update.body": "Por favor, atualize o servidor do Mastodon o mais rápido possível!",
|
||||
"home.pending_critical_update.link": "Ver atualizações",
|
||||
"home.pending_critical_update.title": "Atualização de segurança crítica disponível!",
|
||||
"home.show_announcements": "Mostrar anúncios",
|
||||
"ignore_notifications_modal.disclaimer": "O Mastodon não pode informar aos usuários que você ignorou suas notificações. Ignorar notificações não impedirá que as próprias mensagens sejam enviadas.",
|
||||
"ignore_notifications_modal.disclaimer": "O Mastodon não informa os usuários se você ignorar as notificações deles. Ignorar notificações não impedirá as mensagens de serem enviadas.",
|
||||
"ignore_notifications_modal.filter_instead": "Filtrar em vez disso",
|
||||
"ignore_notifications_modal.filter_to_act_users": "Você ainda conseguirá aceitar, rejeitar ou denunciar usuários",
|
||||
"ignore_notifications_modal.filter_to_avoid_confusion": "A filtragem ajuda a evitar confusão potencial",
|
||||
"ignore_notifications_modal.filter_to_act_users": "Você ainda poderá aceitar, rejeitar ou denunciar",
|
||||
"ignore_notifications_modal.filter_to_avoid_confusion": "Os filtros ajudam a evitar potenciais confusões",
|
||||
"ignore_notifications_modal.filter_to_review_separately": "Você pode rever notificações filtradas separadamente",
|
||||
"ignore_notifications_modal.ignore": "Ignorar notificações",
|
||||
"ignore_notifications_modal.limited_accounts_title": "Ignorar notificações de contas moderadas?",
|
||||
@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "Novas denúncias:",
|
||||
"notifications.column_settings.admin.sign_up": "Novas inscrições:",
|
||||
"notifications.column_settings.alert": "Notificações no computador",
|
||||
"notifications.column_settings.collections": "Coleções:",
|
||||
"notifications.column_settings.favourite": "Favoritos:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Exibir todas as categorias",
|
||||
"notifications.column_settings.filter_bar.category": "Barra de filtro rápido",
|
||||
@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "Editar:",
|
||||
"notifications.filter.all": "Tudo",
|
||||
"notifications.filter.boosts": "Impulsos",
|
||||
"notifications.filter.collections": "Coleções",
|
||||
"notifications.filter.favourites": "Favoritos",
|
||||
"notifications.filter.follows": "Seguidores",
|
||||
"notifications.filter.mentions": "Menções",
|
||||
|
||||
@ -999,6 +999,7 @@
|
||||
"notifications.column_settings.admin.report": "Báo cáo mới:",
|
||||
"notifications.column_settings.admin.sign_up": "Người mới tham gia:",
|
||||
"notifications.column_settings.alert": "Báo trên máy tính",
|
||||
"notifications.column_settings.collections": "Gói khởi đầu:",
|
||||
"notifications.column_settings.favourite": "Lượt thích:",
|
||||
"notifications.column_settings.filter_bar.advanced": "Xếp theo từng loại thông báo",
|
||||
"notifications.column_settings.filter_bar.category": "Phân loại thông báo",
|
||||
@ -1018,6 +1019,7 @@
|
||||
"notifications.column_settings.update": "Sửa tút:",
|
||||
"notifications.filter.all": "Tất cả",
|
||||
"notifications.filter.boosts": "Đăng lại",
|
||||
"notifications.filter.collections": "Gói khởi đầu",
|
||||
"notifications.filter.favourites": "Lượt thích",
|
||||
"notifications.filter.follows": "Người theo dõi mới",
|
||||
"notifications.filter.mentions": "Lượt nhắc đến",
|
||||
|
||||
@ -584,6 +584,12 @@
|
||||
"copy_icon_button.copy_this_text": "复制链接到剪贴板",
|
||||
"copypaste.copied": "已复制",
|
||||
"copypaste.copy_to_clipboard": "复制到剪贴板",
|
||||
"custom_homepage.about": "关于",
|
||||
"custom_homepage.about_this_server": "关于此服务器",
|
||||
"custom_homepage.administered_by": "管理者为",
|
||||
"custom_homepage.contact": "联系方式:",
|
||||
"custom_homepage.latest_activity": "最新动态",
|
||||
"custom_homepage.these_are_the_latest_posts": "以下是来自此服务器账号的最新 40 条嘟文。",
|
||||
"directory.federated": "来自已知联邦宇宙",
|
||||
"directory.local": "仅来自 {domain}",
|
||||
"directory.new_arrivals": "新来者",
|
||||
@ -908,7 +914,7 @@
|
||||
"navigation_bar.live_feed_local": "实时动态(本站)",
|
||||
"navigation_bar.live_feed_public": "实时动态(公开)",
|
||||
"navigation_bar.logout": "退出登录",
|
||||
"navigation_bar.main": "首页",
|
||||
"navigation_bar.main": "主要",
|
||||
"navigation_bar.moderation": "审核",
|
||||
"navigation_bar.more": "更多",
|
||||
"navigation_bar.mutes": "已隐藏的用户",
|
||||
@ -994,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "新举报:",
|
||||
"notifications.column_settings.admin.sign_up": "新注册:",
|
||||
"notifications.column_settings.alert": "桌面通知",
|
||||
"notifications.column_settings.collections": "收藏列表:",
|
||||
"notifications.column_settings.favourite": "喜欢:",
|
||||
"notifications.column_settings.filter_bar.advanced": "显示全部类别",
|
||||
"notifications.column_settings.filter_bar.category": "快速筛选栏",
|
||||
@ -1013,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "编辑:",
|
||||
"notifications.filter.all": "全部",
|
||||
"notifications.filter.boosts": "转嘟",
|
||||
"notifications.filter.collections": "收藏列表",
|
||||
"notifications.filter.favourites": "喜欢",
|
||||
"notifications.filter.follows": "关注",
|
||||
"notifications.filter.mentions": "提及",
|
||||
|
||||
@ -1000,6 +1000,7 @@
|
||||
"notifications.column_settings.admin.report": "新檢舉報告:",
|
||||
"notifications.column_settings.admin.sign_up": "新註冊帳號:",
|
||||
"notifications.column_settings.alert": "桌面通知",
|
||||
"notifications.column_settings.collections": "收藏名單:",
|
||||
"notifications.column_settings.favourite": "最愛:",
|
||||
"notifications.column_settings.filter_bar.advanced": "顯示所有分類",
|
||||
"notifications.column_settings.filter_bar.category": "快速過濾器",
|
||||
@ -1019,6 +1020,7 @@
|
||||
"notifications.column_settings.update": "編輯:",
|
||||
"notifications.filter.all": "全部",
|
||||
"notifications.filter.boosts": "轉嘟",
|
||||
"notifications.filter.collections": "收藏名單",
|
||||
"notifications.filter.favourites": "最愛",
|
||||
"notifications.filter.follows": "跟隨的使用者",
|
||||
"notifications.filter.mentions": "提及",
|
||||
|
||||
@ -41,6 +41,7 @@ const initialState = ImmutableMap({
|
||||
poll: false,
|
||||
status: false,
|
||||
update: false,
|
||||
collections: false,
|
||||
'admin.sign_up': false,
|
||||
'admin.report': false,
|
||||
}),
|
||||
@ -65,6 +66,7 @@ const initialState = ImmutableMap({
|
||||
poll: true,
|
||||
status: true,
|
||||
update: true,
|
||||
collections: true,
|
||||
'admin.sign_up': true,
|
||||
'admin.report': true,
|
||||
}),
|
||||
@ -79,6 +81,7 @@ const initialState = ImmutableMap({
|
||||
poll: true,
|
||||
status: true,
|
||||
update: true,
|
||||
collections: true,
|
||||
'admin.sign_up': true,
|
||||
'admin.report': true,
|
||||
}),
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
import { fetchAccounts } from '@/mastodon/actions/accounts_typed';
|
||||
import { importFetchedAccounts } from '@/mastodon/actions/importer';
|
||||
import {
|
||||
apiCreateCollection,
|
||||
@ -20,6 +21,7 @@ import type {
|
||||
CollectionAccountItem,
|
||||
} from '@/mastodon/api_types/collections';
|
||||
import { initialState, me } from '@/mastodon/initial_state';
|
||||
import type { AppDispatch } from '@/mastodon/store';
|
||||
import {
|
||||
createAppAsyncThunk,
|
||||
createAppSelector,
|
||||
@ -322,16 +324,42 @@ const collectionSlice = createSlice({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Prefetch accounts whose avatars will be displayed in the collection list
|
||||
*/
|
||||
async function importAccountsForPreviewCard(
|
||||
collections: ApiCollectionJSON[],
|
||||
dispatch: AppDispatch,
|
||||
) {
|
||||
const previewAccountIds = collections
|
||||
.flatMap((collection) =>
|
||||
collection.items.slice(0, 3).map((item) => item.account_id),
|
||||
)
|
||||
.filter((id): id is string => !!id);
|
||||
|
||||
await dispatch(
|
||||
fetchAccounts({
|
||||
accountIds: previewAccountIds,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export const fetchCollectionsCreatedByAccount = createDataLoadingThunk(
|
||||
`${collectionSlice.name}/fetchCollectionsCreatedByAccount`,
|
||||
({ accountId }: { accountId: string }) =>
|
||||
apiGetCollectionsCreatedByAccount(accountId),
|
||||
async ({ collections }, { dispatch }) => {
|
||||
await importAccountsForPreviewCard(collections, dispatch);
|
||||
},
|
||||
);
|
||||
|
||||
export const fetchCollectionsFeaturingAccount = createDataLoadingThunk(
|
||||
`${collectionSlice.name}/fetchCollectionsFeaturingAccount`,
|
||||
({ accountId }: { accountId: string }) =>
|
||||
apiGetCollectionsFeaturingAccount(accountId),
|
||||
async ({ collections }, { dispatch }) => {
|
||||
await importAccountsForPreviewCard(collections, dispatch);
|
||||
},
|
||||
);
|
||||
|
||||
export const fetchCollection = createDataLoadingThunk(
|
||||
|
||||
@ -29,7 +29,10 @@ const filterNotificationsByAllowedTypes = (
|
||||
(item) =>
|
||||
item.type === 'gap' ||
|
||||
allowedType === item.type ||
|
||||
(allowedType === 'mention' && item.type === 'quote'),
|
||||
(allowedType === 'mention' && item.type === 'quote') ||
|
||||
(allowedType === 'collection' &&
|
||||
(item.type === 'collection_update' ||
|
||||
item.type === 'added_to_collection')),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -17,10 +17,19 @@ export const selectSettingsNotificationsShows = createSelector(
|
||||
|
||||
export const selectSettingsNotificationsExcludedTypes = createSelector(
|
||||
[selectSettingsNotificationsShows],
|
||||
(shows) =>
|
||||
Object.entries(shows)
|
||||
.filter(([_type, enabled]) => !enabled)
|
||||
.map(([type, _enabled]) => type),
|
||||
(shows) => {
|
||||
const excludedTypes: string[] = [];
|
||||
for (const key in shows) {
|
||||
if (!shows[key]) {
|
||||
if (key === 'collections') {
|
||||
excludedTypes.push('collection_update', 'added_to_collection');
|
||||
} else {
|
||||
excludedTypes.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
return excludedTypes;
|
||||
},
|
||||
);
|
||||
|
||||
export const selectSettingsNotificationsQuickFilterShow = (state: RootState) =>
|
||||
|
||||
@ -12,7 +12,7 @@ export function isProduction() {
|
||||
else return import.meta.env.PROD;
|
||||
}
|
||||
|
||||
export type ServerFeatures = 'fasp' | 'collections';
|
||||
export type ServerFeatures = 'fasp';
|
||||
|
||||
export function isServerFeatureEnabled(feature: ServerFeatures) {
|
||||
return initialState?.features.includes(feature) ?? false;
|
||||
|
||||
@ -5,7 +5,7 @@ class ActivityPub::Activity::Accept < ActivityPub::Activity
|
||||
return accept_follow_for_relay if relay_follow?
|
||||
return accept_follow!(follow_request_from_object) unless follow_request_from_object.nil?
|
||||
return accept_quote!(quote_request_from_object) unless quote_request_from_object.nil?
|
||||
return accept_feature_request! if Mastodon::Feature.collections_enabled? && feature_request_from_object.present?
|
||||
return accept_feature_request! if feature_request_from_object.present?
|
||||
|
||||
case @object['type']
|
||||
when 'Follow'
|
||||
|
||||
@ -13,12 +13,10 @@ class ActivityPub::Activity::Add < ActivityPub::Activity
|
||||
add_featured
|
||||
end
|
||||
when @account.collections_url
|
||||
return unless Mastodon::Feature.collections_enabled?
|
||||
|
||||
add_collection
|
||||
else
|
||||
@collection = @account.collections.find_by(uri: value_or_id(@json['target']))
|
||||
add_collection_item if @collection && Mastodon::Feature.collections_enabled?
|
||||
add_collection_item if @collection
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
class ActivityPub::Activity::Delete < ActivityPub::Activity
|
||||
def perform
|
||||
return delete_person if @account.uri == object_uri
|
||||
return delete_feature_authorization! unless !Mastodon::Feature.collections_enabled? || feature_authorization_from_object.nil?
|
||||
return delete_feature_authorization! unless feature_authorization_from_object.nil?
|
||||
|
||||
delete_object
|
||||
end
|
||||
|
||||
@ -4,7 +4,6 @@ class ActivityPub::Activity::FeatureRequest < ActivityPub::Activity
|
||||
include Payloadable
|
||||
|
||||
def perform
|
||||
return unless Mastodon::Feature.collections_enabled?
|
||||
return if non_matching_uri_hosts?(@account.uri, @json['id'])
|
||||
|
||||
@collection = find_or_fetch_collection
|
||||
|
||||
@ -13,7 +13,7 @@ class ActivityPub::Activity::Update < ActivityPub::Activity
|
||||
update_account
|
||||
elsif supported_object_type? || converted_object_type?
|
||||
update_status
|
||||
elsif equals_or_includes_any?(@object['type'], ['FeaturedCollection']) && Mastodon::Feature.collections_enabled?
|
||||
elsif equals_or_includes_any?(@object['type'], ['FeaturedCollection'])
|
||||
update_collection
|
||||
end
|
||||
end
|
||||
|
||||
@ -5,42 +5,45 @@ class StatusCacheHydrator
|
||||
@status = status
|
||||
end
|
||||
|
||||
def hydrate(account_id, nested: false)
|
||||
def hydrate(account_or_id, nested: false)
|
||||
account = account_or_id.is_a?(Account) ? account_or_id : Account.find(account_or_id)
|
||||
|
||||
# The cache of the serialized hash is generated by the fan-out-on-write service
|
||||
payload = Rails.cache.fetch("fan-out/#{@status.id}") { InlineRenderer.render(@status, nil, :status) }
|
||||
|
||||
# If we're delivering to the author who disabled the display of the application used to create the
|
||||
# status, we need to hydrate the application, since it was not rendered for the basic payload
|
||||
payload[:application] = payload_application if payload[:application].nil? && @status.account_id == account_id
|
||||
payload[:application] = payload_application if payload[:application].nil? && @status.account_id == account.id
|
||||
|
||||
# We take advantage of the fact that some relationships can only occur with an original status, not
|
||||
# the reblog that wraps it, so we can assume that some values are always false
|
||||
if payload[:reblog]
|
||||
hydrate_reblog_payload(payload, account_id, nested:)
|
||||
hydrate_reblog_payload(payload, account, nested:)
|
||||
else
|
||||
hydrate_non_reblog_payload(payload, account_id, nested:)
|
||||
hydrate_non_reblog_payload(payload, account, nested:)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def hydrate_non_reblog_payload(empty_payload, account_id, nested: false)
|
||||
def hydrate_non_reblog_payload(empty_payload, account, nested: false)
|
||||
empty_payload.tap do |payload|
|
||||
fill_status_payload(payload, @status, account_id, fresh: !nested, nested:)
|
||||
fill_status_payload(payload, @status, account, fresh: !nested, nested:)
|
||||
end
|
||||
end
|
||||
|
||||
def hydrate_reblog_payload(empty_payload, account_id, nested: false)
|
||||
def hydrate_reblog_payload(empty_payload, account, nested: false)
|
||||
empty_payload.tap do |payload|
|
||||
payload[:muted] = false
|
||||
payload[:bookmarked] = false
|
||||
payload[:pinned] = false if @status.account_id == account_id
|
||||
payload[:pinned] = false if @status.account_id == account.id
|
||||
|
||||
# If the reblogged status is being delivered to the author who disabled the display of the application
|
||||
# used to create the status, we need to hydrate it here too
|
||||
payload[:reblog][:application] = payload_reblog_application if payload[:reblog][:application].nil? && @status.reblog.account_id == account_id
|
||||
payload[:reblog][:application] = payload_reblog_application if payload[:reblog][:application].nil? && @status.reblog.account_id == account.id
|
||||
|
||||
fill_status_payload(payload[:reblog], @status.reblog, account_id, fresh: false, nested:)
|
||||
hydrate_account(payload[:account], account)
|
||||
fill_status_payload(payload[:reblog], @status.reblog, account, fresh: false, nested:)
|
||||
|
||||
payload[:filtered] = payload[:reblog][:filtered]
|
||||
payload[:favourited] = payload[:reblog][:favourited]
|
||||
@ -49,36 +52,37 @@ class StatusCacheHydrator
|
||||
end
|
||||
end
|
||||
|
||||
def fill_status_payload(payload, status, account_id, nested: false, fresh: true)
|
||||
payload[:favourited] = Favourite.exists?(account_id: account_id, status_id: status.id)
|
||||
payload[:reblogged] = Status.exists?(account_id: account_id, reblog_of_id: status.id)
|
||||
payload[:muted] = ConversationMute.exists?(account_id: account_id, conversation_id: status.conversation_id)
|
||||
payload[:bookmarked] = Bookmark.exists?(account_id: account_id, status_id: status.id)
|
||||
payload[:pinned] = StatusPin.exists?(account_id: account_id, status_id: status.id) if status.account_id == account_id
|
||||
payload[:filtered] = mapped_applied_custom_filter(account_id, status)
|
||||
# TODO: performance optimization by not loading `Account` twice
|
||||
payload[:quote_approval][:current_user] = status.quote_policy_for_account(Account.find_by(id: account_id)) if payload[:quote_approval]
|
||||
payload[:quote] = hydrate_quote_payload(payload[:quote], status.quote, account_id, nested:) if payload[:quote]
|
||||
def fill_status_payload(payload, status, account, nested: false, fresh: true)
|
||||
payload[:favourited] = Favourite.exists?(account_id: account.id, status_id: status.id)
|
||||
payload[:reblogged] = Status.exists?(account_id: account.id, reblog_of_id: status.id)
|
||||
payload[:muted] = ConversationMute.exists?(account_id: account.id, conversation_id: status.conversation_id)
|
||||
payload[:bookmarked] = Bookmark.exists?(account_id: account.id, status_id: status.id)
|
||||
payload[:pinned] = StatusPin.exists?(account_id: account.id, status_id: status.id) if status.account_id == account.id
|
||||
payload[:filtered] = mapped_applied_custom_filter(account, status)
|
||||
payload[:quote_approval][:current_user] = status.quote_policy_for_account(account) if payload[:quote_approval]
|
||||
payload[:quote] = hydrate_quote_payload(payload[:quote], status.quote, account, nested:) if payload[:quote]
|
||||
|
||||
if payload[:poll]
|
||||
if fresh
|
||||
# If the status is brand new, we don't need to look up votes in database
|
||||
payload[:poll][:voted] = status.account_id == account_id
|
||||
payload[:poll][:voted] = status.account_id == account.id
|
||||
payload[:poll][:own_votes] = []
|
||||
elsif status.account_id == account_id
|
||||
elsif status.account_id == account.id
|
||||
payload[:poll][:voted] = true
|
||||
payload[:poll][:own_votes] = []
|
||||
else
|
||||
own_votes = PollVote.where(poll_id: status.poll_id, account_id: account_id).pluck(:choice)
|
||||
own_votes = PollVote.where(poll_id: status.poll_id, account_id: account.id).pluck(:choice)
|
||||
payload[:poll][:voted] = !own_votes.empty?
|
||||
payload[:poll][:own_votes] = own_votes
|
||||
end
|
||||
end
|
||||
|
||||
payload[:card][:missing_attribution] = status.preview_card.unverified_author_account_id == account_id if payload[:card]
|
||||
payload[:card][:missing_attribution] = status.preview_card.unverified_author_account_id == account.id if payload[:card]
|
||||
|
||||
# Nested statuses are more likely to have a stale cache
|
||||
fill_status_stats(payload, status) if nested
|
||||
|
||||
hydrate_account(payload[:account], account)
|
||||
end
|
||||
|
||||
def fill_status_stats(payload, status)
|
||||
@ -88,7 +92,7 @@ class StatusCacheHydrator
|
||||
payload[:quotes_count] = status.quotes_count
|
||||
end
|
||||
|
||||
def hydrate_quote_payload(empty_payload, quote, account_id, nested: false)
|
||||
def hydrate_quote_payload(empty_payload, quote, account, nested: false)
|
||||
return unless quote&.acceptable?
|
||||
|
||||
empty_payload.tap do |payload|
|
||||
@ -100,14 +104,14 @@ class StatusCacheHydrator
|
||||
payload[nested ? :quoted_status_id : :quoted_status] = nil
|
||||
payload[:state] = 'deleted'
|
||||
else
|
||||
filter_state = StatusFilter.new(quote.quoted_status, Account.find_by(id: account_id)).filter_state_for_quote
|
||||
filter_state = StatusFilter.new(quote.quoted_status, account).filter_state_for_quote
|
||||
payload[:state] = filter_state || 'accepted'
|
||||
if filter_state == 'unauthorized'
|
||||
payload[nested ? :quoted_status_id : :quoted_status] = nil
|
||||
elsif nested
|
||||
payload[:quoted_status_id] = quote.quoted_status_id&.to_s
|
||||
else
|
||||
payload[:quoted_status] = StatusCacheHydrator.new(quote.quoted_status).hydrate(account_id, nested: true)
|
||||
payload[:quoted_status] = StatusCacheHydrator.new(quote.quoted_status).hydrate(account, nested: true)
|
||||
end
|
||||
end
|
||||
else
|
||||
@ -116,9 +120,18 @@ class StatusCacheHydrator
|
||||
end
|
||||
end
|
||||
|
||||
def mapped_applied_custom_filter(account_id, status)
|
||||
def hydrate_account(payload, account)
|
||||
return unless payload[:id]
|
||||
|
||||
stale_account = Account.find_by(id: payload[:id])
|
||||
return if stale_account.nil?
|
||||
|
||||
payload[:feature_approval][:current_user] = stale_account.feature_policy_for_account(account)
|
||||
end
|
||||
|
||||
def mapped_applied_custom_filter(account, status)
|
||||
CustomFilter
|
||||
.apply_cached_filters(CustomFilter.cached_filters_for(account_id), status)
|
||||
.apply_cached_filters(CustomFilter.cached_filters_for(account), status)
|
||||
.map { |filter| serialized_filter(filter) }
|
||||
end
|
||||
|
||||
|
||||
@ -34,9 +34,13 @@ class AdminMailer < ApplicationMailer
|
||||
end
|
||||
|
||||
def new_trends(links, tags, statuses)
|
||||
@links = links
|
||||
@tags = tags
|
||||
@statuses = statuses
|
||||
ActiveRecord::Associations::Preloader.new(records: [*links, *tags, *statuses], associations: [:trend]).call
|
||||
|
||||
@links = links.filter { |link| link.trend.present? }
|
||||
@tags = tags.filter { |tag| tag.trend.present? }
|
||||
@statuses = statuses.filter { |status| status.trend.present? }
|
||||
|
||||
return unless @links.any? || @tags.any? || @statuses.any?
|
||||
|
||||
mail subject: default_i18n_subject(instance: @instance)
|
||||
end
|
||||
|
||||
@ -19,17 +19,15 @@ class NotificationMailer < ApplicationMailer
|
||||
|
||||
default to: -> { email_address_with_name(@user.email, @me.username) }
|
||||
|
||||
rescue_from(ActiveRecord::RecordNotFound) { false }
|
||||
|
||||
layout 'mailer'
|
||||
|
||||
def mention
|
||||
return if @status.blank?
|
||||
|
||||
mail subject: default_i18n_subject(name: @status.account.acct)
|
||||
end
|
||||
|
||||
def quote
|
||||
return if @status.blank?
|
||||
|
||||
mail subject: default_i18n_subject(name: @status.account.acct)
|
||||
end
|
||||
|
||||
@ -38,14 +36,10 @@ class NotificationMailer < ApplicationMailer
|
||||
end
|
||||
|
||||
def favourite
|
||||
return if @status.blank?
|
||||
|
||||
mail subject: default_i18n_subject(name: @account.acct)
|
||||
end
|
||||
|
||||
def reblog
|
||||
return if @status.blank?
|
||||
|
||||
mail subject: default_i18n_subject(name: @account.acct)
|
||||
end
|
||||
|
||||
@ -64,7 +58,7 @@ class NotificationMailer < ApplicationMailer
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = @notification.target_status
|
||||
@status = @notification.target_status || raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
def set_account
|
||||
|
||||
@ -10,7 +10,7 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
||||
:moved_to, :property_value, :discoverable, :suspended,
|
||||
:memorial, :indexable, :attribution_domains, :profile_settings
|
||||
|
||||
context_extensions :interaction_policies if Mastodon::Feature.collections_enabled?
|
||||
context_extensions :interaction_policies
|
||||
|
||||
attributes :id, :webfinger, :type, :following, :followers,
|
||||
:inbox, :outbox, :featured, :featured_tags,
|
||||
@ -21,8 +21,8 @@ class ActivityPub::ActorSerializer < ActivityPub::Serializer
|
||||
|
||||
attribute :show_media_replies, key: :show_replies_in_media
|
||||
|
||||
attribute :interaction_policy, if: -> { Mastodon::Feature.collections_enabled? }
|
||||
attribute :featured_collections, if: -> { Mastodon::Feature.collections_enabled? }
|
||||
attribute :interaction_policy
|
||||
attribute :featured_collections
|
||||
|
||||
has_one :public_key, serializer: ActivityPub::PublicKeySerializer
|
||||
|
||||
|
||||
@ -21,7 +21,7 @@ class REST::AccountSerializer < ActiveModel::Serializer
|
||||
|
||||
attribute :memorial, if: :memorial?
|
||||
|
||||
attribute :feature_approval, if: -> { Mastodon::Feature.collections_enabled? }
|
||||
attribute :feature_approval
|
||||
attribute :email_subscriptions, if: -> { Rails.application.config.x.email_subscriptions && Setting.email_subscriptions }
|
||||
|
||||
class AccountDecorator < SimpleDelegator
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
class REST::RoleSerializer < ActiveModel::Serializer
|
||||
attributes :id, :name, :permissions, :color, :highlighted
|
||||
|
||||
attribute :collection_limit, if: -> { Mastodon::Feature.collections_enabled? }
|
||||
attribute :collection_limit
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
|
||||
@ -72,7 +72,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||
unless @options[:only_key] || @account.suspended?
|
||||
check_featured_collection! if @json['featured'].present?
|
||||
check_featured_tags_collection! if @json['featuredTags'].present?
|
||||
check_featured_collections_collection! if @json['featuredCollections'].present? && Mastodon::Feature.collections_enabled?
|
||||
check_featured_collections_collection! if @json['featuredCollections'].present?
|
||||
check_links! if @account.fields.any?(&:requires_verification?)
|
||||
end
|
||||
|
||||
@ -121,7 +121,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||
@account.uri = @uri
|
||||
@account.actor_type = actor_type
|
||||
@account.created_at = @json['published'] if @json['published'].present?
|
||||
@account.feature_approval_policy = feature_approval_policy if Mastodon::Feature.collections_enabled?
|
||||
@account.feature_approval_policy = feature_approval_policy
|
||||
end
|
||||
|
||||
def valid_collection_uri(uri)
|
||||
|
||||
@ -28,7 +28,7 @@ class ResolveURLService < BaseService
|
||||
status = FetchRemoteStatusService.new.call(resource_url, prefetched_body: body)
|
||||
authorize_with @on_behalf_of, status, :show? unless status.nil?
|
||||
status
|
||||
elsif type == 'FeaturedCollection' && Mastodon::Feature.collections_enabled?
|
||||
elsif type == 'FeaturedCollection'
|
||||
collection = ActivityPub::FetchRemoteFeaturedCollectionService.new.call(resource_url, prefetched_body: body)
|
||||
authorize_with @on_behalf_of, collection, :show? unless collection.nil?
|
||||
collection
|
||||
|
||||
@ -67,10 +67,9 @@
|
||||
= material_symbol('photo_camera')
|
||||
= report.media_attachments_count
|
||||
|
||||
- if Mastodon::Feature.collections_enabled?
|
||||
%span.report-card__summary__item__content__icon{ title: t('admin.accounts.collections') }
|
||||
= material_symbol('category')
|
||||
= report.collections.size
|
||||
%span.report-card__summary__item__content__icon{ title: t('admin.accounts.collections') }
|
||||
= material_symbol('category')
|
||||
= report.collections.size
|
||||
|
||||
- if report.forwarded?
|
||||
·
|
||||
|
||||
@ -62,27 +62,26 @@
|
||||
- else
|
||||
= render partial: 'admin/shared/status_batch_row', collection: @statuses, as: :status, locals: { f: f }
|
||||
|
||||
- if Mastodon::Feature.collections_enabled?
|
||||
%details{ open: @collections.any? }
|
||||
%summary
|
||||
= t 'admin.reports.collections', count: @collections.size
|
||||
%details{ open: @collections.any? }
|
||||
%summary
|
||||
= t 'admin.reports.collections', count: @collections.size
|
||||
|
||||
= form_with model: @collection_form, url: batch_admin_account_collections_path(@report.target_account_id, report_id: @report.id) do |f|
|
||||
.batch-table
|
||||
.batch-table__toolbar
|
||||
%label.batch-table__toolbar__select.batch-checkbox-all
|
||||
= check_box_tag :batch_checkbox_all, nil, false
|
||||
.batch-table__toolbar__actions
|
||||
= link_to safe_join([material_symbol('add'), t('admin.reports.add_to_report')]),
|
||||
admin_account_collections_path(@report.target_account_id, report_id: @report.id),
|
||||
class: 'table-action-link'
|
||||
- if !@collections.empty? && @report.unresolved?
|
||||
= f.button safe_join([material_symbol('close'), t('admin.collections.batch.remove_from_report')]), name: :remove_from_report, class: 'table-action-link', type: :submit
|
||||
.batch-table__body
|
||||
- if @collections.empty?
|
||||
= nothing_here 'nothing-here--under-tabs'
|
||||
- else
|
||||
= render partial: 'admin/shared/collection_batch_row', collection: @collections, as: :collection, locals: { f: f }
|
||||
= form_with model: @collection_form, url: batch_admin_account_collections_path(@report.target_account_id, report_id: @report.id) do |f|
|
||||
.batch-table
|
||||
.batch-table__toolbar
|
||||
%label.batch-table__toolbar__select.batch-checkbox-all
|
||||
= check_box_tag :batch_checkbox_all, nil, false
|
||||
.batch-table__toolbar__actions
|
||||
= link_to safe_join([material_symbol('add'), t('admin.reports.add_to_report')]),
|
||||
admin_account_collections_path(@report.target_account_id, report_id: @report.id),
|
||||
class: 'table-action-link'
|
||||
- if !@collections.empty? && @report.unresolved?
|
||||
= f.button safe_join([material_symbol('close'), t('admin.collections.batch.remove_from_report')]), name: :remove_from_report, class: 'table-action-link', type: :submit
|
||||
.batch-table__body
|
||||
- if @collections.empty?
|
||||
= nothing_here 'nothing-here--under-tabs'
|
||||
- else
|
||||
= render partial: 'admin/shared/collection_batch_row', collection: @collections, as: :collection, locals: { f: f }
|
||||
|
||||
- if @report.unresolved?
|
||||
%hr.spacer/
|
||||
|
||||
@ -32,13 +32,11 @@
|
||||
|
||||
%hr.spacer/
|
||||
|
||||
- if Mastodon::Feature.collections_enabled?
|
||||
.fields-group
|
||||
= form.input :collection_limit,
|
||||
wrapper: :with_label
|
||||
|
||||
.fields-group
|
||||
= form.input :collection_limit,
|
||||
wrapper: :with_label
|
||||
|
||||
%hr.spacer/
|
||||
%hr.spacer/
|
||||
|
||||
- unless current_user.role == form.object
|
||||
|
||||
|
||||
@ -956,7 +956,14 @@ de:
|
||||
disabled: Bestimmte Rolle erforderlich
|
||||
public: Alle
|
||||
landing_page:
|
||||
hints:
|
||||
about_html: Eine Seite mit der Beschreibung, den Kontaktinformationen, den Regeln und weiteren Informationen zu diesem Server.
|
||||
local_feed_html: Ein Live-Feed mit den neuesten Beiträgen der Nutzer auf diesem Server.
|
||||
overview_html: Eine Seite, die die Beschreibung deines Servers zusammen mit den neuesten lokalen Beiträgen der Nutzer zeigt.
|
||||
trends_html: Eine Seite mit dem, was auf diesem Server gerade im Trend liegt.
|
||||
values:
|
||||
about: Über-Seite
|
||||
local_feed: Lokaler Live-Feed
|
||||
overview: Übersicht
|
||||
trends: Trendet
|
||||
registrations:
|
||||
|
||||
@ -955,6 +955,17 @@ es-MX:
|
||||
authenticated: Solo usuarios registrados
|
||||
disabled: Requerir un rol de usuario específico
|
||||
public: Todos
|
||||
landing_page:
|
||||
hints:
|
||||
about_html: Una página con la descripción, la información de contacto, las normas y otros datos sobre este servidor.
|
||||
local_feed_html: Una cronología en tiempo real con las publicaciones más recientes de los usuarios de este servidor.
|
||||
overview_html: Una página que muestra la descripción de tu servidor junto con las publicaciones locales más recientes de los usuarios de este servidor.
|
||||
trends_html: Una página que muestra lo que es popular en este servidor en este momento.
|
||||
values:
|
||||
about: Página sobre el servidor
|
||||
local_feed: Cronología local
|
||||
overview: Resumen
|
||||
trends: En tendencia
|
||||
registrations:
|
||||
moderation_recommandation: "¡Por favor, asegúrate de contar con un equipo de moderación adecuado y activo antes de abrir el registro al público!"
|
||||
preamble: Controla quién puede crear una cuenta en tu servidor.
|
||||
|
||||
@ -955,6 +955,17 @@ es:
|
||||
authenticated: Solo usuarios autenticados
|
||||
disabled: Requerir un rol de usuario específico
|
||||
public: Todos
|
||||
landing_page:
|
||||
hints:
|
||||
about_html: Una página con la descripción, información de contacto, reglas y otra información relevante de este servidor.
|
||||
local_feed_html: Una cronología con los mensajes más recientes de los usuarios en este servidor.
|
||||
overview_html: Una página que muestra la descripción de tu servidor junto a las publicaciones locales más recientes de los usuarios en este servidor.
|
||||
trends_html: Una página destacando lo que es popular en este servidor en este momento.
|
||||
values:
|
||||
about: Página sobre el servidor
|
||||
local_feed: Cronología local
|
||||
overview: Resumen
|
||||
trends: En tendencia
|
||||
registrations:
|
||||
moderation_recommandation: Por favor, ¡asegúrate de tener un equipo de moderación adecuado y reactivo antes de abrir los registros a todo el mundo!
|
||||
preamble: Controla quién puede crear una cuenta en tu servidor.
|
||||
|
||||
@ -279,8 +279,8 @@ et:
|
||||
destroy_unavailable_domain_html: "%{name} taastas edastamise domeeni %{target}"
|
||||
destroy_user_role_html: "%{name} kustutas %{target} rolli"
|
||||
destroy_username_block_html: "%{name} eemaldas kasutajanime reegli, milles sisaldub %{target}"
|
||||
disable_2fa_user_html: "%{name} eemaldas kasutaja %{target} kahe etapise nõude"
|
||||
disable_custom_emoji_html: "%{name} keelas emotikooni %{target}"
|
||||
disable_2fa_user_html: "%{name} eemaldas kasutajalt %{target} kahefaktorilise autentimise nõude"
|
||||
disable_custom_emoji_html: "%{name} keelas emoji %{target}"
|
||||
disable_relay_html: "%{name} eemaldas sõnumivahendusserveri kasutuselt: %{target}"
|
||||
disable_sign_in_token_auth_user_html: "%{name} keelas e-posti võtme abil autentimise %{target} jaoks"
|
||||
disable_user_html: "%{name} keelas %{target} sisenemise"
|
||||
@ -295,7 +295,7 @@ et:
|
||||
reject_user_html: "%{name} lükkas %{target} liitumissoovi tagasi"
|
||||
remove_avatar_user_html: "%{name} eemaldas %{target} avatari"
|
||||
reopen_report_html: "%{name} taasavas raporti %{target}"
|
||||
resend_user_html: "%{name} lähtestas %{target} kinnituskirja e-posti"
|
||||
resend_user_html: "%{name} saatis kasutajale %{target} e-postiga uue kinnituskirja"
|
||||
reset_password_user_html: "%{name} lähtestas %{target} kasutaja salasõna"
|
||||
resolve_report_html: "%{name} lahendas raporti %{target}"
|
||||
sensitive_account_html: "%{name} märkis %{target} meedia kui tundlik sisu"
|
||||
@ -955,6 +955,17 @@ et:
|
||||
authenticated: Vaid autenditud kasutajad
|
||||
disabled: Eelda konkreetse kasutajarolli olemasolu
|
||||
public: Kõik
|
||||
landing_page:
|
||||
hints:
|
||||
about_html: Leht kirjeldusega, kontaktiteabega, reeglitega muu olulisega selle koduserveri kohta.
|
||||
local_feed_html: Sisuvoog viimaste selle serveri kasutajate postitustega.
|
||||
overview_html: Leht, kus näidatakse selle serveri kirjeldust koos serveri kasutate viimaste postitustega.
|
||||
trends_html: Leht, mis näitab selles serveris hetkel populaarseid teemasid.
|
||||
values:
|
||||
about: Serveri teabe leht
|
||||
local_feed: Kohalik postituste voog
|
||||
overview: Ülevaade
|
||||
trends: Populaarsust koguv
|
||||
registrations:
|
||||
moderation_recommandation: Enne kõigi jaoks registreerimise avamist veendu, et oleks olemas adekvaatne ja reageerimisvalmis modereerijaskond!
|
||||
preamble: Kes saab serveril konto luua.
|
||||
|
||||
@ -1003,6 +1003,17 @@ ga:
|
||||
authenticated: Úsáideoirí fíordheimhnithe amháin
|
||||
disabled: Éiligh ról úsáideora sonrach
|
||||
public: Gach duine
|
||||
landing_page:
|
||||
hints:
|
||||
about_html: Leathanach leis an gcur síos, eolas teagmhála, rialacha agus faisnéis eile maidir leis an bhfreastalaí seo.
|
||||
local_feed_html: Fotha beo ina bhfuil na postálacha is déanaí ó úsáideoirí ar an bhfreastalaí seo.
|
||||
overview_html: Leathanach a thaispeánann cur síos ar do fhreastalaí in éineacht leis na postálacha áitiúla is déanaí ó úsáideoirí ar an bhfreastalaí seo.
|
||||
trends_html: Leathanach ar a bhfuil an-tóir ar an bhfreastalaí seo faoi láthair.
|
||||
values:
|
||||
about: Maidir le leathanach
|
||||
local_feed: Beatha beo áitiúil
|
||||
overview: Forbhreathnú
|
||||
trends: Treocht
|
||||
registrations:
|
||||
moderation_recommandation: Cinntigh le do thoil go bhfuil foireann mhodhnóireachta imoibríoch leordhóthanach agat sula n-osclaíonn tú clárúcháin do gach duine!
|
||||
preamble: Rialú cé atá in ann cuntas a chruthú ar do fhreastalaí.
|
||||
|
||||
@ -961,6 +961,9 @@ lt:
|
||||
your_appeal_rejected: Tavo apeliacija buvo atmesta
|
||||
edit_profile:
|
||||
other: Kita
|
||||
email_subscription_mailer:
|
||||
confirmation:
|
||||
action: Patvirtinkite el. pašto adresą
|
||||
emoji_styles:
|
||||
auto: Automatinis
|
||||
native: Vietiniai
|
||||
|
||||
@ -148,6 +148,7 @@ lt:
|
||||
avatar: Profilio nuotrauka
|
||||
bot: Tai automatinė paskyra
|
||||
chosen_languages: Filtruoti kalbas
|
||||
confirm_password: Patvirtink slaptažodį
|
||||
display_name: Rodomas vardas
|
||||
email: El. pašto adresas
|
||||
expires_in: Nustoja galioti po
|
||||
@ -261,7 +262,9 @@ lt:
|
||||
jurisdiction: Teisinis teismingumas
|
||||
min_age: Mažiausias amžius
|
||||
user:
|
||||
date_of_birth_1i: Metai
|
||||
date_of_birth_2i: Mėnuo
|
||||
date_of_birth_3i: Diena
|
||||
role: Vaidmuo
|
||||
time_zone: Laiko juosta
|
||||
user_role:
|
||||
|
||||
@ -956,9 +956,16 @@ tr:
|
||||
disabled: Belirli kullanıcı rolü gerekir
|
||||
public: Herkes
|
||||
landing_page:
|
||||
hints:
|
||||
about_html: Bu sunucuya ilişkin açıklama, iletişim bilgileri, kurallar ve diğer bilgilerin yer aldığı bir sayfa.
|
||||
local_feed_html: Bu sunucudaki kullanıcıların en son paylaşımlarını içeren bir canlı akış.
|
||||
overview_html: Sunucunuzun açıklamasını ve bu sunucudaki kullanıcıların en son yerel gönderilerini gösteren bir sayfa.
|
||||
trends_html: Bu sunucuda şu anda popüler olanları gösteren bir sayfa.
|
||||
values:
|
||||
about: Hakkında sayfası
|
||||
local_feed: Yerel canlı akış
|
||||
overview: Genel Bakış
|
||||
trends: Öne çıkanlar
|
||||
registrations:
|
||||
moderation_recommandation: Lütfen kayıtları herkese açmadan önce yeterli ve duyarlı bir denetleyici ekibine sahip olduğunuzdan emin olun!
|
||||
preamble: Sunucunuzda kimin hesap oluşturabileceğini denetleyin.
|
||||
|
||||
@ -939,6 +939,17 @@ zh-CN:
|
||||
authenticated: 仅已登录用户
|
||||
disabled: 需要特定的用户角色
|
||||
public: 每个人
|
||||
landing_page:
|
||||
hints:
|
||||
about_html: 关于此服务器的描述、联系信息、规则及其他信息的页面。
|
||||
local_feed_html: 展示此服务器用户最新嘟文的实时动态。
|
||||
overview_html: 展示你的服务器描述及本站用户最新嘟文的页面。
|
||||
trends_html: 展示此服务器当前热门内容的页面。
|
||||
values:
|
||||
about: 关于本站
|
||||
local_feed: 本站实时动态
|
||||
overview: 概览
|
||||
trends: 当前热门
|
||||
registrations:
|
||||
moderation_recommandation: 在向每个人开放注册之前,请确保你拥有一个人手足够且反应迅速的管理团队!
|
||||
preamble: 控制谁可以在你的服务器上创建账号。
|
||||
|
||||
@ -6,13 +6,16 @@ namespace :api, format: false do
|
||||
|
||||
# Experimental JSON / REST API
|
||||
namespace :v1_alpha do
|
||||
resources :async_refreshes, only: :show
|
||||
end
|
||||
|
||||
# TODO: Remove once apps switch over to v1
|
||||
scope :v1_alpha, as: :v1_alpha, module: :v1 do
|
||||
resources :accounts, only: [] do
|
||||
resources :collections, only: [:index]
|
||||
resources :in_collections, only: [:index]
|
||||
end
|
||||
|
||||
resources :async_refreshes, only: :show
|
||||
|
||||
resources :collections, only: [:show, :create, :update, :destroy] do
|
||||
resources :items, only: [:create, :destroy], controller: 'collection_items' do
|
||||
member do
|
||||
@ -223,6 +226,9 @@ namespace :api, format: false do
|
||||
resources :email_subscriptions, only: :create
|
||||
end
|
||||
|
||||
resources :collections, only: [:index]
|
||||
resources :in_collections, only: [:index]
|
||||
|
||||
member do
|
||||
post :follow
|
||||
post :unfollow
|
||||
@ -329,6 +335,14 @@ namespace :api, format: false do
|
||||
|
||||
resources :tags, only: [:index, :show, :update]
|
||||
end
|
||||
|
||||
resources :collections, only: [:show, :create, :update, :destroy] do
|
||||
resources :items, only: [:create, :destroy], controller: 'collection_items' do
|
||||
member do
|
||||
post :revoke
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
namespace :v2 do
|
||||
|
||||
@ -17,7 +17,7 @@
|
||||
class: Scheduler::ScheduledStatusesScheduler
|
||||
queue: scheduler
|
||||
trends_refresh_scheduler:
|
||||
every: '5m'
|
||||
every: ['5m', first_in: '4m']
|
||||
class: Scheduler::Trends::RefreshScheduler
|
||||
queue: scheduler
|
||||
trends_review_notifications_scheduler:
|
||||
|
||||
@ -45,7 +45,7 @@ module Mastodon
|
||||
|
||||
def api_versions
|
||||
{
|
||||
mastodon: 9,
|
||||
mastodon: 10,
|
||||
glitch: 1,
|
||||
}
|
||||
end
|
||||
|
||||
@ -182,7 +182,7 @@
|
||||
"msw": "^2.12.1",
|
||||
"msw-storybook-addon": "^2.0.6",
|
||||
"oxfmt": "^0.47.0",
|
||||
"playwright": "^1.57.0",
|
||||
"playwright": "^1.60.0",
|
||||
"react-test-renderer": "^18.2.0",
|
||||
"storybook": "^10.3.0",
|
||||
"stylelint": "^17.0.0",
|
||||
|
||||
@ -172,7 +172,7 @@ RSpec.describe ActivityPub::Activity::Accept do
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a FeatureRequest', feature: :collections do
|
||||
context 'with a FeatureRequest' do
|
||||
let(:collection) { Fabricate(:collection, account: recipient) }
|
||||
let(:collection_item) { Fabricate(:collection_item, collection:, account: sender, state: :pending) }
|
||||
let(:object) { collection_item.activity_uri }
|
||||
|
||||
@ -80,7 +80,7 @@ RSpec.describe ActivityPub::Activity::Add do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the target is the `featuredCollections` collection', feature: :collections do
|
||||
context 'when the target is the `featuredCollections` collection' do
|
||||
subject { described_class.new(activity_json, account) }
|
||||
|
||||
let(:account) { Fabricate(:remote_account, collections_url: 'https://example.com/actor/1/featured_collections') }
|
||||
@ -122,7 +122,7 @@ RSpec.describe ActivityPub::Activity::Add do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the target is a collection', feature: :collections do
|
||||
context 'when the target is a collection' do
|
||||
subject { described_class.new(activity_json, collection.account) }
|
||||
|
||||
let(:collection) { Fabricate(:remote_collection) }
|
||||
|
||||
@ -120,7 +120,7 @@ RSpec.describe ActivityPub::Activity::Delete do
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a FeatureAuthorization', feature: :collections do
|
||||
context 'with a FeatureAuthorization' do
|
||||
let(:recipient) { Fabricate(:account) }
|
||||
let(:approval_uri) { 'https://example.com/authorizations/1' }
|
||||
let(:collection) { Fabricate(:collection, account: recipient) }
|
||||
|
||||
@ -20,7 +20,7 @@ RSpec.describe ActivityPub::Activity::FeatureRequest do
|
||||
}
|
||||
end
|
||||
|
||||
describe '#perform', feature: :collections do
|
||||
describe '#perform' do
|
||||
subject { described_class.new(json, sender) }
|
||||
|
||||
context 'when recipient is discoverable' do
|
||||
|
||||
@ -257,7 +257,7 @@ RSpec.describe ActivityPub::Activity::Update do
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a `FeaturedCollection` object', feature: :collections do
|
||||
context 'with a `FeaturedCollection` object' do
|
||||
let(:collection) { Fabricate(:remote_collection, account: sender, name: 'old name', discoverable: false) }
|
||||
let(:account) { Fabricate(:account) }
|
||||
let!(:collection_item) { Fabricate(:collection_item, account:, collection:, uri: 'https://example.com/featured_stamps/1') }
|
||||
|
||||
@ -71,16 +71,18 @@ RSpec.describe AdminMailer do
|
||||
|
||||
describe '.new_trends' do
|
||||
let(:recipient) { Fabricate(:account, username: 'Snurf') }
|
||||
let(:link) { Fabricate(:preview_card, trendable: true, language: 'en') }
|
||||
let(:status) { Fabricate(:status) }
|
||||
let(:tag) { Fabricate(:tag) }
|
||||
let(:mail) { described_class.with(recipient: recipient).new_trends([link], [tag], [status]) }
|
||||
let!(:link) { Fabricate(:preview_card, trendable: true, language: 'en') }
|
||||
let!(:status) { Fabricate(:status) }
|
||||
let!(:tag) { Fabricate(:tag, display_name: 'Test Tag') }
|
||||
let!(:other_tag) { Fabricate(:tag, display_name: 'Other Test Tag') }
|
||||
let(:mail) { described_class.with(recipient: recipient).new_trends([link], [tag, other_tag], [status]) }
|
||||
let(:other_tag_trend) { Fabricate(:tag_trend, tag: other_tag) }
|
||||
|
||||
before do
|
||||
PreviewCardTrend.create!(preview_card: link)
|
||||
StatusTrend.create!(status: status, account: Fabricate(:account))
|
||||
TagTrend.create!(tag: tag)
|
||||
recipient.user.update(locale: :en)
|
||||
Fabricate(:status_trend, status: status, account: Fabricate(:account))
|
||||
Fabricate(:tag_trend, tag: tag)
|
||||
Fabricate(:preview_card_trend, preview_card: link)
|
||||
end
|
||||
|
||||
it 'renders the email' do
|
||||
@ -96,6 +98,44 @@ RSpec.describe AdminMailer do
|
||||
.and match(link.title)
|
||||
.and match(tag.display_name)
|
||||
end
|
||||
|
||||
context 'when between queueing and sending trends gets deleted' do
|
||||
before do
|
||||
recipient.user.update(locale: :en)
|
||||
end
|
||||
|
||||
it 'sends the email when all but one trends were deleted without the respective tag or status or link' do
|
||||
other_tag_trend
|
||||
expect(mail.deliver_later!)
|
||||
.to be_successfully_enqueued
|
||||
|
||||
TagTrend.delete_all
|
||||
StatusTrend.delete_all
|
||||
expect { mail.deliver }
|
||||
.to send_email(
|
||||
to: recipient.user_email,
|
||||
from: 'notifications@localhost',
|
||||
subject: I18n.t('admin_mailer.new_trends.subject', instance: Rails.configuration.x.local_domain)
|
||||
)
|
||||
expect(mail.body)
|
||||
.to have_text(/The following items need a review before they can be displayed publicly/)
|
||||
.and match(link.title)
|
||||
.and not_include(ActivityPub::TagManager.instance.url_for(status))
|
||||
.and not_include(tag.display_name)
|
||||
end
|
||||
|
||||
it 'returns nil when no trends are present' do
|
||||
expect(mail.deliver_later!)
|
||||
.to be_successfully_enqueued
|
||||
|
||||
TagTrend.delete_all
|
||||
StatusTrend.delete_all
|
||||
PreviewCardTrend.delete_all
|
||||
|
||||
expect { mail.deliver }
|
||||
.to_not send_email
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.new_software_updates' do
|
||||
|
||||
@ -33,7 +33,7 @@ RSpec.describe Admin::ModerationAction do
|
||||
expect(report.reload).to be_action_taken
|
||||
end
|
||||
|
||||
context 'with attached collections', feature: :collections do
|
||||
context 'with attached collections' do
|
||||
let(:status_ids) { [] }
|
||||
let(:collections) { Fabricate.times(2, :collection, account: target_account) }
|
||||
|
||||
@ -47,7 +47,7 @@ RSpec.describe Admin::ModerationAction do
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a remote collection', feature: :collections do
|
||||
context 'with a remote collection' do
|
||||
let(:status_ids) { [] }
|
||||
let(:collection) { Fabricate(:remote_collection) }
|
||||
let(:target_account) { collection.account }
|
||||
@ -83,7 +83,7 @@ RSpec.describe Admin::ModerationAction do
|
||||
expect(report.reload).to be_action_taken
|
||||
end
|
||||
|
||||
context 'with attached collections', feature: :collections do
|
||||
context 'with attached collections' do
|
||||
let(:status_ids) { [] }
|
||||
let(:collections) { Fabricate.times(2, :collection, account: target_account) }
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Collections' do
|
||||
describe 'GET /ap/users/@:account_id/featured_collections', feature: :collections do
|
||||
describe 'GET /ap/users/@:account_id/featured_collections' do
|
||||
subject { get ap_account_featured_collections_path(account.id, format: :json) }
|
||||
|
||||
let(:collection) { Fabricate(:collection) }
|
||||
|
||||
@ -47,7 +47,7 @@ RSpec.describe 'Admin Reports' do
|
||||
it_behaves_like 'successful return'
|
||||
end
|
||||
|
||||
context 'with a reported collection', feature: :collections do
|
||||
context 'with a reported collection' do
|
||||
before do
|
||||
report.collections << Fabricate(:collection, account: report.target_account)
|
||||
end
|
||||
@ -55,7 +55,7 @@ RSpec.describe 'Admin Reports' do
|
||||
it_behaves_like 'successful return'
|
||||
end
|
||||
|
||||
context 'with both status and collection', feature: :collections do
|
||||
context 'with both status and collection' do
|
||||
before do
|
||||
status = Fabricate(:status, account: report.target_account)
|
||||
report.update(status_ids: [status.id])
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Api::V1Alpha::CollectionItems', feature: :collections do
|
||||
RSpec.describe 'Api::V1Alpha::CollectionItems' do
|
||||
include_context 'with API authentication', oauth_scopes: 'read:collections write:collections'
|
||||
|
||||
describe 'POST /api/v1_alpha/collections/:collection_id/items' do
|
||||
@ -2,12 +2,12 @@
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Api::V1Alpha::Collections', feature: :collections do
|
||||
RSpec.describe 'Api::V1::Collections' do
|
||||
include_context 'with API authentication', oauth_scopes: 'read:collections write:collections'
|
||||
|
||||
describe 'GET /api/v1_alpha/accounts/:account_id/collections' do
|
||||
describe 'GET /api/v1/accounts/:account_id/collections' do
|
||||
subject do
|
||||
get "/api/v1_alpha/accounts/#{account.id}/collections", headers: headers, params: params
|
||||
get "/api/v1/accounts/#{account.id}/collections", headers: headers, params: params
|
||||
end
|
||||
|
||||
let(:params) { {} }
|
||||
@ -34,7 +34,7 @@ RSpec.describe 'Api::V1Alpha::Collections', feature: :collections do
|
||||
|
||||
expect(response)
|
||||
.to include_pagination_headers(
|
||||
next: api_v1_alpha_account_collections_url(account, limit: 1, offset: 1)
|
||||
next: api_v1_account_collections_url(account, limit: 1, offset: 1)
|
||||
)
|
||||
end
|
||||
end
|
||||
@ -50,8 +50,8 @@ RSpec.describe 'Api::V1Alpha::Collections', feature: :collections do
|
||||
|
||||
expect(response)
|
||||
.to include_pagination_headers(
|
||||
prev: api_v1_alpha_account_collections_url(account, limit: 1, offset: 0),
|
||||
next: api_v1_alpha_account_collections_url(account, limit: 1, offset: 2)
|
||||
prev: api_v1_account_collections_url(account, limit: 1, offset: 0),
|
||||
next: api_v1_account_collections_url(account, limit: 1, offset: 2)
|
||||
)
|
||||
end
|
||||
end
|
||||
@ -96,9 +96,9 @@ RSpec.describe 'Api::V1Alpha::Collections', feature: :collections do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /api/v1_alpha/collections/:id' do
|
||||
describe 'GET /api/v1/collections/:id' do
|
||||
subject do
|
||||
get "/api/v1_alpha/collections/#{collection.id}", headers: headers
|
||||
get "/api/v1/collections/#{collection.id}", headers: headers
|
||||
end
|
||||
|
||||
let(:collection) { Fabricate(:collection) }
|
||||
@ -140,9 +140,9 @@ RSpec.describe 'Api::V1Alpha::Collections', feature: :collections do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST /api/v1_alpha/collections' do
|
||||
describe 'POST /api/v1/collections' do
|
||||
subject do
|
||||
post '/api/v1_alpha/collections', headers: headers, params: params
|
||||
post '/api/v1/collections', headers: headers, params: params
|
||||
end
|
||||
|
||||
let(:params) { {} }
|
||||
@ -187,9 +187,9 @@ RSpec.describe 'Api::V1Alpha::Collections', feature: :collections do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'PATCH /api/v1_alpha/collections/:id' do
|
||||
describe 'PATCH /api/v1/collections/:id' do
|
||||
subject do
|
||||
patch "/api/v1_alpha/collections/#{collection.id}", headers: headers, params: params
|
||||
patch "/api/v1/collections/#{collection.id}", headers: headers, params: params
|
||||
end
|
||||
|
||||
let(:collection) { Fabricate(:collection) }
|
||||
@ -256,9 +256,9 @@ RSpec.describe 'Api::V1Alpha::Collections', feature: :collections do
|
||||
end
|
||||
end
|
||||
|
||||
describe 'DELETE /api/v1_alpha/collections/:id' do
|
||||
describe 'DELETE /api/v1/collections/:id' do
|
||||
subject do
|
||||
delete "/api/v1_alpha/collections/#{collection.id}", headers: headers
|
||||
delete "/api/v1/collections/#{collection.id}", headers: headers
|
||||
end
|
||||
|
||||
let(:collection) { Fabricate(:collection) }
|
||||
@ -2,12 +2,12 @@
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe 'Api::V1Alpha::InCollections', feature: :collections do
|
||||
RSpec.describe 'Api::V1::InCollections' do
|
||||
include_context 'with API authentication', oauth_scopes: 'read:collections write:collections'
|
||||
|
||||
describe 'GET /api/v1_alpha/in_collections' do
|
||||
describe 'GET /api/v1/in_collections' do
|
||||
subject do
|
||||
get "/api/v1_alpha/accounts/#{account.id}/in_collections", headers: headers, params: params
|
||||
get "/api/v1/accounts/#{account.id}/in_collections", headers: headers, params: params
|
||||
end
|
||||
|
||||
let(:params) { {} }
|
||||
@ -33,7 +33,7 @@ RSpec.describe 'Api::V1Alpha::InCollections', feature: :collections do
|
||||
|
||||
expect(response)
|
||||
.to include_pagination_headers(
|
||||
next: api_v1_alpha_account_in_collections_url(account, limit: 1, offset: 1)
|
||||
next: api_v1_account_in_collections_url(account, limit: 1, offset: 1)
|
||||
)
|
||||
end
|
||||
end
|
||||
@ -49,8 +49,8 @@ RSpec.describe 'Api::V1Alpha::InCollections', feature: :collections do
|
||||
|
||||
expect(response)
|
||||
.to include_pagination_headers(
|
||||
prev: api_v1_alpha_account_in_collections_url(account, limit: 1, offset: 0),
|
||||
next: api_v1_alpha_account_in_collections_url(account, limit: 1, offset: 2)
|
||||
prev: api_v1_account_in_collections_url(account, limit: 1, offset: 0),
|
||||
next: api_v1_account_in_collections_url(account, limit: 1, offset: 2)
|
||||
)
|
||||
end
|
||||
end
|
||||
@ -113,7 +113,7 @@ RSpec.describe 'Reports' do
|
||||
end
|
||||
end
|
||||
|
||||
context 'with attached collection', feature: :collections do
|
||||
context 'with attached collection' do
|
||||
let(:collection) { Fabricate(:collection, account: target_account) }
|
||||
let(:collection_ids) { [collection.id] }
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user