Merge remote-tracking branch 'parent/main' into upstream-20240524

This commit is contained in:
KMY 2024-05-24 08:33:49 +09:00
commit c546939a40
213 changed files with 2260 additions and 986 deletions

View file

@ -6,7 +6,8 @@ LOCAL_HTTPS=true
# Elasticsearch
ES_PREFIX=test
# Required by ActiveRecord encryption feature
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=fkSxKD2bF396kdQbrP1EJ7WbU7ZgNokR
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=r0hvVmzBVsjxC7AMlwhOzmtc36ZCOS1E
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=PhdFyyfy5xJ7WVd2lWBpcPScRQHzRTNr
# Secret values required by ActiveRecord encryption feature
# Use `bin/rails db:encryption:init` to generate fresh secrets
ACTIVE_RECORD_ENCRYPTION_DETERMINISTIC_KEY=test_determinist_key_DO_NOT_USE_IN_PRODUCTION
ACTIVE_RECORD_ENCRYPTION_KEY_DERIVATION_SALT=test_salt_DO_NOT_USE_IN_PRODUCTION
ACTIVE_RECORD_ENCRYPTION_PRIMARY_KEY=test_primary_key_DO_NOT_USE_IN_PRODUCTION

View file

@ -10,35 +10,35 @@ GIT
GEM
remote: https://rubygems.org/
specs:
actioncable (7.1.3.2)
actionpack (= 7.1.3.2)
activesupport (= 7.1.3.2)
actioncable (7.1.3.3)
actionpack (= 7.1.3.3)
activesupport (= 7.1.3.3)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (7.1.3.2)
actionpack (= 7.1.3.2)
activejob (= 7.1.3.2)
activerecord (= 7.1.3.2)
activestorage (= 7.1.3.2)
activesupport (= 7.1.3.2)
actionmailbox (7.1.3.3)
actionpack (= 7.1.3.3)
activejob (= 7.1.3.3)
activerecord (= 7.1.3.3)
activestorage (= 7.1.3.3)
activesupport (= 7.1.3.3)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.1.3.2)
actionpack (= 7.1.3.2)
actionview (= 7.1.3.2)
activejob (= 7.1.3.2)
activesupport (= 7.1.3.2)
actionmailer (7.1.3.3)
actionpack (= 7.1.3.3)
actionview (= 7.1.3.3)
activejob (= 7.1.3.3)
activesupport (= 7.1.3.3)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.2)
actionpack (7.1.3.2)
actionview (= 7.1.3.2)
activesupport (= 7.1.3.2)
actionpack (7.1.3.3)
actionview (= 7.1.3.3)
activesupport (= 7.1.3.3)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4)
@ -46,15 +46,15 @@ GEM
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
actiontext (7.1.3.2)
actionpack (= 7.1.3.2)
activerecord (= 7.1.3.2)
activestorage (= 7.1.3.2)
activesupport (= 7.1.3.2)
actiontext (7.1.3.3)
actionpack (= 7.1.3.3)
activerecord (= 7.1.3.3)
activestorage (= 7.1.3.3)
activesupport (= 7.1.3.3)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.1.3.2)
activesupport (= 7.1.3.2)
actionview (7.1.3.3)
activesupport (= 7.1.3.3)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
@ -64,22 +64,22 @@ GEM
activemodel (>= 4.1)
case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.1.3.2)
activesupport (= 7.1.3.2)
activejob (7.1.3.3)
activesupport (= 7.1.3.3)
globalid (>= 0.3.6)
activemodel (7.1.3.2)
activesupport (= 7.1.3.2)
activerecord (7.1.3.2)
activemodel (= 7.1.3.2)
activesupport (= 7.1.3.2)
activemodel (7.1.3.3)
activesupport (= 7.1.3.3)
activerecord (7.1.3.3)
activemodel (= 7.1.3.3)
activesupport (= 7.1.3.3)
timeout (>= 0.4.0)
activestorage (7.1.3.2)
actionpack (= 7.1.3.2)
activejob (= 7.1.3.2)
activerecord (= 7.1.3.2)
activesupport (= 7.1.3.2)
activestorage (7.1.3.3)
actionpack (= 7.1.3.3)
activejob (= 7.1.3.3)
activerecord (= 7.1.3.3)
activesupport (= 7.1.3.3)
marcel (~> 1.0)
activesupport (7.1.3.2)
activesupport (7.1.3.3)
base64
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
@ -425,7 +425,7 @@ GEM
mime-types-data (3.2024.0507)
mini_mime (1.1.5)
mini_portile2 (2.8.6)
minitest (5.22.3)
minitest (5.23.0)
msgpack (1.7.2)
multi_json (1.15.0)
multipart-post (2.4.0)
@ -444,7 +444,7 @@ GEM
timeout
net-smtp (0.5.0)
net-protocol
nio4r (2.7.1)
nio4r (2.7.3)
nokogiri (1.16.5)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
@ -610,7 +610,7 @@ GEM
pundit (2.3.2)
activesupport (>= 3.0.0)
raabro (1.4.0)
racc (1.7.3)
racc (1.8.0)
rack (2.2.9)
rack-attack (6.7.0)
rack (>= 1.0, < 4)
@ -634,20 +634,20 @@ GEM
rackup (1.0.0)
rack (< 3)
webrick
rails (7.1.3.2)
actioncable (= 7.1.3.2)
actionmailbox (= 7.1.3.2)
actionmailer (= 7.1.3.2)
actionpack (= 7.1.3.2)
actiontext (= 7.1.3.2)
actionview (= 7.1.3.2)
activejob (= 7.1.3.2)
activemodel (= 7.1.3.2)
activerecord (= 7.1.3.2)
activestorage (= 7.1.3.2)
activesupport (= 7.1.3.2)
rails (7.1.3.3)
actioncable (= 7.1.3.3)
actionmailbox (= 7.1.3.3)
actionmailer (= 7.1.3.3)
actionpack (= 7.1.3.3)
actiontext (= 7.1.3.3)
actionview (= 7.1.3.3)
activejob (= 7.1.3.3)
activemodel (= 7.1.3.3)
activerecord (= 7.1.3.3)
activestorage (= 7.1.3.3)
activesupport (= 7.1.3.3)
bundler (>= 1.15.0)
railties (= 7.1.3.2)
railties (= 7.1.3.3)
rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1)
@ -662,9 +662,9 @@ GEM
rails-i18n (7.0.9)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
railties (7.1.3.2)
actionpack (= 7.1.3.2)
activesupport (= 7.1.3.2)
railties (7.1.3.3)
actionpack (= 7.1.3.3)
activesupport (= 7.1.3.3)
irb
rackup (>= 1.0.0)
rake (>= 12.2)
@ -685,7 +685,7 @@ GEM
redis (>= 4)
redlock (1.3.2)
redis (>= 3.0.0, < 6.0)
regexp_parser (2.9.0)
regexp_parser (2.9.2)
reline (0.5.7)
io-console (~> 0.5)
request_store (1.6.0)
@ -746,7 +746,7 @@ GEM
rubocop-performance (1.21.0)
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rails (2.24.1)
rubocop-rails (2.25.0)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
@ -775,7 +775,7 @@ GEM
scenic (1.8.0)
activerecord (>= 4.0.0)
railties (>= 4.0.0)
selenium-webdriver (4.21.0)
selenium-webdriver (4.21.1)
base64 (~> 0.2)
rexml (~> 3.2, >= 3.2.5)
rubyzip (>= 1.2.2, < 3.0)

View file

@ -142,7 +142,7 @@ module Admin
def unblock_email
authorize @account, :unblock_email?
CanonicalEmailBlock.matching_account(@account).delete_all
CanonicalEmailBlock.where(reference_account: @account).delete_all
log_action :unblock_email, @account

View file

@ -4,6 +4,6 @@ class Api::V1::Apps::CredentialsController < Api::BaseController
def show
return doorkeeper_render_error unless valid_doorkeeper_token?
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key client_id scopes)
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer
end
end

View file

@ -5,7 +5,7 @@ class Api::V1::AppsController < Api::BaseController
def create
@app = Doorkeeper::Application.create!(application_options)
render json: @app, serializer: REST::ApplicationSerializer
render json: @app, serializer: REST::CredentialApplicationSerializer
end
private
@ -24,6 +24,6 @@ class Api::V1::AppsController < Api::BaseController
end
def app_params
params.permit(:client_name, :redirect_uris, :scopes, :website)
params.permit(:client_name, :scopes, :website, :redirect_uris, redirect_uris: [])
end
end

View file

@ -254,6 +254,10 @@ module ApplicationHelper
prerender_custom_emojis(html, JSON.parse([custom_emojis_hash].to_json, object_class: OpenStruct)) # rubocop:disable Style/OpenStructUse
end
def mascot_url
full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg'))
end
def instance_presenter
@instance_presenter ||= InstancePresenter.new
end

View file

@ -1,11 +0,0 @@
# frozen_string_literal: true
module MascotHelper
def mascot_url
full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg'))
end
def instance_presenter
@instance_presenter ||= InstancePresenter.new
end
end

View file

