Implements tag suggestions for collections topic field (#38307)
This commit is contained in:
1 parent
6507a61d30
commit
8bce0b99d4
11 files changed
+323
-215
No files matched your search
@@ -25,8 +25,8 @@ import {
|
||||
ItemList,
|
||||
Scrollable,
|
||||
} from 'mastodon/components/scrollable_list/components';
|
||||
import { useSearchAccounts } from 'mastodon/features/lists/use_search_accounts';
|
||||
import { useAccount } from 'mastodon/hooks/useAccount';
|
||||
import { useSearchAccounts } from 'mastodon/hooks/useSearchAccounts';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import {
|
||||
addCollectionItem,
|
||||
@@ -374,6 +374,7 @@ export const CollectionAccounts: React.FC<{
|
||||
onSelectItem={
|
||||
isEditMode ? instantToggleAccountItem : toggleAccountItem
|
||||
}
|
||||
closeOnSelect={false}
|
||||
/>
|
||||
{hasMaxAccounts && (
|
||||
<FormattedMessage
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
@@ -9,6 +9,7 @@ import { isFulfilled } from '@reduxjs/toolkit';
|
||||
import {
|
||||
hasSpecialCharacters,
|
||||
inputToHashtag,
|
||||
trimHashFromStart,
|
||||
} from '@/mastodon/utils/hashtags';
|
||||
import type {
|
||||
ApiCreateCollectionPayload,
|
||||
@@ -17,12 +18,15 @@ import type {
|
||||
import { Button } from 'mastodon/components/button';
|
||||
import {
|
||||
CheckboxField,
|
||||
ComboboxField,
|
||||
Fieldset,
|
||||
FormStack,
|
||||
RadioButtonField,
|
||||
TextAreaField,
|
||||
} from 'mastodon/components/form_fields';
|
||||
import { TextInputField } from 'mastodon/components/form_fields/text_input_field';
|
||||
import { useSearchTags } from 'mastodon/hooks/useSearchTags';
|
||||
import type { TagSearchResult } from 'mastodon/hooks/useSearchTags';
|
||||
import {
|
||||
createCollection,
|
||||
updateCollection,
|
||||
@@ -34,7 +38,6 @@ import classes from './styles.module.scss';
|
||||
import { WizardStepHeader } from './wizard_step_header';
|
||||
|
||||
export const CollectionDetails: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const history = useHistory();
|
||||
const { id, name, description, topic, discoverable, sensitive, accountIds } =
|
||||
@@ -64,18 +67,6 @@ export const CollectionDetails: React.FC = () => {
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
const handleTopicChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(
|
||||
updateCollectionEditorField({
|
||||
field: 'topic',
|
||||
value: inputToHashtag(event.target.value),
|
||||
}),
|
||||
);
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
const handleDiscoverableChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(
|
||||
@@ -156,11 +147,6 @@ export const CollectionDetails: React.FC = () => {
|
||||
],
|
||||
);
|
||||
|
||||
const topicHasSpecialCharacters = useMemo(
|
||||
() => hasSpecialCharacters(topic),
|
||||
[topic],
|
||||
);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className={classes.form}>
|
||||
<FormStack className={classes.formFieldStack}>
|
||||
@@ -213,39 +199,7 @@ export const CollectionDetails: React.FC = () => {
|
||||
maxLength={100}
|
||||
/>
|
||||
|
||||
<TextInputField
|
||||
required={false}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='collections.collection_topic'
|
||||
defaultMessage='Topic'
|
||||
/>
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='collections.topic_hint'
|
||||
defaultMessage='Add a hashtag that helps others understand the main topic of this collection.'
|
||||
/>
|
||||
}
|
||||
value={topic}
|
||||
onChange={handleTopicChange}
|
||||
autoCapitalize='off'
|
||||
autoCorrect='off'
|
||||
spellCheck='false'
|
||||
maxLength={40}
|
||||
status={
|
||||
topicHasSpecialCharacters
|
||||
? {
|
||||
variant: 'warning',
|
||||
message: intl.formatMessage({
|
||||
id: 'collections.topic_special_chars_hint',
|
||||
defaultMessage:
|
||||
'Special characters will be removed when saving',
|
||||
}),
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
<TopicField />
|
||||
|
||||
<Fieldset
|
||||
legend={
|
||||
@@ -335,3 +289,95 @@ export const CollectionDetails: React.FC = () => {
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
const TopicField: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
const { id, topic } = useAppSelector((state) => state.collections.editor);
|
||||
|
||||
const collection = useAppSelector((state) =>
|
||||
id ? state.collections.collections[id] : undefined,
|
||||
);
|
||||
const [isInitialValue, setIsInitialValue] = useState(
|
||||
() => trimHashFromStart(topic) === (collection?.tag?.name ?? ''),
|
||||
);
|
||||
|
||||
const { tags, isLoading, searchTags } = useSearchTags({
|
||||
query: topic,
|
||||
});
|
||||
|
||||
const handleTopicChange = useCallback(
|
||||
(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setIsInitialValue(false);
|
||||
dispatch(
|
||||
updateCollectionEditorField({
|
||||
field: 'topic',
|
||||
value: inputToHashtag(event.target.value),
|
||||
}),
|
||||
);
|
||||
searchTags(event.target.value);
|
||||
},
|
||||
[dispatch, searchTags],
|
||||
);
|
||||
|
||||
const handleSelectTopicSuggestion = useCallback(
|
||||
(item: TagSearchResult) => {
|
||||
dispatch(
|
||||
updateCollectionEditorField({
|
||||
field: 'topic',
|
||||
value: inputToHashtag(item.name),
|
||||
}),
|
||||
);
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
|
||||
const topicHasSpecialCharacters = useMemo(
|
||||
() => hasSpecialCharacters(topic),
|
||||
[topic],
|
||||
);
|
||||
|
||||
return (
|
||||
<ComboboxField
|
||||
required={false}
|
||||
icon={null}
|
||||
label={
|
||||
<FormattedMessage
|
||||
id='collections.collection_topic'
|
||||
defaultMessage='Topic'
|
||||
/>
|
||||
}
|
||||
hint={
|
||||
<FormattedMessage
|
||||
id='collections.topic_hint'
|
||||
defaultMessage='Add a hashtag that helps others understand the main topic of this collection.'
|
||||
/>
|
||||
}
|
||||
value={topic}
|
||||
items={tags}
|
||||
isLoading={isLoading}
|
||||
renderItem={renderItem}
|
||||
onSelectItem={handleSelectTopicSuggestion}
|
||||
onChange={handleTopicChange}
|
||||
autoCapitalize='off'
|
||||
autoCorrect='off'
|
||||
spellCheck='false'
|
||||
maxLength={40}
|
||||
status={
|
||||
topicHasSpecialCharacters
|
||||
? {
|
||||
variant: 'warning',
|
||||
message: intl.formatMessage({
|
||||
id: 'collections.topic_special_chars_hint',
|
||||
defaultMessage:
|
||||
'Special characters will be removed when saving',
|
||||
}),
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
suppressMenu={isInitialValue}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderItem = (item: TagSearchResult) => item.label ?? `#${item.name}`;
|
||||
Reference in new issue
Block a user