Allow managing interaction policies
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
40141191c6
commit
4c990c8fff
15 changed files with 280 additions and 532 deletions
|
@ -132,7 +132,7 @@
|
||||||
"multiselect-react-dropdown": "^2.0.25",
|
"multiselect-react-dropdown": "^2.0.25",
|
||||||
"object-to-formdata": "^4.5.1",
|
"object-to-formdata": "^4.5.1",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
"pl-api": "^0.0.18",
|
"pl-api": "^0.0.20",
|
||||||
"postcss": "^8.4.29",
|
"postcss": "^8.4.29",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"punycode": "^2.1.1",
|
"punycode": "^2.1.1",
|
||||||
|
|
|
@ -10,13 +10,9 @@ import {
|
||||||
authorizeFollowRequest,
|
authorizeFollowRequest,
|
||||||
blockAccount,
|
blockAccount,
|
||||||
createAccount,
|
createAccount,
|
||||||
expandFollowers,
|
|
||||||
expandFollowing,
|
|
||||||
expandFollowRequests,
|
expandFollowRequests,
|
||||||
fetchAccount,
|
fetchAccount,
|
||||||
fetchAccountByUsername,
|
fetchAccountByUsername,
|
||||||
fetchFollowers,
|
|
||||||
fetchFollowing,
|
|
||||||
fetchFollowRequests,
|
fetchFollowRequests,
|
||||||
fetchRelationships,
|
fetchRelationships,
|
||||||
muteAccount,
|
muteAccount,
|
||||||
|
@ -841,322 +837,6 @@ describe('removeFromFollowers()', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('fetchFollowers()', () => {
|
|
||||||
const id = '1';
|
|
||||||
|
|
||||||
describe('when logged in', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const state = rootState.set('me', '123');
|
|
||||||
store = mockStore(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a successful API request', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
__stub((mock) => {
|
|
||||||
mock.onGet(`/api/v1/accounts/${id}/followers`).reply(200, [], {
|
|
||||||
link: `<https://example.com/api/v1/accounts/${id}/followers?since_id=1>; rel='prev'`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should dispatch the correct actions', async() => {
|
|
||||||
const expectedActions = [
|
|
||||||
{ type: 'FOLLOWERS_FETCH_REQUEST', id },
|
|
||||||
{ type: 'ACCOUNTS_IMPORT', accounts: [] },
|
|
||||||
{
|
|
||||||
type: 'FOLLOWERS_FETCH_SUCCESS',
|
|
||||||
id,
|
|
||||||
accounts: [],
|
|
||||||
next: null,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
await store.dispatch(fetchFollowers(id));
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual(expectedActions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with an unsuccessful API request', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
__stub((mock) => {
|
|
||||||
mock.onGet(`/api/v1/accounts/${id}/followers`).networkError();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should dispatch the correct actions', async() => {
|
|
||||||
const expectedActions = [
|
|
||||||
{ type: 'FOLLOWERS_FETCH_REQUEST', id },
|
|
||||||
{ type: 'FOLLOWERS_FETCH_FAIL', id, error: new Error('Network Error') },
|
|
||||||
];
|
|
||||||
await store.dispatch(fetchFollowers(id));
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual(expectedActions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('expandFollowers()', () => {
|
|
||||||
const id = '1';
|
|
||||||
|
|
||||||
describe('when logged out', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const state = rootState.set('me', null);
|
|
||||||
store = mockStore(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should do nothing', async() => {
|
|
||||||
await store.dispatch(expandFollowers(id));
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when logged in', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const state = rootState
|
|
||||||
.set('user_lists', ReducerRecord({
|
|
||||||
followers: ImmutableMap({
|
|
||||||
[id]: ListRecord({
|
|
||||||
next: 'next_url',
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
.set('me', '123');
|
|
||||||
store = mockStore(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the url is null', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const state = rootState
|
|
||||||
.set('user_lists', ReducerRecord({
|
|
||||||
followers: ImmutableMap({
|
|
||||||
[id]: ListRecord({
|
|
||||||
next: null,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
.set('me', '123');
|
|
||||||
store = mockStore(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should do nothing', async() => {
|
|
||||||
await store.dispatch(expandFollowers(id));
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a successful API request', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
__stub((mock) => {
|
|
||||||
mock.onGet('next_url').reply(200, [], {
|
|
||||||
link: `<https://example.com/api/v1/accounts/${id}/followers?since_id=1>; rel='prev'`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should dispatch the correct actions', async() => {
|
|
||||||
const expectedActions = [
|
|
||||||
{ type: 'FOLLOWERS_EXPAND_REQUEST', id },
|
|
||||||
{ type: 'ACCOUNTS_IMPORT', accounts: [] },
|
|
||||||
{
|
|
||||||
type: 'FOLLOWERS_EXPAND_SUCCESS',
|
|
||||||
id,
|
|
||||||
accounts: [],
|
|
||||||
next: null,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
await store.dispatch(expandFollowers(id));
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual(expectedActions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with an unsuccessful API request', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
__stub((mock) => {
|
|
||||||
mock.onGet('next_url').networkError();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should dispatch the correct actions', async() => {
|
|
||||||
const expectedActions = [
|
|
||||||
{ type: 'FOLLOWERS_EXPAND_REQUEST', id },
|
|
||||||
{ type: 'FOLLOWERS_EXPAND_FAIL', id, error: new Error('Network Error') },
|
|
||||||
];
|
|
||||||
await store.dispatch(expandFollowers(id));
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual(expectedActions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('fetchFollowing()', () => {
|
|
||||||
const id = '1';
|
|
||||||
|
|
||||||
describe('when logged in', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const state = rootState.set('me', '123');
|
|
||||||
store = mockStore(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a successful API request', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
__stub((mock) => {
|
|
||||||
mock.onGet(`/api/v1/accounts/${id}/following`).reply(200, [], {
|
|
||||||
link: `<https://example.com/api/v1/accounts/${id}/following?since_id=1>; rel='prev'`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should dispatch the correct actions', async() => {
|
|
||||||
const expectedActions = [
|
|
||||||
{ type: 'FOLLOWING_FETCH_REQUEST', id },
|
|
||||||
{ type: 'ACCOUNTS_IMPORT', accounts: [] },
|
|
||||||
{
|
|
||||||
type: 'FOLLOWING_FETCH_SUCCESS',
|
|
||||||
id,
|
|
||||||
accounts: [],
|
|
||||||
next: null,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
await store.dispatch(fetchFollowing(id));
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual(expectedActions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with an unsuccessful API request', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
__stub((mock) => {
|
|
||||||
mock.onGet(`/api/v1/accounts/${id}/following`).networkError();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should dispatch the correct actions', async() => {
|
|
||||||
const expectedActions = [
|
|
||||||
{ type: 'FOLLOWING_FETCH_REQUEST', id },
|
|
||||||
{ type: 'FOLLOWING_FETCH_FAIL', id, error: new Error('Network Error') },
|
|
||||||
];
|
|
||||||
await store.dispatch(fetchFollowing(id));
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual(expectedActions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('expandFollowing()', () => {
|
|
||||||
const id = '1';
|
|
||||||
|
|
||||||
describe('when logged out', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const state = rootState.set('me', null);
|
|
||||||
store = mockStore(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should do nothing', async() => {
|
|
||||||
await store.dispatch(expandFollowing(id));
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when logged in', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const state = rootState
|
|
||||||
.set('user_lists', ReducerRecord({
|
|
||||||
following: ImmutableMap({
|
|
||||||
[id]: ListRecord({
|
|
||||||
next: 'next_url',
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
.set('me', '123');
|
|
||||||
store = mockStore(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('when the url is null', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
const state = rootState
|
|
||||||
.set('user_lists', ReducerRecord({
|
|
||||||
following: ImmutableMap({
|
|
||||||
[id]: ListRecord({
|
|
||||||
next: null,
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
}))
|
|
||||||
.set('me', '123');
|
|
||||||
store = mockStore(state);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should do nothing', async() => {
|
|
||||||
await store.dispatch(expandFollowing(id));
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual([]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with a successful API request', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
__stub((mock) => {
|
|
||||||
mock.onGet('next_url').reply(200, [], {
|
|
||||||
link: `<https://example.com/api/v1/accounts/${id}/following?since_id=1>; rel='prev'`,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should dispatch the correct actions', async() => {
|
|
||||||
const expectedActions = [
|
|
||||||
{ type: 'FOLLOWING_EXPAND_REQUEST', id },
|
|
||||||
{ type: 'ACCOUNTS_IMPORT', accounts: [] },
|
|
||||||
{
|
|
||||||
type: 'FOLLOWING_EXPAND_SUCCESS',
|
|
||||||
id,
|
|
||||||
accounts: [],
|
|
||||||
next: null,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
await store.dispatch(expandFollowing(id));
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual(expectedActions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('with an unsuccessful API request', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
__stub((mock) => {
|
|
||||||
mock.onGet('next_url').networkError();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should dispatch the correct actions', async() => {
|
|
||||||
const expectedActions = [
|
|
||||||
{ type: 'FOLLOWING_EXPAND_REQUEST', id },
|
|
||||||
{ type: 'FOLLOWING_EXPAND_FAIL', id, error: new Error('Network Error') },
|
|
||||||
];
|
|
||||||
await store.dispatch(expandFollowing(id));
|
|
||||||
const actions = store.getActions();
|
|
||||||
|
|
||||||
expect(actions).toEqual(expectedActions);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('fetchRelationships()', () => {
|
describe('fetchRelationships()', () => {
|
||||||
const id = '1';
|
const id = '1';
|
||||||
|
|
||||||
|
|
|
@ -62,22 +62,6 @@ const ACCOUNT_LOOKUP_REQUEST = 'ACCOUNT_LOOKUP_REQUEST' as const;
|
||||||
const ACCOUNT_LOOKUP_SUCCESS = 'ACCOUNT_LOOKUP_SUCCESS' as const;
|
const ACCOUNT_LOOKUP_SUCCESS = 'ACCOUNT_LOOKUP_SUCCESS' as const;
|
||||||
const ACCOUNT_LOOKUP_FAIL = 'ACCOUNT_LOOKUP_FAIL' as const;
|
const ACCOUNT_LOOKUP_FAIL = 'ACCOUNT_LOOKUP_FAIL' as const;
|
||||||
|
|
||||||
const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST' as const;
|
|
||||||
const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS' as const;
|
|
||||||
const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL' as const;
|
|
||||||
|
|
||||||
const FOLLOWERS_EXPAND_REQUEST = 'FOLLOWERS_EXPAND_REQUEST' as const;
|
|
||||||
const FOLLOWERS_EXPAND_SUCCESS = 'FOLLOWERS_EXPAND_SUCCESS' as const;
|
|
||||||
const FOLLOWERS_EXPAND_FAIL = 'FOLLOWERS_EXPAND_FAIL' as const;
|
|
||||||
|
|
||||||
const FOLLOWING_FETCH_REQUEST = 'FOLLOWING_FETCH_REQUEST' as const;
|
|
||||||
const FOLLOWING_FETCH_SUCCESS = 'FOLLOWING_FETCH_SUCCESS' as const;
|
|
||||||
const FOLLOWING_FETCH_FAIL = 'FOLLOWING_FETCH_FAIL' as const;
|
|
||||||
|
|
||||||
const FOLLOWING_EXPAND_REQUEST = 'FOLLOWING_EXPAND_REQUEST' as const;
|
|
||||||
const FOLLOWING_EXPAND_SUCCESS = 'FOLLOWING_EXPAND_SUCCESS' as const;
|
|
||||||
const FOLLOWING_EXPAND_FAIL = 'FOLLOWING_EXPAND_FAIL' as const;
|
|
||||||
|
|
||||||
const RELATIONSHIPS_FETCH_REQUEST = 'RELATIONSHIPS_FETCH_REQUEST' as const;
|
const RELATIONSHIPS_FETCH_REQUEST = 'RELATIONSHIPS_FETCH_REQUEST' as const;
|
||||||
const RELATIONSHIPS_FETCH_SUCCESS = 'RELATIONSHIPS_FETCH_SUCCESS' as const;
|
const RELATIONSHIPS_FETCH_SUCCESS = 'RELATIONSHIPS_FETCH_SUCCESS' as const;
|
||||||
const RELATIONSHIPS_FETCH_FAIL = 'RELATIONSHIPS_FETCH_FAIL' as const;
|
const RELATIONSHIPS_FETCH_FAIL = 'RELATIONSHIPS_FETCH_FAIL' as const;
|
||||||
|
@ -370,147 +354,6 @@ const removeFromFollowersFail = (accountId: string, error: unknown) => ({
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
const fetchFollowers = (accountId: string) =>
|
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
|
||||||
dispatch(fetchFollowersRequest(accountId));
|
|
||||||
|
|
||||||
return getClient(getState()).accounts.getAccountFollowers(accountId)
|
|
||||||
.then(response => {
|
|
||||||
dispatch(importFetchedAccounts(response.items));
|
|
||||||
dispatch(fetchFollowersSuccess(accountId, response.items, response.next));
|
|
||||||
dispatch(fetchRelationships(response.items.map((item) => item.id)));
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
dispatch(fetchFollowersFail(accountId, error));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchFollowersRequest = (accountId: string) => ({
|
|
||||||
type: FOLLOWERS_FETCH_REQUEST,
|
|
||||||
accountId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchFollowersSuccess = (accountId: string, accounts: Array<Account>, next: (() => Promise<PaginatedResponse<Account>>) | null) => ({
|
|
||||||
type: FOLLOWERS_FETCH_SUCCESS,
|
|
||||||
accountId,
|
|
||||||
accounts,
|
|
||||||
next,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchFollowersFail = (accountId: string, error: unknown) => ({
|
|
||||||
type: FOLLOWERS_FETCH_FAIL,
|
|
||||||
accountId,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expandFollowers = (accountId: string) =>
|
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
|
||||||
if (!isLoggedIn(getState)) return null;
|
|
||||||
|
|
||||||
const next = getState().user_lists.followers.get(accountId)!.next;
|
|
||||||
|
|
||||||
if (next === null) return;
|
|
||||||
|
|
||||||
dispatch(expandFollowersRequest(accountId));
|
|
||||||
|
|
||||||
next().then(response => {
|
|
||||||
dispatch(importFetchedAccounts(response.items));
|
|
||||||
dispatch(expandFollowersSuccess(accountId, response.items, response.next));
|
|
||||||
dispatch(fetchRelationships(response.items.map((item) => item.id)));
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
dispatch(expandFollowersFail(accountId, error));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const expandFollowersRequest = (accountId: string) => ({
|
|
||||||
type: FOLLOWERS_EXPAND_REQUEST,
|
|
||||||
accountId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expandFollowersSuccess = (accountId: string, accounts: Array<Account>, next: (() => Promise<PaginatedResponse<Account>>) | null) => ({
|
|
||||||
type: FOLLOWERS_EXPAND_SUCCESS,
|
|
||||||
accountId,
|
|
||||||
accounts,
|
|
||||||
next,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expandFollowersFail = (accountId: string, error: unknown) => ({
|
|
||||||
type: FOLLOWERS_EXPAND_FAIL,
|
|
||||||
accountId,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchFollowing = (accountId: string) =>
|
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
|
||||||
dispatch(fetchFollowingRequest(accountId));
|
|
||||||
|
|
||||||
return getClient(getState()).accounts.getAccountFollowing(accountId)
|
|
||||||
.then(response => {
|
|
||||||
dispatch(importFetchedAccounts(response.items));
|
|
||||||
dispatch(fetchFollowingSuccess(accountId, response.items, response.next));
|
|
||||||
dispatch(fetchRelationships(response.items.map((item) => item.id)));
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
dispatch(fetchFollowingFail(accountId, error));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const fetchFollowingRequest = (accountId: string) => ({
|
|
||||||
type: FOLLOWING_FETCH_REQUEST,
|
|
||||||
accountId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchFollowingSuccess = (accountId: string, accounts: Array<Account>, next: (() => Promise<PaginatedResponse<Account>>) | null) => ({
|
|
||||||
type: FOLLOWING_FETCH_SUCCESS,
|
|
||||||
accountId,
|
|
||||||
accounts,
|
|
||||||
next,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchFollowingFail = (accountId: string, error: unknown) => ({
|
|
||||||
type: FOLLOWING_FETCH_FAIL,
|
|
||||||
accountId,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expandFollowing = (accountId: string) =>
|
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
|
||||||
if (!isLoggedIn(getState)) return null;
|
|
||||||
|
|
||||||
const next = getState().user_lists.following.get(accountId)!.next;
|
|
||||||
|
|
||||||
if (next === null) return;
|
|
||||||
|
|
||||||
dispatch(expandFollowingRequest(accountId));
|
|
||||||
|
|
||||||
next().then(response => {
|
|
||||||
dispatch(importFetchedAccounts(response.items));
|
|
||||||
dispatch(expandFollowingSuccess(accountId, response.items, response.next));
|
|
||||||
dispatch(fetchRelationships(response.items.map((item) => item.id)));
|
|
||||||
}).catch(error => {
|
|
||||||
dispatch(expandFollowingFail(accountId, error));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const expandFollowingRequest = (accountId: string) => ({
|
|
||||||
type: FOLLOWING_EXPAND_REQUEST,
|
|
||||||
accountId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expandFollowingSuccess = (accountId: string, accounts: Array<Account>, next: (() => Promise<PaginatedResponse<Account>>) | null) => ({
|
|
||||||
type: FOLLOWING_EXPAND_SUCCESS,
|
|
||||||
accountId,
|
|
||||||
accounts,
|
|
||||||
next,
|
|
||||||
});
|
|
||||||
|
|
||||||
const expandFollowingFail = (accountId: string, error: unknown) => ({
|
|
||||||
type: FOLLOWING_EXPAND_FAIL,
|
|
||||||
accountId,
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
|
|
||||||
const fetchRelationships = (accountIds: string[]) =>
|
const fetchRelationships = (accountIds: string[]) =>
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
if (!isLoggedIn(getState)) return null;
|
if (!isLoggedIn(getState)) return null;
|
||||||
|
@ -874,18 +717,6 @@ export {
|
||||||
ACCOUNT_LOOKUP_REQUEST,
|
ACCOUNT_LOOKUP_REQUEST,
|
||||||
ACCOUNT_LOOKUP_SUCCESS,
|
ACCOUNT_LOOKUP_SUCCESS,
|
||||||
ACCOUNT_LOOKUP_FAIL,
|
ACCOUNT_LOOKUP_FAIL,
|
||||||
FOLLOWERS_FETCH_REQUEST,
|
|
||||||
FOLLOWERS_FETCH_SUCCESS,
|
|
||||||
FOLLOWERS_FETCH_FAIL,
|
|
||||||
FOLLOWERS_EXPAND_REQUEST,
|
|
||||||
FOLLOWERS_EXPAND_SUCCESS,
|
|
||||||
FOLLOWERS_EXPAND_FAIL,
|
|
||||||
FOLLOWING_FETCH_REQUEST,
|
|
||||||
FOLLOWING_FETCH_SUCCESS,
|
|
||||||
FOLLOWING_FETCH_FAIL,
|
|
||||||
FOLLOWING_EXPAND_REQUEST,
|
|
||||||
FOLLOWING_EXPAND_SUCCESS,
|
|
||||||
FOLLOWING_EXPAND_FAIL,
|
|
||||||
RELATIONSHIPS_FETCH_REQUEST,
|
RELATIONSHIPS_FETCH_REQUEST,
|
||||||
RELATIONSHIPS_FETCH_SUCCESS,
|
RELATIONSHIPS_FETCH_SUCCESS,
|
||||||
RELATIONSHIPS_FETCH_FAIL,
|
RELATIONSHIPS_FETCH_FAIL,
|
||||||
|
@ -936,22 +767,6 @@ export {
|
||||||
removeFromFollowersRequest,
|
removeFromFollowersRequest,
|
||||||
removeFromFollowersSuccess,
|
removeFromFollowersSuccess,
|
||||||
removeFromFollowersFail,
|
removeFromFollowersFail,
|
||||||
fetchFollowers,
|
|
||||||
fetchFollowersRequest,
|
|
||||||
fetchFollowersSuccess,
|
|
||||||
fetchFollowersFail,
|
|
||||||
expandFollowers,
|
|
||||||
expandFollowersRequest,
|
|
||||||
expandFollowersSuccess,
|
|
||||||
expandFollowersFail,
|
|
||||||
fetchFollowing,
|
|
||||||
fetchFollowingRequest,
|
|
||||||
fetchFollowingSuccess,
|
|
||||||
fetchFollowingFail,
|
|
||||||
expandFollowing,
|
|
||||||
expandFollowingRequest,
|
|
||||||
expandFollowingSuccess,
|
|
||||||
expandFollowingFail,
|
|
||||||
fetchRelationships,
|
fetchRelationships,
|
||||||
fetchRelationshipsRequest,
|
fetchRelationshipsRequest,
|
||||||
fetchRelationshipsSuccess,
|
fetchRelationshipsSuccess,
|
||||||
|
|
|
@ -404,7 +404,7 @@ const submitCompose = (composeId: string, opts: SubmitComposeOpts = {}) =>
|
||||||
scheduled_at: compose.schedule?.toISOString(),
|
scheduled_at: compose.schedule?.toISOString(),
|
||||||
language: compose.language || compose.suggested_language || undefined,
|
language: compose.language || compose.suggested_language || undefined,
|
||||||
to: to.size ? to.toArray() : undefined,
|
to: to.size ? to.toArray() : undefined,
|
||||||
federated: compose.federated,
|
local_only: !compose.federated,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (compose.poll) {
|
if (compose.poll) {
|
||||||
|
|
|
@ -192,12 +192,8 @@ const updateAuthAccount = (url: string, settings: any) => {
|
||||||
const key = `authAccount:${url}`;
|
const key = `authAccount:${url}`;
|
||||||
return KVStore.getItem(key).then((oldAccount: any) => {
|
return KVStore.getItem(key).then((oldAccount: any) => {
|
||||||
if (!oldAccount) return;
|
if (!oldAccount) return;
|
||||||
if (!oldAccount.__meta) oldAccount.__meta = { pleroma: { settings_store: {} } };
|
if (!oldAccount.settings_store) oldAccount.settings_store = {};
|
||||||
else if (!oldAccount.__meta.pleroma) oldAccount.__meta.pleroma = { settings_store: {} };
|
oldAccount.settings_store[FE_NAME] = settings;
|
||||||
else if (!oldAccount.__meta.pleroma.settings_store) oldAccount.__meta.pleroma.settings_store = {};
|
|
||||||
oldAccount.__meta.pleroma.settings_store[FE_NAME] = settings;
|
|
||||||
// const settingsStore = oldAccount?.__meta || {};
|
|
||||||
// settingsStore[FE_NAME] = settings;
|
|
||||||
KVStore.setItem(key, oldAccount);
|
KVStore.setItem(key, oldAccount);
|
||||||
}).catch(console.error);
|
}).catch(console.error);
|
||||||
};
|
};
|
||||||
|
|
|
@ -31,6 +31,9 @@ export { useUpdateGroup } from './groups/useUpdateGroup';
|
||||||
// Instance
|
// Instance
|
||||||
export { useTranslationLanguages } from './instance/useTranslationLanguages';
|
export { useTranslationLanguages } from './instance/useTranslationLanguages';
|
||||||
|
|
||||||
|
// Settings
|
||||||
|
export { useInteractionPolicies } from './settings/useInteractionPolicies';
|
||||||
|
|
||||||
// Statuses
|
// Statuses
|
||||||
export { useBookmarkFolders } from './statuses/useBookmarkFolders';
|
export { useBookmarkFolders } from './statuses/useBookmarkFolders';
|
||||||
export { useBookmarkFolder } from './statuses/useBookmarkFolder';
|
export { useBookmarkFolder } from './statuses/useBookmarkFolder';
|
||||||
|
|
41
src/api/hooks/settings/useInteractionPolicies.ts
Normal file
41
src/api/hooks/settings/useInteractionPolicies.ts
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
|
import { type InteractionPolicies, interactionPoliciesSchema } from 'pl-api';
|
||||||
|
|
||||||
|
import { useClient, useFeatures, useLoggedIn } from 'soapbox/hooks';
|
||||||
|
import { queryClient } from 'soapbox/queries/client';
|
||||||
|
|
||||||
|
const emptySchema = interactionPoliciesSchema.parse({});
|
||||||
|
|
||||||
|
const useInteractionPolicies = () => {
|
||||||
|
const client = useClient();
|
||||||
|
const { isLoggedIn } = useLoggedIn();
|
||||||
|
const features = useFeatures();
|
||||||
|
|
||||||
|
const { data, ...result } = useQuery({
|
||||||
|
queryKey: ['interactionPolicies'],
|
||||||
|
queryFn: client.settings.getInteractionPolicies,
|
||||||
|
placeholderData: emptySchema,
|
||||||
|
enabled: isLoggedIn && features.interactionRequests,
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
mutate: updateInteractionPolicies,
|
||||||
|
isPending: isUpdating,
|
||||||
|
} = useMutation({
|
||||||
|
mutationFn: (policy: InteractionPolicies) =>
|
||||||
|
client.settings.updateInteractionPolicies(policy),
|
||||||
|
retry: false,
|
||||||
|
onSuccess: (policy) => {
|
||||||
|
queryClient.setQueryData(['interactionPolicies'], policy);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
interactionPolicies: data || emptySchema,
|
||||||
|
updateInteractionPolicies,
|
||||||
|
isUpdating,
|
||||||
|
...result,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export { useInteractionPolicies };
|
47
src/components/ui/inline-multiselect/inline-multiselect.tsx
Normal file
47
src/components/ui/inline-multiselect/inline-multiselect.tsx
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import clsx from 'clsx';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface IInlineMultiselect<T extends string> {
|
||||||
|
items: Record<T, string>;
|
||||||
|
value?: T[];
|
||||||
|
onChange: ((values: T[]) => void);
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const InlineMultiselect = <T extends string>({ items, value, onChange, disabled }: IInlineMultiselect<T>) => (
|
||||||
|
<div className='flex w-fit overflow-hidden rounded-md border border-gray-400 bg-white black:bg-black dark:border-gray-800 dark:bg-gray-900'>
|
||||||
|
{Object.entries(items).map(([key, label], i) => {
|
||||||
|
const checked = value?.includes(key as T);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<label
|
||||||
|
className={clsx('px-3 py-2 text-white transition-colors hover:bg-primary-700 [&:has(:focus-visible)]:bg-primary-700', {
|
||||||
|
'cursor-pointer': !disabled,
|
||||||
|
'opacity-75': disabled,
|
||||||
|
'bg-gray-500': !checked,
|
||||||
|
'bg-primary-600': checked,
|
||||||
|
'border-l border-gray-400 dark:border-gray-800': i !== 0,
|
||||||
|
})}
|
||||||
|
key={key}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
name={key}
|
||||||
|
type='checkbox'
|
||||||
|
className='sr-only'
|
||||||
|
checked={checked}
|
||||||
|
onChange={({ target }) => onChange((target.checked ? [...(value || []), target.name] : value?.filter(key => key !== target.name) || []) as Array<T>)}
|
||||||
|
disabled={disabled}
|
||||||
|
/>
|
||||||
|
{label as string}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export {
|
||||||
|
InlineMultiselect,
|
||||||
|
};
|
175
src/features/interaction-policies/index.tsx
Normal file
175
src/features/interaction-policies/index.tsx
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { useInteractionPolicies } from 'soapbox/api/hooks';
|
||||||
|
import List, { ListItem } from 'soapbox/components/list';
|
||||||
|
import { Button, CardTitle, Column, Form, FormActions, Tabs } from 'soapbox/components/ui';
|
||||||
|
import { InlineMultiselect } from 'soapbox/components/ui/inline-multiselect/inline-multiselect';
|
||||||
|
import toast from 'soapbox/toast';
|
||||||
|
|
||||||
|
import Warning from '../compose/components/warning';
|
||||||
|
|
||||||
|
type Visibility = 'public' | 'unlisted' | 'private';
|
||||||
|
type Policy = 'can_favourite' | 'can_reblog' | 'can_reply';
|
||||||
|
type Rule = 'always' | 'with_approval';
|
||||||
|
type Scope = 'followers' | 'following' | 'mentioned' | 'public';
|
||||||
|
|
||||||
|
const policies: Array<Policy> = ['can_favourite', 'can_reply', 'can_reblog'];
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
heading: { id: 'column.interaction_policies', defaultMessage: 'Interaction policies' },
|
||||||
|
public: { id: 'interaction_policies.tabs.public', defaultMessage: 'Public' },
|
||||||
|
unlisted: { id: 'interaction_policies.tabs.unlisted', defaultMessage: 'Unlisted' },
|
||||||
|
private: { id: 'interaction_policies.tabs.private', defaultMessage: 'Followers-only' },
|
||||||
|
submit: { id: 'interaction_policies.update', defaultMessage: 'Update' },
|
||||||
|
success: { id: 'interaction_policies.success', defaultMessage: 'Updated interaction policies' },
|
||||||
|
fail: { id: 'interaction_policies.fail', defaultMessage: 'Failed to update interaction policies' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const scopeMessages = defineMessages({
|
||||||
|
followers: { id: 'interaction_policies.entry.followers', defaultMessage: 'Followers' },
|
||||||
|
following: { id: 'interaction_policies.entry.following', defaultMessage: 'People I follow' },
|
||||||
|
mentioned: { id: 'interaction_policies.entry.mentioned', defaultMessage: 'Mentioned' },
|
||||||
|
public: { id: 'interaction_policies.entry.public', defaultMessage: 'Everyone' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const titleMessages = {
|
||||||
|
public: defineMessages({
|
||||||
|
can_favourite: { id: 'interaction_policies.title.public.can_favourite', defaultMessage: 'Who can like a public post?' },
|
||||||
|
can_reply: { id: 'interaction_policies.title.public.can_reply', defaultMessage: 'Who can reply to a public post?' },
|
||||||
|
can_reblog: { id: 'interaction_policies.title.public.can_reblog', defaultMessage: 'Who can repost a public post?' },
|
||||||
|
}),
|
||||||
|
unlisted: defineMessages({
|
||||||
|
can_favourite: { id: 'interaction_policies.title.unlisted.can_favourite', defaultMessage: 'Who can like an unlisted post?' },
|
||||||
|
can_reply: { id: 'interaction_policies.title.unlisted.can_reply', defaultMessage: 'Who can reply to an unlisted post?' },
|
||||||
|
can_reblog: { id: 'interaction_policies.title.unlisted.can_reblog', defaultMessage: 'Who can repost an unlisted post?' },
|
||||||
|
}),
|
||||||
|
private: defineMessages({
|
||||||
|
can_favourite: { id: 'interaction_policies.title.private.can_favourite', defaultMessage: 'Who can like a followers-only post?' },
|
||||||
|
can_reply: { id: 'interaction_policies.title.private.can_reply', defaultMessage: 'Who can reply to a followers-only post?' },
|
||||||
|
can_reblog: { id: 'interaction_policies.title.private.can_reblog', defaultMessage: 'Who can repost a followers-only post?' },
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const options: Record<Visibility, Record<Policy, Array<Scope>>> = {
|
||||||
|
public: {
|
||||||
|
can_favourite: ['followers', 'following', 'mentioned', 'public'],
|
||||||
|
can_reblog: ['followers', 'following', 'mentioned', 'public'],
|
||||||
|
can_reply: ['followers', 'following', 'public'],
|
||||||
|
},
|
||||||
|
unlisted: {
|
||||||
|
can_favourite: ['followers', 'following', 'mentioned', 'public'],
|
||||||
|
can_reblog: ['followers', 'following', 'mentioned', 'public'],
|
||||||
|
can_reply: ['followers', 'following', 'public'],
|
||||||
|
},
|
||||||
|
private: {
|
||||||
|
can_favourite: ['followers'],
|
||||||
|
can_reblog: [],
|
||||||
|
can_reply: ['followers'],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const InteractionPolicies = () => {
|
||||||
|
const { interactionPolicies: initial, updateInteractionPolicies, isUpdating } = useInteractionPolicies();
|
||||||
|
const intl = useIntl();
|
||||||
|
const [interactionPolicies, setInteractionPolicies] = useState(initial);
|
||||||
|
const [visibility, setVisibility] = useState<Visibility>('public');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setInteractionPolicies(initial);
|
||||||
|
}, [initial]);
|
||||||
|
|
||||||
|
const getItems = (visibility: Visibility, policy: Policy) => Object.fromEntries(options[visibility][policy].map(scope => [scope, intl.formatMessage(scopeMessages[scope])])) as Record<Scope, string>;
|
||||||
|
|
||||||
|
const handleChange = (visibility: Visibility, policy: Policy, rule: Rule) => (value: Array<Scope>) => {
|
||||||
|
const newPolicies = { ...interactionPolicies };
|
||||||
|
newPolicies[visibility][policy][rule] = value;
|
||||||
|
newPolicies[visibility][policy][rule === 'always' ? 'with_approval' : 'always'] = newPolicies[visibility][policy][rule === 'always' ? 'with_approval' : 'always'].filter(rule => !value.includes(rule as any));
|
||||||
|
|
||||||
|
setInteractionPolicies(newPolicies);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
updateInteractionPolicies(interactionPolicies, {
|
||||||
|
onSuccess: () => toast.success(messages.success),
|
||||||
|
onError: () => toast.success(messages.fail),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderPolicy = (visibility: 'public' | 'unlisted' | 'private') => (
|
||||||
|
<>
|
||||||
|
{policies.map((policy) => {
|
||||||
|
const items = getItems(visibility, policy);
|
||||||
|
|
||||||
|
if (!Object.keys(items).length) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment key={policy}>
|
||||||
|
<CardTitle
|
||||||
|
title={intl.formatMessage(titleMessages[visibility][policy])}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{policy === 'can_reply' && (
|
||||||
|
<Warning message={<FormattedMessage id='interaction_policies.mentioned_warning' defaultMessage='Mentioned users can always reply.' />} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
<List>
|
||||||
|
<ListItem label='Always'>
|
||||||
|
<InlineMultiselect<Scope>
|
||||||
|
items={items}
|
||||||
|
value={interactionPolicies[visibility][policy].always as Array<Scope>}
|
||||||
|
onChange={handleChange(visibility, policy, 'always')}
|
||||||
|
disabled={isUpdating}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem label='With approval'>
|
||||||
|
<InlineMultiselect
|
||||||
|
items={items}
|
||||||
|
value={interactionPolicies[visibility][policy].with_approval as Array<Scope>}
|
||||||
|
onChange={handleChange(visibility, policy, 'with_approval')}
|
||||||
|
disabled={isUpdating}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column label={intl.formatMessage(messages.heading)} backHref='/settings'>
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<Tabs
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(messages.public),
|
||||||
|
action: () => setVisibility('public'),
|
||||||
|
name: 'public',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(messages.unlisted),
|
||||||
|
action: () => setVisibility('unlisted'),
|
||||||
|
name: 'unlisted',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: intl.formatMessage(messages.private),
|
||||||
|
action: () => setVisibility('private'),
|
||||||
|
name: 'private',
|
||||||
|
}]}
|
||||||
|
activeItem={visibility}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{renderPolicy(visibility)}
|
||||||
|
|
||||||
|
<FormActions>
|
||||||
|
<Button type='submit' theme='primary' disabled={isUpdating}>
|
||||||
|
{intl.formatMessage(messages.submit)}
|
||||||
|
</Button>
|
||||||
|
</FormActions>
|
||||||
|
</Form>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export { InteractionPolicies as default };
|
|
@ -26,6 +26,7 @@ const messages = defineMessages({
|
||||||
exportData: { id: 'column.export_data', defaultMessage: 'Export data' },
|
exportData: { id: 'column.export_data', defaultMessage: 'Export data' },
|
||||||
filters: { id: 'navigation_bar.filters', defaultMessage: 'Filters' },
|
filters: { id: 'navigation_bar.filters', defaultMessage: 'Filters' },
|
||||||
importData: { id: 'navigation_bar.import_data', defaultMessage: 'Import data' },
|
importData: { id: 'navigation_bar.import_data', defaultMessage: 'Import data' },
|
||||||
|
interactionPolicies: { id: 'column.interaction_policies', defaultMessage: 'Interaction policies' },
|
||||||
mfaDisabled: { id: 'mfa.disabled', defaultMessage: 'Disabled' },
|
mfaDisabled: { id: 'mfa.disabled', defaultMessage: 'Disabled' },
|
||||||
mfaEnabled: { id: 'mfa.enabled', defaultMessage: 'Enabled' },
|
mfaEnabled: { id: 'mfa.enabled', defaultMessage: 'Enabled' },
|
||||||
mutes: { id: 'settings.mutes', defaultMessage: 'Mutes' },
|
mutes: { id: 'settings.mutes', defaultMessage: 'Mutes' },
|
||||||
|
@ -82,6 +83,7 @@ const Settings = () => {
|
||||||
<ListItem label={intl.formatMessage(messages.blocks)} to='/blocks' />
|
<ListItem label={intl.formatMessage(messages.blocks)} to='/blocks' />
|
||||||
{(features.filters || features.filtersV2) && <ListItem label={intl.formatMessage(messages.filters)} to='/filters' />}
|
{(features.filters || features.filtersV2) && <ListItem label={intl.formatMessage(messages.filters)} to='/filters' />}
|
||||||
{features.federating && <ListItem label={intl.formatMessage(messages.domainBlocks)} to='/domain_blocks' />}
|
{features.federating && <ListItem label={intl.formatMessage(messages.domainBlocks)} to='/domain_blocks' />}
|
||||||
|
{features.interactionRequests && <ListItem label={intl.formatMessage(messages.interactionPolicies)} to='/interaction_policies' />}
|
||||||
</List>
|
</List>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
|
||||||
|
|
|
@ -129,6 +129,7 @@ import {
|
||||||
DraftStatuses,
|
DraftStatuses,
|
||||||
Circle,
|
Circle,
|
||||||
BubbleTimeline,
|
BubbleTimeline,
|
||||||
|
InteractionPolicies,
|
||||||
} from './util/async-components';
|
} from './util/async-components';
|
||||||
import GlobalHotkeys from './util/global-hotkeys';
|
import GlobalHotkeys from './util/global-hotkeys';
|
||||||
import { WrappedRoute } from './util/react-router-helpers';
|
import { WrappedRoute } from './util/react-router-helpers';
|
||||||
|
@ -297,6 +298,7 @@ const SwitchingColumnsArea: React.FC<ISwitchingColumnsArea> = ({ children }) =>
|
||||||
<WrappedRoute path='/settings/account' layout={DefaultLayout} component={DeleteAccount} content={children} />
|
<WrappedRoute path='/settings/account' layout={DefaultLayout} component={DeleteAccount} content={children} />
|
||||||
<WrappedRoute path='/settings/mfa' layout={DefaultLayout} component={MfaForm} exact />
|
<WrappedRoute path='/settings/mfa' layout={DefaultLayout} component={MfaForm} exact />
|
||||||
<WrappedRoute path='/settings/tokens' layout={DefaultLayout} component={AuthTokenList} content={children} />
|
<WrappedRoute path='/settings/tokens' layout={DefaultLayout} component={AuthTokenList} content={children} />
|
||||||
|
{features.interactionRequests && <WrappedRoute path='/settings/interaction_policies' layout={DefaultLayout} component={InteractionPolicies} content={children} />}
|
||||||
<WrappedRoute path='/settings' layout={DefaultLayout} component={Settings} content={children} />
|
<WrappedRoute path='/settings' layout={DefaultLayout} component={Settings} content={children} />
|
||||||
<WrappedRoute path='/soapbox/config' adminOnly layout={DefaultLayout} component={SoapboxConfig} content={children} />
|
<WrappedRoute path='/soapbox/config' adminOnly layout={DefaultLayout} component={SoapboxConfig} content={children} />
|
||||||
|
|
||||||
|
|
|
@ -122,4 +122,4 @@ export const Rules = lazy(() => import('soapbox/features/admin/rules'));
|
||||||
export const DraftStatuses = lazy(() => import('soapbox/features/draft-statuses'));
|
export const DraftStatuses = lazy(() => import('soapbox/features/draft-statuses'));
|
||||||
export const Circle = lazy(() => import('soapbox/features/circle'));
|
export const Circle = lazy(() => import('soapbox/features/circle'));
|
||||||
export const BubbleTimeline = lazy(() => import('soapbox/features/bubble-timeline'));
|
export const BubbleTimeline = lazy(() => import('soapbox/features/bubble-timeline'));
|
||||||
|
export const InteractionPolicies = lazy(() => import('soapbox/features/interaction-policies'));
|
||||||
|
|
|
@ -27,7 +27,7 @@ const updateFrequentLanguages = (state: State, language: string) =>
|
||||||
|
|
||||||
const importSettings = (state: State, account: APIEntity) => {
|
const importSettings = (state: State, account: APIEntity) => {
|
||||||
account = fromJS(account);
|
account = fromJS(account);
|
||||||
const prefs = account.getIn(['__meta', 'pleroma', 'settings_store', FE_NAME], ImmutableMap());
|
const prefs = account.getIn(['settings_store', FE_NAME], ImmutableMap());
|
||||||
return state.merge(prefs) as State;
|
return state.merge(prefs) as State;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -41,7 +41,6 @@ const settings = (
|
||||||
): State => {
|
): State => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case ME_FETCH_SUCCESS:
|
case ME_FETCH_SUCCESS:
|
||||||
console.log('importing', action.me);
|
|
||||||
return importSettings(state, action.me);
|
return importSettings(state, action.me);
|
||||||
case NOTIFICATIONS_FILTER_SET:
|
case NOTIFICATIONS_FILTER_SET:
|
||||||
case SEARCH_FILTER_SET:
|
case SEARCH_FILTER_SET:
|
||||||
|
|
|
@ -6,10 +6,6 @@ import {
|
||||||
import { AnyAction } from 'redux';
|
import { AnyAction } from 'redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FOLLOWERS_FETCH_SUCCESS,
|
|
||||||
FOLLOWERS_EXPAND_SUCCESS,
|
|
||||||
FOLLOWING_FETCH_SUCCESS,
|
|
||||||
FOLLOWING_EXPAND_SUCCESS,
|
|
||||||
FOLLOW_REQUESTS_FETCH_SUCCESS,
|
FOLLOW_REQUESTS_FETCH_SUCCESS,
|
||||||
FOLLOW_REQUESTS_EXPAND_SUCCESS,
|
FOLLOW_REQUESTS_EXPAND_SUCCESS,
|
||||||
FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
|
FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
|
||||||
|
@ -138,14 +134,6 @@ const normalizeFollowRequest = (state: State, notification: Notification) =>
|
||||||
|
|
||||||
const userLists = (state = ReducerRecord(), action: DirectoryAction | AnyAction) => {
|
const userLists = (state = ReducerRecord(), action: DirectoryAction | AnyAction) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case FOLLOWERS_FETCH_SUCCESS:
|
|
||||||
return normalizeList(state, ['followers', action.accountId], action.accounts, action.next);
|
|
||||||
case FOLLOWERS_EXPAND_SUCCESS:
|
|
||||||
return appendToList(state, ['followers', action.accountId], action.accounts, action.next);
|
|
||||||
case FOLLOWING_FETCH_SUCCESS:
|
|
||||||
return normalizeList(state, ['following', action.accountId], action.accounts, action.next);
|
|
||||||
case FOLLOWING_EXPAND_SUCCESS:
|
|
||||||
return appendToList(state, ['following', action.accountId], action.accounts, action.next);
|
|
||||||
case REBLOGS_FETCH_SUCCESS:
|
case REBLOGS_FETCH_SUCCESS:
|
||||||
return normalizeList(state, ['reblogged_by', action.statusId], action.accounts, action.next);
|
return normalizeList(state, ['reblogged_by', action.statusId], action.accounts, action.next);
|
||||||
case REBLOGS_EXPAND_SUCCESS:
|
case REBLOGS_EXPAND_SUCCESS:
|
||||||
|
|
|
@ -8385,10 +8385,10 @@ pkg-types@^1.0.3:
|
||||||
mlly "^1.2.0"
|
mlly "^1.2.0"
|
||||||
pathe "^1.1.0"
|
pathe "^1.1.0"
|
||||||
|
|
||||||
pl-api@^0.0.18:
|
pl-api@^0.0.20:
|
||||||
version "0.0.18"
|
version "0.0.20"
|
||||||
resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-0.0.18.tgz#23542323cb9450e4c084a38e411a8dc5f3e806f5"
|
resolved "https://registry.yarnpkg.com/pl-api/-/pl-api-0.0.20.tgz#2839820d399d8018ca3c89b8529e2d4c239b77c1"
|
||||||
integrity sha512-JYNR3hKO8bHUOnMNERCXwYj6PMNToY9uqPC5lFbe0vQry77ioHzaqiGzA4F1cK1nSSYZhDR41psuBqisvIp9kg==
|
integrity sha512-FL5eCZnJDPuazGK9zMrIHsKmM9Mb1kvcaYMR6ecbGpkzmustjNL0f8gdH86rFYL1k33zyKLA3sd7OAXB4zFoMg==
|
||||||
dependencies:
|
dependencies:
|
||||||
blurhash "^2.0.5"
|
blurhash "^2.0.5"
|
||||||
http-link-header "^1.1.3"
|
http-link-header "^1.1.3"
|
||||||
|
|
Loading…
Reference in a new issue