@ -1,18 +1,10 @@
import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships';
import { createAppAsyncThunk } from 'mastodon/store/typed_functions';
import { apiSubmitAccountNote } from 'mastodon/api/accounts';
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
import api from '../api';
export const submitAccountNote = createAppAsyncThunk(
export const submitAccountNote = createDataLoadingThunk(
'account_note/submit',
async (args: { id: string; value: string }, { getState }) => {
const response = await api(getState).post<ApiRelationshipJSON>(
`/api/v1/accounts/${args.id}/note`,
{
comment: args.value,
},
);
return { relationship: response.data };
},
({ accountId, note }: { accountId: string; note: string }) =>
apiSubmitAccountNote(accountId, note),
(relationship) => ({ relationship }),
{ skipLoading: true },
);

View file

@ -76,11 +76,11 @@ export const ACCOUNT_REVEAL = 'ACCOUNT_REVEAL';
export * from './accounts_typed';
export function fetchAccount(id) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(fetchRelationships([id]));
dispatch(fetchAccountRequest(id));
api(getState).get(`/api/v1/accounts/${id}`).then(response => {
api().get(`/api/v1/accounts/${id}`).then(response => {
dispatch(importFetchedAccount(response.data));
dispatch(fetchAccountSuccess());
}).catch(error => {
@ -89,10 +89,10 @@ export function fetchAccount(id) {
};
}
export const lookupAccount = acct => (dispatch, getState) => {
export const lookupAccount = acct => (dispatch) => {
dispatch(lookupAccountRequest(acct));
api(getState).get('/api/v1/accounts/lookup', { params: { acct } }).then(response => {
api().get('/api/v1/accounts/lookup', { params: { acct } }).then(response => {
dispatch(fetchRelationships([response.data.id]));
dispatch(importFetchedAccount(response.data));
dispatch(lookupAccountSuccess());
@ -146,7 +146,7 @@ export function followAccount(id, options = { reblogs: true }) {
dispatch(followAccountRequest({ id, locked }));
api(getState).post(`/api/v1/accounts/${id}/follow`, options).then(response => {
api().post(`/api/v1/accounts/${id}/follow`, options).then(response => {
dispatch(followAccountSuccess({relationship: response.data, alreadyFollowing}));
}).catch(error => {
dispatch(followAccountFail({ id, error, locked }));
@ -158,7 +158,7 @@ export function unfollowAccount(id) {
return (dispatch, getState) => {
dispatch(unfollowAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/unfollow`).then(response => {
api().post(`/api/v1/accounts/${id}/unfollow`).then(response => {
dispatch(unfollowAccountSuccess({relationship: response.data, statuses: getState().get('statuses')}));
}).catch(error => {
dispatch(unfollowAccountFail({ id, error }));
@ -170,7 +170,7 @@ export function blockAccount(id) {
return (dispatch, getState) => {
dispatch(blockAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/block`).then(response => {
api().post(`/api/v1/accounts/${id}/block`).then(response => {
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
dispatch(blockAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') }));
}).catch(error => {
@ -180,10 +180,10 @@ export function blockAccount(id) {
}
export function unblockAccount(id) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(unblockAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/unblock`).then(response => {
api().post(`/api/v1/accounts/${id}/unblock`).then(response => {
dispatch(unblockAccountSuccess({ relationship: response.data }));
}).catch(error => {
dispatch(unblockAccountFail({ id, error }));
@ -223,7 +223,7 @@ export function muteAccount(id, notifications, duration=0) {
return (dispatch, getState) => {
dispatch(muteAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/mute`, { notifications, duration }).then(response => {
api().post(`/api/v1/accounts/${id}/mute`, { notifications, duration }).then(response => {
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
dispatch(muteAccountSuccess({ relationship: response.data, statuses: getState().get('statuses') }));
}).catch(error => {
@ -233,10 +233,10 @@ export function muteAccount(id, notifications, duration=0) {
}
export function unmuteAccount(id) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(unmuteAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/unmute`).then(response => {
api().post(`/api/v1/accounts/${id}/unmute`).then(response => {
dispatch(unmuteAccountSuccess({ relationship: response.data }));
}).catch(error => {
dispatch(unmuteAccountFail({ id, error }));
@ -274,10 +274,10 @@ export function unmuteAccountFail(error) {
export function fetchFollowers(id) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(fetchFollowersRequest(id));
api(getState).get(`/api/v1/accounts/${id}/followers`).then(response => {
api().get(`/api/v1/accounts/${id}/followers`).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
@ -324,7 +324,7 @@ export function expandFollowers(id) {
dispatch(expandFollowersRequest(id));
api(getState).get(url).then(response => {
api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
@ -361,10 +361,10 @@ export function expandFollowersFail(id, error) {
}
export function fetchFollowing(id) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(fetchFollowingRequest(id));
api(getState).get(`/api/v1/accounts/${id}/following`).then(response => {
api().get(`/api/v1/accounts/${id}/following`).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
@ -411,7 +411,7 @@ export function expandFollowing(id) {
dispatch(expandFollowingRequest(id));
api(getState).get(url).then(response => {
api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
@ -460,7 +460,7 @@ export function fetchRelationships(accountIds) {
dispatch(fetchRelationshipsRequest(newAccountIds));
api(getState).get(`/api/v1/accounts/relationships?with_suspended=true&${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
api().get(`/api/v1/accounts/relationships?with_suspended=true&${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => {
dispatch(fetchRelationshipsSuccess({ relationships: response.data }));
}).catch(error => {
dispatch(fetchRelationshipsFail(error));
@ -486,10 +486,10 @@ export function fetchRelationshipsFail(error) {
}
export function fetchFollowRequests() {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(fetchFollowRequestsRequest());
api(getState).get('/api/v1/follow_requests').then(response => {
api().get('/api/v1/follow_requests').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null));
@ -528,7 +528,7 @@ export function expandFollowRequests() {
dispatch(expandFollowRequestsRequest());
api(getState).get(url).then(response => {
api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null));
@ -558,10 +558,10 @@ export function expandFollowRequestsFail(error) {
}
export function authorizeFollowRequest(id) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(authorizeFollowRequestRequest(id));
api(getState)
api()
.post(`/api/v1/follow_requests/${id}/authorize`)
.then(() => dispatch(authorizeFollowRequestSuccess({ id })))
.catch(error => dispatch(authorizeFollowRequestFail(id, error)));
@ -585,10 +585,10 @@ export function authorizeFollowRequestFail(id, error) {
export function rejectFollowRequest(id) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(rejectFollowRequestRequest(id));
api(getState)
api()
.post(`/api/v1/follow_requests/${id}/reject`)
.then(() => dispatch(rejectFollowRequestSuccess({ id })))
.catch(error => dispatch(rejectFollowRequestFail(id, error)));
@ -611,10 +611,10 @@ export function rejectFollowRequestFail(id, error) {
}
export function pinAccount(id) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(pinAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/pin`).then(response => {
api().post(`/api/v1/accounts/${id}/pin`).then(response => {
dispatch(pinAccountSuccess({ relationship: response.data }));
}).catch(error => {
dispatch(pinAccountFail(error));
@ -623,10 +623,10 @@ export function pinAccount(id) {
}
export function unpinAccount(id) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(unpinAccountRequest(id));
api(getState).post(`/api/v1/accounts/${id}/unpin`).then(response => {
api().post(`/api/v1/accounts/${id}/unpin`).then(response => {
dispatch(unpinAccountSuccess({ relationship: response.data }));
}).catch(error => {
dispatch(unpinAccountFail(error));
@ -662,7 +662,7 @@ export function unpinAccountFail(error) {
};
}
export const updateAccount = ({ displayName, note, avatar, header, discoverable, indexable }) => (dispatch, getState) => {
export const updateAccount = ({ displayName, note, avatar, header, discoverable, indexable }) => (dispatch) => {
const data = new FormData();
data.append('display_name', displayName);
@ -672,7 +672,7 @@ export const updateAccount = ({ displayName, note, avatar, header, discoverable,
data.append('discoverable', discoverable);
data.append('indexable', indexable);
return api(getState).patch('/api/v1/accounts/update_credentials', data).then(response => {
return api().patch('/api/v1/accounts/update_credentials', data).then(response => {
dispatch(importFetchedAccount(response.data));
});
};

View file

@ -26,10 +26,10 @@ export const ANNOUNCEMENTS_TOGGLE_SHOW = 'ANNOUNCEMENTS_TOGGLE_SHOW';
const noOp = () => {};
export const fetchAnnouncements = (done = noOp) => (dispatch, getState) => {
export const fetchAnnouncements = (done = noOp) => (dispatch) => {
dispatch(fetchAnnouncementsRequest());
api(getState).get('/api/v1/announcements').then(response => {
api().get('/api/v1/announcements').then(response => {
dispatch(fetchAnnouncementsSuccess(response.data.map(x => normalizeAnnouncement(x))));
}).catch(error => {
dispatch(fetchAnnouncementsFail(error));
@ -61,10 +61,10 @@ export const updateAnnouncements = announcement => ({
announcement: normalizeAnnouncement(announcement),
});
export const dismissAnnouncement = announcementId => (dispatch, getState) => {
export const dismissAnnouncement = announcementId => (dispatch) => {
dispatch(dismissAnnouncementRequest(announcementId));
api(getState).post(`/api/v1/announcements/${announcementId}/dismiss`).then(() => {
api().post(`/api/v1/announcements/${announcementId}/dismiss`).then(() => {
dispatch(dismissAnnouncementSuccess(announcementId));
}).catch(error => {
dispatch(dismissAnnouncementFail(announcementId, error));
@ -103,7 +103,7 @@ export const addReaction = (announcementId, name) => (dispatch, getState) => {
dispatch(addReactionRequest(announcementId, name, alreadyAdded));
}
api(getState).put(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => {
api().put(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => {
dispatch(addReactionSuccess(announcementId, name, alreadyAdded));
}).catch(err => {
if (!alreadyAdded) {
@ -134,10 +134,10 @@ export const addReactionFail = (announcementId, name, error) => ({
skipLoading: true,
});
export const removeReaction = (announcementId, name) => (dispatch, getState) => {
export const removeReaction = (announcementId, name) => (dispatch) => {
dispatch(removeReactionRequest(announcementId, name));
api(getState).delete(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => {
api().delete(`/api/v1/announcements/${announcementId}/reactions/${encodeURIComponent(name)}`).then(() => {
dispatch(removeReactionSuccess(announcementId, name));
}).catch(err => {
dispatch(removeReactionFail(announcementId, name, err));

View file

@ -13,10 +13,10 @@ export const BLOCKS_EXPAND_SUCCESS = 'BLOCKS_EXPAND_SUCCESS';
export const BLOCKS_EXPAND_FAIL = 'BLOCKS_EXPAND_FAIL';
export function fetchBlocks() {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(fetchBlocksRequest());
api(getState).get('/api/v1/blocks').then(response => {
api().get('/api/v1/blocks').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
dispatch(fetchBlocksSuccess(response.data, next ? next.uri : null));
@ -56,7 +56,7 @@ export function expandBlocks() {
dispatch(expandBlocksRequest());
api(getState).get(url).then(response => {
api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
dispatch(expandBlocksSuccess(response.data, next ? next.uri : null));

View file

@ -20,7 +20,7 @@ export function fetchBookmarkedStatuses() {
dispatch(fetchBookmarkedStatusesRequest());
api(getState).get('/api/v1/bookmarks').then(response => {
api().get('/api/v1/bookmarks').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(fetchBookmarkedStatusesSuccess(response.data, next ? next.uri : null));
@ -61,7 +61,7 @@ export function expandBookmarkedStatuses() {
dispatch(expandBookmarkedStatusesRequest());
api(getState).get(url).then(response => {
api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(expandBookmarkedStatusesSuccess(response.data, next ? next.uri : null));

View file

@ -207,7 +207,7 @@ export function submitCompose(routerHistory) {
});
}
api(getState).request({
api().request({
url: statusId === null ? '/api/v1/statuses' : `/api/v1/statuses/${statusId}`,
method: statusId === null ? 'post' : 'put',
data: {
@ -327,7 +327,7 @@ export function uploadCompose(files) {
const data = new FormData();
data.append('file', file);
api(getState).post('/api/v2/media', data, {
api().post('/api/v2/media', data, {
onUploadProgress: function({ loaded }){
progress[i] = loaded;
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
@ -348,7 +348,7 @@ export function uploadCompose(files) {
let tryCount = 1;
const poll = () => {
api(getState).get(`/api/v1/media/${data.id}`).then(response => {
api().get(`/api/v1/media/${data.id}`).then(response => {
if (response.status === 200) {
dispatch(uploadComposeSuccess(response.data, file));
} else if (response.status === 206) {
@ -370,7 +370,7 @@ export const uploadComposeProcessing = () => ({
type: COMPOSE_UPLOAD_PROCESSING,
});
export const uploadThumbnail = (id, file) => (dispatch, getState) => {
export const uploadThumbnail = (id, file) => (dispatch) => {
dispatch(uploadThumbnailRequest());
const total = file.size;
@ -378,7 +378,7 @@ export const uploadThumbnail = (id, file) => (dispatch, getState) => {
data.append('thumbnail', file);
api(getState).put(`/api/v1/media/${id}`, data, {
api().put(`/api/v1/media/${id}`, data, {
onUploadProgress: ({ loaded }) => {
dispatch(uploadThumbnailProgress(loaded, total));
},
@ -461,7 +461,7 @@ export function changeUploadCompose(id, params) {
dispatch(changeUploadComposeSuccess(data, true));
} else {
api(getState).put(`/api/v1/media/${id}`, params).then(response => {
api().put(`/api/v1/media/${id}`, params).then(response => {
dispatch(changeUploadComposeSuccess(response.data, false));
}).catch(error => {
dispatch(changeUploadComposeFail(id, error));
@ -549,7 +549,7 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) =>
fetchComposeSuggestionsAccountsController = new AbortController();
api(getState).get('/api/v1/accounts/search', {
api().get('/api/v1/accounts/search', {
signal: fetchComposeSuggestionsAccountsController.signal,
params: {
@ -583,7 +583,7 @@ const fetchComposeSuggestionsTags = throttle((dispatch, getState, token) => {
fetchComposeSuggestionsTagsController = new AbortController();
api(getState).get('/api/v2/search', {
api().get('/api/v2/search', {
signal: fetchComposeSuggestionsTagsController.signal,
params: {

View file

@ -28,13 +28,13 @@ export const unmountConversations = () => ({
type: CONVERSATIONS_UNMOUNT,
});
export const markConversationRead = conversationId => (dispatch, getState) => {
export const markConversationRead = conversationId => (dispatch) => {
dispatch({
type: CONVERSATIONS_READ,
id: conversationId,
});
api(getState).post(`/api/v1/conversations/${conversationId}/read`);
api().post(`/api/v1/conversations/${conversationId}/read`);
};
export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => {
@ -48,7 +48,7 @@ export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => {
const isLoadingRecent = !!params.since_id;
api(getState).get('/api/v1/conversations', { params })
api().get('/api/v1/conversations', { params })
.then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
@ -88,10 +88,10 @@ export const updateConversations = conversation => dispatch => {
});
};
export const deleteConversation = conversationId => (dispatch, getState) => {
export const deleteConversation = conversationId => (dispatch) => {
dispatch(deleteConversationRequest(conversationId));
api(getState).delete(`/api/v1/conversations/${conversationId}`)
api().delete(`/api/v1/conversations/${conversationId}`)
.then(() => dispatch(deleteConversationSuccess(conversationId)))
.catch(error => dispatch(deleteConversationFail(conversationId, error)));
};

View file

@ -5,10 +5,10 @@ export const CUSTOM_EMOJIS_FETCH_SUCCESS = 'CUSTOM_EMOJIS_FETCH_SUCCESS';
export const CUSTOM_EMOJIS_FETCH_FAIL = 'CUSTOM_EMOJIS_FETCH_FAIL';
export function fetchCustomEmojis() {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(fetchCustomEmojisRequest());
api(getState).get('/api/v1/custom_emojis').then(response => {
api().get('/api/v1/custom_emojis').then(response => {
dispatch(fetchCustomEmojisSuccess(response.data));
}).catch(error => {
dispatch(fetchCustomEmojisFail(error));

View file

@ -11,10 +11,10 @@ export const DIRECTORY_EXPAND_REQUEST = 'DIRECTORY_EXPAND_REQUEST';
export const DIRECTORY_EXPAND_SUCCESS = 'DIRECTORY_EXPAND_SUCCESS';
export const DIRECTORY_EXPAND_FAIL = 'DIRECTORY_EXPAND_FAIL';
export const fetchDirectory = params => (dispatch, getState) => {
export const fetchDirectory = params => (dispatch) => {
dispatch(fetchDirectoryRequest());
api(getState).get('/api/v1/directory', { params: { ...params, limit: 20 } }).then(({ data }) => {
api().get('/api/v1/directory', { params: { ...params, limit: 20 } }).then(({ data }) => {
dispatch(importFetchedAccounts(data));
dispatch(fetchDirectorySuccess(data));
dispatch(fetchRelationships(data.map(x => x.id)));
@ -40,7 +40,7 @@ export const expandDirectory = params => (dispatch, getState) => {
const loadedItems = getState().getIn(['user_lists', 'directory', 'items']).size;
api(getState).get('/api/v1/directory', { params: { ...params, offset: loadedItems, limit: 20 } }).then(({ data }) => {
api().get('/api/v1/directory', { params: { ...params, offset: loadedItems, limit: 20 } }).then(({ data }) => {
dispatch(importFetchedAccounts(data));
dispatch(expandDirectorySuccess(data));
dispatch(fetchRelationships(data.map(x => x.id)));

View file

@ -24,7 +24,7 @@ export function blockDomain(domain) {
return (dispatch, getState) => {
dispatch(blockDomainRequest(domain));
api(getState).post('/api/v1/domain_blocks', { domain }).then(() => {
api().post('/api/v1/domain_blocks', { domain }).then(() => {
const at_domain = '@' + domain;
const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id'));
@ -54,7 +54,7 @@ export function unblockDomain(domain) {
return (dispatch, getState) => {
dispatch(unblockDomainRequest(domain));
api(getState).delete('/api/v1/domain_blocks', { params: { domain } }).then(() => {
api().delete('/api/v1/domain_blocks', { params: { domain } }).then(() => {
const at_domain = '@' + domain;
const accounts = getState().get('accounts').filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id'));
dispatch(unblockDomainSuccess({ domain, accounts }));
@ -80,10 +80,10 @@ export function unblockDomainFail(domain, error) {
}
export function fetchDomainBlocks() {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(fetchDomainBlocksRequest());
api(getState).get('/api/v1/domain_blocks').then(response => {
api().get('/api/v1/domain_blocks').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(fetchDomainBlocksSuccess(response.data, next ? next.uri : null));
}).catch(err => {
@ -123,7 +123,7 @@ export function expandDomainBlocks() {
dispatch(expandDomainBlocksRequest());
api(getState).get(url).then(response => {
api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(expandDomainBlocksSuccess(response.data, next ? next.uri : null));
}).catch(err => {

View file

@ -20,7 +20,7 @@ export function fetchFavouritedStatuses() {
dispatch(fetchFavouritedStatusesRequest());
api(getState).get('/api/v1/favourites').then(response => {
api().get('/api/v1/favourites').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(fetchFavouritedStatusesSuccess(response.data, next ? next.uri : null));
@ -64,7 +64,7 @@ export function expandFavouritedStatuses() {
dispatch(expandFavouritedStatusesRequest());
api(getState).get(url).then(response => {
api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(expandFavouritedStatusesSuccess(response.data, next ? next.uri : null));

View file

@ -11,7 +11,7 @@ export const fetchFeaturedTags = (id) => (dispatch, getState) => {
dispatch(fetchFeaturedTagsRequest(id));
api(getState).get(`/api/v1/accounts/${id}/featured_tags`)
api().get(`/api/v1/accounts/${id}/featured_tags`)
.then(({ data }) => dispatch(fetchFeaturedTagsSuccess(id, data)))
.catch(err => dispatch(fetchFeaturedTagsFail(id, err)));
};

View file

@ -23,13 +23,13 @@ export const initAddFilter = (status, { contextType }) => dispatch =>
},
}));
export const fetchFilters = () => (dispatch, getState) => {
export const fetchFilters = () => (dispatch) => {
dispatch({
type: FILTERS_FETCH_REQUEST,
skipLoading: true,
});
api(getState)
api()
.get('/api/v2/filters')
.then(({ data }) => dispatch({
type: FILTERS_FETCH_SUCCESS,
@ -44,10 +44,10 @@ export const fetchFilters = () => (dispatch, getState) => {
}));
};
export const createFilterStatus = (params, onSuccess, onFail) => (dispatch, getState) => {
export const createFilterStatus = (params, onSuccess, onFail) => (dispatch) => {
dispatch(createFilterStatusRequest());
api(getState).post(`/api/v2/filters/${params.filter_id}/statuses`, params).then(response => {
api().post(`/api/v2/filters/${params.filter_id}/statuses`, params).then(response => {
dispatch(createFilterStatusSuccess(response.data));
if (onSuccess) onSuccess();
}).catch(error => {
@ -70,10 +70,10 @@ export const createFilterStatusFail = error => ({
error,
});
export const createFilter = (params, onSuccess, onFail) => (dispatch, getState) => {
export const createFilter = (params, onSuccess, onFail) => (dispatch) => {
dispatch(createFilterRequest());
api(getState).post('/api/v2/filters', params).then(response => {
api().post('/api/v2/filters', params).then(response => {
dispatch(createFilterSuccess(response.data));
if (onSuccess) onSuccess(response.data);
}).catch(error => {

View file

@ -15,7 +15,7 @@ export const fetchHistory = statusId => (dispatch, getState) => {
dispatch(fetchHistoryRequest(statusId));
api(getState).get(`/api/v1/statuses/${statusId}/history`).then(({ data }) => {
api().get(`/api/v1/statuses/${statusId}/history`).then(({ data }) => {
dispatch(importFetchedAccounts(data.map(x => x.account)));
dispatch(fetchHistorySuccess(statusId, data));
}).catch(error => dispatch(fetchHistoryFail(error)));

View file

@ -3,10 +3,6 @@ import api, { getLinks } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts, importFetchedStatus, importFetchedStatuses } from './importer';
export const REBLOG_REQUEST = 'REBLOG_REQUEST';
export const REBLOG_SUCCESS = 'REBLOG_SUCCESS';
export const REBLOG_FAIL = 'REBLOG_FAIL';
export const REBLOGS_EXPAND_REQUEST = 'REBLOGS_EXPAND_REQUEST';
export const REBLOGS_EXPAND_SUCCESS = 'REBLOGS_EXPAND_SUCCESS';
export const REBLOGS_EXPAND_FAIL = 'REBLOGS_EXPAND_FAIL';
@ -19,10 +15,6 @@ export const EMOJIREACT_REQUEST = 'EMOJIREACT_REQUEST';
export const EMOJIREACT_SUCCESS = 'EMOJIREACT_SUCCESS';
export const EMOJIREACT_FAIL = 'EMOJIREACT_FAIL';
export const UNREBLOG_REQUEST = 'UNREBLOG_REQUEST';
export const UNREBLOG_SUCCESS = 'UNREBLOG_SUCCESS';
export const UNREBLOG_FAIL = 'UNREBLOG_FAIL';
export const UNFAVOURITE_REQUEST = 'UNFAVOURITE_REQUEST';
export const UNFAVOURITE_SUCCESS = 'UNFAVOURITE_SUCCESS';
export const UNFAVOURITE_FAIL = 'UNFAVOURITE_FAIL';
@ -79,89 +71,13 @@ export const MENTIONED_USERS_EXPAND_REQUEST = 'MENTIONED_USERS_EXPAND_REQUEST';
export const MENTIONED_USERS_EXPAND_SUCCESS = 'MENTIONED_USERS_EXPAND_SUCCESS';
export const MENTIONED_USERS_EXPAND_FAIL = 'MENTIONED_USERS_EXPAND_FAIL';
export function reblog(status, visibility) {
return function (dispatch, getState) {
dispatch(reblogRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`, { visibility }).then(function (response) {
// The reblog API method returns a new status wrapped around the original. In this case we are only
// interested in how the original is modified, hence passing it skipping the wrapper
dispatch(importFetchedStatus(response.data.reblog));
dispatch(reblogSuccess(status));
}).catch(function (error) {
dispatch(reblogFail(status, error));
});
};
}
export function unreblog(status) {
return (dispatch, getState) => {
dispatch(unreblogRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/unreblog`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(unreblogSuccess(status));
}).catch(error => {
dispatch(unreblogFail(status, error));
});
};
}
export function reblogRequest(status) {
return {
type: REBLOG_REQUEST,
status: status,
skipLoading: true,
};
}
export function reblogSuccess(status) {
return {
type: REBLOG_SUCCESS,
status: status,
skipLoading: true,
};
}
export function reblogFail(status, error) {
return {
type: REBLOG_FAIL,
status: status,
error: error,
skipLoading: true,
};
}
export function unreblogRequest(status) {
return {
type: UNREBLOG_REQUEST,
status: status,
skipLoading: true,
};
}
export function unreblogSuccess(status) {
return {
type: UNREBLOG_SUCCESS,
status: status,
skipLoading: true,
};
}
export function unreblogFail(status, error) {
return {
type: UNREBLOG_FAIL,
status: status,
error: error,
skipLoading: true,
};
}
export * from "./interactions_typed";
export function favourite(status) {
return function (dispatch, getState) {
return function (dispatch) {
dispatch(favouriteRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/favourite`).then(function (response) {
api().post(`/api/v1/statuses/${status.get('id')}/favourite`).then(function (response) {
dispatch(importFetchedStatus(response.data));
dispatch(favouriteSuccess(status));
}).catch(function (error) {
@ -171,10 +87,10 @@ export function favourite(status) {
}
export function unfavourite(status) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(unfavouriteRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => {
api().post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(unfavouriteSuccess(status));
}).catch(error => {
@ -319,10 +235,10 @@ export function unEmojiReactFail(status, emoji, error) {
}
export function bookmark(status) {
return function (dispatch, getState) {
return function (dispatch) {
dispatch(bookmarkRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/bookmark`).then(function (response) {
api().post(`/api/v1/statuses/${status.get('id')}/bookmark`).then(function (response) {
dispatch(importFetchedStatus(response.data));
dispatch(bookmarkSuccess(status, response.data));
}).catch(function (error) {
@ -332,10 +248,10 @@ export function bookmark(status) {
}
export function unbookmark(status) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(unbookmarkRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/unbookmark`).then(response => {
api().post(`/api/v1/statuses/${status.get('id')}/unbookmark`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(unbookmarkSuccess(status, response.data));
}).catch(error => {
@ -391,10 +307,10 @@ export function unbookmarkFail(status, error) {
}
export function fetchReblogs(id) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(fetchReblogsRequest(id));
api(getState).get(`/api/v1/statuses/${id}/reblogged_by`).then(response => {
api().get(`/api/v1/statuses/${id}/reblogged_by`).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
dispatch(fetchReblogsSuccess(id, response.data, next ? next.uri : null));
@ -438,7 +354,7 @@ export function expandReblogs(id) {
dispatch(expandReblogsRequest(id));
api(getState).get(url).then(response => {
api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
@ -473,10 +389,10 @@ export function expandReblogsFail(id, error) {
}
export function fetchFavourites(id) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(fetchFavouritesRequest(id));
api(getState).get(`/api/v1/statuses/${id}/favourited_by`).then(response => {
api().get(`/api/v1/statuses/${id}/favourited_by`).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
dispatch(fetchFavouritesSuccess(id, response.data, next ? next.uri : null));
@ -520,7 +436,7 @@ export function expandFavourites(id) {
dispatch(expandFavouritesRequest(id));
api(getState).get(url).then(response => {
api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
@ -669,10 +585,10 @@ export function fetchStatusReferencesFail(id, error) {
}
export function pin(status) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(pinRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => {
api().post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(pinSuccess(status));
}).catch(error => {
@ -707,10 +623,10 @@ export function pinFail(status, error) {
}
export function unpin (status) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(unpinRequest(status));
api(getState).post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => {
api().post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(unpinSuccess(status));
}).catch(error => {

View file

@ -0,0 +1,35 @@
import { apiReblog, apiUnreblog } from 'mastodon/api/interactions';
import type { StatusVisibility } from 'mastodon/models/status';
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
import { importFetchedStatus } from './importer';
export const reblog = createDataLoadingThunk(
'status/reblog',
({
statusId,
visibility,
}: {
statusId: string;
visibility: StatusVisibility;
}) => apiReblog(statusId, visibility),
(data, { dispatch, discardLoadData }) => {
// The reblog API method returns a new status wrapped around the original. In this case we are only
// interested in how the original is modified, hence passing it skipping the wrapper
dispatch(importFetchedStatus(data.reblog));
// The payload is not used in any actions
return discardLoadData;
},
);
export const unreblog = createDataLoadingThunk(
'status/unreblog',
({ statusId }: { statusId: string }) => apiUnreblog(statusId),
(data, { dispatch, discardLoadData }) => {
dispatch(importFetchedStatus(data));
// The payload is not used in any actions
return discardLoadData;
},
);

View file

@ -59,7 +59,7 @@ export const fetchList = id => (dispatch, getState) => {
dispatch(fetchListRequest(id));
api(getState).get(`/api/v1/lists/${id}`)
api().get(`/api/v1/lists/${id}`)
.then(({ data }) => dispatch(fetchListSuccess(data)))
.catch(err => dispatch(fetchListFail(id, err)));
};
@ -80,10 +80,10 @@ export const fetchListFail = (id, error) => ({
error,
});
export const fetchLists = () => (dispatch, getState) => {
export const fetchLists = () => (dispatch) => {
dispatch(fetchListsRequest());
api(getState).get('/api/v1/lists')
api().get('/api/v1/lists')
.then(({ data }) => dispatch(fetchListsSuccess(data)))
.catch(err => dispatch(fetchListsFail(err)));
};
@ -127,10 +127,10 @@ export const changeListEditorTitle = value => ({
value,
});
export const createList = (title, shouldReset) => (dispatch, getState) => {
export const createList = (title, shouldReset) => (dispatch) => {
dispatch(createListRequest());
api(getState).post('/api/v1/lists', { title }).then(({ data }) => {
api().post('/api/v1/lists', { title }).then(({ data }) => {
dispatch(createListSuccess(data));
if (shouldReset) {
@ -153,10 +153,10 @@ export const createListFail = error => ({
error,
});
export const updateList = (id, title, shouldReset, isExclusive, replies_policy, notify) => (dispatch, getState) => {
export const updateList = (id, title, shouldReset, isExclusive, replies_policy, notify) => (dispatch) => {
dispatch(updateListRequest(id));
api(getState).put(`/api/v1/lists/${id}`, {
api().put(`/api/v1/lists/${id}`, {
title,
replies_policy,
exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive,
@ -190,10 +190,10 @@ export const resetListEditor = () => ({
type: LIST_EDITOR_RESET,
});
export const deleteList = id => (dispatch, getState) => {
export const deleteList = id => (dispatch) => {
dispatch(deleteListRequest(id));
api(getState).delete(`/api/v1/lists/${id}`)
api().delete(`/api/v1/lists/${id}`)
.then(() => dispatch(deleteListSuccess(id)))
.catch(err => dispatch(deleteListFail(id, err)));
};
@ -214,10 +214,10 @@ export const deleteListFail = (id, error) => ({
error,
});
export const fetchListAccounts = listId => (dispatch, getState) => {
export const fetchListAccounts = listId => (dispatch) => {
dispatch(fetchListAccountsRequest(listId));
api(getState).get(`/api/v1/lists/${listId}/accounts`, { params: { limit: 0 } }).then(({ data }) => {
api().get(`/api/v1/lists/${listId}/accounts`, { params: { limit: 0 } }).then(({ data }) => {
dispatch(importFetchedAccounts(data));
dispatch(fetchListAccountsSuccess(listId, data));
}).catch(err => dispatch(fetchListAccountsFail(listId, err)));
@ -241,14 +241,14 @@ export const fetchListAccountsFail = (id, error) => ({
error,
});
export const fetchListSuggestions = q => (dispatch, getState) => {
export const fetchListSuggestions = q => (dispatch) => {
const params = {
q,
resolve: false,
following: true,
};
api(getState).get('/api/v1/accounts/search', { params }).then(({ data }) => {
api().get('/api/v1/accounts/search', { params }).then(({ data }) => {
dispatch(importFetchedAccounts(data));
dispatch(fetchListSuggestionsReady(q, data));
}).catch(error => dispatch(showAlertForError(error)));
@ -273,10 +273,10 @@ export const addToListEditor = accountId => (dispatch, getState) => {
dispatch(addToList(getState().getIn(['listEditor', 'listId']), accountId));
};
export const addToList = (listId, accountId) => (dispatch, getState) => {
export const addToList = (listId, accountId) => (dispatch) => {
dispatch(addToListRequest(listId, accountId));
api(getState).post(`/api/v1/lists/${listId}/accounts`, { account_ids: [accountId] })
api().post(`/api/v1/lists/${listId}/accounts`, { account_ids: [accountId] })
.then(() => dispatch(addToListSuccess(listId, accountId)))
.catch(err => dispatch(addToListFail(listId, accountId, err)));
};
@ -304,10 +304,10 @@ export const removeFromListEditor = accountId => (dispatch, getState) => {
dispatch(removeFromList(getState().getIn(['listEditor', 'listId']), accountId));
};
export const removeFromList = (listId, accountId) => (dispatch, getState) => {
export const removeFromList = (listId, accountId) => (dispatch) => {
dispatch(removeFromListRequest(listId, accountId));
api(getState).delete(`/api/v1/lists/${listId}/accounts`, { params: { account_ids: [accountId] } })
api().delete(`/api/v1/lists/${listId}/accounts`, { params: { account_ids: [accountId] } })
.then(() => dispatch(removeFromListSuccess(listId, accountId)))
.catch(err => dispatch(removeFromListFail(listId, accountId, err)));
};
@ -344,10 +344,10 @@ export const setupListAdder = accountId => (dispatch, getState) => {
dispatch(fetchAccountLists(accountId));
};
export const fetchAccountLists = accountId => (dispatch, getState) => {
export const fetchAccountLists = accountId => (dispatch) => {
dispatch(fetchAccountListsRequest(accountId));
api(getState).get(`/api/v1/accounts/${accountId}/lists`)
api().get(`/api/v1/accounts/${accountId}/lists`)
.then(({ data }) => dispatch(fetchAccountListsSuccess(accountId, data)))
.catch(err => dispatch(fetchAccountListsFail(accountId, err)));
};
@ -376,4 +376,3 @@ export const addToListAdder = listId => (dispatch, getState) => {
export const removeFromListAdder = listId => (dispatch, getState) => {
dispatch(removeFromList(listId, getState().getIn(['listAdder', 'accountId'])));
};

View file

@ -1,19 +1,24 @@
import { debounce } from 'lodash';
import type { MarkerJSON } from 'mastodon/api_types/markers';
import { getAccessToken } from 'mastodon/initial_state';
import type { AppDispatch, RootState } from 'mastodon/store';
import { createAppAsyncThunk } from 'mastodon/store/typed_functions';
import api, { authorizationTokenFromState } from '../api';
import api from '../api';
import { compareId } from '../compare_id';
export const synchronouslySubmitMarkers = createAppAsyncThunk(
'markers/submit',
async (_args, { getState }) => {
const accessToken = authorizationTokenFromState(getState);
const accessToken = getAccessToken();
const params = buildPostMarkersParams(getState());
if (Object.keys(params).length === 0 || !accessToken) {
if (
Object.keys(params).length === 0 ||
!accessToken ||
accessToken === ''
) {
return;
}
@ -96,14 +101,14 @@ export const submitMarkersAction = createAppAsyncThunk<{
home: string | undefined;
notifications: string | undefined;
}>('markers/submitAction', async (_args, { getState }) => {
const accessToken = authorizationTokenFromState(getState);
const accessToken = getAccessToken();
const params = buildPostMarkersParams(getState());
if (Object.keys(params).length === 0 || accessToken === '') {
if (Object.keys(params).length === 0 || !accessToken || accessToken === '') {
return { home: undefined, notifications: undefined };
}
await api(getState).post<MarkerJSON>('/api/v1/markers', params);
await api().post<MarkerJSON>('/api/v1/markers', params);
return {
home: params.home?.last_read_id,
@ -133,14 +138,11 @@ export const submitMarkers = createAppAsyncThunk(
},
);
export const fetchMarkers = createAppAsyncThunk(
'markers/fetch',
async (_args, { getState }) => {
const response = await api(getState).get<Record<string, MarkerJSON>>(
`/api/v1/markers`,
{ params: { timeline: ['notifications'] } },
);
export const fetchMarkers = createAppAsyncThunk('markers/fetch', async () => {
const response = await api().get<Record<string, MarkerJSON>>(
`/api/v1/markers`,
{ params: { timeline: ['notifications'] } },
);
return { markers: response.data };
},
);
return { markers: response.data };
});

View file

@ -13,10 +13,10 @@ export const MUTES_EXPAND_SUCCESS = 'MUTES_EXPAND_SUCCESS';
export const MUTES_EXPAND_FAIL = 'MUTES_EXPAND_FAIL';
export function fetchMutes() {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(fetchMutesRequest());
api(getState).get('/api/v1/mutes').then(response => {
api().get('/api/v1/mutes').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
dispatch(fetchMutesSuccess(response.data, next ? next.uri : null));
@ -56,7 +56,7 @@ export function expandMutes() {
dispatch(expandMutesRequest());
api(getState).get(url).then(response => {
api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
dispatch(expandMutesSuccess(response.data, next ? next.uri : null));

View file

@ -233,7 +233,7 @@ export function expandNotifications({ maxId, forceLoad } = {}, done = noOp) {
dispatch(expandNotificationsRequest(isLoadingMore));
api(getState).get('/api/v1/notifications', { params, signal: expandNotificationsController.signal }).then(response => {
api().get('/api/v1/notifications', { params, signal: expandNotificationsController.signal }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data.map(item => item.account)));
@ -279,12 +279,12 @@ export function expandNotificationsFail(error, isLoadingMore) {
}
export function clearNotifications() {
return (dispatch, getState) => {
return (dispatch) => {
dispatch({
type: NOTIFICATIONS_CLEAR,
});
api(getState).post('/api/v1/notifications/clear');
api().post('/api/v1/notifications/clear');
};
}
@ -363,10 +363,10 @@ export function setBrowserPermission (value) {
};
}
export const fetchNotificationPolicy = () => (dispatch, getState) => {
export const fetchNotificationPolicy = () => (dispatch) => {
dispatch(fetchNotificationPolicyRequest());
api(getState).get('/api/v1/notifications/policy').then(({ data }) => {
api().get('/api/v1/notifications/policy').then(({ data }) => {
dispatch(fetchNotificationPolicySuccess(data));
}).catch(err => {
dispatch(fetchNotificationPolicyFail(err));
@ -387,10 +387,10 @@ export const fetchNotificationPolicyFail = error => ({
error,
});
export const updateNotificationsPolicy = params => (dispatch, getState) => {
export const updateNotificationsPolicy = params => (dispatch) => {
dispatch(fetchNotificationPolicyRequest());
api(getState).put('/api/v1/notifications/policy', params).then(({ data }) => {
api().put('/api/v1/notifications/policy', params).then(({ data }) => {
dispatch(fetchNotificationPolicySuccess(data));
}).catch(err => {
dispatch(fetchNotificationPolicyFail(err));
@ -410,7 +410,7 @@ export const fetchNotificationRequests = () => (dispatch, getState) => {
dispatch(fetchNotificationRequestsRequest());
api(getState).get('/api/v1/notifications/requests', { params }).then(response => {
api().get('/api/v1/notifications/requests', { params }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data.map(x => x.account)));
dispatch(fetchNotificationRequestsSuccess(response.data, next ? next.uri : null));
@ -443,7 +443,7 @@ export const expandNotificationRequests = () => (dispatch, getState) => {
dispatch(expandNotificationRequestsRequest());
api(getState).get(url).then(response => {
api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data.map(x => x.account)));
dispatch(expandNotificationRequestsSuccess(response.data, next?.uri));
@ -476,7 +476,7 @@ export const fetchNotificationRequest = id => (dispatch, getState) => {
dispatch(fetchNotificationRequestRequest(id));
api(getState).get(`/api/v1/notifications/requests/${id}`).then(({ data }) => {
api().get(`/api/v1/notifications/requests/${id}`).then(({ data }) => {
dispatch(fetchNotificationRequestSuccess(data));
}).catch(err => {
dispatch(fetchNotificationRequestFail(id, err));
@ -499,10 +499,10 @@ export const fetchNotificationRequestFail = (id, error) => ({
error,
});
export const acceptNotificationRequest = id => (dispatch, getState) => {
export const acceptNotificationRequest = id => (dispatch) => {
dispatch(acceptNotificationRequestRequest(id));
api(getState).post(`/api/v1/notifications/requests/${id}/accept`).then(() => {
api().post(`/api/v1/notifications/requests/${id}/accept`).then(() => {
dispatch(acceptNotificationRequestSuccess(id));
}).catch(err => {
dispatch(acceptNotificationRequestFail(id, err));
@ -525,10 +525,10 @@ export const acceptNotificationRequestFail = (id, error) => ({
error,
});
export const dismissNotificationRequest = id => (dispatch, getState) => {
export const dismissNotificationRequest = id => (dispatch) => {
dispatch(dismissNotificationRequestRequest(id));
api(getState).post(`/api/v1/notifications/requests/${id}/dismiss`).then(() =>{
api().post(`/api/v1/notifications/requests/${id}/dismiss`).then(() =>{
dispatch(dismissNotificationRequestSuccess(id));
}).catch(err => {
dispatch(dismissNotificationRequestFail(id, err));
@ -567,7 +567,7 @@ export const fetchNotificationsForRequest = accountId => (dispatch, getState) =>
dispatch(fetchNotificationsForRequestRequest());
api(getState).get('/api/v1/notifications', { params }).then(response => {
api().get('/api/v1/notifications', { params }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data.map(item => item.account)));
dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status)));
@ -603,7 +603,7 @@ export const expandNotificationsForRequest = () => (dispatch, getState) => {
dispatch(expandNotificationsForRequestRequest());
api(getState).get(url).then(response => {
api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data.map(item => item.account)));
dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status)));

View file

@ -8,10 +8,10 @@ export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS';
export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL';
export function fetchPinnedStatuses() {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(fetchPinnedStatusesRequest());
api(getState).get(`/api/v1/accounts/${me}/statuses`, { params: { pinned: true } }).then(response => {
api().get(`/api/v1/accounts/${me}/statuses`, { params: { pinned: true } }).then(response => {
dispatch(importFetchedStatuses(response.data));
dispatch(fetchPinnedStatusesSuccess(response.data, null));
}).catch(error => {

View file

@ -10,10 +10,10 @@ export const POLL_FETCH_REQUEST = 'POLL_FETCH_REQUEST';
export const POLL_FETCH_SUCCESS = 'POLL_FETCH_SUCCESS';
export const POLL_FETCH_FAIL = 'POLL_FETCH_FAIL';
export const vote = (pollId, choices) => (dispatch, getState) => {
export const vote = (pollId, choices) => (dispatch) => {
dispatch(voteRequest());
api(getState).post(`/api/v1/polls/${pollId}/votes`, { choices })
api().post(`/api/v1/polls/${pollId}/votes`, { choices })
.then(({ data }) => {
dispatch(importFetchedPoll(data));
dispatch(voteSuccess(data));
@ -21,10 +21,10 @@ export const vote = (pollId, choices) => (dispatch, getState) => {
.catch(err => dispatch(voteFail(err)));
};
export const fetchPoll = pollId => (dispatch, getState) => {
export const fetchPoll = pollId => (dispatch) => {
dispatch(fetchPollRequest());
api(getState).get(`/api/v1/polls/${pollId}`)
api().get(`/api/v1/polls/${pollId}`)
.then(({ data }) => {
dispatch(importFetchedPoll(data));
dispatch(fetchPollSuccess(data));

View file

@ -15,10 +15,10 @@ export const initReport = (account, status) => dispatch =>
},
}));
export const submitReport = (params, onSuccess, onFail) => (dispatch, getState) => {
export const submitReport = (params, onSuccess, onFail) => (dispatch) => {
dispatch(submitReportRequest());
api(getState).post('/api/v1/reports', params).then(response => {
api().post('/api/v1/reports', params).then(response => {
dispatch(submitReportSuccess(response.data));
if (onSuccess) onSuccess();
}).catch(error => {

View file

@ -46,7 +46,7 @@ export function submitSearch(type) {
dispatch(fetchSearchRequest(type));
api(getState).get('/api/v2/search', {
api().get('/api/v2/search', {
params: {
q: value,
resolve: signedIn,
@ -99,7 +99,7 @@ export const expandSearch = type => (dispatch, getState) => {
dispatch(expandSearchRequest(type));
api(getState).get('/api/v2/search', {
api().get('/api/v2/search', {
params: {
q: value,
type,
@ -156,7 +156,7 @@ export const openURL = (value, history, onFailure) => (dispatch, getState) => {
dispatch(fetchSearchRequest());
api(getState).get('/api/v2/search', { params: { q: value, resolve: true } }).then(response => {
api().get('/api/v2/search', { params: { q: value, resolve: true } }).then(response => {
if (response.data.accounts?.length > 0) {
dispatch(importFetchedAccounts(response.data.accounts));
history.push(`/@${response.data.accounts[0].acct}`);

View file

@ -25,7 +25,7 @@ export const fetchServer = () => (dispatch, getState) => {
dispatch(fetchServerRequest());
api(getState)
api()
.get('/api/v2/instance').then(({ data }) => {
if (data.contact.account) dispatch(importFetchedAccount(data.contact.account));
dispatch(fetchServerSuccess(data));
@ -46,10 +46,10 @@ const fetchServerFail = error => ({
error,
});
export const fetchServerTranslationLanguages = () => (dispatch, getState) => {
export const fetchServerTranslationLanguages = () => (dispatch) => {
dispatch(fetchServerTranslationLanguagesRequest());
api(getState)
api()
.get('/api/v1/instance/translation_languages').then(({ data }) => {
dispatch(fetchServerTranslationLanguagesSuccess(data));
}).catch(err => dispatch(fetchServerTranslationLanguagesFail(err)));
@ -76,7 +76,7 @@ export const fetchExtendedDescription = () => (dispatch, getState) => {
dispatch(fetchExtendedDescriptionRequest());
api(getState)
api()
.get('/api/v1/instance/extended_description')
.then(({ data }) => dispatch(fetchExtendedDescriptionSuccess(data)))
.catch(err => dispatch(fetchExtendedDescriptionFail(err)));
@ -103,7 +103,7 @@ export const fetchDomainBlocks = () => (dispatch, getState) => {
dispatch(fetchDomainBlocksRequest());
api(getState)
api()
.get('/api/v1/instance/domain_blocks')
.then(({ data }) => dispatch(fetchDomainBlocksSuccess(true, data)))
.catch(err => {

View file

@ -61,7 +61,7 @@ export function fetchStatus(id, forceFetch = false) {
dispatch(fetchStatusRequest(id, skipLoading));
api(getState).get(`/api/v1/statuses/${id}`).then(response => {
api().get(`/api/v1/statuses/${id}`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(fetchStatusSuccess(skipLoading));
}).catch(error => {
@ -104,7 +104,7 @@ export const editStatus = (id, routerHistory) => (dispatch, getState) => {
dispatch(fetchStatusSourceRequest());
api(getState).get(`/api/v1/statuses/${id}/source`).then(response => {
api().get(`/api/v1/statuses/${id}/source`).then(response => {
dispatch(fetchStatusSourceSuccess());
ensureComposeIsVisible(getState, routerHistory);
dispatch(setComposeToStatus(status, response.data.text, response.data.spoiler_text));
@ -136,7 +136,7 @@ export function deleteStatus(id, routerHistory, withRedraft = false) {
dispatch(deleteStatusRequest(id));
api(getState).delete(`/api/v1/statuses/${id}`).then(response => {
api().delete(`/api/v1/statuses/${id}`).then(response => {
dispatch(deleteStatusSuccess(id));
dispatch(deleteFromTimelines(id));
dispatch(importFetchedAccount(response.data.account));
@ -177,10 +177,10 @@ export const updateStatus = status => dispatch =>
dispatch(importFetchedStatus(status));
export function fetchContext(id) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(fetchContextRequest(id));
api(getState).get(`/api/v1/statuses/${id}/context?with_reference=1`).then(response => {
api().get(`/api/v1/statuses/${id}/context?with_reference=1`).then(response => {
dispatch(importFetchedStatuses(response.data.ancestors.concat(response.data.descendants).concat(response.data.references)));
dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants, response.data.references));
@ -222,10 +222,10 @@ export function fetchContextFail(id, error) {
}
export function muteStatus(id) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(muteStatusRequest(id));
api(getState).post(`/api/v1/statuses/${id}/mute`).then(() => {
api().post(`/api/v1/statuses/${id}/mute`).then(() => {
dispatch(muteStatusSuccess(id));
}).catch(error => {
dispatch(muteStatusFail(id, error));
@ -256,10 +256,10 @@ export function muteStatusFail(id, error) {
}
export function unmuteStatus(id) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(unmuteStatusRequest(id));
api(getState).post(`/api/v1/statuses/${id}/unmute`).then(() => {
api().post(`/api/v1/statuses/${id}/unmute`).then(() => {
dispatch(unmuteStatusSuccess(id));
}).catch(error => {
dispatch(unmuteStatusFail(id, error));
@ -319,10 +319,10 @@ export function toggleStatusCollapse(id, isCollapsed) {
};
}
export const translateStatus = id => (dispatch, getState) => {
export const translateStatus = id => (dispatch) => {
dispatch(translateStatusRequest(id));
api(getState).post(`/api/v1/statuses/${id}/translate`).then(response => {
api().post(`/api/v1/statuses/${id}/translate`).then(response => {
dispatch(translateStatusSuccess(id, response.data));
}).catch(error => {
dispatch(translateStatusFail(id, error));

View file

@ -10,10 +10,10 @@ export const SUGGESTIONS_FETCH_FAIL = 'SUGGESTIONS_FETCH_FAIL';
export const SUGGESTIONS_DISMISS = 'SUGGESTIONS_DISMISS';
export function fetchSuggestions(withRelationships = false) {
return (dispatch, getState) => {
return (dispatch) => {
dispatch(fetchSuggestionsRequest());
api(getState).get('/api/v2/suggestions', { params: { limit: 20 } }).then(response => {
api().get('/api/v2/suggestions', { params: { limit: 20 } }).then(response => {
dispatch(importFetchedAccounts(response.data.map(x => x.account)));
dispatch(fetchSuggestionsSuccess(response.data));
@ -48,11 +48,11 @@ export function fetchSuggestionsFail(error) {
};
}
export const dismissSuggestion = accountId => (dispatch, getState) => {
export const dismissSuggestion = accountId => (dispatch) => {
dispatch({
type: SUGGESTIONS_DISMISS,
id: accountId,
});
api(getState).delete(`/api/v1/suggestions/${accountId}`).catch(() => {});
api().delete(`/api/v1/suggestions/${accountId}`).catch(() => {});
};

View file

@ -20,10 +20,10 @@ export const HASHTAG_UNFOLLOW_REQUEST = 'HASHTAG_UNFOLLOW_REQUEST';
export const HASHTAG_UNFOLLOW_SUCCESS = 'HASHTAG_UNFOLLOW_SUCCESS';
export const HASHTAG_UNFOLLOW_FAIL = 'HASHTAG_UNFOLLOW_FAIL';
export const fetchHashtag = name => (dispatch, getState) => {
export const fetchHashtag = name => (dispatch) => {
dispatch(fetchHashtagRequest());
api(getState).get(`/api/v1/tags/${name}`).then(({ data }) => {
api().get(`/api/v1/tags/${name}`).then(({ data }) => {
dispatch(fetchHashtagSuccess(name, data));
}).catch(err => {
dispatch(fetchHashtagFail(err));
@ -45,10 +45,10 @@ export const fetchHashtagFail = error => ({
error,
});
export const fetchFollowedHashtags = () => (dispatch, getState) => {
export const fetchFollowedHashtags = () => (dispatch) => {
dispatch(fetchFollowedHashtagsRequest());
api(getState).get('/api/v1/followed_tags').then(response => {
api().get('/api/v1/followed_tags').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(fetchFollowedHashtagsSuccess(response.data, next ? next.uri : null));
}).catch(err => {
@ -87,7 +87,7 @@ export function expandFollowedHashtags() {
dispatch(expandFollowedHashtagsRequest());
api(getState).get(url).then(response => {
api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(expandFollowedHashtagsSuccess(response.data, next ? next.uri : null));
}).catch(error => {
@ -117,10 +117,10 @@ export function expandFollowedHashtagsFail(error) {
};
}
export const followHashtag = name => (dispatch, getState) => {
export const followHashtag = name => (dispatch) => {
dispatch(followHashtagRequest(name));
api(getState).post(`/api/v1/tags/${name}/follow`).then(({ data }) => {
api().post(`/api/v1/tags/${name}/follow`).then(({ data }) => {
dispatch(followHashtagSuccess(name, data));
}).catch(err => {
dispatch(followHashtagFail(name, err));
@ -144,10 +144,10 @@ export const followHashtagFail = (name, error) => ({
error,
});
export const unfollowHashtag = name => (dispatch, getState) => {
export const unfollowHashtag = name => (dispatch) => {
dispatch(unfollowHashtagRequest(name));
api(getState).post(`/api/v1/tags/${name}/unfollow`).then(({ data }) => {
api().post(`/api/v1/tags/${name}/unfollow`).then(({ data }) => {
dispatch(unfollowHashtagSuccess(name, data));
}).catch(err => {
dispatch(unfollowHashtagFail(name, err));

View file

@ -114,7 +114,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
dispatch(expandTimelineRequest(timelineId, isLoadingMore));
api(getState).get(path, { params }).then(response => {
api().get(path, { params }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));

View file

@ -18,10 +18,10 @@ export const TRENDS_STATUSES_EXPAND_REQUEST = 'TRENDS_STATUSES_EXPAND_REQUEST';
export const TRENDS_STATUSES_EXPAND_SUCCESS = 'TRENDS_STATUSES_EXPAND_SUCCESS';
export const TRENDS_STATUSES_EXPAND_FAIL = 'TRENDS_STATUSES_EXPAND_FAIL';
export const fetchTrendingHashtags = () => (dispatch, getState) => {
export const fetchTrendingHashtags = () => (dispatch) => {
dispatch(fetchTrendingHashtagsRequest());
api(getState)
api()
.get('/api/v1/trends/tags')
.then(({ data }) => dispatch(fetchTrendingHashtagsSuccess(data)))
.catch(err => dispatch(fetchTrendingHashtagsFail(err)));
@ -45,10 +45,10 @@ export const fetchTrendingHashtagsFail = error => ({
skipAlert: true,
});
export const fetchTrendingLinks = () => (dispatch, getState) => {
export const fetchTrendingLinks = () => (dispatch) => {
dispatch(fetchTrendingLinksRequest());
api(getState)
api()
.get('/api/v1/trends/links')
.then(({ data }) => dispatch(fetchTrendingLinksSuccess(data)))
.catch(err => dispatch(fetchTrendingLinksFail(err)));
@ -79,7 +79,7 @@ export const fetchTrendingStatuses = () => (dispatch, getState) => {
dispatch(fetchTrendingStatusesRequest());
api(getState).get('/api/v1/trends/statuses').then(response => {
api().get('/api/v1/trends/statuses').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(fetchTrendingStatusesSuccess(response.data, next ? next.uri : null));
@ -115,7 +115,7 @@ export const expandTrendingStatuses = () => (dispatch, getState) => {
dispatch(expandTrendingStatusesRequest());
api(getState).get(url).then(response => {
api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(expandTrendingStatusesSuccess(response.data, next ? next.uri : null));

View file

@ -1,9 +1,9 @@
import type { AxiosResponse, RawAxiosRequestHeaders } from 'axios';
import type { AxiosResponse, Method, RawAxiosRequestHeaders } from 'axios';
import axios from 'axios';
import LinkHeader from 'http-link-header';
import { getAccessToken } from './initial_state';
import ready from './ready';
import type { GetState } from './store';
export const getLinks = (response: AxiosResponse) => {
const value = response.headers.link as string | undefined;
@ -29,30 +29,22 @@ const setCSRFHeader = () => {
void ready(setCSRFHeader);
export const authorizationTokenFromState = (getState?: GetState) => {
return (
getState && (getState().meta.get('access_token', '') as string | false)
);
};
const authorizationTokenFromInitialState = (): RawAxiosRequestHeaders => {
const accessToken = getAccessToken();
const authorizationHeaderFromState = (getState?: GetState) => {
const accessToken = authorizationTokenFromState(getState);
if (!accessToken) {
return {};
}
if (!accessToken) return {};
return {
Authorization: `Bearer ${accessToken}`,
} as RawAxiosRequestHeaders;
};
};
// eslint-disable-next-line import/no-default-export
export default function api(getState: GetState) {
export default function api(withAuthorization = true) {
return axios.create({
headers: {
...csrfHeader,
...authorizationHeaderFromState(getState),
...(withAuthorization ? authorizationTokenFromInitialState() : {}),
},
transformResponse: [
@ -66,3 +58,17 @@ export default function api(getState: GetState) {
],
});
}
export async function apiRequest<ApiResponse = unknown>(
method: Method,
url: string,
params?: Record<string, unknown>,
) {
const { data } = await api().request<ApiResponse>({
method,
url: '/api/' + url,
data: params,
});
return data;
}

View file

@ -0,0 +1,7 @@
import { apiRequest } from 'mastodon/api';
import type { ApiRelationshipJSON } from 'mastodon/api_types/relationships';
export const apiSubmitAccountNote = (id: string, value: string) =>
apiRequest<ApiRelationshipJSON>('post', `v1/accounts/${id}/note`, {
comment: value,
});

View file

@ -0,0 +1,10 @@
import { apiRequest } from 'mastodon/api';
import type { Status, StatusVisibility } from 'mastodon/models/status';
export const apiReblog = (statusId: string, visibility: StatusVisibility) =>
apiRequest<{ reblog: Status }>('post', `v1/statuses/${statusId}/reblog`, {
visibility,
});
export const apiUnreblog = (statusId: string) =>
apiRequest<Status>('post', `v1/statuses/${statusId}/unreblog`);

View file

@ -49,7 +49,7 @@ export default class Counter extends PureComponent {
componentDidMount () {
const { measure, start_at, end_at, params } = this.props;
api().post('/api/v1/admin/measures', { keys: [measure], start_at, end_at, [measure]: params }).then(res => {
api(false).post('/api/v1/admin/measures', { keys: [measure], start_at, end_at, [measure]: params }).then(res => {
this.setState({
loading: false,
data: res.data,

View file

@ -27,7 +27,7 @@ export default class Dimension extends PureComponent {
componentDidMount () {
const { start_at, end_at, dimension, limit, params } = this.props;
api().post('/api/v1/admin/dimensions', { keys: [dimension], start_at, end_at, limit, [dimension]: params }).then(res => {
api(false).post('/api/v1/admin/dimensions', { keys: [dimension], start_at, end_at, limit, [dimension]: params }).then(res => {
this.setState({
loading: false,
data: res.data,

View file

@ -27,7 +27,7 @@ export default class ImpactReport extends PureComponent {
include_subdomains: true,
};
api().post('/api/v1/admin/measures', {
api(false).post('/api/v1/admin/measures', {
keys: ['instance_accounts', 'instance_follows', 'instance_followers'],
start_at: null,
end_at: null,

View file

@ -105,7 +105,7 @@ class ReportReasonSelector extends PureComponent {
};
componentDidMount() {
api().get('/api/v1/instance').then(res => {
api(false).get('/api/v1/instance').then(res => {
this.setState({
rules: res.data.rules,
});
@ -122,7 +122,7 @@ class ReportReasonSelector extends PureComponent {
return;
}
api().put(`/api/v1/admin/reports/${id}`, {
api(false).put(`/api/v1/admin/reports/${id}`, {
category,
rule_ids: category === 'violation' ? rule_ids : [],
}).catch(err => {

View file

@ -34,7 +34,7 @@ export default class Retention extends PureComponent {
componentDidMount () {
const { start_at, end_at, frequency } = this.props;
api().post('/api/v1/admin/retention', { start_at, end_at, frequency }).then(res => {
api(false).post('/api/v1/admin/retention', { start_at, end_at, frequency }).then(res => {
this.setState({
loading: false,
data: res.data,

View file

@ -22,7 +22,7 @@ export default class Trends extends PureComponent {
componentDidMount () {
const { limit } = this.props;
api().get('/api/v1/admin/trends/tags', { params: { limit } }).then(res => {
api(false).get('/api/v1/admin/trends/tags', { params: { limit } }).then(res => {
this.setState({
loading: false,
data: res.data,

View file

@ -14,8 +14,10 @@ import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
import { Icon } from 'mastodon/components/icon';
import { ButtonInTabsBar } from 'mastodon/features/ui/util/columns_context';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import { useAppHistory } from './router';
const messages = defineMessages({
@ -51,12 +53,8 @@ BackButton.propTypes = {
};
class ColumnHeader extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
title: PropTypes.node,
icon: PropTypes.string,
@ -171,7 +169,7 @@ class ColumnHeader extends PureComponent {
);
}
if (this.context.identity.signedIn && (children || (multiColumn && this.props.onPin))) {
if (this.props.identity.signedIn && (children || (multiColumn && this.props.onPin))) {
collapseButton = (
<button
className={collapsibleButtonClassName}
@ -232,4 +230,4 @@ class ColumnHeader extends PureComponent {
}
export default injectIntl(withRouter(ColumnHeader));
export default injectIntl(withIdentity(withRouter(ColumnHeader)));

View file

@ -14,6 +14,7 @@ import CheckIcon from '@/material-icons/400-24px/check.svg?react';
import { Icon } from 'mastodon/components/icon';
import emojify from 'mastodon/features/emoji/emoji';
import Motion from 'mastodon/features/ui/util/optional_motion';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { RelativeTimestamp } from './relative_timestamp';
@ -38,12 +39,8 @@ const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
}, {});
class Poll extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
poll: ImmutablePropTypes.map,
lang: PropTypes.string,
intl: PropTypes.object.isRequired,
@ -235,7 +232,7 @@ class Poll extends ImmutablePureComponent {
</ul>
<div className='poll__footer'>
{!showResults && <button className='button button-secondary' disabled={disabled || !this.context.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
{!showResults && <button className='button button-secondary' disabled={disabled || !this.props.identity.signedIn} onClick={this.handleVote}><FormattedMessage id='poll.vote' defaultMessage='Vote' /></button>}
{!showResults && <><button className='poll__link' onClick={this.handleReveal}><FormattedMessage id='poll.reveal' defaultMessage='See results' /></button> · </>}
{showResults && !this.props.disabled && <><button className='poll__link' onClick={this.handleRefresh}><FormattedMessage id='poll.refresh' defaultMessage='Refresh' /></button> · </>}
{votesCount}
@ -247,4 +244,4 @@ class Poll extends ImmutablePureComponent {
}
export default injectIntl(Poll);
export default injectIntl(withIdentity(Poll));

View file

@ -23,6 +23,7 @@ import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -85,12 +86,8 @@ const mapStateToProps = (state, { status }) => ({
});
class StatusActionBar extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
relationship: ImmutablePropTypes.record,
onReply: PropTypes.func,
@ -134,7 +131,7 @@ class StatusActionBar extends ImmutablePureComponent {
];
handleReplyClick = () => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onReply(this.props.status, this.props.history);
@ -152,7 +149,7 @@ class StatusActionBar extends ImmutablePureComponent {
};
handleFavouriteClick = () => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onFavourite(this.props.status);
@ -174,7 +171,7 @@ class StatusActionBar extends ImmutablePureComponent {
handleEmojiPickInnerButton = () => {};
handleReblogClick = e => {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
this.props.onReblog(this.props.status, e);
@ -306,7 +303,7 @@ class StatusActionBar extends ImmutablePureComponent {
render () {
const { status, relationship, intl, withDismiss, withCounters, scrollKey } = this.props;
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
const publicStatus = ['public', 'unlisted', 'public_unlisted', 'login'].includes(status.get('visibility_ex'));
const anonymousStatus = ['public', 'unlisted', 'public_unlisted'].includes(status.get('visibility_ex'));
@ -546,4 +543,4 @@ class StatusActionBar extends ImmutablePureComponent {
}
export default withRouter(connect(mapStateToProps)(injectIntl(StatusActionBar)));
export default withRouter(withIdentity(connect(mapStateToProps)(injectIntl(StatusActionBar))));

View file

@ -12,8 +12,10 @@ import { connect } from 'react-redux';
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
import { Icon } from 'mastodon/components/icon';
import PollContainer from 'mastodon/containers/poll_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state';
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
/**
@ -67,12 +69,8 @@ const mapStateToProps = state => ({
});
class StatusContent extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
statusContent: PropTypes.string,
expanded: PropTypes.bool,
@ -246,7 +244,7 @@ class StatusContent extends PureComponent {
const contentLocale = intl.locale.replace(/[_-].*/, '');
const targetLanguages = this.props.languages?.get(status.get('language') || 'und');
const renderTranslate = this.props.onTranslate &&
this.context.identity.signedIn &&
this.props.identity.signedIn &&
(['public', 'unlisted'].includes(status.get('visibility')) || status.getIn(['account', 'other_settings', 'translatable_private'])) &&
status.get('search_index').trim().length > 0 &&
status.get('language') &&
@ -333,4 +331,4 @@ class StatusContent extends PureComponent {
}
export default withRouter(connect(mapStateToProps)(injectIntl(StatusContent)));
export default withRouter(withIdentity(connect(mapStateToProps)(injectIntl(StatusContent))));

View file

@ -1,4 +1,3 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { Helmet } from 'react-helmet';
@ -16,6 +15,7 @@ import { connectUserStream } from 'mastodon/actions/streaming';
import ErrorBoundary from 'mastodon/components/error_boundary';
import { Router } from 'mastodon/components/router';
import UI from 'mastodon/features/ui';
import { IdentityContext, createIdentityContext } from 'mastodon/identity_context';
import initialState, { title as siteTitle } from 'mastodon/initial_state';
import { IntlProvider } from 'mastodon/locales';
import { store } from 'mastodon/store';
@ -32,33 +32,9 @@ if (initialState.meta.me) {
store.dispatch(fetchCircles());
}
const createIdentityContext = state => ({
signedIn: !!state.meta.me,
accountId: state.meta.me,
disabledAccountId: state.meta.disabled_account_id,
accessToken: state.meta.access_token,
permissions: state.role ? state.role.permissions : 0,
});
export default class Mastodon extends PureComponent {
static childContextTypes = {
identity: PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
disabledAccountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired,
};
identity = createIdentityContext(initialState);
getChildContext() {
return {
identity: this.identity,
};
}
componentDidMount() {
if (this.identity.signedIn) {
this.disconnect = store.dispatch(connectUserStream());
@ -78,19 +54,21 @@ export default class Mastodon extends PureComponent {
render () {
return (
<IntlProvider>
<ReduxProvider store={store}>
<ErrorBoundary>
<Router>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<Route path='/' component={UI} />
</ScrollContext>
</Router>
<IdentityContext.Provider value={this.identity}>
<IntlProvider>
<ReduxProvider store={store}>
<ErrorBoundary>
<Router>
<ScrollContext shouldUpdateScroll={this.shouldUpdateScroll}>
<Route path='/' component={UI} />
</ScrollContext>
</Router>
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
</ErrorBoundary>
</ReduxProvider>
</IntlProvider>
<Helmet defaultTitle={title} titleTemplate={`%s - ${title}`} />
</ErrorBoundary>
</ReduxProvider>
</IntlProvider>
</IdentityContext.Provider>
);
}

View file

@ -101,9 +101,9 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
onModalReblog (status, privacy) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
dispatch(unreblog({ statusId: status.get('id') }));
} else {
dispatch(reblog(status, privacy));
dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
}
},

View file

@ -26,6 +26,7 @@ import { IconButton } from 'mastodon/components/icon_button';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import { ShortNumber } from 'mastodon/components/short_number';
import DropdownMenuContainer from 'mastodon/containers/dropdown_menu_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { autoPlayGif, me, domain as localDomain, isShowItem } from 'mastodon/initial_state';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -115,6 +116,7 @@ const dateFormatOptions = {
class Header extends ImmutablePureComponent {
static propTypes = {
identity: identityContextPropShape,
account: ImmutablePropTypes.record,
featuredTags: PropTypes.array,
identity_props: ImmutablePropTypes.list,
@ -144,10 +146,6 @@ class Header extends ImmutablePureComponent {
...WithRouterPropTypes,
};
static contextTypes = {
identity: PropTypes.object,
};
setRef = c => {
this.node = c;
};
@ -277,7 +275,7 @@ class Header extends ImmutablePureComponent {
render () {
const { account, hidden, intl } = this.props;
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
if (!account) {
return null;
@ -554,4 +552,4 @@ class Header extends ImmutablePureComponent {
}
export default withRouter(injectIntl(Header));
export default withRouter(withIdentity(injectIntl(Header)));

View file

@ -11,7 +11,7 @@ const mapStateToProps = (state, { account }) => ({
const mapDispatchToProps = (dispatch, { account }) => ({
onSave (value) {
dispatch(submitAccountNote({ id: account.get('id'), value}));
dispatch(submitAccountNote({ accountId: account.get('id'), note: value }));
},
});

View file

@ -9,6 +9,7 @@ import { connect } from 'react-redux';
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -38,16 +39,12 @@ const mapStateToProps = (state, { columnId }) => {
};
class CommunityTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = {
onlyMedia: false,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
columnId: PropTypes.string,
intl: PropTypes.object.isRequired,
@ -77,7 +74,7 @@ class CommunityTimeline extends PureComponent {
componentDidMount () {
const { dispatch, onlyMedia } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
dispatch(expandCommunityTimeline({ onlyMedia }));
@ -87,7 +84,7 @@ class CommunityTimeline extends PureComponent {
}
componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia) {
const { dispatch, onlyMedia } = this.props;
@ -161,4 +158,4 @@ class CommunityTimeline extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(CommunityTimeline));
export default withIdentity(connect(mapStateToProps)(injectIntl(CommunityTimeline)));

View file

@ -12,6 +12,7 @@ import CancelIcon from '@/material-icons/400-24px/cancel-fill.svg?react';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
import { Icon } from 'mastodon/components/icon';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain, searchEnabled } from 'mastodon/initial_state';
import { HASHTAG_REGEX } from 'mastodon/utils/hashtags';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -33,12 +34,8 @@ const labelForRecentSearch = search => {
};
class Search extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
value: PropTypes.string.isRequired,
recent: ImmutablePropTypes.orderedSet,
submitted: PropTypes.bool,
@ -279,7 +276,7 @@ class Search extends PureComponent {
}
_calculateOptions (value) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const trimmedValue = value.trim();
const options = [];
@ -321,7 +318,7 @@ class Search extends PureComponent {
render () {
const { intl, value, submitted, recent } = this.props;
const { expanded, options, selectedOption } = this.state;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const hasValue = value.length > 0 || submitted;
@ -405,4 +402,4 @@ class Search extends PureComponent {
}
export default withRouter(injectIntl(Search));
export default withRouter(withIdentity(injectIntl(Search)));

View file

@ -13,6 +13,7 @@ import SearchIcon from '@/material-icons/400-24px/search.svg?react';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import Search from 'mastodon/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { trendsEnabled } from 'mastodon/initial_state';
import Links from './links';
@ -32,12 +33,8 @@ const mapStateToProps = state => ({
});
class Explore extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
isSearching: PropTypes.bool,
@ -53,7 +50,7 @@ class Explore extends PureComponent {
render() {
const { intl, multiColumn, isSearching } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
@ -114,4 +111,4 @@ class Explore extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(Explore));
export default withIdentity(connect(mapStateToProps)(injectIntl(Explore)));

View file

@ -6,13 +6,14 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
import { Helmet } from 'react-helmet';
import { NavLink } from 'react-router-dom';
import { useIdentity } from '@/mastodon/identity_context';
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { addColumn } from 'mastodon/actions/columns';
import { changeSetting } from 'mastodon/actions/settings';
import { connectPublicStream, connectCommunityStream } from 'mastodon/actions/streaming';
import { expandPublicTimeline, expandCommunityTimeline } from 'mastodon/actions/timelines';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import initialState, { domain, enableLocalTimeline } from 'mastodon/initial_state';
import { domain, enableLocalTimeline } from 'mastodon/initial_state';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
import Column from '../../components/column';
@ -24,15 +25,6 @@ const messages = defineMessages({
title: { id: 'column.firehose', defaultMessage: 'Live feeds' },
});
// TODO: use a proper React context later on
const useIdentity = () => ({
signedIn: !!initialState.meta.me,
accountId: initialState.meta.me,
disabledAccountId: initialState.meta.disabled_account_id,
accessToken: initialState.meta.access_token,
permissions: initialState.role ? initialState.role.permissions : 0,
});
const ColumnSettings = () => {
const dispatch = useAppDispatch();
const settings = useAppSelector((state) => state.getIn(['settings', 'firehose']));

View file

@ -26,6 +26,7 @@ import { fetchFollowRequests } from 'mastodon/actions/accounts';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import LinkFooter from 'mastodon/features/ui/components/link_footer';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { dtlTag, enableDtlMenu, me, showTrends } from '../../initial_state';
import { NavigationBar } from '../compose/components/navigation_bar';
@ -80,12 +81,8 @@ const badgeDisplay = (number, limit) => {
};
class GettingStarted extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
myAccount: ImmutablePropTypes.record,
multiColumn: PropTypes.bool,
@ -96,7 +93,7 @@ class GettingStarted extends ImmutablePureComponent {
componentDidMount () {
const { fetchFollowRequests } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -107,7 +104,7 @@ class GettingStarted extends ImmutablePureComponent {
render () {
const { intl, myAccount, multiColumn, unreadFollowRequests } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const navItems = [];
@ -183,4 +180,4 @@ class GettingStarted extends ImmutablePureComponent {
}
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(GettingStarted));
export default withIdentity(connect(mapStateToProps, mapDispatchToProps)(injectIntl(GettingStarted)));

View file

@ -17,6 +17,7 @@ import { fetchHashtag, followHashtag, unfollowHashtag } from 'mastodon/actions/t
import { expandHashtagTimeline, clearTimeline } from 'mastodon/actions/timelines';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import StatusListContainer from '../ui/containers/status_list_container';
@ -29,14 +30,10 @@ const mapStateToProps = (state, props) => ({
});
class HashtagTimeline extends PureComponent {
disconnects = [];
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired,
columnId: PropTypes.string,
dispatch: PropTypes.func.isRequired,
@ -94,7 +91,7 @@ class HashtagTimeline extends PureComponent {
};
_subscribe (dispatch, id, tags = {}, local) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -168,7 +165,7 @@ class HashtagTimeline extends PureComponent {
handleFollow = () => {
const { dispatch, params, tag } = this.props;
const { id } = params;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (!signedIn) {
return;
@ -185,7 +182,7 @@ class HashtagTimeline extends PureComponent {
const { hasUnread, columnId, multiColumn, tag } = this.props;
const { id, local } = this.props.params;
const pinned = !!columnId;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}>
@ -225,4 +222,4 @@ class HashtagTimeline extends PureComponent {
}
export default connect(mapStateToProps)(HashtagTimeline);
export default connect(mapStateToProps)(withIdentity(HashtagTimeline));

View file

@ -14,6 +14,7 @@ import { fetchAnnouncements, toggleShowAnnouncements } from 'mastodon/actions/an
import { IconWithBadge } from 'mastodon/components/icon_with_badge';
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
import AnnouncementsContainer from 'mastodon/features/getting_started/containers/announcements_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { criticalUpdatesPending } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -40,12 +41,8 @@ const mapStateToProps = state => ({
});
class HomeTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
hasUnread: PropTypes.bool,
@ -126,7 +123,7 @@ class HomeTimeline extends PureComponent {
render () {
const { intl, hasUnread, columnId, multiColumn, hasAnnouncements, unreadAnnouncements, showAnnouncements } = this.props;
const pinned = !!columnId;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const banners = [];
let announcementsButton;
@ -190,4 +187,4 @@ class HomeTimeline extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(HomeTimeline));
export default connect(mapStateToProps)(withIdentity(injectIntl(HomeTimeline)));

View file

@ -5,6 +5,7 @@ import { FormattedMessage } from 'react-intl';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { enableEmojiReaction } from 'mastodon/initial_state';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_REPORTS } from 'mastodon/permissions';
@ -13,13 +14,9 @@ import ClearColumnButton from './clear_column_button';
import GrantPermissionButton from './grant_permission_button';
import SettingToggle from './setting_toggle';
export default class ColumnSettings extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
class ColumnSettings extends PureComponent {
static propTypes = {
identity: identityContextPropShape,
settings: ImmutablePropTypes.map.isRequired,
pushSettings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
@ -258,7 +255,7 @@ export default class ColumnSettings extends PureComponent {
</div>
</section>
{((this.context.identity.permissions & PERMISSION_MANAGE_USERS) === PERMISSION_MANAGE_USERS) && (
{((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>
@ -271,7 +268,7 @@ export default class ColumnSettings extends PureComponent {
</section>
)}
{((this.context.identity.permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS) && (
{((this.props.identity.permissions & PERMISSION_MANAGE_REPORTS) === PERMISSION_MANAGE_REPORTS) && (
<section role='group' aria-labelledby='notifications-admin-report'>
<h3 id='notifications-status'><FormattedMessage id='notifications.column_settings.admin.report' defaultMessage='New reports:' /></h3>
@ -288,3 +285,5 @@ export default class ColumnSettings extends PureComponent {
}
}
export default withIdentity(ColumnSettings);

View file

@ -40,12 +40,12 @@ const mapDispatchToProps = dispatch => ({
},
onModalReblog (status, privacy) {
dispatch(reblog(status, privacy));
dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
},
onReblog (status, e) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
dispatch(unreblog({ statusId: status.get('id') }));
} else {
if (e.shiftKey || !boostModal) {
this.onModalReblog(status);

View file

@ -17,6 +17,7 @@ import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?
import { compareId } from 'mastodon/compare_id';
import { Icon } from 'mastodon/components/icon';
import { NotSignedInIndicator } from 'mastodon/components/not_signed_in_indicator';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
import { submitMarkers } from '../../actions/markers';
@ -77,12 +78,8 @@ const mapStateToProps = state => ({
});
class Notifications extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
columnId: PropTypes.string,
notifications: ImmutablePropTypes.list.isRequired,
dispatch: PropTypes.func.isRequired,
@ -190,7 +187,7 @@ class Notifications extends PureComponent {
const { intl, notifications, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, lastReadId, canMarkAsRead, needsNotificationPermission } = this.props;
const pinned = !!columnId;
const emptyMessage = <FormattedMessage id='empty_column.notifications' defaultMessage="You don't have any notifications yet. When other people interact with you, you will see it here." />;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
let scrollableContent = null;
@ -299,4 +296,4 @@ class Notifications extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(Notifications));
export default connect(mapStateToProps)(withIdentity(injectIntl(Notifications)));

View file

@ -18,6 +18,7 @@ import { replyCompose } from 'mastodon/actions/compose';
import { reblog, favourite, unreblog, unfavourite } from 'mastodon/actions/interactions';
import { openModal } from 'mastodon/actions/modal';
import { IconButton } from 'mastodon/components/icon_button';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { me, boostModal } from 'mastodon/initial_state';
import { makeGetStatus } from 'mastodon/selectors';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -47,12 +48,8 @@ const makeMapStateToProps = () => {
};
class Footer extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
statusId: PropTypes.string.isRequired,
status: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired,
@ -75,7 +72,7 @@ class Footer extends ImmutablePureComponent {
handleReplyClick = () => {
const { dispatch, askReplyConfirmation, status, intl } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (askReplyConfirmation) {
@ -104,7 +101,7 @@ class Footer extends ImmutablePureComponent {
handleFavouriteClick = () => {
const { dispatch, status } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('favourited')) {
@ -126,16 +123,16 @@ class Footer extends ImmutablePureComponent {
_performReblog = (status, privacy) => {
const { dispatch } = this.props;
dispatch(reblog(status, privacy));
dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
};
handleReblogClick = e => {
const { dispatch, status } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
dispatch(unreblog({ statusId: status.get('id') }));
} else if ((e && e.shiftKey) || !boostModal) {
this._performReblog(status);
} else {
@ -209,4 +206,4 @@ class Footer extends ImmutablePureComponent {
}
export default connect(makeMapStateToProps)(withRouter(injectIntl(Footer)));
export default connect(makeMapStateToProps)(withIdentity(withRouter(injectIntl(Footer))));

View file

@ -9,6 +9,7 @@ import { connect } from 'react-redux';
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import { DismissableBanner } from 'mastodon/components/dismissable_banner';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain } from 'mastodon/initial_state';
import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
@ -40,16 +41,12 @@ const mapStateToProps = (state, { columnId }) => {
};
class PublicTimeline extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static defaultProps = {
onlyMedia: false,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
columnId: PropTypes.string,
@ -80,7 +77,7 @@ class PublicTimeline extends PureComponent {
componentDidMount () {
const { dispatch, onlyMedia, onlyRemote } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
dispatch(expandPublicTimeline({ onlyMedia, onlyRemote }));
@ -90,7 +87,7 @@ class PublicTimeline extends PureComponent {
}
componentDidUpdate (prevProps) {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (prevProps.onlyMedia !== this.props.onlyMedia || prevProps.onlyRemote !== this.props.onlyRemote) {
const { dispatch, onlyMedia, onlyRemote } = this.props;
@ -164,4 +161,4 @@ class PublicTimeline extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(PublicTimeline));
export default connect(mapStateToProps)(withIdentity(injectIntl(PublicTimeline)));

View file

@ -22,6 +22,7 @@ import RepeatActiveIcon from '@/svg-icons/repeat_active.svg?react';
import RepeatDisabledIcon from '@/svg-icons/repeat_disabled.svg?react';
import RepeatPrivateIcon from '@/svg-icons/repeat_private.svg?react';
import RepeatPrivateActiveIcon from '@/svg-icons/repeat_private_active.svg?react';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -77,12 +78,8 @@ const mapStateToProps = (state, { status }) => ({
});
class ActionBar extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
status: ImmutablePropTypes.map.isRequired,
relationship: ImmutablePropTypes.record,
onReply: PropTypes.func.isRequired,
@ -243,7 +240,7 @@ class ActionBar extends PureComponent {
render () {
const { status, relationship, intl } = this.props;
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
const publicStatus = ['public', 'unlisted', 'public_unlisted', 'login'].includes(status.get('visibility_ex'));
const anonymousStatus = ['public', 'unlisted', 'public_unlisted'].includes(status.get('visibility_ex'));
@ -449,4 +446,4 @@ class ActionBar extends PureComponent {
}
export default withRouter(connect(mapStateToProps)(injectIntl(ActionBar)));
export default withRouter(connect(mapStateToProps)(withIdentity(injectIntl(ActionBar))));

View file

@ -76,12 +76,12 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
},
onModalReblog (status, privacy) {
dispatch(reblog(status, privacy));
dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
},
onReblog (status, e) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
dispatch(unreblog({ statusId: status.get('id') }));
} else {
if (e.shiftKey || !boostModal) {
this.onModalReblog(status);

View file

@ -20,6 +20,7 @@ import { Icon } from 'mastodon/components/icon';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import ScrollContainer from 'mastodon/containers/scroll_container';
import BundleColumnError from 'mastodon/features/ui/components/bundle_column_error';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
import {
@ -201,12 +202,8 @@ const titleFromStatus = (intl, status) => {
};
class Status extends ImmutablePureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
status: ImmutablePropTypes.map,
@ -257,7 +254,7 @@ class Status extends ImmutablePureComponent {
handleFavouriteClick = (status) => {
const { dispatch } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('favourited')) {
@ -310,7 +307,7 @@ class Status extends ImmutablePureComponent {
handleReplyClick = (status) => {
const { askReplyConfirmation, dispatch, intl } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (askReplyConfirmation) {
@ -338,16 +335,16 @@ class Status extends ImmutablePureComponent {
};
handleModalReblog = (status, privacy) => {
this.props.dispatch(reblog(status, privacy));
this.props.dispatch(reblog({ statusId: status.get('id'), visibility: privacy }));
};
handleReblogClick = (status, e, force = false) => {
const { dispatch } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
if (signedIn) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
dispatch(unreblog({ statusId: status.get('id') }));
} else {
if (!force && ((e && e.shiftKey) || !boostModal)) {
this.handleModalReblog(status);
@ -829,4 +826,4 @@ class Status extends ImmutablePureComponent {
}
export default withRouter(injectIntl(connect(makeMapStateToProps)(Status)));
export default withRouter(injectIntl(connect(makeMapStateToProps)(withIdentity(Status))));

View file

@ -7,16 +7,13 @@ import { changeComposing, mountCompose, unmountCompose } from 'mastodon/actions/
import ServerBanner from 'mastodon/components/server_banner';
import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container';
import SearchContainer from 'mastodon/features/compose/containers/search_container';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import LinkFooter from './link_footer';
class ComposePanel extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
};
@ -41,7 +38,7 @@ class ComposePanel extends PureComponent {
}
render() {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
return (
<div className='compose-panel' onFocus={this.onFocus}>
@ -65,4 +62,4 @@ class ComposePanel extends PureComponent {
}
export default connect()(ComposePanel);
export default connect()(withIdentity(ComposePanel));

View file

@ -14,6 +14,7 @@ import { fetchServer } from 'mastodon/actions/server';
import { Avatar } from 'mastodon/components/avatar';
import { Icon } from 'mastodon/components/icon';
import { WordmarkLogo, SymbolLogo } from 'mastodon/components/logo';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { registrationsOpen, me, sso_redirect } from 'mastodon/initial_state';
const Account = connect(state => ({
@ -43,12 +44,8 @@ const mapDispatchToProps = (dispatch) => ({
});
class Header extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
openClosedRegistrationsModal: PropTypes.func,
location: PropTypes.object,
signupUrl: PropTypes.string.isRequired,
@ -67,7 +64,7 @@ class Header extends PureComponent {
}
render () {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const { location, openClosedRegistrationsModal, signupUrl, intl } = this.props;
let content;
@ -129,4 +126,4 @@ class Header extends PureComponent {
}
export default injectIntl(withRouter(connect(mapStateToProps, mapDispatchToProps)(Header)));
export default injectIntl(withRouter(withIdentity(connect(mapStateToProps, mapDispatchToProps)(Header))));

View file

@ -8,6 +8,7 @@ import { Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { openModal } from 'mastodon/actions/modal';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { domain, version, source_url, statusPageUrl, profile_directory as profileDirectory } from 'mastodon/initial_state';
import { PERMISSION_INVITE_USERS } from 'mastodon/permissions';
import { logOut } from 'mastodon/utils/log_out';
@ -32,12 +33,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
});
class LinkFooter extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
multiColumn: PropTypes.bool,
onLogout: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
@ -53,7 +50,7 @@ class LinkFooter extends PureComponent {
};
render () {
const { signedIn, permissions } = this.context.identity;
const { signedIn, permissions } = this.props.identity;
const { multiColumn } = this.props;
const canInvite = signedIn && ((permissions & PERMISSION_INVITE_USERS) === PERMISSION_INVITE_USERS);
@ -108,4 +105,4 @@ class LinkFooter extends PureComponent {
}
export default injectIntl(connect(null, mapDispatchToProps)(LinkFooter));
export default injectIntl(withIdentity(connect(null, mapDispatchToProps)(LinkFooter)));

View file

@ -33,6 +33,7 @@ import { fetchFollowRequests } from 'mastodon/actions/accounts';
import { IconWithBadge } from 'mastodon/components/icon_with_badge';
import { WordmarkLogo } from 'mastodon/components/logo';
import { NavigationPortal } from 'mastodon/components/navigation_portal';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { enableDtlMenu, timelinePreview, trendsEnabled, dtlTag, enableLocalTimeline, isHideItem } from 'mastodon/initial_state';
import { transientSingleColumn } from 'mastodon/is_mobile';
@ -103,12 +104,8 @@ const FollowRequestsLink = () => {
};
class NavigationPanel extends Component {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
intl: PropTypes.object.isRequired,
};
@ -122,7 +119,7 @@ class NavigationPanel extends Component {
render () {
const { intl } = this.props;
const { signedIn, disabledAccountId } = this.context.identity;
const { signedIn, disabledAccountId } = this.props.identity;
const explorer = (trendsEnabled ? (
<ColumnLink transparent to='/explore' icon='explore' iconComponent={ExploreIcon} activeIconComponent={ExploreActiveIcon} text={intl.formatMessage(messages.explore)} />
@ -226,4 +223,4 @@ class NavigationPanel extends Component {
}
export default injectIntl(NavigationPanel);
export default injectIntl(withIdentity(NavigationPanel));

View file

@ -15,6 +15,7 @@ import { focusApp, unfocusApp, changeLayout } from 'mastodon/actions/app';
import { synchronouslySubmitMarkers, submitMarkers, fetchMarkers } from 'mastodon/actions/markers';
import { INTRODUCTION_VERSION } from 'mastodon/actions/onboarding';
import { PictureInPicture } from 'mastodon/features/picture_in_picture';
import { identityContextPropShape, withIdentity } from 'mastodon/identity_context';
import { layoutFromWindow } from 'mastodon/is_mobile';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -134,12 +135,8 @@ const keyMap = {
};
class SwitchingColumnsArea extends PureComponent {
static contextTypes = {
identity: PropTypes.object,
};
static propTypes = {
identity: identityContextPropShape,
children: PropTypes.node,
location: PropTypes.object,
singleColumn: PropTypes.bool,
@ -174,7 +171,7 @@ class SwitchingColumnsArea extends PureComponent {
render () {
const { children, singleColumn } = this.props;
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
const pathName = this.props.location.pathname;
let redirect;
@ -282,12 +279,8 @@ class SwitchingColumnsArea extends PureComponent {
}
class UI extends PureComponent {
static contextTypes = {
identity: PropTypes.object.isRequired,
};
static propTypes = {
identity: identityContextPropShape,
dispatch: PropTypes.func.isRequired,
children: PropTypes.node,
isComposing: PropTypes.bool,
@ -339,7 +332,7 @@ class UI extends PureComponent {
this.dragTargets.push(e.target);
}
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.context.identity.signedIn) {
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.props.identity.signedIn) {
this.setState({ draggingOver: true });
}
};
@ -367,7 +360,7 @@ class UI extends PureComponent {
this.setState({ draggingOver: false });
this.dragTargets = [];
if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.context.identity.signedIn) {
if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore && this.props.identity.signedIn) {
this.props.dispatch(uploadCompose(e.dataTransfer.files));
}
};
@ -419,7 +412,7 @@ class UI extends PureComponent {
};
componentDidMount () {
const { signedIn } = this.context.identity;
const { signedIn } = this.props.identity;
window.addEventListener('focus', this.handleWindowFocus, false);
window.addEventListener('blur', this.handleWindowBlur, false);
@ -621,7 +614,7 @@ class UI extends PureComponent {
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef}>
<Header />
<SwitchingColumnsArea location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
<SwitchingColumnsArea identity={this.props.identity} location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>
{children}
</SwitchingColumnsArea>
@ -637,4 +630,4 @@ class UI extends PureComponent {
}
export default connect(mapStateToProps)(injectIntl(withRouter(UI)));
export default connect(mapStateToProps)(injectIntl(withRouter(withIdentity(UI))));

View file

@ -0,0 +1,70 @@
import PropTypes from 'prop-types';
import { createContext, useContext } from 'react';
import hoistStatics from 'hoist-non-react-statics';
import type { InitialState } from 'mastodon/initial_state';
export interface IdentityContextType {
signedIn: boolean;
accountId: string | undefined;
disabledAccountId: string | undefined;
permissions: number;
}
export const identityContextPropShape = PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
disabledAccountId: PropTypes.string,
}).isRequired;
export const createIdentityContext = (state: InitialState) => ({
signedIn: !!state.meta.me,
accountId: state.meta.me,
disabledAccountId: state.meta.disabled_account_id,
permissions: state.role?.permissions ?? 0,
});
export const IdentityContext = createContext<IdentityContextType>({
signedIn: false,
permissions: 0,
accountId: undefined,
disabledAccountId: undefined,
});
export const useIdentity = () => useContext(IdentityContext);
export interface IdentityProps {
ref?: unknown;
wrappedComponentRef?: unknown;
}
/* Injects an `identity` props into the wrapped component to be able to use the new context in class components */
export function withIdentity<
ComponentType extends React.ComponentType<IdentityProps>,
>(Component: ComponentType) {
const displayName = `withIdentity(${Component.displayName ?? Component.name})`;
const C = (props: React.ComponentProps<ComponentType>) => {
const { wrappedComponentRef, ...remainingProps } = props;
return (
<IdentityContext.Consumer>
{(context) => {
return (
// @ts-expect-error - Dynamic covariant generic components are tough to type.
<Component
{...remainingProps}
identity={context}
ref={wrappedComponentRef}
/>
);
}}
</IdentityContext.Consumer>
);
};
C.displayName = displayName;
C.WrappedComponent = Component;
return hoistStatics(C, Component);
}

View file

@ -71,12 +71,22 @@
* @property {string} sso_redirect
*/
/**
* @typedef Role
* @property {string} id
* @property {string} name
* @property {string} permissions
* @property {string} color
* @property {boolean} highlighted
*/
/**
* @typedef InitialState
* @property {Record<string, import("./api_types/accounts").ApiAccountJSON>} accounts
* @property {InitialStateLanguage[]} languages
* @property {boolean=} critical_updates_pending
* @property {InitialStateMeta} meta
* @property {Role?} role
*/
const element = document.getElementById('initial-state');
@ -159,4 +169,11 @@ export const criticalUpdatesPending = initialState?.critical_updates_pending;
export const statusPageUrl = getMeta('status_page_url');
export const sso_redirect = getMeta('sso_redirect');
/**
* @returns {string | undefined}
*/
export function getAccessToken() {
return getMeta('access_token');
}
export default initialState;

View file

@ -469,6 +469,7 @@
"notification.follow": "{name} падпісаўся на вас",
"notification.follow_request": "{name} адправіў запыт на падпіску",
"notification.mention": "{name} згадаў вас",
"notification.moderation-warning.learn_more": "Даведацца больш",
"notification.own_poll": "Ваша апытанне скончылася",
"notification.poll": "Апытанне, дзе вы прынялі ўдзел, скончылася",
"notification.reblog": "{name} пашырыў ваш допіс",

View file

@ -50,6 +50,8 @@
"admin.dashboard.retention.cohort_size": "Mga bagong tagagamit",
"alert.rate_limited.message": "Mangyaring subukan muli pagkatapos ng {retry_time, time, medium}.",
"audio.hide": "Itago ang tunog",
"block_modal.show_less": "Magpakita ng mas kaunti",
"block_modal.show_more": "Magpakita ng higit pa",
"block_modal.title": "Harangan ang tagagamit?",
"bundle_column_error.error.title": "Naku!",
"bundle_column_error.network.body": "Nagkaroon ng kamalian habang sinusubukang i-karga ang pahinang ito. Maaaring dahil ito sa pansamantalang problema ng iyong koneksyon sa internet o ang server na ito.",
@ -102,6 +104,7 @@
"compose_form.encryption_warning": "Ang mga post sa Mastodon ay hindi naka-encrypt nang dulo-dulo. Huwag magbahagi ng anumang sensitibong impormasyon sa Mastodon.",
"compose_form.hashtag_warning": "Hindi maililista ang post na ito sa anumang hashtag dahil hindi ito nakapubliko. Mga nakapublikong post lamang ang mahahanap ayon sa hashtag.",
"compose_form.placeholder": "Anong nangyari?",
"compose_form.poll.duration": "Tagal ng botohan",
"compose_form.poll.multiple": "Maraming pagpipilian",
"compose_form.poll.single": "Piliin ang isa",
"compose_form.reply": "Tumugon",
@ -173,6 +176,7 @@
"empty_column.list": "Wala pang laman ang listahang ito. Kapag naglathala ng mga bagong post ang mga miyembro ng listahang ito, makikita iyon dito.",
"empty_column.lists": "Wala ka pang mga listahan. Kapag gumawa ka ng isa, makikita yun dito.",
"explore.search_results": "Mga resulta ng paghahanap",
"explore.suggested_follows": "Mga tao",
"explore.title": "Tuklasin",
"explore.trending_links": "Mga balita",
"filter_modal.select_filter.search": "Hanapin o gumawa",
@ -186,9 +190,14 @@
"follow_suggestions.who_to_follow": "Sinong maaaring sundan",
"footer.about": "Tungkol dito",
"footer.get_app": "Kunin ang app",
"footer.status": "Katayuan",
"generic.saved": "Nakaimbak",
"hashtag.column_header.tag_mode.all": "at {additional}",
"hashtag.column_header.tag_mode.any": "o {additional}",
"hashtag.column_settings.tag_mode.all": "Lahat ng nandito",
"hashtag.column_settings.tag_mode.any": "Ilan dito",
"hashtag.column_settings.tag_mode.none": "Wala dito",
"hashtags.and_other": "…at {count, plural, one {# iba pa} other {# na iba pa}}",
"home.column_settings.show_replies": "Ipakita ang mga tugon",
"home.pending_critical_update.body": "Mangyaring i-update ang iyong serbiro ng Mastodon sa lalong madaling panahon!",
"interaction_modal.login.action": "Iuwi mo ako",
@ -199,6 +208,7 @@
"intervals.full.days": "{number, plural, one {# araw} other {# na araw}}",
"intervals.full.hours": "{number, plural, one {# oras} other {# na oras}}",
"intervals.full.minutes": "{number, plural, one {# minuto} other {# na minuto}}",
"keyboard_shortcuts.blocked": "Buksan ang talaan ng mga nakaharang na mga tagagamit",
"keyboard_shortcuts.description": "Paglalarawan",
"keyboard_shortcuts.down": "Ilipat pababa sa talaan",
"keyboard_shortcuts.mention": "Banggitin ang may-akda",
@ -215,17 +225,23 @@
"lists.replies_policy.title": "Ipakita ang mga tugon sa:",
"lists.subheading": "Iyong mga talaan",
"loading_indicator.label": "Kumakarga…",
"mute_modal.hide_from_notifications": "Itago mula sa mga abiso",
"navigation_bar.about": "Tungkol dito",
"navigation_bar.blocks": "Nakaharang na mga tagagamit",
"navigation_bar.direct": "Mga palihim na banggit",
"navigation_bar.discover": "Tuklasin",
"navigation_bar.explore": "Tuklasin",
"navigation_bar.favourites": "Mga paborito",
"navigation_bar.follow_requests": "Mga hiling sa pagsunod",
"navigation_bar.follows_and_followers": "Mga sinusundan at tagasunod",
"navigation_bar.lists": "Mga listahan",
"navigation_bar.public_timeline": "Pinagsamang timeline",
"navigation_bar.search": "Maghanap",
"notification.admin.report": "Iniulat ni {name} si {target}",
"notification.follow": "Sinundan ka ni {name}",
"notification.follow_request": "Hinihiling ni {name} na sundan ka",
"notification.mention": "Binanggit ka ni {name}",
"notification.moderation_warning": "Mayroong kang natanggap na babala sa pagtitimpi",
"notification.relationships_severance_event.learn_more": "Matuto nang higit pa",
"notification_requests.accept": "Tanggapin",
"notification_requests.notifications_from": "Mga abiso mula kay/sa {name}",
@ -234,10 +250,12 @@
"notifications.column_settings.alert": "Mga abiso sa Desktop",
"notifications.column_settings.favourite": "Mga paborito:",
"notifications.column_settings.follow": "Mga bagong tagasunod:",
"notifications.column_settings.poll": "Resulta ng botohan:",
"notifications.column_settings.unread_notifications.category": "Hindi Nabasang mga Abiso",
"notifications.column_settings.update": "Mga pagbago:",
"notifications.filter.all": "Lahat",
"notifications.filter.favourites": "Mga paborito",
"notifications.filter.polls": "Resulta ng botohan",
"notifications.mark_as_read": "Markahan lahat ng abiso bilang nabasa na",
"notifications.policy.filter_not_followers_title": "Mga taong hindi ka susundan",
"notifications.policy.filter_not_following_title": "Mga taong hindi mo sinusundan",
@ -246,6 +264,7 @@
"onboarding.profile.note_hint": "Maaari mong @bangitin ang ibang mga tao o mga #hashtag…",
"onboarding.profile.save_and_continue": "Iimbak at magpatuloy",
"onboarding.share.next_steps": "Mga posibleng susunod na hakbang:",
"picture_in_picture.restore": "Ilagay ito pabalik",
"poll.closed": "Sarado",
"poll.reveal": "Ipakita ang mga resulta",
"poll.voted": "Binoto mo para sa sagot na ito",
@ -280,10 +299,13 @@
"report.thanks.title": "Ayaw mo bang makita ito?",
"report.thanks.title_actionable": "Salamat sa pag-uulat, titingnan namin ito.",
"report_notification.categories.other": "Iba pa",
"report_notification.categories.violation": "Paglabag sa patakaran",
"report_notification.open": "Buksan ang ulat",
"search.quick_action.open_url": "Buksan ang URL sa Mastodon",
"search.search_or_paste": "Maghanap o ilagay ang URL",
"search_popout.full_text_search_disabled_message": "Hindi magagamit sa {domain}.",
"search_popout.full_text_search_logged_out_message": "Magagamit lamang kapag naka-log in.",
"search_popout.recent": "Kamakailang mga paghahanap",
"search_results.all": "Lahat",
"search_results.see_all": "Ipakita lahat",
"server_banner.learn_more": "Matuto nang higit pa",

View file

@ -19,7 +19,7 @@
"account.block_domain": "Blocar dominio {domain}",
"account.block_short": "Blocar",
"account.blocked": "Blocate",
"account.browse_more_on_origin_server": "Navigar plus sur le profilo original",
"account.browse_more_on_origin_server": "Percurrer plus sur le profilo original",
"account.cancel_follow_request": "Cancellar sequimento",
"account.copy": "Copiar ligamine a profilo",
"account.direct": "Mentionar privatemente @{name}",
@ -122,7 +122,7 @@
"column.direct": "Mentiones private",
"column.directory": "Navigar profilos",
"column.domain_blocks": "Dominios blocate",
"column.favourites": "Favoritos",
"column.favourites": "Favorites",
"column.firehose": "Fluxos in directo",
"column.follow_requests": "Requestas de sequimento",
"column.home": "Initio",
@ -204,7 +204,7 @@
"disabled_account_banner.account_settings": "Parametros de conto",
"disabled_account_banner.text": "Tu conto {disabledAccount} es actualmente disactivate.",
"dismissable_banner.community_timeline": "Ecce le messages public le plus recente del personas con contos sur {domain}.",
"dismissable_banner.dismiss": "Dimitter",
"dismissable_banner.dismiss": "Clauder",
"dismissable_banner.explore_links": "Istes es le articulos de novas que se condivide le plus sur le rete social hodie. Le articulos de novas le plus recente, publicate per plus personas differente, se classifica plus in alto.",
"dismissable_banner.explore_statuses": "Ecce le messages de tote le rete social que gania popularitate hodie. Le messages plus nove con plus impulsos e favorites se classifica plus in alto.",
"dismissable_banner.explore_tags": "Ecce le hashtags que gania popularitate sur le rete social hodie. Le hashtags usate per plus personas differente se classifica plus in alto.",
@ -212,8 +212,8 @@
"domain_block_modal.block": "Blocar le servitor",
"domain_block_modal.block_account_instead": "Blocar @{name} in su loco",
"domain_block_modal.they_can_interact_with_old_posts": "Le personas de iste servitor pote interager con tu messages ancian.",
"domain_block_modal.they_cant_follow": "Nulle persona ab iste servitor pote sequer te.",
"domain_block_modal.they_wont_know": "Illes non sapera que illes ha essite blocate.",
"domain_block_modal.they_cant_follow": "Necuno de iste servitor pote sequer te.",
"domain_block_modal.they_wont_know": "Ille non sapera que ille ha essite blocate.",
"domain_block_modal.title": "Blocar dominio?",
"domain_block_modal.you_will_lose_followers": "Omne sequitores ab iste servitor essera removite.",
"domain_block_modal.you_wont_see_posts": "Tu non videra messages e notificationes ab usatores sur iste servitor.",
@ -307,7 +307,7 @@
"follow_request.reject": "Rejectar",
"follow_requests.unlocked_explanation": "Benque tu conto non es serrate, le personal de {domain} pensa que es un bon idea que tu revide manualmente le sequente requestas de iste contos.",
"follow_suggestions.curated_suggestion": "Selection del equipa",
"follow_suggestions.dismiss": "Non monstrar novemente",
"follow_suggestions.dismiss": "Non monstrar de novo",
"follow_suggestions.featured_longer": "Seligite con cura per le equipa de {domain}",
"follow_suggestions.friends_of_friends_longer": "Popular inter le gente que tu seque",
"follow_suggestions.hints.featured": "Iste profilo ha essite seligite manualmente per le equipa de {domain}.",
@ -412,7 +412,7 @@
"lightbox.next": "Sequente",
"lightbox.previous": "Precedente",
"limited_account_hint.action": "Monstrar profilo in omne caso",
"limited_account_hint.title": "Iste profilo esseva celate per le moderatores de {domain}.",
"limited_account_hint.title": "Iste profilo ha essite celate per le moderatores de {domain}.",
"link_preview.author": "Per {name}",
"lists.account.add": "Adder al lista",
"lists.account.remove": "Remover del lista",
@ -432,12 +432,12 @@
"loading_indicator.label": "Cargante…",
"media_gallery.toggle_visible": "{number, plural, one {Celar imagine} other {Celar imagines}}",
"moved_to_account_banner.text": "Tu conto {disabledAccount} es actualmente disactivate perque tu ha cambiate de conto a {movedToAccount}.",
"mute_modal.hide_from_notifications": "Celar ab notificationes",
"mute_modal.hide_from_notifications": "Celar in notificationes",
"mute_modal.hide_options": "Celar optiones",
"mute_modal.indefinite": "Usque io dissilentia iste persona",
"mute_modal.show_options": "Monstrar optiones",
"mute_modal.they_can_mention_and_follow": "Illes pote mentionar te e sequer te, ma tu non potera vider los.",
"mute_modal.they_wont_know": "Illes non sapera que illes ha essite silentiate.",
"mute_modal.they_can_mention_and_follow": "Ille pote mentionar te e sequer te, ma tu non potera vider le.",
"mute_modal.they_wont_know": "Ille non sapera que ille ha essite silentiate.",
"mute_modal.title": "Silentiar le usator?",
"mute_modal.you_wont_see_mentions": "Tu non videra le messages que mentiona iste persona.",
"mute_modal.you_wont_see_posts": "Iste persona pote totevia vider tu messages, ma tu non videra le sues.",
@ -451,13 +451,13 @@
"navigation_bar.discover": "Discoperir",
"navigation_bar.domain_blocks": "Dominios blocate",
"navigation_bar.explore": "Explorar",
"navigation_bar.favourites": "Favoritos",
"navigation_bar.favourites": "Favorites",
"navigation_bar.filters": "Parolas silentiate",
"navigation_bar.follow_requests": "Requestas de sequimento",
"navigation_bar.followed_tags": "Hashtags sequite",
"navigation_bar.follows_and_followers": "Sequites e sequitores",
"navigation_bar.lists": "Listas",
"navigation_bar.logout": "Clauder le session",
"navigation_bar.logout": "Clauder session",
"navigation_bar.mutes": "Usatores silentiate",
"navigation_bar.opened_in_classic_interface": "Messages, contos e altere paginas specific es aperite per predefinition in le interfacie web classic.",
"navigation_bar.personal": "Personal",
@ -501,7 +501,7 @@
"notifications.column_settings.admin.report": "Nove signalationes:",
"notifications.column_settings.admin.sign_up": "Nove inscriptiones:",
"notifications.column_settings.alert": "Notificationes de scriptorio",
"notifications.column_settings.favourite": "Favoritos:",
"notifications.column_settings.favourite": "Favorites:",
"notifications.column_settings.filter_bar.advanced": "Monstrar tote le categorias",
"notifications.column_settings.filter_bar.category": "Barra de filtro rapide",
"notifications.column_settings.follow": "Nove sequitores:",
@ -518,7 +518,7 @@
"notifications.column_settings.update": "Modificationes:",
"notifications.filter.all": "Toto",
"notifications.filter.boosts": "Impulsos",
"notifications.filter.favourites": "Favoritos",
"notifications.filter.favourites": "Favorites",
"notifications.filter.follows": "Sequites",
"notifications.filter.mentions": "Mentiones",
"notifications.filter.polls": "Resultatos del sondage",
@ -717,7 +717,7 @@
"status.edited": "Ultime modification le {date}",
"status.edited_x_times": "Modificate {count, plural, one {{count} vice} other {{count} vices}}",
"status.embed": "Incastrar",
"status.favourite": "Adder al favoritos",
"status.favourite": "Adder al favorites",
"status.favourites": "{count, plural, one {favorite} other {favorites}}",
"status.filter": "Filtrar iste message",
"status.filtered": "Filtrate",

View file

@ -90,7 +90,7 @@
"attachments_list.unprocessed": "(neapstrādāti)",
"audio.hide": "Slēpt audio",
"block_modal.remote_users_caveat": "Mēs vaicāsim serverim {domain} ņemt vērā Tavu lēmumu. Tomēr atbilstība nav nodrošināta, jo atsevišķi serveri var apstrādāt bloķēšanu citādi. Publiski ieraksti joprojām var būt redzami lietotājiem, kuri nav pieteikušies.",
"block_modal.show_less": "Parādīt vairāk",
"block_modal.show_less": "Rādīt mazāk",
"block_modal.show_more": "Parādīt mazāk",
"boost_modal.combo": "Nospied {combo}, lai nākamreiz šo izlaistu",
"bundle_column_error.copy_stacktrace": "Kopēt kļūdu ziņojumu",
@ -309,7 +309,7 @@
"hashtag.counter_by_uses_today": "{count, plural, zero {{counter} ierakstu} one {{counter} ieraksts} other {{counter} ieraksti}} šodien",
"hashtag.follow": "Sekot tēmturim",
"hashtag.unfollow": "Pārstāt sekot tēmturim",
"hashtags.and_other": "..un {count, plural, other {# vairāk}}",
"hashtags.and_other": "… un {count, plural, other {vēl #}}",
"home.column_settings.show_reblogs": "Rādīt pastiprinātos ierakstus",
"home.column_settings.show_replies": "Rādīt atbildes",
"home.hide_announcements": "Slēpt paziņojumus",
@ -491,7 +491,7 @@
"onboarding.actions.go_to_home": "Dodieties uz manu mājas plūsmu",
"onboarding.compose.template": "Sveiki, #Mastodon!",
"onboarding.follows.empty": "Diemžēl pašlaik nevar parādīt rezultātus. Vari mēģināt izmantot meklēšanu vai pārlūkot izpētes lapu, lai atrastu cilvēkus, kuriem sekot, vai vēlāk mēģināt vēlreiz.",
"onboarding.follows.lead": "Tava mājas plūsma ir galvenais veids, kā izbaudīt Mastodon. Jo vairāk cilvēku sekosi, jo aktīvāk un interesantāk tas būs. Lai sāktu, šeit ir daži ieteikumi:",
"onboarding.follows.lead": "Tava mājas plūsma ir galvenais veids, kā pieredzēt Mastodon. Jo vairāk cilvēkiem sekosi, jo dzīvīgāka un aizraujošāka tā būs. Lai sāktu, šeit ir daži ieteikumi:",
"onboarding.follows.title": "Pielāgo savu mājas barotni",
"onboarding.profile.discoverable": "Padarīt manu profilu atklājamu",
"onboarding.profile.display_name": "Attēlojamais vārds",

View file

@ -468,6 +468,7 @@
"notification.follow": "{name} подписался (-лась) на вас",
"notification.follow_request": "{name} отправил запрос на подписку",
"notification.mention": "{name} упомянул(а) вас",
"notification.moderation_warning.action_delete_statuses": "Некоторые из ваших публикаций были удалены.",
"notification.own_poll": "Ваш опрос закончился",
"notification.poll": "Опрос, в котором вы приняли участие, завершился",
"notification.reblog": "{name} продвинул(а) ваш пост",

View file

@ -474,6 +474,7 @@
"notification.follow_request": "{name} vam želi slediti",
"notification.mention": "{name} vas je omenil/a",
"notification.moderation-warning.learn_more": "Več o tem",
"notification.moderation_warning": "Prejeli ste opozorilo moderatorjev",
"notification.moderation_warning.action_delete_statuses": "Nekatere vaše objave so odstranjene.",
"notification.moderation_warning.action_disable": "Vaš račun je bil onemogočen.",
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Nekatere vaše objave so bile označene kot občutljive.",

View file

@ -158,7 +158,7 @@
"compose_form.poll.option_placeholder": "ตัวเลือก {number}",
"compose_form.poll.single": "เลือกอย่างใดอย่างหนึ่ง",
"compose_form.poll.switch_to_multiple": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตหลายตัวเลือก",
"compose_form.poll.switch_to_single": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตตัวเลือกเดียว",
"compose_form.poll.switch_to_single": "เปลี่ยนการสำรวจความคิดเห็นเป็นอนุญาตตัวเลือกเดียว",
"compose_form.poll.type": "ลักษณะ",
"compose_form.publish": "โพสต์",
"compose_form.publish_form": "โพสต์ใหม่",

View file

@ -6,7 +6,6 @@ import { layoutFromWindow } from 'mastodon/is_mobile';
const initialState = ImmutableMap({
streaming_api_base_url: null,
access_token: null,
layout: layoutFromWindow(),
permissions: '0',
});
@ -14,7 +13,8 @@ const initialState = ImmutableMap({
export default function meta(state = initialState, action) {
switch(action.type) {
case STORE_HYDRATE:
return state.merge(action.state.get('meta')).set('permissions', action.state.getIn(['role', 'permissions']));
// we do not want `access_token` to be stored in the state
return state.merge(action.state.get('meta')).delete('access_token').set('permissions', action.state.getIn(['role', 'permissions']));
case changeLayout.type:
return state.set('layout', action.payload.layout);
default:

View file

@ -9,10 +9,6 @@ import {
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
import { normalizeStatusTranslation } from '../actions/importer/normalizer';
import {
REBLOG_REQUEST,
REBLOG_FAIL,
UNREBLOG_REQUEST,
UNREBLOG_FAIL,
FAVOURITE_REQUEST,
FAVOURITE_FAIL,
UNFAVOURITE_REQUEST,
@ -22,6 +18,10 @@ import {
UNBOOKMARK_REQUEST,
UNBOOKMARK_FAIL,
} from '../actions/interactions';
import {
reblog,
unreblog,
} from '../actions/interactions_typed';
import {
STATUS_MUTE_SUCCESS,
STATUS_UNMUTE_SUCCESS,
@ -99,6 +99,7 @@ const statusTranslateUndo = (state, id) => {
const initialState = ImmutableMap();
/** @type {import('@reduxjs/toolkit').Reducer<typeof initialState>} */
export default function statuses(state = initialState, action) {
switch(action.type) {
case STATUS_FETCH_REQUEST:
@ -129,14 +130,6 @@ export default function statuses(state = initialState, action) {
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], false);
case UNBOOKMARK_FAIL:
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], true);
case REBLOG_REQUEST:
return state.setIn([action.status.get('id'), 'reblogged'], true);
case REBLOG_FAIL:
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], false);
case UNREBLOG_REQUEST:
return state.setIn([action.status.get('id'), 'reblogged'], false);
case UNREBLOG_FAIL:
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], true);
case STATUS_MUTE_SUCCESS:
return state.setIn([action.id, 'muted'], true);
case STATUS_UNMUTE_SUCCESS:
@ -168,6 +161,15 @@ export default function statuses(state = initialState, action) {
case STATUS_EMOJI_REACTION_UPDATE:
return updateStatusEmojiReaction(state, action.emoji_reaction);
default:
return state;
if(reblog.pending.match(action))
return state.setIn([action.meta.arg.statusId, 'reblogged'], true);
else if(reblog.rejected.match(action))
return state.get(action.meta.arg.statusId) === undefined ? state : state.setIn([action.meta.arg.statusId, 'reblogged'], false);
else if(unreblog.pending.match(action))
return state.setIn([action.meta.arg.statusId, 'reblogged'], false);
else if(unreblog.rejected.match(action))
return state.get(action.meta.arg.statusId) === undefined ? state : state.setIn([action.meta.arg.statusId, 'reblogged'], true);
else
return state;
}
}

View file

@ -2,6 +2,8 @@ import { createAsyncThunk } from '@reduxjs/toolkit';
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
import { useDispatch, useSelector } from 'react-redux';
import type { BaseThunkAPI } from '@reduxjs/toolkit/dist/createAsyncThunk';
import type { AppDispatch, RootState } from './store';
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
@ -13,8 +15,192 @@ export interface AsyncThunkRejectValue {
error?: unknown;
}
interface AppMeta {
skipLoading?: boolean;
}
export const createAppAsyncThunk = createAsyncThunk.withTypes<{
state: RootState;
dispatch: AppDispatch;
rejectValue: AsyncThunkRejectValue;
}>();
type AppThunkApi = Pick<
BaseThunkAPI<
RootState,
unknown,
AppDispatch,
AsyncThunkRejectValue,
AppMeta,
AppMeta
>,
'getState' | 'dispatch'
>;
interface AppThunkOptions {
skipLoading?: boolean;
}
const createBaseAsyncThunk = createAsyncThunk.withTypes<{
state: RootState;
dispatch: AppDispatch;
rejectValue: AsyncThunkRejectValue;
fulfilledMeta: AppMeta;
rejectedMeta: AppMeta;
}>();
export function createThunk<Arg = void, Returned = void>(
name: string,
creator: (arg: Arg, api: AppThunkApi) => Returned | Promise<Returned>,
options: AppThunkOptions = {},
) {
return createBaseAsyncThunk(
name,
async (
arg: Arg,
{ getState, dispatch, fulfillWithValue, rejectWithValue },
) => {
try {
const result = await creator(arg, { dispatch, getState });
return fulfillWithValue(result, {
skipLoading: options.skipLoading,
});
} catch (error) {
return rejectWithValue({ error }, { skipLoading: true });
}
},
{
getPendingMeta() {
if (options.skipLoading) return { skipLoading: true };
return {};
},
},
);
}
const discardLoadDataInPayload = Symbol('discardLoadDataInPayload');
type DiscardLoadData = typeof discardLoadDataInPayload;
type OnData<LoadDataResult, ReturnedData> = (
data: LoadDataResult,
api: AppThunkApi & {
discardLoadData: DiscardLoadData;
},
) => ReturnedData | DiscardLoadData | Promise<ReturnedData | DiscardLoadData>;
// Overload when there is no `onData` method, the payload is the `onData` result
export function createDataLoadingThunk<
LoadDataResult,
Args extends Record<string, unknown>,
>(
name: string,
loadData: (args: Args) => Promise<LoadDataResult>,
thunkOptions?: AppThunkOptions,
): ReturnType<typeof createThunk<Args, LoadDataResult>>;
// Overload when the `onData` method returns discardLoadDataInPayload, then the payload is empty
export function createDataLoadingThunk<
LoadDataResult,
Args extends Record<string, unknown>,
>(
name: string,
loadData: (args: Args) => Promise<LoadDataResult>,
onDataOrThunkOptions?:
| AppThunkOptions
| OnData<LoadDataResult, DiscardLoadData>,
thunkOptions?: AppThunkOptions,
): ReturnType<typeof createThunk<Args, void>>;
// Overload when the `onData` method returns nothing, then the mayload is the `onData` result
export function createDataLoadingThunk<
LoadDataResult,
Args extends Record<string, unknown>,
>(
name: string,
loadData: (args: Args) => Promise<LoadDataResult>,
onDataOrThunkOptions?: AppThunkOptions | OnData<LoadDataResult, void>,
thunkOptions?: AppThunkOptions,
): ReturnType<typeof createThunk<Args, LoadDataResult>>;
// Overload when there is an `onData` method returning something
export function createDataLoadingThunk<
LoadDataResult,
Args extends Record<string, unknown>,
Returned,
>(
name: string,
loadData: (args: Args) => Promise<LoadDataResult>,
onDataOrThunkOptions?: AppThunkOptions | OnData<LoadDataResult, Returned>,
thunkOptions?: AppThunkOptions,
): ReturnType<typeof createThunk<Args, Returned>>;
/**
* This function creates a Redux Thunk that handles loading data asynchronously (usually from the API), dispatching `pending`, `fullfilled` and `rejected` actions.
*
* You can run a callback on the `onData` results to either dispatch side effects or modify the payload.
*
* It is a wrapper around RTK's [`createAsyncThunk`](https://redux-toolkit.js.org/api/createAsyncThunk)
* @param name Prefix for the actions types
* @param loadData Function that loads the data. It's (object) argument will become the thunk's argument
* @param onDataOrThunkOptions
* Callback called on the results from `loadData`.
*
* First argument will be the return from `loadData`.
*
* Second argument is an object with: `dispatch`, `getState` and `discardLoadData`.
* It can return:
* - `undefined` (or no explicit return), meaning that the `onData` results will be the payload
* - `discardLoadData` to discard the `onData` results and return an empty payload
* - anything else, which will be the payload
*
* You can also omit this parameter and pass `thunkOptions` directly
* @param maybeThunkOptions
* Additional Mastodon specific options for the thunk. Currently supports:
* - `skipLoading` to avoid showing the loading bar when the request is in progress
* @returns The created thunk
*/
export function createDataLoadingThunk<
LoadDataResult,
Args extends Record<string, unknown>,
Returned,
>(
name: string,
loadData: (args: Args) => Promise<LoadDataResult>,
onDataOrThunkOptions?: AppThunkOptions | OnData<LoadDataResult, Returned>,
maybeThunkOptions?: AppThunkOptions,
) {
let onData: OnData<LoadDataResult, Returned> | undefined;
let thunkOptions: AppThunkOptions | undefined;
if (typeof onDataOrThunkOptions === 'function') onData = onDataOrThunkOptions;
else if (typeof onDataOrThunkOptions === 'object')
thunkOptions = onDataOrThunkOptions;
if (maybeThunkOptions) {
thunkOptions = maybeThunkOptions;
}
return createThunk<Args, Returned>(
name,
async (arg, { getState, dispatch }) => {
const data = await loadData(arg);
if (!onData) return data as Returned;
const result = await onData(data, {
dispatch,
getState,
discardLoadData: discardLoadDataInPayload,
});
// if there is no return in `onData`, we return the `onData` result
if (typeof result === 'undefined') return data as Returned;
// the user explicitely asked to discard the payload
else if (result === discardLoadDataInPayload)
return undefined as Returned;
else return result;
},
thunkOptions,
);
}

View file

@ -2,6 +2,8 @@
import WebSocketClient from '@gamestdio/websocket';
import { getAccessToken } from './initial_state';
/**
* @type {WebSocketClient | undefined}
*/
@ -147,9 +149,11 @@ const channelNameWithInlineParams = (channelName, params) => {
// @ts-expect-error
export const connectStream = (channelName, params, callbacks) => (dispatch, getState) => {
const streamingAPIBaseURL = getState().getIn(['meta', 'streaming_api_base_url']);
const accessToken = getState().getIn(['meta', 'access_token']);
const accessToken = getAccessToken();
const { onConnect, onReceive, onDisconnect } = callbacks(dispatch, getState);
if(!accessToken) throw new Error("Trying to connect to the streaming server but no access token is available.");
// If we cannot use a websockets connection, we must fall back
// to using individual connections for each channel
if (!streamingAPIBaseURL.startsWith('ws')) {

View file

@ -1,7 +1,3 @@
import PropTypes from 'prop-types';
import type { PropsWithChildren } from 'react';
import { Component } from 'react';
import { IntlProvider } from 'react-intl';
import { MemoryRouter } from 'react-router';
@ -9,44 +5,26 @@ import { MemoryRouter } from 'react-router';
// eslint-disable-next-line import/no-extraneous-dependencies
import { render as rtlRender } from '@testing-library/react';
class FakeIdentityWrapper extends Component<
PropsWithChildren<{ signedIn: boolean }>
> {
static childContextTypes = {
identity: PropTypes.shape({
signedIn: PropTypes.bool.isRequired,
accountId: PropTypes.string,
disabledAccountId: PropTypes.string,
accessToken: PropTypes.string,
}).isRequired,
};
getChildContext() {
return {
identity: {
signedIn: this.props.signedIn,
accountId: '123',
accessToken: 'test-access-token',
},
};
}
render() {
return this.props.children;
}
}
import { IdentityContext } from './identity_context';
function render(
ui: React.ReactElement,
{ locale = 'en', signedIn = true, ...renderOptions } = {},
) {
const fakeIdentity = {
signedIn: signedIn,
accountId: '123',
disabledAccountId: undefined,
permissions: 0,
};
const Wrapper = (props: { children: React.ReactNode }) => {
return (
<MemoryRouter>
<IntlProvider locale={locale}>
<FakeIdentityWrapper signedIn={signedIn}>
<IdentityContext.Provider value={fakeIdentity}>
{props.children}
</FakeIdentityWrapper>
</IdentityContext.Provider>
</IntlProvider>
</MemoryRouter>
);

View file

@ -4521,7 +4521,7 @@ a.status-card {
color: $primary-text-color;
}
.icon {
.icon-sliders {
transform: rotate(60deg);
}
}
@ -4532,7 +4532,7 @@ a.status-card {
}
}
.no-reduce-motion .column-header__button .icon {
.no-reduce-motion .column-header__button .icon-sliders {
transition: transform 150ms ease-in-out;
}

View file

@ -3,6 +3,8 @@
class ActivityPub::Parser::StatusParser
include JsonLdHelper
NORMALIZED_LOCALE_NAMES = LanguagesHelper::SUPPORTED_LOCALES.keys.index_by(&:downcase).freeze
# @param [Hash] json
# @param [Hash] options
# @option options [String] :followers_collection
@ -118,10 +120,13 @@ class ActivityPub::Parser::StatusParser
end
def language
@language ||= original_language || (misskey_software? ? 'ja' : nil)
lang = raw_language_code || (misskey_software? ? 'ja' : nil)
lang.presence && NORMALIZED_LOCALE_NAMES.fetch(lang.downcase.to_sym, lang)
end
def original_language
private
def raw_language_code
if content_language_map?
@object['contentMap'].keys.first
elsif name_language_map?
@ -131,8 +136,6 @@ class ActivityPub::Parser::StatusParser
end
end
private
def audience_to
as_array(@object['to'] || @json['to']).map { |x| value_or_id(x) }
end

View file

@ -23,6 +23,12 @@ module ApplicationExtension
redirect_uri.lines.first.strip
end
def redirect_uris
# Doorkeeper stores the redirect_uri value as a newline delimeted list in
# the database:
redirect_uri.split
end
def push_to_streaming_api
# TODO: #28793 Combine into a single topic
payload = Oj.dump(event: :kill)

View file

@ -9,10 +9,10 @@ class Vacuum::ImportsVacuum
private
def clean_unconfirmed_imports!
BulkImport.state_unconfirmed.where('created_at <= ?', 10.minutes.ago).reorder(nil).in_batches.delete_all
BulkImport.state_unconfirmed.where(created_at: ..10.minutes.ago).reorder(nil).in_batches.delete_all
end
def clean_old_imports!
BulkImport.where('created_at <= ?', 1.week.ago).reorder(nil).in_batches.delete_all
BulkImport.where(created_at: ..1.week.ago).reorder(nil).in_batches.delete_all
end
end

View file

@ -34,7 +34,7 @@ class Vacuum::StatusesVacuum
def statuses_scope
scope = Status.unscoped.kept
.joins(:account).merge(Account.remote)
.where('statuses.id < ?', retention_period_as_id)
.where(statuses: { id: ...retention_period_as_id })
if Setting.delete_content_cache_without_reaction
scope = scope.where.not(id: favourited_statuses)

View file

@ -5,7 +5,6 @@ class UserMailer < Devise::Mailer
helper :accounts
helper :application
helper :mascot
helper :formatting
helper :instance
helper :routing

View file

@ -61,6 +61,7 @@ class Admin::ActionLogFilter
unsuspend_account: { target_type: 'Account', action: 'unsuspend' }.freeze,
update_announcement: { target_type: 'Announcement', action: 'update' }.freeze,
update_custom_emoji: { target_type: 'CustomEmoji', action: 'update' }.freeze,
update_report: { target_type: 'Report', action: 'update' }.freeze,
update_status: { target_type: 'Status', action: 'update' }.freeze,
force_cw_status: { target_type: 'Status', action: 'force_cw' }.freeze,
force_sensitive_status: { target_type: 'Status', action: 'force_sensitive' }.freeze,

View file

@ -20,7 +20,6 @@ class CanonicalEmailBlock < ApplicationRecord
validates :canonical_email_hash, presence: true, uniqueness: true
scope :matching_email, ->(email) { where(canonical_email_hash: email_to_canonical_email_hash(email)) }
scope :matching_account, ->(account) { matching_email(account&.user_email).or(where(reference_account: account)) }
def to_log_human_identifier
canonical_email_hash

View file

@ -4,7 +4,7 @@ module Expireable
extend ActiveSupport::Concern
included do
scope :expired, -> { where.not(expires_at: nil).where('expires_at < ?', Time.now.utc) }
scope :expired, -> { where.not(expires_at: nil).where(expires_at: ...Time.now.utc) }
def expires_in
return @expires_in if defined?(@expires_in)

View file

@ -24,7 +24,7 @@ class Invite < ApplicationRecord
belongs_to :user, inverse_of: :invites
has_many :users, inverse_of: :invite, dependent: nil
scope :available, -> { where(expires_at: nil).or(where('expires_at >= ?', Time.now.utc)) }
scope :available, -> { where(expires_at: nil).or(where(expires_at: Time.now.utc..)) }
validates :comment, length: { maximum: COMMENT_SIZE_LIMIT }

View file

@ -4,6 +4,6 @@ class BackupPolicy < ApplicationPolicy
MIN_AGE = 6.days
def create?
user_signed_in? && current_user.backups.where('created_at >= ?', MIN_AGE.ago).count.zero?
user_signed_in? && current_user.backups.where(created_at: MIN_AGE.ago..).count.zero?
end
end

View file

@ -1,24 +1,18 @@
# frozen_string_literal: true
class REST::ApplicationSerializer < ActiveModel::Serializer
attributes :id, :name, :website, :scopes, :redirect_uri,
:client_id, :client_secret
attributes :id, :name, :website, :scopes, :redirect_uris
# NOTE: Deprecated in 4.3.0, needs to be removed in 5.0.0
attribute :vapid_key
# We should consider this property deprecated for 4.3.0
attribute :redirect_uri
def id
object.id.to_s
end
def client_id
object.uid
end
def client_secret
object.secret
end
def website
object.website.presence
end

Some files were not shown because too many files have changed in this diff Show more