Types, add max height to reactions modal
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
ff338a06bf
commit
c22898cebc
11 changed files with 115 additions and 95 deletions
|
@ -1,9 +1,7 @@
|
||||||
import type { AnyAction } from 'redux';
|
const SET_BROWSER_SUPPORT = 'PUSH_NOTIFICATIONS_SET_BROWSER_SUPPORT' as const;
|
||||||
|
const SET_SUBSCRIPTION = 'PUSH_NOTIFICATIONS_SET_SUBSCRIPTION' as const;
|
||||||
const SET_BROWSER_SUPPORT = 'PUSH_NOTIFICATIONS_SET_BROWSER_SUPPORT';
|
const CLEAR_SUBSCRIPTION = 'PUSH_NOTIFICATIONS_CLEAR_SUBSCRIPTION' as const;
|
||||||
const SET_SUBSCRIPTION = 'PUSH_NOTIFICATIONS_SET_SUBSCRIPTION';
|
const SET_ALERTS = 'PUSH_NOTIFICATIONS_SET_ALERTS' as const;
|
||||||
const CLEAR_SUBSCRIPTION = 'PUSH_NOTIFICATIONS_CLEAR_SUBSCRIPTION';
|
|
||||||
const SET_ALERTS = 'PUSH_NOTIFICATIONS_SET_ALERTS';
|
|
||||||
|
|
||||||
const setBrowserSupport = (value: boolean) => ({
|
const setBrowserSupport = (value: boolean) => ({
|
||||||
type: SET_BROWSER_SUPPORT,
|
type: SET_BROWSER_SUPPORT,
|
||||||
|
@ -19,13 +17,17 @@ const clearSubscription = () => ({
|
||||||
type: CLEAR_SUBSCRIPTION,
|
type: CLEAR_SUBSCRIPTION,
|
||||||
});
|
});
|
||||||
|
|
||||||
const setAlerts = (path: Array<string>, value: any) =>
|
const setAlerts = (path: Array<string>, value: any) => ({
|
||||||
(dispatch: React.Dispatch<AnyAction>) =>
|
type: SET_ALERTS,
|
||||||
dispatch({
|
path,
|
||||||
type: SET_ALERTS,
|
value,
|
||||||
path,
|
});
|
||||||
value,
|
|
||||||
});
|
type SetterAction =
|
||||||
|
| ReturnType<typeof setBrowserSupport>
|
||||||
|
| ReturnType<typeof setSubscription>
|
||||||
|
| ReturnType<typeof clearSubscription>
|
||||||
|
| ReturnType<typeof setAlerts>;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
SET_BROWSER_SUPPORT,
|
SET_BROWSER_SUPPORT,
|
||||||
|
@ -36,4 +38,5 @@ export {
|
||||||
setSubscription,
|
setSubscription,
|
||||||
clearSubscription,
|
clearSubscription,
|
||||||
setAlerts,
|
setAlerts,
|
||||||
|
type SetterAction,
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,32 +12,22 @@ import type { APIEntity } from 'soapbox/types/entities';
|
||||||
const SOAPBOX_CONFIG_REQUEST_SUCCESS = 'SOAPBOX_CONFIG_REQUEST_SUCCESS' as const;
|
const SOAPBOX_CONFIG_REQUEST_SUCCESS = 'SOAPBOX_CONFIG_REQUEST_SUCCESS' as const;
|
||||||
const SOAPBOX_CONFIG_REQUEST_FAIL = 'SOAPBOX_CONFIG_REQUEST_FAIL' as const;
|
const SOAPBOX_CONFIG_REQUEST_FAIL = 'SOAPBOX_CONFIG_REQUEST_FAIL' as const;
|
||||||
|
|
||||||
const SOAPBOX_CONFIG_REMEMBER_REQUEST = 'SOAPBOX_CONFIG_REMEMBER_REQUEST' as const;
|
|
||||||
const SOAPBOX_CONFIG_REMEMBER_SUCCESS = 'SOAPBOX_CONFIG_REMEMBER_SUCCESS' as const;
|
const SOAPBOX_CONFIG_REMEMBER_SUCCESS = 'SOAPBOX_CONFIG_REMEMBER_SUCCESS' as const;
|
||||||
const SOAPBOX_CONFIG_REMEMBER_FAIL = 'SOAPBOX_CONFIG_REMEMBER_FAIL' as const;
|
|
||||||
|
|
||||||
const getSoapboxConfig = createSelector([
|
const getSoapboxConfig = createSelector([
|
||||||
(state: RootState) => state.soapbox,
|
(state: RootState) => state.soapbox,
|
||||||
(state: RootState) => state.auth.client.features,
|
(state: RootState) => state.auth.client.features,
|
||||||
], (soapbox, features) => {
|
], (soapbox, features) => {
|
||||||
// Do some additional normalization with the state
|
// Do some additional normalization with the state
|
||||||
return normalizeSoapboxConfig(soapbox).withMutations(soapboxConfig => {
|
return normalizeSoapboxConfig(soapbox);
|
||||||
// If displayFqn isn't set, infer it from federation
|
|
||||||
if (soapbox.get('displayFqn') === undefined) {
|
|
||||||
soapboxConfig.set('displayFqn', features.federating);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const rememberSoapboxConfig = (host: string | null) =>
|
const rememberSoapboxConfig = (host: string | null) =>
|
||||||
(dispatch: AppDispatch) => {
|
(dispatch: AppDispatch) => {
|
||||||
dispatch({ type: SOAPBOX_CONFIG_REMEMBER_REQUEST, host });
|
|
||||||
return KVStore.getItemOrError(`soapbox_config:${host}`).then(soapboxConfig => {
|
return KVStore.getItemOrError(`soapbox_config:${host}`).then(soapboxConfig => {
|
||||||
dispatch({ type: SOAPBOX_CONFIG_REMEMBER_SUCCESS, host, soapboxConfig });
|
dispatch({ type: SOAPBOX_CONFIG_REMEMBER_SUCCESS, host, soapboxConfig });
|
||||||
return soapboxConfig;
|
return soapboxConfig;
|
||||||
}).catch(error => {
|
}).catch(() => {});
|
||||||
dispatch({ type: SOAPBOX_CONFIG_REMEMBER_FAIL, host, error, skipAlert: true });
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchFrontendConfigurations = () =>
|
const fetchFrontendConfigurations = () =>
|
||||||
|
@ -107,9 +97,7 @@ const isObject = (o: any) => o instanceof Object && o.constructor === Object;
|
||||||
export {
|
export {
|
||||||
SOAPBOX_CONFIG_REQUEST_SUCCESS,
|
SOAPBOX_CONFIG_REQUEST_SUCCESS,
|
||||||
SOAPBOX_CONFIG_REQUEST_FAIL,
|
SOAPBOX_CONFIG_REQUEST_FAIL,
|
||||||
SOAPBOX_CONFIG_REMEMBER_REQUEST,
|
|
||||||
SOAPBOX_CONFIG_REMEMBER_SUCCESS,
|
SOAPBOX_CONFIG_REMEMBER_SUCCESS,
|
||||||
SOAPBOX_CONFIG_REMEMBER_FAIL,
|
|
||||||
getSoapboxConfig,
|
getSoapboxConfig,
|
||||||
rememberSoapboxConfig,
|
rememberSoapboxConfig,
|
||||||
fetchFrontendConfigurations,
|
fetchFrontendConfigurations,
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { getClient } from '../api';
|
||||||
|
|
||||||
import { importFetchedStatuses } from './importer';
|
import { importFetchedStatuses } from './importer';
|
||||||
|
|
||||||
|
import type { Status as BaseStatus, PaginatedResponse } from 'pl-api';
|
||||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||||
|
|
||||||
const STATUS_QUOTES_FETCH_REQUEST = 'STATUS_QUOTES_FETCH_REQUEST' as const;
|
const STATUS_QUOTES_FETCH_REQUEST = 'STATUS_QUOTES_FETCH_REQUEST' as const;
|
||||||
|
@ -14,34 +15,70 @@ const STATUS_QUOTES_EXPAND_FAIL = 'STATUS_QUOTES_EXPAND_FAIL' as const;
|
||||||
|
|
||||||
const noOp = () => new Promise(f => f(null));
|
const noOp = () => new Promise(f => f(null));
|
||||||
|
|
||||||
|
interface FetchStatusQuotesRequestAction {
|
||||||
|
type: typeof STATUS_QUOTES_FETCH_REQUEST;
|
||||||
|
statusId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FetchStatusQuotesSuccessAction {
|
||||||
|
type: typeof STATUS_QUOTES_FETCH_SUCCESS;
|
||||||
|
statusId: string;
|
||||||
|
statuses: Array<BaseStatus>;
|
||||||
|
next: (() => Promise<PaginatedResponse<BaseStatus>>) | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FetchStatusQuotesFailAction {
|
||||||
|
type: typeof STATUS_QUOTES_FETCH_FAIL;
|
||||||
|
statusId: string;
|
||||||
|
error: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
const fetchStatusQuotes = (statusId: string) =>
|
const fetchStatusQuotes = (statusId: string) =>
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
if (getState().status_lists.getIn([`quotes:${statusId}`, 'isLoading'])) {
|
if (getState().status_lists.getIn([`quotes:${statusId}`, 'isLoading'])) {
|
||||||
return dispatch(noOp);
|
return dispatch(noOp);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
const action: FetchStatusQuotesRequestAction = { type: STATUS_QUOTES_FETCH_REQUEST, statusId };
|
||||||
statusId,
|
dispatch(action);
|
||||||
type: STATUS_QUOTES_FETCH_REQUEST,
|
|
||||||
});
|
|
||||||
|
|
||||||
return getClient(getState).statuses.getStatusQuotes(statusId).then(response => {
|
return getClient(getState).statuses.getStatusQuotes(statusId).then(response => {
|
||||||
dispatch(importFetchedStatuses(response.items));
|
dispatch(importFetchedStatuses(response.items));
|
||||||
return dispatch({
|
const action: FetchStatusQuotesSuccessAction = {
|
||||||
type: STATUS_QUOTES_FETCH_SUCCESS,
|
type: STATUS_QUOTES_FETCH_SUCCESS,
|
||||||
statusId,
|
statusId,
|
||||||
statuses: response.items,
|
statuses: response.items,
|
||||||
next: response.next,
|
next: response.next,
|
||||||
});
|
};
|
||||||
|
return dispatch(action);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({
|
const action: FetchStatusQuotesFailAction = {
|
||||||
type: STATUS_QUOTES_FETCH_FAIL,
|
type: STATUS_QUOTES_FETCH_FAIL,
|
||||||
statusId,
|
statusId,
|
||||||
error,
|
error,
|
||||||
});
|
};
|
||||||
|
dispatch(action);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface ExpandStatusQuotesRequestAction {
|
||||||
|
type: typeof STATUS_QUOTES_EXPAND_REQUEST;
|
||||||
|
statusId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExpandStatusQuotesSuccessAction {
|
||||||
|
type: typeof STATUS_QUOTES_EXPAND_SUCCESS;
|
||||||
|
statusId: string;
|
||||||
|
statuses: Array<BaseStatus>;
|
||||||
|
next: (() => Promise<PaginatedResponse<BaseStatus>>) | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExpandStatusQuotesFailAction {
|
||||||
|
type: typeof STATUS_QUOTES_EXPAND_FAIL;
|
||||||
|
statusId: string;
|
||||||
|
error: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
const expandStatusQuotes = (statusId: string) =>
|
const expandStatusQuotes = (statusId: string) =>
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
const next = getState().status_lists.get(`quotes:${statusId}`)?.next || null;
|
const next = getState().status_lists.get(`quotes:${statusId}`)?.next || null;
|
||||||
|
@ -50,28 +87,39 @@ const expandStatusQuotes = (statusId: string) =>
|
||||||
return dispatch(noOp);
|
return dispatch(noOp);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
const action: ExpandStatusQuotesRequestAction = {
|
||||||
type: STATUS_QUOTES_EXPAND_REQUEST,
|
type: STATUS_QUOTES_EXPAND_REQUEST,
|
||||||
statusId,
|
statusId,
|
||||||
});
|
};
|
||||||
|
dispatch(action);
|
||||||
|
|
||||||
return next().then(response => {
|
return next().then(response => {
|
||||||
dispatch(importFetchedStatuses(response.items));
|
dispatch(importFetchedStatuses(response.items));
|
||||||
dispatch({
|
const action: ExpandStatusQuotesSuccessAction = {
|
||||||
type: STATUS_QUOTES_EXPAND_SUCCESS,
|
type: STATUS_QUOTES_EXPAND_SUCCESS,
|
||||||
statusId,
|
statusId,
|
||||||
statuses: response.items,
|
statuses: response.items,
|
||||||
next: response.next,
|
next: response.next,
|
||||||
});
|
};
|
||||||
|
dispatch(action);
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({
|
const action: ExpandStatusQuotesFailAction = {
|
||||||
type: STATUS_QUOTES_EXPAND_FAIL,
|
type: STATUS_QUOTES_EXPAND_FAIL,
|
||||||
statusId,
|
statusId,
|
||||||
error,
|
error,
|
||||||
});
|
};
|
||||||
|
dispatch(action);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type StatusQuotesAction =
|
||||||
|
| FetchStatusQuotesRequestAction
|
||||||
|
| FetchStatusQuotesSuccessAction
|
||||||
|
| FetchStatusQuotesFailAction
|
||||||
|
| ExpandStatusQuotesRequestAction
|
||||||
|
| ExpandStatusQuotesSuccessAction
|
||||||
|
| ExpandStatusQuotesFailAction;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
STATUS_QUOTES_FETCH_REQUEST,
|
STATUS_QUOTES_FETCH_REQUEST,
|
||||||
STATUS_QUOTES_FETCH_SUCCESS,
|
STATUS_QUOTES_FETCH_SUCCESS,
|
||||||
|
@ -81,4 +129,5 @@ export {
|
||||||
STATUS_QUOTES_EXPAND_FAIL,
|
STATUS_QUOTES_EXPAND_FAIL,
|
||||||
fetchStatusQuotes,
|
fetchStatusQuotes,
|
||||||
expandStatusQuotes,
|
expandStatusQuotes,
|
||||||
|
type StatusQuotesAction,
|
||||||
};
|
};
|
||||||
|
|
|
@ -332,6 +332,11 @@ const changeStatusLanguage = (statusId: string, language: string) => ({
|
||||||
language,
|
language,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type StatusesAction =
|
||||||
|
| ReturnType<typeof undoStatusTranslation>
|
||||||
|
| ReturnType<typeof unfilterStatus>
|
||||||
|
| ReturnType<typeof changeStatusLanguage>;
|
||||||
|
|
||||||
export {
|
export {
|
||||||
STATUS_CREATE_REQUEST,
|
STATUS_CREATE_REQUEST,
|
||||||
STATUS_CREATE_SUCCESS,
|
STATUS_CREATE_SUCCESS,
|
||||||
|
@ -379,4 +384,5 @@ export {
|
||||||
undoStatusTranslation,
|
undoStatusTranslation,
|
||||||
unfilterStatus,
|
unfilterStatus,
|
||||||
changeStatusLanguage,
|
changeStatusLanguage,
|
||||||
|
type StatusesAction,
|
||||||
};
|
};
|
||||||
|
|
|
@ -105,14 +105,14 @@ interface TimelineDeleteAction {
|
||||||
statusId: string;
|
statusId: string;
|
||||||
accountId: string;
|
accountId: string;
|
||||||
references: ImmutableMap<string, readonly [statusId: string, accountId: string]>;
|
references: ImmutableMap<string, readonly [statusId: string, accountId: string]>;
|
||||||
reblogOf: unknown;
|
reblogOf: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteFromTimelines = (statusId: string) =>
|
const deleteFromTimelines = (statusId: string) =>
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
const accountId = getState().statuses.get(statusId)?.account?.id!;
|
const accountId = getState().statuses.get(statusId)?.account?.id!;
|
||||||
const references = getState().statuses.filter(status => status.reblog_id === statusId).map(status => [status.id, status.account.id] as const);
|
const references = getState().statuses.filter(status => status.reblog_id === statusId).map(status => [status.id, status.account.id] as const);
|
||||||
const reblogOf = getState().statuses.getIn([statusId, 'reblog'], null);
|
const reblogOf = getState().statuses.get(statusId)?.reblog_id || null;
|
||||||
|
|
||||||
const action: TimelineDeleteAction = {
|
const action: TimelineDeleteAction = {
|
||||||
type: TIMELINE_DELETE,
|
type: TIMELINE_DELETE,
|
||||||
|
@ -125,9 +125,7 @@ const deleteFromTimelines = (statusId: string) =>
|
||||||
dispatch(action);
|
dispatch(action);
|
||||||
};
|
};
|
||||||
|
|
||||||
const clearTimeline = (timeline: string) =>
|
const clearTimeline = (timeline: string) => ({ type: TIMELINE_CLEAR, timeline });
|
||||||
(dispatch: AppDispatch) =>
|
|
||||||
dispatch({ type: TIMELINE_CLEAR, timeline });
|
|
||||||
|
|
||||||
const noOp = () => { };
|
const noOp = () => { };
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ const StatusLanguagePicker: React.FC<IStatusLanguagePicker> = ({ status, showLab
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
if (!status.contentMapHtml || !Object.keys(status.contentMapHtml).length) return null;
|
if (!status.contentMapHtml || Object.keys(status.contentMapHtml).length < 2) return null;
|
||||||
|
|
||||||
const icon = <Icon className='h-5 w-5 text-gray-700 dark:text-gray-600' src={require('@tabler/icons/outline/language.svg')} />;
|
const icon = <Icon className='h-5 w-5 text-gray-700 dark:text-gray-600' src={require('@tabler/icons/outline/language.svg')} />;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { FormattedMessage, useIntl } from 'react-intl';
|
import { FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { translateStatus, undoStatusTranslation } from 'soapbox/actions/statuses';
|
import { translateStatus, undoStatusTranslation } from 'soapbox/actions/statuses';
|
||||||
|
@ -19,7 +19,6 @@ const TranslateButton: React.FC<ITranslateButton> = ({ status }) => {
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
const instance = useInstance();
|
const instance = useInstance();
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const [autoTranslating, setAutoTranslating] = useState(false);
|
|
||||||
const autoTranslate = settings.autoTranslate;
|
const autoTranslate = settings.autoTranslate;
|
||||||
const knownLanguages = autoTranslate ? [...settings.knownLanguages, intl.locale] : [intl.locale];
|
const knownLanguages = autoTranslate ? [...settings.knownLanguages, intl.locale] : [intl.locale];
|
||||||
|
|
||||||
|
@ -46,16 +45,13 @@ const TranslateButton: React.FC<ITranslateButton> = ({ status }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!status.translation && settings.autoTranslate && features.translations && renderTranslate && supportsLanguages && status.translation !== false && status.language !== null && !knownLanguages.includes(status.language)) {
|
if (status.translation === null && settings.autoTranslate && features.translations && renderTranslate && supportsLanguages && status.translation !== false && status.language !== null && !knownLanguages.includes(status.language)) {
|
||||||
setAutoTranslating(true);
|
|
||||||
dispatch(translateStatus(status.id, intl.locale, true));
|
dispatch(translateStatus(status.id, intl.locale, true));
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!features.translations || !renderTranslate || !supportsLanguages || status.translation === false) return null;
|
if (!features.translations || !renderTranslate || !supportsLanguages || status.translation === false) return null;
|
||||||
|
|
||||||
if (settings.autoTranslate && !status.translating) return null;
|
|
||||||
|
|
||||||
const button = (
|
const button = (
|
||||||
<button className='w-fit' onClick={handleTranslate}>
|
<button className='w-fit' onClick={handleTranslate}>
|
||||||
<HStack alignItems='center' space={1} className='text-primary-600 hover:underline dark:text-accent-blue'>
|
<HStack alignItems='center' space={1} className='text-primary-600 hover:underline dark:text-accent-blue'>
|
||||||
|
@ -77,8 +73,6 @@ const TranslateButton: React.FC<ITranslateButton> = ({ status }) => {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (status.translation) {
|
if (status.translation) {
|
||||||
if (autoTranslating) return null;
|
|
||||||
|
|
||||||
const languageNames = new Intl.DisplayNames([intl.locale], { type: 'language' });
|
const languageNames = new Intl.DisplayNames([intl.locale], { type: 'language' });
|
||||||
const languageName = languageNames.of(status.language!);
|
const languageName = languageNames.of(status.language!);
|
||||||
const provider = status.translation.provider;
|
const provider = status.translation.provider;
|
||||||
|
|
|
@ -103,6 +103,7 @@ const ReactionsModal: React.FC<BaseModalProps & ReactionsModalProps> = ({ onClos
|
||||||
})}
|
})}
|
||||||
listClassName='max-w-full'
|
listClassName='max-w-full'
|
||||||
itemClassName='pb-3'
|
itemClassName='pb-3'
|
||||||
|
style={{ height: '80vh' }}
|
||||||
>
|
>
|
||||||
{accounts.map((account) =>
|
{accounts.map((account) =>
|
||||||
<AccountContainer key={`${account.id}-${account.reaction}`} id={account.id} emoji={account.reaction} emojiUrl={account.reactionUrl} />,
|
<AccountContainer key={`${account.id}-${account.reaction}`} id={account.id} emoji={account.reaction} emojiUrl={account.reactionUrl} />,
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import { useFloating } from '@floating-ui/react';
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { fetchOwnAccounts, logOut, switchAccount } from 'soapbox/actions/auth';
|
import { fetchOwnAccounts, logOut, switchAccount } from 'soapbox/actions/auth';
|
||||||
import Account from 'soapbox/components/account';
|
import Account from 'soapbox/components/account';
|
||||||
|
import DropdownMenu from 'soapbox/components/dropdown-menu';
|
||||||
import { MenuDivider } from 'soapbox/components/ui';
|
import { MenuDivider } from 'soapbox/components/ui';
|
||||||
import { useAppDispatch, useAppSelector, useClickOutside, useFeatures } from 'soapbox/hooks';
|
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
|
||||||
import { makeGetAccount } from 'soapbox/selectors';
|
import { makeGetAccount } from 'soapbox/selectors';
|
||||||
|
|
||||||
import ThemeToggle from './theme-toggle';
|
import ThemeToggle from './theme-toggle';
|
||||||
|
@ -41,8 +41,6 @@ const ProfileDropdown: React.FC<IProfileDropdown> = ({ account, children }) => {
|
||||||
const features = useFeatures();
|
const features = useFeatures();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const [visible, setVisible] = useState(false);
|
|
||||||
const { x, y, strategy, refs } = useFloating<HTMLButtonElement>({ placement: 'bottom-end' });
|
|
||||||
const authUsers = useAppSelector((state) => state.auth.users);
|
const authUsers = useAppSelector((state) => state.auth.users);
|
||||||
const otherAccounts = useAppSelector((state) => authUsers.map((authUser: any) => getAccount(state, authUser.id)!));
|
const otherAccounts = useAppSelector((state) => authUsers.map((authUser: any) => getAccount(state, authUser.id)!));
|
||||||
|
|
||||||
|
@ -62,7 +60,7 @@ const ProfileDropdown: React.FC<IProfileDropdown> = ({ account, children }) => {
|
||||||
<Account account={account} showProfileHoverCard={false} withLinkToProfile={false} hideActions />
|
<Account account={account} showProfileHoverCard={false} withLinkToProfile={false} hideActions />
|
||||||
);
|
);
|
||||||
|
|
||||||
const menu: IMenuItem[] = useMemo(() => {
|
const ProfileDropdownMenu = useMemo(() => {
|
||||||
const menu: IMenuItem[] = [];
|
const menu: IMenuItem[] = [];
|
||||||
|
|
||||||
menu.push({ text: renderAccount(account), to: `/@${account.acct}` });
|
menu.push({ text: renderAccount(account), to: `/@${account.acct}` });
|
||||||
|
@ -93,47 +91,30 @@ const ProfileDropdown: React.FC<IProfileDropdown> = ({ account, children }) => {
|
||||||
icon: require('@tabler/icons/outline/logout.svg'),
|
icon: require('@tabler/icons/outline/logout.svg'),
|
||||||
});
|
});
|
||||||
|
|
||||||
return menu;
|
return () => (
|
||||||
|
<>
|
||||||
|
{menu.map((menuItem, i) => (
|
||||||
|
<MenuItem key={i} menuItem={menuItem} />
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}, [account, authUsers, features]);
|
}, [account, authUsers, features]);
|
||||||
|
|
||||||
const toggleVisible = () => setVisible(!visible);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchOwnAccountThrottled();
|
fetchOwnAccountThrottled();
|
||||||
}, [account, authUsers]);
|
}, [account, authUsers]);
|
||||||
|
|
||||||
useClickOutside(refs, () => {
|
|
||||||
setVisible(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<DropdownMenu
|
||||||
|
component={ProfileDropdownMenu}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
className='w-full rounded-full focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:ring-gray-800 dark:ring-offset-0 dark:focus:ring-primary-500'
|
className='w-full rounded-full focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:ring-gray-800 dark:ring-offset-0 dark:focus:ring-primary-500'
|
||||||
type='button'
|
type='button'
|
||||||
ref={refs.setReference}
|
|
||||||
onClick={toggleVisible}
|
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</button>
|
</button>
|
||||||
|
</DropdownMenu>
|
||||||
{visible && (
|
|
||||||
<div
|
|
||||||
ref={refs.setFloating}
|
|
||||||
className='z-[1003] mt-2 max-w-xs rounded-md bg-white shadow-lg focus:outline-none black:bg-black dark:bg-gray-900 dark:ring-2 dark:ring-primary-700'
|
|
||||||
style={{
|
|
||||||
position: strategy,
|
|
||||||
top: y ?? 0,
|
|
||||||
left: x ?? 0,
|
|
||||||
width: 'max-content',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{menu.map((menuItem, i) => (
|
|
||||||
<MenuItem key={i} menuItem={menuItem} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { Map as ImmutableMap, Record as ImmutableRecord } from 'immutable';
|
||||||
|
|
||||||
import { SET_BROWSER_SUPPORT, SET_SUBSCRIPTION, CLEAR_SUBSCRIPTION, SET_ALERTS } from '../actions/push-notifications';
|
import { SET_BROWSER_SUPPORT, SET_SUBSCRIPTION, CLEAR_SUBSCRIPTION, SET_ALERTS } from '../actions/push-notifications';
|
||||||
|
|
||||||
import type { AnyAction } from 'redux';
|
import type { SetterAction } from 'soapbox/actions/push-notifications/setter';
|
||||||
|
|
||||||
const SubscriptionRecord = ImmutableRecord({
|
const SubscriptionRecord = ImmutableRecord({
|
||||||
id: '',
|
id: '',
|
||||||
|
@ -25,7 +25,7 @@ const ReducerRecord = ImmutableRecord({
|
||||||
|
|
||||||
type Subscription = ReturnType<typeof SubscriptionRecord>;
|
type Subscription = ReturnType<typeof SubscriptionRecord>;
|
||||||
|
|
||||||
const push_subscriptions = (state = ReducerRecord(), action: AnyAction) => {
|
const push_subscriptions = (state = ReducerRecord(), action: SetterAction) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case SET_SUBSCRIPTION:
|
case SET_SUBSCRIPTION:
|
||||||
return state
|
return state
|
||||||
|
|
|
@ -162,7 +162,7 @@ const shouldDelete = (timelineId: string, excludeAccount?: string) => {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteStatus = (state: State, statusId: string, accountId: string, references: ImmutableMap<string, [string, string]> | Array<[string, string]>, excludeAccount?: string) =>
|
const deleteStatus = (state: State, statusId: string, references: ImmutableMap<string, [string, string]> | Array<[string, string]>, excludeAccount?: string) =>
|
||||||
state.withMutations(state => {
|
state.withMutations(state => {
|
||||||
state.keySeq().forEach(timelineId => {
|
state.keySeq().forEach(timelineId => {
|
||||||
if (shouldDelete(timelineId, excludeAccount)) {
|
if (shouldDelete(timelineId, excludeAccount)) {
|
||||||
|
@ -173,7 +173,7 @@ const deleteStatus = (state: State, statusId: string, accountId: string, referen
|
||||||
|
|
||||||
// Remove reblogs of deleted status
|
// Remove reblogs of deleted status
|
||||||
references.forEach(ref => {
|
references.forEach(ref => {
|
||||||
deleteStatus(state, ref[0], ref[1], [], excludeAccount);
|
deleteStatus(state, ref[0], [], excludeAccount);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -208,7 +208,7 @@ const filterTimelines = (state: State, relationship: Relationship, statuses: Imm
|
||||||
statuses.forEach(status => {
|
statuses.forEach(status => {
|
||||||
if (status.account.id !== relationship.id) return;
|
if (status.account.id !== relationship.id) return;
|
||||||
const references = buildReferencesTo(statuses, status);
|
const references = buildReferencesTo(statuses, status);
|
||||||
deleteStatus(state, status.id, status.account.id, references, relationship.id);
|
deleteStatus(state, status.id, references, relationship.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -329,7 +329,7 @@ const timelines = (state: State = initialState, action: AnyAction) => {
|
||||||
case TIMELINE_DEQUEUE:
|
case TIMELINE_DEQUEUE:
|
||||||
return timelineDequeue(state, action.timeline);
|
return timelineDequeue(state, action.timeline);
|
||||||
case TIMELINE_DELETE:
|
case TIMELINE_DELETE:
|
||||||
return deleteStatus(state, action.statusId, action.accountId, action.references, action.reblogOf);
|
return deleteStatus(state, action.statusId, action.references, action.reblogOf);
|
||||||
case TIMELINE_CLEAR:
|
case TIMELINE_CLEAR:
|
||||||
return clearTimeline(state, action.timeline);
|
return clearTimeline(state, action.timeline);
|
||||||
case ACCOUNT_BLOCK_SUCCESS:
|
case ACCOUNT_BLOCK_SUCCESS:
|
||||||
|
|
Loading…
Reference in a new issue