mirror of
https://github.com/glitch-soc/mastodon.git
synced 2026-03-29 03:00:33 +02:00
Profile editing: Allow adding arbitrary featured tags (#38012)
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import type { ApiAccountFieldJSON } from './accounts';
|
import type { ApiAccountFieldJSON } from './accounts';
|
||||||
|
import type { ApiFeaturedTagJSON } from './tags';
|
||||||
|
|
||||||
export interface ApiProfileJSON {
|
export interface ApiProfileJSON {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -20,6 +21,7 @@ export interface ApiProfileJSON {
|
|||||||
show_media_replies: boolean;
|
show_media_replies: boolean;
|
||||||
show_featured: boolean;
|
show_featured: boolean;
|
||||||
attribution_domains: string[];
|
attribution_domains: string[];
|
||||||
|
featured_tags: ApiFeaturedTagJSON[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiProfileUpdateParams = Partial<
|
export type ApiProfileUpdateParams = Partial<
|
||||||
|
|||||||
@@ -17,16 +17,6 @@ export interface ApiHashtagJSON extends ApiHashtagBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiFeaturedTagJSON extends ApiHashtagBase {
|
export interface ApiFeaturedTagJSON extends ApiHashtagBase {
|
||||||
statuses_count: number;
|
statuses_count: string;
|
||||||
last_status_at: string | null;
|
last_status_at: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hashtagToFeaturedTag(tag: ApiHashtagJSON): ApiFeaturedTagJSON {
|
|
||||||
return {
|
|
||||||
id: tag.id,
|
|
||||||
name: tag.name,
|
|
||||||
url: tag.url,
|
|
||||||
statuses_count: 0,
|
|
||||||
last_status_at: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -63,7 +63,10 @@ interface ComboboxProps<T extends ComboboxItem> extends TextInputProps {
|
|||||||
* Customise the rendering of each option.
|
* Customise the rendering of each option.
|
||||||
* The rendered content must not contain other interactive content!
|
* The rendered content must not contain other interactive content!
|
||||||
*/
|
*/
|
||||||
renderItem: (item: T, state: ComboboxItemState) => React.ReactElement;
|
renderItem: (
|
||||||
|
item: T,
|
||||||
|
state: ComboboxItemState,
|
||||||
|
) => React.ReactElement | string;
|
||||||
/**
|
/**
|
||||||
* The main selection handler, called when an option is selected or deselected.
|
* The main selection handler, called when an option is selected or deselected.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { ChangeEventHandler, FC } from 'react';
|
import type { ChangeEventHandler, FC } from 'react';
|
||||||
import { useCallback } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
import { useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import type { ApiFeaturedTagJSON } from '@/mastodon/api_types/tags';
|
import type { ApiHashtagJSON } from '@/mastodon/api_types/tags';
|
||||||
import { Combobox } from '@/mastodon/components/form_fields';
|
import { Combobox } from '@/mastodon/components/form_fields';
|
||||||
import {
|
import {
|
||||||
addFeaturedTag,
|
addFeaturedTag,
|
||||||
@@ -15,10 +15,50 @@ import SearchIcon from '@/material-icons/400-24px/search.svg?react';
|
|||||||
|
|
||||||
import classes from '../styles.module.scss';
|
import classes from '../styles.module.scss';
|
||||||
|
|
||||||
|
type SearchResult = Omit<ApiHashtagJSON, 'url' | 'history'> & {
|
||||||
|
label?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
placeholder: {
|
||||||
|
id: 'account_edit_tags.search_placeholder',
|
||||||
|
defaultMessage: 'Enter a hashtag…',
|
||||||
|
},
|
||||||
|
addTag: {
|
||||||
|
id: 'account_edit_tags.add_tag',
|
||||||
|
defaultMessage: 'Add #{tagName}',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const AccountEditTagSearch: FC = () => {
|
export const AccountEditTagSearch: FC = () => {
|
||||||
const { query, isLoading, results } = useAppSelector(
|
const intl = useIntl();
|
||||||
(state) => state.profileEdit.search,
|
|
||||||
);
|
const {
|
||||||
|
query,
|
||||||
|
isLoading,
|
||||||
|
results: rawResults,
|
||||||
|
} = useAppSelector((state) => state.profileEdit.search);
|
||||||
|
const results = useMemo(() => {
|
||||||
|
if (!rawResults) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const results: SearchResult[] = [...rawResults]; // Make array mutable
|
||||||
|
const trimmedQuery = query.trim();
|
||||||
|
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;
|
||||||
|
}, [intl, query, rawResults]);
|
||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const handleSearchChange: ChangeEventHandler<HTMLInputElement> = useCallback(
|
const handleSearchChange: ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||||
@@ -28,10 +68,8 @@ export const AccountEditTagSearch: FC = () => {
|
|||||||
[dispatch],
|
[dispatch],
|
||||||
);
|
);
|
||||||
|
|
||||||
const intl = useIntl();
|
|
||||||
|
|
||||||
const handleSelect = useCallback(
|
const handleSelect = useCallback(
|
||||||
(item: ApiFeaturedTagJSON) => {
|
(item: SearchResult) => {
|
||||||
void dispatch(clearSearch());
|
void dispatch(clearSearch());
|
||||||
void dispatch(addFeaturedTag({ name: item.name }));
|
void dispatch(addFeaturedTag({ name: item.name }));
|
||||||
},
|
},
|
||||||
@@ -42,11 +80,8 @@ export const AccountEditTagSearch: FC = () => {
|
|||||||
<Combobox
|
<Combobox
|
||||||
value={query}
|
value={query}
|
||||||
onChange={handleSearchChange}
|
onChange={handleSearchChange}
|
||||||
placeholder={intl.formatMessage({
|
placeholder={intl.formatMessage(messages.placeholder)}
|
||||||
id: 'account_edit_tags.search_placeholder',
|
items={results}
|
||||||
defaultMessage: 'Enter a hashtag…',
|
|
||||||
})}
|
|
||||||
items={results ?? []}
|
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
onSelectItem={handleSelect}
|
onSelectItem={handleSelect}
|
||||||
@@ -57,4 +92,4 @@ export const AccountEditTagSearch: FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderItem = (item: ApiFeaturedTagJSON) => <p>#{item.name}</p>;
|
const renderItem = (item: SearchResult) => item.label ?? `#${item.name}`;
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ import type { FC } from 'react';
|
|||||||
|
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import type { ApiFeaturedTagJSON } from '@/mastodon/api_types/tags';
|
|
||||||
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
|
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
|
||||||
import { Tag } from '@/mastodon/components/tags/tag';
|
import { Tag } from '@/mastodon/components/tags/tag';
|
||||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||||
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
|
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
|
||||||
|
import type { TagData } from '@/mastodon/reducers/slices/profile_edit';
|
||||||
import {
|
import {
|
||||||
addFeaturedTag,
|
addFeaturedTag,
|
||||||
deleteFeaturedTag,
|
deleteFeaturedTag,
|
||||||
fetchFeaturedTags,
|
fetchProfile,
|
||||||
fetchSuggestedTags,
|
fetchSuggestedTags,
|
||||||
} from '@/mastodon/reducers/slices/profile_edit';
|
} from '@/mastodon/reducers/slices/profile_edit';
|
||||||
import {
|
import {
|
||||||
@@ -35,9 +35,9 @@ const messages = defineMessages({
|
|||||||
const selectTags = createAppSelector(
|
const selectTags = createAppSelector(
|
||||||
[(state) => state.profileEdit],
|
[(state) => state.profileEdit],
|
||||||
(profileEdit) => ({
|
(profileEdit) => ({
|
||||||
tags: profileEdit.tags ?? [],
|
tags: profileEdit.profile?.featuredTags ?? [],
|
||||||
tagSuggestions: profileEdit.tagSuggestions ?? [],
|
tagSuggestions: profileEdit.tagSuggestions ?? [],
|
||||||
isLoading: !profileEdit.tags || !profileEdit.tagSuggestions,
|
isLoading: !profileEdit.profile || !profileEdit.tagSuggestions,
|
||||||
isPending: profileEdit.isPending,
|
isPending: profileEdit.isPending,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -52,7 +52,7 @@ export const AccountEditFeaturedTags: FC = () => {
|
|||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void dispatch(fetchFeaturedTags());
|
void dispatch(fetchProfile());
|
||||||
void dispatch(fetchSuggestedTags());
|
void dispatch(fetchSuggestedTags());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
@@ -78,7 +78,9 @@ export const AccountEditFeaturedTags: FC = () => {
|
|||||||
defaultMessage='Featured hashtags help users discover and interact with your profile. They appear as filters on your Profile page’s Activity view.'
|
defaultMessage='Featured hashtags help users discover and interact with your profile. They appear as filters on your Profile page’s Activity view.'
|
||||||
tagName='p'
|
tagName='p'
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AccountEditTagSearch />
|
<AccountEditTagSearch />
|
||||||
|
|
||||||
{tagSuggestions.length > 0 && (
|
{tagSuggestions.length > 0 && (
|
||||||
<div className={classes.tagSuggestions}>
|
<div className={classes.tagSuggestions}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
@@ -90,7 +92,9 @@ export const AccountEditFeaturedTags: FC = () => {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isLoading && <LoadingIndicator />}
|
{isLoading && <LoadingIndicator />}
|
||||||
|
|
||||||
<AccountEditItemList
|
<AccountEditItemList
|
||||||
items={tags}
|
items={tags}
|
||||||
disabled={isPending}
|
disabled={isPending}
|
||||||
@@ -102,15 +106,15 @@ export const AccountEditFeaturedTags: FC = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderTag(tag: ApiFeaturedTagJSON) {
|
function renderTag(tag: TagData) {
|
||||||
return (
|
return (
|
||||||
<div className={classes.tagItem}>
|
<div className={classes.tagItem}>
|
||||||
<h4>#{tag.name}</h4>
|
<h4>#{tag.name}</h4>
|
||||||
{tag.statuses_count > 0 && (
|
{tag.statusesCount > 0 && (
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='account_edit_tags.tag_status_count'
|
id='account_edit_tags.tag_status_count'
|
||||||
defaultMessage='{count, plural, one {# post} other {# posts}}'
|
defaultMessage='{count, plural, one {# post} other {# posts}}'
|
||||||
values={{ count: tag.statuses_count }}
|
values={{ count: tag.statusesCount }}
|
||||||
tagName='p'
|
tagName='p'
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -15,10 +15,7 @@ import { useElementHandledLink } from '@/mastodon/components/status/handled_link
|
|||||||
import { useAccount } from '@/mastodon/hooks/useAccount';
|
import { useAccount } from '@/mastodon/hooks/useAccount';
|
||||||
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
|
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
|
||||||
import { autoPlayGif } from '@/mastodon/initial_state';
|
import { autoPlayGif } from '@/mastodon/initial_state';
|
||||||
import {
|
import { fetchProfile } from '@/mastodon/reducers/slices/profile_edit';
|
||||||
fetchFeaturedTags,
|
|
||||||
fetchProfile,
|
|
||||||
} from '@/mastodon/reducers/slices/profile_edit';
|
|
||||||
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
|
||||||
|
|
||||||
import { AccountEditColumn, AccountEditEmptyColumn } from './components/column';
|
import { AccountEditColumn, AccountEditEmptyColumn } from './components/column';
|
||||||
@@ -87,9 +84,8 @@ export const AccountEdit: FC = () => {
|
|||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const { profile, tags = [] } = useAppSelector((state) => state.profileEdit);
|
const { profile } = useAppSelector((state) => state.profileEdit);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void dispatch(fetchFeaturedTags());
|
|
||||||
void dispatch(fetchProfile());
|
void dispatch(fetchProfile());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
|
|
||||||
@@ -127,7 +123,7 @@ export const AccountEdit: FC = () => {
|
|||||||
const headerSrc = autoPlayGif ? profile.header : profile.headerStatic;
|
const headerSrc = autoPlayGif ? profile.header : profile.headerStatic;
|
||||||
const hasName = !!profile.displayName;
|
const hasName = !!profile.displayName;
|
||||||
const hasBio = !!profile.bio;
|
const hasBio = !!profile.bio;
|
||||||
const hasTags = tags.length > 0;
|
const hasTags = profile.featuredTags.length > 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AccountEditColumn
|
<AccountEditColumn
|
||||||
@@ -190,7 +186,7 @@ export const AccountEdit: FC = () => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{tags.map((tag) => `#${tag.name}`).join(', ')}
|
{profile.featuredTags.map((tag) => `#${tag.name}`).join(', ')}
|
||||||
</AccountEditSection>
|
</AccountEditSection>
|
||||||
|
|
||||||
<AccountEditSection
|
<AccountEditSection
|
||||||
|
|||||||
@@ -173,6 +173,7 @@
|
|||||||
"account_edit.profile_tab.subtitle": "Customize the tabs on your profile and what they display.",
|
"account_edit.profile_tab.subtitle": "Customize the tabs on your profile and what they display.",
|
||||||
"account_edit.profile_tab.title": "Profile tab settings",
|
"account_edit.profile_tab.title": "Profile tab settings",
|
||||||
"account_edit.save": "Save",
|
"account_edit.save": "Save",
|
||||||
|
"account_edit_tags.add_tag": "Add #{tagName}",
|
||||||
"account_edit_tags.column_title": "Edit featured hashtags",
|
"account_edit_tags.column_title": "Edit featured hashtags",
|
||||||
"account_edit_tags.help_text": "Featured hashtags help users discover and interact with your profile. They appear as filters on your Profile page’s Activity view.",
|
"account_edit_tags.help_text": "Featured hashtags help users discover and interact with your profile. They appear as filters on your Profile page’s Activity view.",
|
||||||
"account_edit_tags.search_placeholder": "Enter a hashtag…",
|
"account_edit_tags.search_placeholder": "Enter a hashtag…",
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ import type {
|
|||||||
ApiProfileJSON,
|
ApiProfileJSON,
|
||||||
ApiProfileUpdateParams,
|
ApiProfileUpdateParams,
|
||||||
} from '@/mastodon/api_types/profile';
|
} from '@/mastodon/api_types/profile';
|
||||||
import { hashtagToFeaturedTag } from '@/mastodon/api_types/tags';
|
import type {
|
||||||
import type { ApiFeaturedTagJSON } from '@/mastodon/api_types/tags';
|
ApiFeaturedTagJSON,
|
||||||
|
ApiHashtagJSON,
|
||||||
|
} from '@/mastodon/api_types/tags';
|
||||||
import type { AppDispatch } from '@/mastodon/store';
|
import type { AppDispatch } from '@/mastodon/store';
|
||||||
import {
|
import {
|
||||||
createAppAsyncThunk,
|
createAppAsyncThunk,
|
||||||
@@ -28,21 +30,30 @@ import type { SnakeToCamelCase } from '@/mastodon/utils/types';
|
|||||||
type ProfileData = {
|
type ProfileData = {
|
||||||
[Key in keyof Omit<
|
[Key in keyof Omit<
|
||||||
ApiProfileJSON,
|
ApiProfileJSON,
|
||||||
'note'
|
'note' | 'featured_tags'
|
||||||
> as SnakeToCamelCase<Key>]: ApiProfileJSON[Key];
|
> as SnakeToCamelCase<Key>]: ApiProfileJSON[Key];
|
||||||
} & {
|
} & {
|
||||||
bio: ApiProfileJSON['note'];
|
bio: ApiProfileJSON['note'];
|
||||||
|
featuredTags: TagData[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TagData = {
|
||||||
|
[Key in keyof Omit<
|
||||||
|
ApiFeaturedTagJSON,
|
||||||
|
'statuses_count'
|
||||||
|
> as SnakeToCamelCase<Key>]: ApiFeaturedTagJSON[Key];
|
||||||
|
} & {
|
||||||
|
statusesCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface ProfileEditState {
|
export interface ProfileEditState {
|
||||||
profile?: ProfileData;
|
profile?: ProfileData;
|
||||||
tags?: ApiFeaturedTagJSON[];
|
tagSuggestions?: ApiHashtagJSON[];
|
||||||
tagSuggestions?: ApiFeaturedTagJSON[];
|
|
||||||
isPending: boolean;
|
isPending: boolean;
|
||||||
search: {
|
search: {
|
||||||
query: string;
|
query: string;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
results?: ApiFeaturedTagJSON[];
|
results?: ApiHashtagJSON[];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +75,7 @@ const profileEditSlice = createSlice({
|
|||||||
}
|
}
|
||||||
|
|
||||||
state.search.query = action.payload;
|
state.search.query = action.payload;
|
||||||
state.search.isLoading = false;
|
state.search.isLoading = true;
|
||||||
state.search.results = undefined;
|
state.search.results = undefined;
|
||||||
},
|
},
|
||||||
clearSearch(state) {
|
clearSearch(state) {
|
||||||
@@ -78,10 +89,7 @@ const profileEditSlice = createSlice({
|
|||||||
state.profile = action.payload;
|
state.profile = action.payload;
|
||||||
});
|
});
|
||||||
builder.addCase(fetchSuggestedTags.fulfilled, (state, action) => {
|
builder.addCase(fetchSuggestedTags.fulfilled, (state, action) => {
|
||||||
state.tagSuggestions = action.payload.map(hashtagToFeaturedTag);
|
state.tagSuggestions = action.payload;
|
||||||
});
|
|
||||||
builder.addCase(fetchFeaturedTags.fulfilled, (state, action) => {
|
|
||||||
state.tags = action.payload;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.addCase(patchProfile.pending, (state) => {
|
builder.addCase(patchProfile.pending, (state) => {
|
||||||
@@ -102,13 +110,14 @@ const profileEditSlice = createSlice({
|
|||||||
state.isPending = false;
|
state.isPending = false;
|
||||||
});
|
});
|
||||||
builder.addCase(addFeaturedTag.fulfilled, (state, action) => {
|
builder.addCase(addFeaturedTag.fulfilled, (state, action) => {
|
||||||
if (!state.tags) {
|
if (!state.profile) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.tags = [...state.tags, action.payload].toSorted(
|
state.profile.featuredTags = [
|
||||||
(a, b) => b.statuses_count - a.statuses_count,
|
...state.profile.featuredTags,
|
||||||
);
|
transformTag(action.payload),
|
||||||
|
].toSorted((a, b) => a.name.localeCompare(b.name));
|
||||||
if (state.tagSuggestions) {
|
if (state.tagSuggestions) {
|
||||||
state.tagSuggestions = state.tagSuggestions.filter(
|
state.tagSuggestions = state.tagSuggestions.filter(
|
||||||
(tag) => tag.name !== action.meta.arg.name,
|
(tag) => tag.name !== action.meta.arg.name,
|
||||||
@@ -124,11 +133,13 @@ const profileEditSlice = createSlice({
|
|||||||
state.isPending = false;
|
state.isPending = false;
|
||||||
});
|
});
|
||||||
builder.addCase(deleteFeaturedTag.fulfilled, (state, action) => {
|
builder.addCase(deleteFeaturedTag.fulfilled, (state, action) => {
|
||||||
if (!state.tags) {
|
if (!state.profile) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.tags = state.tags.filter((tag) => tag.id !== action.meta.arg.tagId);
|
state.profile.featuredTags = state.profile.featuredTags.filter(
|
||||||
|
(tag) => tag.id !== action.meta.arg.tagId,
|
||||||
|
);
|
||||||
state.isPending = false;
|
state.isPending = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -141,14 +152,16 @@ const profileEditSlice = createSlice({
|
|||||||
});
|
});
|
||||||
builder.addCase(fetchSearchResults.fulfilled, (state, action) => {
|
builder.addCase(fetchSearchResults.fulfilled, (state, action) => {
|
||||||
state.search.isLoading = false;
|
state.search.isLoading = false;
|
||||||
const searchResults: ApiFeaturedTagJSON[] = [];
|
const searchResults: ApiHashtagJSON[] = [];
|
||||||
const currentTags = new Set((state.tags ?? []).map((tag) => tag.name));
|
const currentTags = new Set(
|
||||||
|
(state.profile?.featuredTags ?? []).map((tag) => tag.name),
|
||||||
|
);
|
||||||
|
|
||||||
for (const tag of action.payload) {
|
for (const tag of action.payload) {
|
||||||
if (currentTags.has(tag.name)) {
|
if (currentTags.has(tag.name)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
searchResults.push(hashtagToFeaturedTag(tag));
|
searchResults.push(tag);
|
||||||
if (searchResults.length >= 10) {
|
if (searchResults.length >= 10) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -161,6 +174,14 @@ const profileEditSlice = createSlice({
|
|||||||
export const profileEdit = profileEditSlice.reducer;
|
export const profileEdit = profileEditSlice.reducer;
|
||||||
export const { clearSearch } = profileEditSlice.actions;
|
export const { clearSearch } = profileEditSlice.actions;
|
||||||
|
|
||||||
|
const transformTag = (result: ApiFeaturedTagJSON): TagData => ({
|
||||||
|
id: result.id,
|
||||||
|
name: result.name,
|
||||||
|
url: result.url,
|
||||||
|
statusesCount: Number.parseInt(result.statuses_count),
|
||||||
|
lastStatusAt: result.last_status_at,
|
||||||
|
});
|
||||||
|
|
||||||
const transformProfile = (result: ApiProfileJSON): ProfileData => ({
|
const transformProfile = (result: ApiProfileJSON): ProfileData => ({
|
||||||
id: result.id,
|
id: result.id,
|
||||||
displayName: result.display_name,
|
displayName: result.display_name,
|
||||||
@@ -181,6 +202,7 @@ const transformProfile = (result: ApiProfileJSON): ProfileData => ({
|
|||||||
showMediaReplies: result.show_media_replies,
|
showMediaReplies: result.show_media_replies,
|
||||||
showFeatured: result.show_featured,
|
showFeatured: result.show_featured,
|
||||||
attributionDomains: result.attribution_domains,
|
attributionDomains: result.attribution_domains,
|
||||||
|
featuredTags: result.featured_tags.map(transformTag),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const fetchProfile = createDataLoadingThunk(
|
export const fetchProfile = createDataLoadingThunk(
|
||||||
@@ -215,8 +237,10 @@ export const addFeaturedTag = createDataLoadingThunk(
|
|||||||
condition(arg, { getState }) {
|
condition(arg, { getState }) {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
return (
|
return (
|
||||||
!!state.profileEdit.tags &&
|
!!state.profileEdit.profile &&
|
||||||
!state.profileEdit.tags.some((tag) => tag.name === arg.name)
|
!state.profileEdit.profile.featuredTags.some(
|
||||||
|
(tag) => tag.name === arg.name,
|
||||||
|
)
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user