= ({ chatId }) => {
setHeight(fullHeight - top + offset);
};
- useEffect(() => {
+ useLayoutEffect(() => {
calculateHeight();
}, [containerRef.current]);
diff --git a/app/soapbox/features/chats/components/chat-page/components/chat-page-main.tsx b/app/soapbox/features/chats/components/chat-page/components/chat-page-main.tsx
index 638a7fea1..da838f0cb 100644
--- a/app/soapbox/features/chats/components/chat-page/components/chat-page-main.tsx
+++ b/app/soapbox/features/chats/components/chat-page/components/chat-page-main.tsx
@@ -38,8 +38,8 @@ const messages = defineMessages({
autoDelete14Days: { id: 'chat_settings.auto_delete.14days', defaultMessage: '14 days' },
autoDelete30Days: { id: 'chat_settings.auto_delete.30days', defaultMessage: '30 days' },
autoDelete90Days: { id: 'chat_settings.auto_delete.90days', defaultMessage: '90 days' },
- autoDeleteMessage: { id: 'chat_window.auto_delete_label', defaultMessage: 'Auto-delete after {day} days' },
- autoDeleteMessageTooltip: { id: 'chat_window.auto_delete_tooltip', defaultMessage: 'Chat messages are set to auto-delete after {day} days upon sending.' },
+ autoDeleteMessage: { id: 'chat_window.auto_delete_label', defaultMessage: 'Auto-delete after {day, plural, one {# day} other {# days}}' },
+ autoDeleteMessageTooltip: { id: 'chat_window.auto_delete_tooltip', defaultMessage: 'Chat messages are set to auto-delete after {day, plural, one {# day} other {# days}} upon sending.' },
});
const ChatPageMain = () => {
@@ -238,7 +238,7 @@ const ChatPageMain = () => {
diff --git a/app/soapbox/features/chats/components/chat-page/components/chat-page-new.tsx b/app/soapbox/features/chats/components/chat-page/components/chat-page-new.tsx
index c362a4159..db2f46a22 100644
--- a/app/soapbox/features/chats/components/chat-page/components/chat-page-new.tsx
+++ b/app/soapbox/features/chats/components/chat-page/components/chat-page-new.tsx
@@ -1,15 +1,21 @@
import React from 'react';
+import { defineMessages, useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
import { CardTitle, HStack, IconButton, Stack } from 'soapbox/components/ui';
import ChatSearch from '../../chat-search/chat-search';
+const messages = defineMessages({
+ title: { id: 'chat.new_message.title', defaultMessage: 'New Message' },
+});
+
interface IChatPageNew {
}
/** New message form to create a chat. */
const ChatPageNew: React.FC = () => {
+ const intl = useIntl();
const history = useHistory();
return (
@@ -22,7 +28,7 @@ const ChatPageNew: React.FC = () => {
onClick={() => history.push('/chats')}
/>
-
+
@@ -31,4 +37,4 @@ const ChatPageNew: React.FC = () => {
);
};
-export default ChatPageNew;
\ No newline at end of file
+export default ChatPageNew;
diff --git a/app/soapbox/features/chats/components/chat-pane/__tests__/chat-pane.test.tsx b/app/soapbox/features/chats/components/chat-pane/__tests__/chat-pane.test.tsx
index b28f8aafd..5dcfda549 100644
--- a/app/soapbox/features/chats/components/chat-pane/__tests__/chat-pane.test.tsx
+++ b/app/soapbox/features/chats/components/chat-pane/__tests__/chat-pane.test.tsx
@@ -5,7 +5,7 @@ import { __stub } from 'soapbox/api';
import { ChatContext } from 'soapbox/contexts/chat-context';
import { StatProvider } from 'soapbox/contexts/stat-context';
import chats from 'soapbox/jest/fixtures/chats.json';
-import { render, screen, waitFor } from 'soapbox/jest/test-helpers';
+import { mockStore, render, rootState, screen, waitFor } from 'soapbox/jest/test-helpers';
import ChatPane from '../chat-pane';
@@ -23,7 +23,12 @@ const renderComponentWithChatContext = (store = {}) => render(
describe('', () => {
describe('when there are no chats', () => {
+ let store: ReturnType;
+
beforeEach(() => {
+ const state = rootState.setIn(['instance', 'version'], '2.7.2 (compatible; Pleroma 2.2.0)');
+ store = mockStore(state);
+
__stub((mock) => {
mock.onGet('/api/v1/pleroma/chats').reply(200, [], {
link: null,
@@ -32,7 +37,7 @@ describe('', () => {
});
it('renders the blankslate', async () => {
- renderComponentWithChatContext();
+ renderComponentWithChatContext(store);
await waitFor(() => {
expect(screen.getByTestId('chat-pane-blankslate')).toBeInTheDocument();
@@ -57,4 +62,4 @@ describe('', () => {
});
});
});
-});
\ No newline at end of file
+});
diff --git a/app/soapbox/features/chats/components/chat-search/chat-search.tsx b/app/soapbox/features/chats/components/chat-search/chat-search.tsx
index 03ff9ba2a..c10dc61c5 100644
--- a/app/soapbox/features/chats/components/chat-search/chat-search.tsx
+++ b/app/soapbox/features/chats/components/chat-search/chat-search.tsx
@@ -1,15 +1,16 @@
import { useMutation } from '@tanstack/react-query';
import { AxiosError } from 'axios';
import React, { useState } from 'react';
+import { defineMessages, useIntl } from 'react-intl';
import { useHistory } from 'react-router-dom';
-import snackbar from 'soapbox/actions/snackbar';
import { Icon, Input, Stack } from 'soapbox/components/ui';
import { ChatWidgetScreens, useChatContext } from 'soapbox/contexts/chat-context';
-import { useAppDispatch, useDebounce } from 'soapbox/hooks';
+import { useDebounce } from 'soapbox/hooks';
import { useChats } from 'soapbox/queries/chats';
import { queryClient } from 'soapbox/queries/client';
import useAccountSearch from 'soapbox/queries/search';
+import toast from 'soapbox/toast';
import { ChatKeys } from '../../../../queries/chats';
@@ -17,15 +18,19 @@ import Blankslate from './blankslate';
import EmptyResultsBlankslate from './empty-results-blankslate';
import Results from './results';
+const messages = defineMessages({
+ placeholder: { id: 'chat_search.placeholder', defaultMessage: 'Type a name' },
+});
+
interface IChatSearch {
isMainPage?: boolean
}
const ChatSearch = (props: IChatSearch) => {
+ const intl = useIntl();
const { isMainPage = false } = props;
const debounce = useDebounce;
- const dispatch = useAppDispatch();
const history = useHistory();
const { changeScreen } = useChatContext();
@@ -45,7 +50,7 @@ const ChatSearch = (props: IChatSearch) => {
}, {
onError: (error: AxiosError) => {
const data = error.response?.data as any;
- dispatch(snackbar.error(data?.error));
+ toast.error(data?.error);
},
onSuccess: (response) => {
if (isMainPage) {
@@ -89,7 +94,7 @@ const ChatSearch = (props: IChatSearch) => {
data-testid='search'
type='text'
autoFocus
- placeholder='Type a name'
+ placeholder={intl.formatMessage(messages.placeholder)}
value={value || ''}
onChange={(event) => setValue(event.target.value)}
outerClassName='mt-0'
@@ -113,4 +118,4 @@ const ChatSearch = (props: IChatSearch) => {
);
};
-export default ChatSearch;
\ No newline at end of file
+export default ChatSearch;
diff --git a/app/soapbox/features/chats/components/chat-search/results.tsx b/app/soapbox/features/chats/components/chat-search/results.tsx
index 48909f67e..ae1caa4e1 100644
--- a/app/soapbox/features/chats/components/chat-search/results.tsx
+++ b/app/soapbox/features/chats/components/chat-search/results.tsx
@@ -6,6 +6,8 @@ import { Avatar, HStack, Stack, Text } from 'soapbox/components/ui';
import VerificationBadge from 'soapbox/components/verification-badge';
import useAccountSearch from 'soapbox/queries/search';
+import type { Account } from 'soapbox/types/entities';
+
interface IResults {
accountSearchResult: ReturnType
onSelect(id: string): void
@@ -23,7 +25,7 @@ const Results = ({ accountSearchResult, onSelect }: IResults) => {
}
};
- const renderAccount = useCallback((_index, account) => (
+ const renderAccount = useCallback((_index: number, account: Account) => (
diff --git a/app/soapbox/features/compose/components/emoji-picker/emoji-picker-menu.tsx b/app/soapbox/features/compose/components/emoji-picker/emoji-picker-menu.tsx
index 834b556f2..87362cef5 100644
--- a/app/soapbox/features/compose/components/emoji-picker/emoji-picker-menu.tsx
+++ b/app/soapbox/features/compose/components/emoji-picker/emoji-picker-menu.tsx
@@ -72,8 +72,8 @@ const EmojiPickerMenu: React.FC = ({
categoriesSort.splice(1, 0, ...Array.from(categoriesFromEmojis(customEmojis) as Set).sort());
- const handleDocumentClick = useCallback(e => {
- if (node.current && !node.current.contains(e.target)) {
+ const handleDocumentClick = useCallback((e: MouseEvent | TouchEvent) => {
+ if (node.current && !node.current.contains(e.target as Node)) {
onClose();
}
}, []);
diff --git a/app/soapbox/features/compose/components/emoji-picker/modifier-picker-menu.tsx b/app/soapbox/features/compose/components/emoji-picker/modifier-picker-menu.tsx
index b62053ca5..e2405d5d6 100644
--- a/app/soapbox/features/compose/components/emoji-picker/modifier-picker-menu.tsx
+++ b/app/soapbox/features/compose/components/emoji-picker/modifier-picker-menu.tsx
@@ -19,8 +19,8 @@ const ModifierPickerMenu: React.FC = ({ active, onSelect, o
onSelect(+e.currentTarget.getAttribute('data-index')! * 1);
};
- const handleDocumentClick = useCallback((e => {
- if (node.current && !node.current.contains(e.target)) {
+ const handleDocumentClick = useCallback(((e: MouseEvent | TouchEvent) => {
+ if (node.current && !node.current.contains(e.target as Node)) {
onClose();
}
}), []);
diff --git a/app/soapbox/features/compose/components/reply-indicator.tsx b/app/soapbox/features/compose/components/reply-indicator.tsx
index 4ecc37816..86fd3a376 100644
--- a/app/soapbox/features/compose/components/reply-indicator.tsx
+++ b/app/soapbox/features/compose/components/reply-indicator.tsx
@@ -40,6 +40,7 @@ const ReplyIndicator: React.FC = ({ status, hideActions, onCanc
timestamp={status.created_at}
showProfileHoverCard={false}
withLinkToProfile={false}
+ hideActions={hideActions}
/>
{
const intl = useIntl();
const dispatch = useAppDispatch();
+ const features = useFeatures();
const value = useAppSelector((state) => state.search.submittedValue);
const results = useAppSelector((state) => state.search.results);
@@ -51,7 +52,8 @@ const SearchResults = () => {
const selectFilter = (newActiveFilter: SearchFilter) => dispatch(setFilter(newActiveFilter));
const renderFilterBar = () => {
- const items = [
+ const items = [];
+ items.push(
{
text: intl.formatMessage(messages.accounts),
action: () => selectFilter('accounts'),
@@ -62,17 +64,23 @@ const SearchResults = () => {
action: () => selectFilter('statuses'),
name: 'statuses',
},
+ );
+
+ if (features.groups) items.push(
{
text: intl.formatMessage(messages.groups),
action: () => selectFilter('groups'),
name: 'groups',
},
+ );
+
+ items.push(
{
text: intl.formatMessage(messages.hashtags),
action: () => selectFilter('hashtags'),
name: 'hashtags',
},
- ];
+ );
return ;
};
diff --git a/app/soapbox/features/compose/components/search.tsx b/app/soapbox/features/compose/components/search.tsx
index f4b8c1876..5c7db5f05 100644
--- a/app/soapbox/features/compose/components/search.tsx
+++ b/app/soapbox/features/compose/components/search.tsx
@@ -1,9 +1,7 @@
import classNames from 'clsx';
-import { Map as ImmutableMap } from 'immutable';
import debounce from 'lodash/debounce';
import React, { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
-import { useDispatch } from 'react-redux';
import { useHistory } from 'react-router-dom';
import {
@@ -17,7 +15,8 @@ import {
import AutosuggestAccountInput from 'soapbox/components/autosuggest-account-input';
import { Input } from 'soapbox/components/ui';
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
-import { useAppSelector } from 'soapbox/hooks';
+import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
+import { AppDispatch, RootState } from 'soapbox/store';
const messages = defineMessages({
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
@@ -25,7 +24,7 @@ const messages = defineMessages({
});
function redirectToAccount(accountId: string, routerHistory: any) {
- return (_dispatch: any, getState: () => ImmutableMap) => {
+ return (_dispatch: AppDispatch, getState: () => RootState) => {
const acct = getState().getIn(['accounts', accountId, 'acct']);
if (acct && routerHistory) {
@@ -49,7 +48,7 @@ const Search = (props: ISearch) => {
openInRoute = false,
} = props;
- const dispatch = useDispatch();
+ const dispatch = useAppDispatch();
const history = useHistory();
const intl = useIntl();
@@ -127,6 +126,7 @@ const Search = (props: ISearch) => {
onFocus: handleFocus,
autoFocus: autoFocus,
theme: 'search',
+ className: 'pr-10 rtl:pl-10 rtl:pr-3',
};
if (autosuggest) {
diff --git a/app/soapbox/features/compose/components/upload-button.tsx b/app/soapbox/features/compose/components/upload-button.tsx
index 7d6f31c65..e5c12e8b5 100644
--- a/app/soapbox/features/compose/components/upload-button.tsx
+++ b/app/soapbox/features/compose/components/upload-button.tsx
@@ -22,6 +22,7 @@ export interface IUploadButton {
resetFileKey: number | null,
className?: string,
iconClassName?: string,
+ icon?: string,
}
const UploadButton: React.FC = ({
@@ -31,6 +32,7 @@ const UploadButton: React.FC = ({
resetFileKey,
className = 'text-gray-600 hover:text-gray-700 dark:hover:text-gray-500',
iconClassName,
+ icon,
}) => {
const intl = useIntl();
const { configuration } = useInstance();
@@ -52,9 +54,11 @@ const UploadButton: React.FC = ({
return null;
}
- const src = onlyImages(attachmentTypes)
- ? require('@tabler/icons/photo.svg')
- : require('@tabler/icons/paperclip.svg');
+ const src = icon || (
+ onlyImages(attachmentTypes)
+ ? require('@tabler/icons/photo.svg')
+ : require('@tabler/icons/paperclip.svg')
+ );
return (
diff --git a/app/soapbox/features/compose/components/visual-character-counter.tsx b/app/soapbox/features/compose/components/visual-character-counter.tsx
index 8bf37af29..1169014d5 100644
--- a/app/soapbox/features/compose/components/visual-character-counter.tsx
+++ b/app/soapbox/features/compose/components/visual-character-counter.tsx
@@ -5,7 +5,7 @@ import { length } from 'stringz';
import ProgressCircle from 'soapbox/components/progress-circle';
const messages = defineMessages({
- title: { id: 'compose.character_counter.title', defaultMessage: 'Used {chars} out of {maxChars} characters' },
+ title: { id: 'compose.character_counter.title', defaultMessage: 'Used {chars} out of {maxChars} {maxChars, plural, one {character} other {characters}}' },
});
interface IVisualCharacterCounter {
diff --git a/app/soapbox/features/crypto-donate/components/crypto-donate-panel.tsx b/app/soapbox/features/crypto-donate/components/crypto-donate-panel.tsx
index 7105fdf28..92a231b8b 100644
--- a/app/soapbox/features/crypto-donate/components/crypto-donate-panel.tsx
+++ b/app/soapbox/features/crypto-donate/components/crypto-donate-panel.tsx
@@ -8,7 +8,7 @@ import { useInstance, useSoapboxConfig } from 'soapbox/hooks';
import SiteWallet from './site-wallet';
const messages = defineMessages({
- actionTitle: { id: 'crypto_donate_panel.actions.view', defaultMessage: 'Click to see {count} {count, plural, one {wallet} other {wallets}}' },
+ actionTitle: { id: 'crypto_donate_panel.actions.view', defaultMessage: 'Click to see {count, plural, one {# wallet} other {# wallets}}' },
});
interface ICryptoDonatePanel {
diff --git a/app/soapbox/features/crypto-donate/components/detailed-crypto-address.tsx b/app/soapbox/features/crypto-donate/components/detailed-crypto-address.tsx
index c0ab30bf9..180e3d116 100644
--- a/app/soapbox/features/crypto-donate/components/detailed-crypto-address.tsx
+++ b/app/soapbox/features/crypto-donate/components/detailed-crypto-address.tsx
@@ -36,7 +36,7 @@ const DetailedCryptoAddress: React.FC = ({ address, tick
{note && {note}
}
-
+
diff --git a/app/soapbox/features/delete-account/index.tsx b/app/soapbox/features/delete-account/index.tsx
index 9bafff592..aaeecc789 100644
--- a/app/soapbox/features/delete-account/index.tsx
+++ b/app/soapbox/features/delete-account/index.tsx
@@ -2,9 +2,9 @@ import React from 'react';
import { defineMessages, useIntl } from 'react-intl';
import { deleteAccount } from 'soapbox/actions/security';
-import snackbar from 'soapbox/actions/snackbar';
import { Button, Card, CardBody, CardHeader, CardTitle, Form, FormActions, FormGroup, Input, Stack, Text } from 'soapbox/components/ui';
import { useAppDispatch, useFeatures } from 'soapbox/hooks';
+import toast from 'soapbox/toast';
const messages = defineMessages({
passwordFieldLabel: { id: 'security.fields.password.label', defaultMessage: 'Password' },
@@ -34,12 +34,12 @@ const DeleteAccount = () => {
setLoading(true);
dispatch(deleteAccount(password)).then(() => {
setPassword('');
- dispatch(snackbar.success(intl.formatMessage(messages.deleteAccountSuccess)));
+ toast.success(intl.formatMessage(messages.deleteAccountSuccess));
}).finally(() => {
setLoading(false);
}).catch(() => {
setPassword('');
- dispatch(snackbar.error(intl.formatMessage(messages.deleteAccountFail)));
+ toast.error(intl.formatMessage(messages.deleteAccountFail));
});
}, [password, dispatch, intl]);
diff --git a/app/soapbox/features/developers/developers-challenge.tsx b/app/soapbox/features/developers/developers-challenge.tsx
index 25fafd76a..ee83ba809 100644
--- a/app/soapbox/features/developers/developers-challenge.tsx
+++ b/app/soapbox/features/developers/developers-challenge.tsx
@@ -1,10 +1,10 @@
import React, { useState } from 'react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
-import { useDispatch } from 'react-redux';
import { changeSettingImmediate } from 'soapbox/actions/settings';
-import snackbar from 'soapbox/actions/snackbar';
import { Column, Button, Form, FormActions, FormGroup, Input, Text } from 'soapbox/components/ui';
+import { useAppDispatch } from 'soapbox/hooks';
+import toast from 'soapbox/toast';
const messages = defineMessages({
heading: { id: 'column.developers', defaultMessage: 'Developers' },
@@ -15,7 +15,7 @@ const messages = defineMessages({
});
const DevelopersChallenge = () => {
- const dispatch = useDispatch();
+ const dispatch = useAppDispatch();
const intl = useIntl();
const [answer, setAnswer] = useState('');
@@ -27,9 +27,9 @@ const DevelopersChallenge = () => {
const handleSubmit = () => {
if (answer === 'boxsoap') {
dispatch(changeSettingImmediate(['isDeveloper'], true));
- dispatch(snackbar.success(intl.formatMessage(messages.success)));
+ toast.success(intl.formatMessage(messages.success));
} else {
- dispatch(snackbar.error(intl.formatMessage(messages.fail)));
+ toast.error(intl.formatMessage(messages.fail));
}
};
diff --git a/app/soapbox/features/developers/developers-menu.tsx b/app/soapbox/features/developers/developers-menu.tsx
index 2db25d527..61e8c1de6 100644
--- a/app/soapbox/features/developers/developers-menu.tsx
+++ b/app/soapbox/features/developers/developers-menu.tsx
@@ -1,12 +1,12 @@
import React from 'react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
-import { useDispatch } from 'react-redux';
import { Link, useHistory } from 'react-router-dom';
import { changeSettingImmediate } from 'soapbox/actions/settings';
-import snackbar from 'soapbox/actions/snackbar';
import { Column, Text } from 'soapbox/components/ui';
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
+import { useAppDispatch } from 'soapbox/hooks';
+import toast from 'soapbox/toast';
import sourceCode from 'soapbox/utils/code';
const messages = defineMessages({
@@ -31,7 +31,7 @@ const DashWidget: React.FC = ({ to, onClick, children }) => {
};
const Developers: React.FC = () => {
- const dispatch = useDispatch();
+ const dispatch = useAppDispatch();
const history = useHistory();
const intl = useIntl();
@@ -39,10 +39,19 @@ const Developers: React.FC = () => {
e.preventDefault();
dispatch(changeSettingImmediate(['isDeveloper'], false));
- dispatch(snackbar.success(intl.formatMessage(messages.leave)));
+ toast.success(intl.formatMessage(messages.leave));
history.push('/');
};
+ const showToast = (event: React.MouseEvent) => {
+ event.preventDefault();
+
+ toast.success('Hello world!', {
+ action: () => alert('hi'),
+ actionLabel: 'Click me',
+ });
+ };
+
return (
<>
@@ -102,6 +111,14 @@ const Developers: React.FC = () => {
+
+
+
+
+
+
+
+