Merge remote-tracking branch 'soapbox/develop' into mastodon-groups

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak
2023-01-27 23:04:42 +01:00
443 changed files with 10101 additions and 30568 deletions

View File

@@ -3,7 +3,7 @@ import React from 'react';
import { __stub } from 'soapbox/api';
import { render, screen } from '../../../../jest/test-helpers';
import { render, screen, waitFor } from '../../../../jest/test-helpers';
import Search from '../search';
describe('<Search />', () => {
@@ -22,7 +22,9 @@ describe('<Search />', () => {
await user.type(screen.getByLabelText('Search'), '@jus');
expect(screen.getByLabelText('Search')).toHaveValue('@jus');
expect(screen.getByTestId('account')).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByLabelText('Search')).toHaveValue('@jus');
expect(screen.getByTestId('account')).toBeInTheDocument();
});
});
});

View File

@@ -15,7 +15,6 @@ import {
} from 'soapbox/actions/compose';
import AutosuggestInput, { AutoSuggestion } from 'soapbox/components/autosuggest-input';
import AutosuggestTextarea from 'soapbox/components/autosuggest-textarea';
import Icon from 'soapbox/components/icon';
import { Button, HStack, Stack } from 'soapbox/components/ui';
import { useAppDispatch, useAppSelector, useCompose, useFeatures, useInstance, usePrevious } from 'soapbox/hooks';
import { isMobile } from 'soapbox/is-mobile';
@@ -76,7 +75,7 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
const showSearch = useAppSelector((state) => state.search.submitted && !state.search.hidden);
const isModalOpen = useAppSelector((state) => !!(state.modals.size && state.modals.last()!.modalType === 'COMPOSE'));
const maxTootChars = configuration.getIn(['statuses', 'max_characters']) as number;
const scheduledStatusCount = useAppSelector((state) => state.get('scheduled_statuses').size);
const scheduledStatusCount = useAppSelector((state) => state.scheduled_statuses.size);
const features = useFeatures();
const { text, suggestions, spoiler, spoiler_text: spoilerText, privacy, focusDate, caretPosition, is_submitting: isSubmitting, is_changing_upload: isChangingUpload, is_uploading: isUploading, schedule: scheduledAt, group_id: groupId } = compose;
@@ -242,25 +241,18 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
const disabledButton = disabled || isUploading || isChangingUpload || length(countedText) > maxTootChars || (countedText.length !== 0 && countedText.trim().length === 0 && !anyMedia);
const shouldAutoFocus = autoFocus && !showSearch && !isMobile(window.innerWidth);
let publishText: string | JSX.Element = '';
let publishText: string = '';
let publishIcon: string | undefined;
let textareaPlaceholder: MessageDescriptor;
if (isEditing) {
publishText = intl.formatMessage(messages.saveChanges);
} else if (privacy === 'direct') {
publishText = (
<>
<Icon src={require('@tabler/icons/mail.svg')} />
{intl.formatMessage(messages.message)}
</>
);
publishText = intl.formatMessage(messages.message);
publishIcon = require('@tabler/icons/mail.svg');
} else if (privacy === 'private') {
publishText = (
<>
<Icon src={require('@tabler/icons/lock.svg')} />
{intl.formatMessage(messages.publish)}
</>
);
publishText = intl.formatMessage(messages.publish);
publishIcon = require('@tabler/icons/lock.svg');
} else {
publishText = privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
}
@@ -356,7 +348,7 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
</HStack>
)}
<Button type='submit' theme='primary' text={publishText} disabled={disabledButton} />
<Button type='submit' theme='primary' text={publishText} icon={publishIcon} disabled={disabledButton} />
</HStack>
</div>
</Stack>

View File

@@ -72,8 +72,8 @@ const EmojiPickerMenu: React.FC<IEmojiPickerMenu> = ({
categoriesSort.splice(1, 0, ...Array.from(categoriesFromEmojis(customEmojis) as Set<string>).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();
}
}, []);

View File

@@ -19,8 +19,8 @@ const ModifierPickerMenu: React.FC<IModifierPickerMenu> = ({ 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();
}
}), []);

View File

@@ -40,6 +40,7 @@ const ReplyIndicator: React.FC<IReplyIndicator> = ({ status, hideActions, onCanc
timestamp={status.created_at}
showProfileHoverCard={false}
withLinkToProfile={false}
hideActions={hideActions}
/>
<Text

View File

@@ -15,7 +15,7 @@ import PlaceholderAccount from 'soapbox/features/placeholder/components/placehol
import PlaceholderGroupCard from 'soapbox/features/placeholder/components/placeholder-group-card';
import PlaceholderHashtag from 'soapbox/features/placeholder/components/placeholder-hashtag';
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder-status';
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks';
import type { OrderedSet as ImmutableOrderedSet } from 'immutable';
import type { VirtuosoHandle } from 'react-virtuoso';
@@ -33,6 +33,7 @@ const SearchResults = () => {
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 <Tabs items={items} activeItem={selectedFilter} />;
};

View File

@@ -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<string, any>) => {
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) {

View File

@@ -22,6 +22,7 @@ export interface IUploadButton {
resetFileKey: number | null,
className?: string,
iconClassName?: string,
icon?: string,
}
const UploadButton: React.FC<IUploadButton> = ({
@@ -31,6 +32,7 @@ const UploadButton: React.FC<IUploadButton> = ({
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<IUploadButton> = ({
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 (
<div>

View File

@@ -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 {