mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-14 08:19:05 +00:00
[Glitch] Update Redux to handle quote posts
Port 8ee4b3f906 to glitch-soc
Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
@@ -246,6 +246,8 @@ export function submitCompose(overridePrivacy = null) {
|
|||||||
visibility: overridePrivacy || getState().getIn(['compose', 'privacy']),
|
visibility: overridePrivacy || getState().getIn(['compose', 'privacy']),
|
||||||
poll: getState().getIn(['compose', 'poll'], null),
|
poll: getState().getIn(['compose', 'poll'], null),
|
||||||
language: getState().getIn(['compose', 'language']),
|
language: getState().getIn(['compose', 'language']),
|
||||||
|
quoted_status_id: getState().getIn(['compose', 'quoted_status_id']),
|
||||||
|
quote_approval_policy: getState().getIn(['compose', 'quote_policy']),
|
||||||
},
|
},
|
||||||
headers: {
|
headers: {
|
||||||
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
|
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
import type { List as ImmutableList, Map as ImmutableMap } from 'immutable';
|
import type { List as ImmutableList, Map as ImmutableMap } from 'immutable';
|
||||||
|
|
||||||
import { apiUpdateMedia } from 'flavours/glitch/api/compose';
|
import { apiUpdateMedia } from 'flavours/glitch/api/compose';
|
||||||
import type { ApiMediaAttachmentJSON } from 'flavours/glitch/api_types/media_attachments';
|
import type { ApiMediaAttachmentJSON } from 'flavours/glitch/api_types/media_attachments';
|
||||||
import type { MediaAttachment } from 'flavours/glitch/models/media_attachment';
|
import type { MediaAttachment } from 'flavours/glitch/models/media_attachment';
|
||||||
import { createDataLoadingThunk } from 'flavours/glitch/store/typed_functions';
|
import {
|
||||||
|
createDataLoadingThunk,
|
||||||
|
createAppThunk,
|
||||||
|
} from 'flavours/glitch/store/typed_functions';
|
||||||
|
|
||||||
|
import type { ApiQuotePolicy } from '../api_types/quotes';
|
||||||
|
import type { Status } from '../models/status';
|
||||||
|
|
||||||
|
import { ensureComposeIsVisible } from './compose';
|
||||||
|
|
||||||
type SimulatedMediaAttachmentJSON = ApiMediaAttachmentJSON & {
|
type SimulatedMediaAttachmentJSON = ApiMediaAttachmentJSON & {
|
||||||
unattached?: boolean;
|
unattached?: boolean;
|
||||||
@@ -68,3 +77,26 @@ export const changeUploadCompose = createDataLoadingThunk(
|
|||||||
useLoadingBar: false,
|
useLoadingBar: false,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const quoteComposeByStatus = createAppThunk(
|
||||||
|
'compose/quoteComposeStatus',
|
||||||
|
(status: Status, { getState }) => {
|
||||||
|
ensureComposeIsVisible(getState);
|
||||||
|
return status;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const quoteComposeById = createAppThunk(
|
||||||
|
(statusId: string, { dispatch, getState }) => {
|
||||||
|
const status = getState().statuses.get(statusId);
|
||||||
|
if (status) {
|
||||||
|
dispatch(quoteComposeByStatus(status));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export const quoteComposeCancel = createAction('compose/quoteComposeCancel');
|
||||||
|
|
||||||
|
export const setQuotePolicy = createAction<ApiQuotePolicy>(
|
||||||
|
'compose/setQuotePolicy',
|
||||||
|
);
|
||||||
|
|||||||
23
app/javascript/flavours/glitch/api_types/quotes.ts
Normal file
23
app/javascript/flavours/glitch/api_types/quotes.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type { ApiStatusJSON } from './statuses';
|
||||||
|
|
||||||
|
export type ApiQuoteState = 'accepted' | 'pending' | 'revoked' | 'unauthorized';
|
||||||
|
export type ApiQuotePolicy = 'public' | 'followers' | 'nobody';
|
||||||
|
|
||||||
|
interface ApiQuoteEmptyJSON {
|
||||||
|
state: Exclude<ApiQuoteState, 'accepted'>;
|
||||||
|
quoted_status: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ApiNestedQuoteJSON {
|
||||||
|
state: 'accepted';
|
||||||
|
quoted_status_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ApiQuoteAcceptedJSON {
|
||||||
|
state: 'accepted';
|
||||||
|
quoted_status: Omit<ApiStatusJSON, 'quote'> & {
|
||||||
|
quote: ApiNestedQuoteJSON | ApiQuoteEmptyJSON;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ApiQuoteJSON = ApiQuoteAcceptedJSON | ApiQuoteEmptyJSON;
|
||||||
@@ -4,6 +4,7 @@ import type { ApiAccountJSON } from './accounts';
|
|||||||
import type { ApiCustomEmojiJSON } from './custom_emoji';
|
import type { ApiCustomEmojiJSON } from './custom_emoji';
|
||||||
import type { ApiMediaAttachmentJSON } from './media_attachments';
|
import type { ApiMediaAttachmentJSON } from './media_attachments';
|
||||||
import type { ApiPollJSON } from './polls';
|
import type { ApiPollJSON } from './polls';
|
||||||
|
import type { ApiQuoteJSON } from './quotes';
|
||||||
|
|
||||||
// See app/modals/status.rb
|
// See app/modals/status.rb
|
||||||
export type StatusVisibility =
|
export type StatusVisibility =
|
||||||
@@ -118,6 +119,7 @@ export interface ApiStatusJSON {
|
|||||||
|
|
||||||
card?: ApiPreviewCardJSON;
|
card?: ApiPreviewCardJSON;
|
||||||
poll?: ApiPollJSON;
|
poll?: ApiPollJSON;
|
||||||
|
quote?: ApiQuoteJSON;
|
||||||
|
|
||||||
// glitch-soc additions
|
// glitch-soc additions
|
||||||
local_only?: boolean;
|
local_only?: boolean;
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
|
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
|
||||||
|
|
||||||
import { changeUploadCompose } from 'flavours/glitch/actions/compose_typed';
|
import {
|
||||||
|
changeUploadCompose,
|
||||||
|
quoteComposeByStatus,
|
||||||
|
quoteComposeCancel,
|
||||||
|
setQuotePolicy,
|
||||||
|
} from 'flavours/glitch/actions/compose_typed';
|
||||||
import { timelineDelete } from 'flavours/glitch/actions/timelines_typed';
|
import { timelineDelete } from 'flavours/glitch/actions/timelines_typed';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -109,6 +114,11 @@ const initialState = ImmutableMap({
|
|||||||
adaptiveStroke: true,
|
adaptiveStroke: true,
|
||||||
smoothing: false,
|
smoothing: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
// Quotes
|
||||||
|
quoted_status_id: null,
|
||||||
|
quote_policy: 'public',
|
||||||
|
default_quote_policy: 'public', // Set in hydration.
|
||||||
});
|
});
|
||||||
|
|
||||||
const initialPoll = ImmutableMap({
|
const initialPoll = ImmutableMap({
|
||||||
@@ -169,6 +179,8 @@ function clearAll(state) {
|
|||||||
map.set('progress', 0);
|
map.set('progress', 0);
|
||||||
map.set('poll', null);
|
map.set('poll', null);
|
||||||
map.set('idempotencyKey', uuid());
|
map.set('idempotencyKey', uuid());
|
||||||
|
map.set('quoted_status_id', null);
|
||||||
|
map.set('quote_policy', state.get('default_quote_policy'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,6 +405,15 @@ export const composeReducer = (state = initialState, action) => {
|
|||||||
return state.set('is_changing_upload', true);
|
return state.set('is_changing_upload', true);
|
||||||
} else if (changeUploadCompose.rejected.match(action)) {
|
} else if (changeUploadCompose.rejected.match(action)) {
|
||||||
return state.set('is_changing_upload', false);
|
return state.set('is_changing_upload', false);
|
||||||
|
} else if (quoteComposeByStatus.match(action)) {
|
||||||
|
const status = action.payload;
|
||||||
|
if (status.getIn(['quote_approval', 'current_user']) === 'automatic') {
|
||||||
|
return state.set('quoted_status_id', status.get('id'));
|
||||||
|
}
|
||||||
|
} else if (quoteComposeCancel.match(action)) {
|
||||||
|
return state.set('quoted_status_id', null);
|
||||||
|
} else if (setQuotePolicy.match(action)) {
|
||||||
|
return state.set('quote_policy', action.payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
import type { GetThunkAPI } from '@reduxjs/toolkit';
|
import type {
|
||||||
import { createAsyncThunk, createSelector } from '@reduxjs/toolkit';
|
ActionCreatorWithPreparedPayload,
|
||||||
|
GetThunkAPI,
|
||||||
|
} from '@reduxjs/toolkit';
|
||||||
|
import {
|
||||||
|
createAsyncThunk as rtkCreateAsyncThunk,
|
||||||
|
createSelector,
|
||||||
|
createAction,
|
||||||
|
} from '@reduxjs/toolkit';
|
||||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
@@ -18,7 +25,7 @@ interface AppMeta {
|
|||||||
useLoadingBar?: boolean;
|
useLoadingBar?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createAppAsyncThunk = createAsyncThunk.withTypes<{
|
export const createAppAsyncThunk = rtkCreateAsyncThunk.withTypes<{
|
||||||
state: RootState;
|
state: RootState;
|
||||||
dispatch: AppDispatch;
|
dispatch: AppDispatch;
|
||||||
rejectValue: AsyncThunkRejectValue;
|
rejectValue: AsyncThunkRejectValue;
|
||||||
@@ -43,9 +50,88 @@ interface AppThunkOptions<Arg> {
|
|||||||
) => boolean;
|
) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const createBaseAsyncThunk = createAsyncThunk.withTypes<AppThunkConfig>();
|
// Type definitions for the sync thunks.
|
||||||
|
type AppThunk<Arg = void, Returned = void> = (
|
||||||
|
arg: Arg,
|
||||||
|
) => (dispatch: AppDispatch, getState: () => RootState) => Returned;
|
||||||
|
|
||||||
export function createThunk<Arg = void, Returned = void>(
|
type AppThunkCreator<Arg = void, Returned = void, ExtraArg = unknown> = (
|
||||||
|
arg: Arg,
|
||||||
|
api: AppThunkApi,
|
||||||
|
extra?: ExtraArg,
|
||||||
|
) => Returned;
|
||||||
|
|
||||||
|
type AppThunkActionCreator<
|
||||||
|
Arg = void,
|
||||||
|
Returned = void,
|
||||||
|
> = ActionCreatorWithPreparedPayload<
|
||||||
|
[Returned, Arg],
|
||||||
|
Returned,
|
||||||
|
string,
|
||||||
|
never,
|
||||||
|
{ arg: Arg }
|
||||||
|
>;
|
||||||
|
|
||||||
|
// Version that does not dispatch it's own action.
|
||||||
|
export function createAppThunk<Arg = void, Returned = void, ExtraArg = unknown>(
|
||||||
|
creator: AppThunkCreator<Arg, Returned, ExtraArg>,
|
||||||
|
extra?: ExtraArg,
|
||||||
|
): AppThunk<Arg, Returned>;
|
||||||
|
|
||||||
|
// Version that dispatches an named action with the result of the creator callback.
|
||||||
|
export function createAppThunk<Arg = void, Returned = void, ExtraArg = unknown>(
|
||||||
|
name: string,
|
||||||
|
creator: AppThunkCreator<Arg, Returned, ExtraArg>,
|
||||||
|
extra?: ExtraArg,
|
||||||
|
): AppThunk<Arg, Returned> & AppThunkActionCreator<Arg, Returned>;
|
||||||
|
|
||||||
|
/** Creates a thunk that dispatches an action. */
|
||||||
|
export function createAppThunk<Arg = void, Returned = void, ExtraArg = unknown>(
|
||||||
|
nameOrCreator: string | AppThunkCreator<Arg, Returned, ExtraArg>,
|
||||||
|
maybeCreatorOrExtra?: AppThunkCreator<Arg, Returned, ExtraArg> | ExtraArg,
|
||||||
|
maybeExtra?: ExtraArg,
|
||||||
|
) {
|
||||||
|
const isDispatcher = typeof nameOrCreator === 'string';
|
||||||
|
const name = isDispatcher ? nameOrCreator : undefined;
|
||||||
|
const creator = isDispatcher
|
||||||
|
? (maybeCreatorOrExtra as AppThunkCreator<Arg, Returned, ExtraArg>)
|
||||||
|
: nameOrCreator;
|
||||||
|
const extra = isDispatcher ? maybeExtra : (maybeCreatorOrExtra as ExtraArg);
|
||||||
|
let action: null | AppThunkActionCreator<Arg, Returned> = null;
|
||||||
|
|
||||||
|
// Creates a thunk that dispatches the action with the result of the creator.
|
||||||
|
const actionCreator: AppThunk<Arg, Returned> = (arg) => {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const result = creator(arg, { dispatch, getState }, extra);
|
||||||
|
if (action) {
|
||||||
|
// Dispatches the action with the result.
|
||||||
|
const actionObj = action(result, arg);
|
||||||
|
dispatch(actionObj);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// No action name provided, return the thunk directly.
|
||||||
|
if (!name) {
|
||||||
|
return actionCreator;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the action and assign the action creator to it in order
|
||||||
|
// to have things like `toString` and `match` available.
|
||||||
|
action = createAction(name, (payload: Returned, arg: Arg) => ({
|
||||||
|
payload,
|
||||||
|
meta: {
|
||||||
|
arg,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
return Object.assign({}, action, actionCreator);
|
||||||
|
}
|
||||||
|
|
||||||
|
const createBaseAsyncThunk = rtkCreateAsyncThunk.withTypes<AppThunkConfig>();
|
||||||
|
|
||||||
|
export function createAsyncThunk<Arg = void, Returned = void>(
|
||||||
name: string,
|
name: string,
|
||||||
creator: (arg: Arg, api: AppThunkApi) => Returned | Promise<Returned>,
|
creator: (arg: Arg, api: AppThunkApi) => Returned | Promise<Returned>,
|
||||||
options: AppThunkOptions<Arg> = {},
|
options: AppThunkOptions<Arg> = {},
|
||||||
@@ -104,7 +190,7 @@ export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
|
|||||||
name: string,
|
name: string,
|
||||||
loadData: (args: Args) => Promise<LoadDataResult>,
|
loadData: (args: Args) => Promise<LoadDataResult>,
|
||||||
thunkOptions?: AppThunkOptions<Args>,
|
thunkOptions?: AppThunkOptions<Args>,
|
||||||
): ReturnType<typeof createThunk<Args, LoadDataResult>>;
|
): ReturnType<typeof createAsyncThunk<Args, LoadDataResult>>;
|
||||||
|
|
||||||
// Overload when the `onData` method returns discardLoadDataInPayload, then the payload is empty
|
// Overload when the `onData` method returns discardLoadDataInPayload, then the payload is empty
|
||||||
export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
|
export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
|
||||||
@@ -114,7 +200,7 @@ export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
|
|||||||
| AppThunkOptions<Args>
|
| AppThunkOptions<Args>
|
||||||
| OnData<Args, LoadDataResult, DiscardLoadData>,
|
| OnData<Args, LoadDataResult, DiscardLoadData>,
|
||||||
thunkOptions?: AppThunkOptions<Args>,
|
thunkOptions?: AppThunkOptions<Args>,
|
||||||
): ReturnType<typeof createThunk<Args, void>>;
|
): ReturnType<typeof createAsyncThunk<Args, void>>;
|
||||||
|
|
||||||
// Overload when the `onData` method returns nothing, then the mayload is the `onData` result
|
// Overload when the `onData` method returns nothing, then the mayload is the `onData` result
|
||||||
export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
|
export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
|
||||||
@@ -124,7 +210,7 @@ export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
|
|||||||
| AppThunkOptions<Args>
|
| AppThunkOptions<Args>
|
||||||
| OnData<Args, LoadDataResult, void>,
|
| OnData<Args, LoadDataResult, void>,
|
||||||
thunkOptions?: AppThunkOptions<Args>,
|
thunkOptions?: AppThunkOptions<Args>,
|
||||||
): ReturnType<typeof createThunk<Args, LoadDataResult>>;
|
): ReturnType<typeof createAsyncThunk<Args, LoadDataResult>>;
|
||||||
|
|
||||||
// Overload when there is an `onData` method returning something
|
// Overload when there is an `onData` method returning something
|
||||||
export function createDataLoadingThunk<
|
export function createDataLoadingThunk<
|
||||||
@@ -138,7 +224,7 @@ export function createDataLoadingThunk<
|
|||||||
| AppThunkOptions<Args>
|
| AppThunkOptions<Args>
|
||||||
| OnData<Args, LoadDataResult, Returned>,
|
| OnData<Args, LoadDataResult, Returned>,
|
||||||
thunkOptions?: AppThunkOptions<Args>,
|
thunkOptions?: AppThunkOptions<Args>,
|
||||||
): ReturnType<typeof createThunk<Args, Returned>>;
|
): ReturnType<typeof createAsyncThunk<Args, Returned>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This function creates a Redux Thunk that handles loading data asynchronously (usually from the API), dispatching `pending`, `fullfilled` and `rejected` actions.
|
* This function creates a Redux Thunk that handles loading data asynchronously (usually from the API), dispatching `pending`, `fullfilled` and `rejected` actions.
|
||||||
@@ -189,7 +275,7 @@ export function createDataLoadingThunk<
|
|||||||
thunkOptions = maybeThunkOptions;
|
thunkOptions = maybeThunkOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
return createThunk<Args, Returned>(
|
return createAsyncThunk<Args, Returned>(
|
||||||
name,
|
name,
|
||||||
async (arg, { getState, dispatch }) => {
|
async (arg, { getState, dispatch }) => {
|
||||||
const data = await loadData(arg, {
|
const data = await loadData(arg, {
|
||||||
|
|||||||
Reference in New Issue
Block a user