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
@@ -0,0 +1,82 @@
|
||||
import { useRef, useState } from 'react';
|
||||
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
import { fetchRelationships } from 'mastodon/actions/accounts';
|
||||
import { importFetchedAccounts } from 'mastodon/actions/importer';
|
||||
import { apiRequest } from 'mastodon/api';
|
||||
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
|
||||
import { useAppDispatch } from 'mastodon/store';
|
||||
|
||||
export function useSearchAccounts({
|
||||
onSettled,
|
||||
filterResults,
|
||||
resetOnInputClear = true,
|
||||
withRelationships = false,
|
||||
}: {
|
||||
onSettled?: (value: string) => void;
|
||||
filterResults?: (account: ApiAccountJSON) => boolean;
|
||||
resetOnInputClear?: boolean;
|
||||
withRelationships?: boolean;
|
||||
} = {}) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [accountIds, setAccountIds] = useState<string[]>([]);
|
||||
const [loadingState, setLoadingState] = useState<
|
||||
'idle' | 'loading' | 'error'
|
||||
>('idle');
|
||||
|
||||
const searchRequestRef = useRef<AbortController | null>(null);
|
||||
|
||||
const searchAccounts = useDebouncedCallback(
|
||||
(value: string) => {
|
||||
if (searchRequestRef.current) {
|
||||
searchRequestRef.current.abort();
|
||||
}
|
||||
|
||||
if (value.trim().length === 0) {
|
||||
onSettled?.('');
|
||||
if (resetOnInputClear) {
|
||||
setAccountIds([]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setLoadingState('loading');
|
||||
|
||||
searchRequestRef.current = new AbortController();
|
||||
|
||||
void apiRequest<ApiAccountJSON[]>('GET', 'v1/accounts/search', {
|
||||
signal: searchRequestRef.current.signal,
|
||||
params: {
|
||||
q: value,
|
||||
resolve: true,
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
const accounts = filterResults ? data.filter(filterResults) : data;
|
||||
const accountIds = accounts.map((a) => a.id);
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
if (withRelationships) {
|
||||
dispatch(fetchRelationships(accountIds));
|
||||
}
|
||||
setAccountIds(accountIds);
|
||||
setLoadingState('idle');
|
||||
onSettled?.(value);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoadingState('error');
|
||||
onSettled?.(value);
|
||||
});
|
||||
},
|
||||
500,
|
||||
{ leading: true, trailing: true },
|
||||
);
|
||||
|
||||
return {
|
||||
searchAccounts,
|
||||
accountIds,
|
||||
isLoading: loadingState === 'loading',
|
||||
isError: loadingState === 'error',
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import { useCallback, useMemo, useRef, useState } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
|
||||
import { apiGetSearch } from 'mastodon/api/search';
|
||||
import type { ApiHashtagJSON } from 'mastodon/api_types/tags';
|
||||
import { trimHashFromStart } from 'mastodon/utils/hashtags';
|
||||
|
||||
export type TagSearchResult = Omit<ApiHashtagJSON, 'url' | 'history'> & {
|
||||
label?: string;
|
||||
};
|
||||
|
||||
const messages = defineMessages({
|
||||
addTag: {
|
||||
id: 'account_edit_tags.add_tag',
|
||||
defaultMessage: 'Add #{tagName}',
|
||||
},
|
||||
});
|
||||
|
||||
const fetchSearchHashtags = ({
|
||||
q,
|
||||
limit,
|
||||
signal,
|
||||
}: {
|
||||
q: string;
|
||||
limit: number;
|
||||
signal: AbortSignal;
|
||||
}) => apiGetSearch({ q, type: 'hashtags', limit }, { signal });
|
||||
|
||||
export function useSearchTags({
|
||||
query,
|
||||
limit = 11,
|
||||
filterResults,
|
||||
}: {
|
||||
query?: string;
|
||||
limit?: number;
|
||||
filterResults?: (account: ApiHashtagJSON) => boolean;
|
||||
} = {}) {
|
||||
const intl = useIntl();
|
||||
const [fetchedTags, setFetchedTags] = useState<ApiHashtagJSON[]>([]);
|
||||
const [loadingState, setLoadingState] = useState<
|
||||
'idle' | 'loading' | 'error'
|
||||
>('idle');
|
||||
|
||||
const searchRequestRef = useRef<AbortController | null>(null);
|
||||
|
||||
const searchTags = useDebouncedCallback(
|
||||
(value: string) => {
|
||||
if (searchRequestRef.current) {
|
||||
searchRequestRef.current.abort();
|
||||
}
|
||||
|
||||
const trimmedQuery = trimHashFromStart(value.trim());
|
||||
|
||||
if (trimmedQuery.length === 0) {
|
||||
setFetchedTags([]);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoadingState('loading');
|
||||
|
||||
searchRequestRef.current = new AbortController();
|
||||
|
||||
void fetchSearchHashtags({
|
||||
q: trimmedQuery,
|
||||
limit,
|
||||
signal: searchRequestRef.current.signal,
|
||||
})
|
||||
.then(({ hashtags }) => {
|
||||
const tags = filterResults
|
||||
? hashtags.filter(filterResults)
|
||||
: hashtags;
|
||||
setFetchedTags(tags);
|
||||
setLoadingState('idle');
|
||||
})
|
||||
.catch(() => {
|
||||
setLoadingState('error');
|
||||
});
|
||||
},
|
||||
500,
|
||||
{ leading: true, trailing: true },
|
||||
);
|
||||
|
||||
const resetSearch = useCallback(() => {
|
||||
setFetchedTags([]);
|
||||
setLoadingState('idle');
|
||||
}, []);
|
||||
|
||||
// Add dedicated item for adding the current query
|
||||
const tags = useMemo(() => {
|
||||
const trimmedQuery = query ? trimHashFromStart(query.trim()) : '';
|
||||
if (!trimmedQuery || !fetchedTags.length) {
|
||||
return fetchedTags;
|
||||
}
|
||||
|
||||
const results: TagSearchResult[] = [...fetchedTags]; // Make array mutable
|
||||
if (
|
||||
trimmedQuery.length > 0 &&
|
||||
results.every(
|
||||
(result) => result.name.toLowerCase() !== trimmedQuery.toLowerCase(),
|
||||
)
|
||||
) {
|
||||
results.push({
|
||||
id: 'new',
|
||||
name: trimmedQuery,
|
||||
label: intl.formatMessage(messages.addTag, { tagName: trimmedQuery }),
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}, [fetchedTags, query, intl]);
|
||||
|
||||
return {
|
||||
tags,
|
||||
searchTags,
|
||||
resetSearch,
|
||||
isLoading: loadingState === 'loading',
|
||||
isError: loadingState === 'error',
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user