Merge commit '0ef43a431d353a17419ea6664ed745b5dbfbf2f9' into glitch-soc/merge-upstream

This commit is contained in:
Claire committed 2026-03-24 12:13:39 +01:00
commit 60236bffe6
99 files changed
+2171 -1313

No files matched your search

@@ -6,7 +6,7 @@ import { defineMessages, useIntl } from 'react-intl';
import { Combobox } from '@/mastodon/components/form_fields';
import { useSearchTags } from '@/mastodon/hooks/useSearchTags';
import type { TagSearchResult } from '@/mastodon/hooks/useSearchTags';
import { addFeaturedTag } from '@/mastodon/reducers/slices/profile_edit';
import { addFeaturedTags } from '@/mastodon/reducers/slices/profile_edit';
import { useAppDispatch } from '@/mastodon/store';
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
@@ -47,7 +47,7 @@ export const AccountEditTagSearch: FC = () => {
(item: TagSearchResult) => {
resetSearch();
setQuery('');
void dispatch(addFeaturedTag({ name: item.name }));
void dispatch(addFeaturedTags({ names: [item.name] }));
},
[dispatch, resetSearch],
);
@@ -9,7 +9,7 @@ import { useAccount } from '@/mastodon/hooks/useAccount';
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
import type { TagData } from '@/mastodon/reducers/slices/profile_edit';
import {
addFeaturedTag,
addFeaturedTags,
deleteFeaturedTag,
fetchProfile,
fetchSuggestedTags,
@@ -128,7 +128,7 @@ const SuggestedTag: FC<{ name: string; disabled?: boolean }> = ({
}) => {
const dispatch = useAppDispatch();
const handleAddTag = useCallback(() => {
void dispatch(addFeaturedTag({ name }));
void dispatch(addFeaturedTags({ names: [name] }));
}, [dispatch, name]);
return <Tag name={name} onClick={handleAddTag} disabled={disabled} />;
};
@@ -18,7 +18,10 @@ import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
import { RemoteHint } from '@/mastodon/components/remote_hint';
import StatusList from '@/mastodon/components/status_list';
import BundleColumnError from '@/mastodon/features/ui/components/bundle_column_error';
import { useAccountId } from '@/mastodon/hooks/useAccountId';
import {
useAccountId,
useCurrentAccountId,
} from '@/mastodon/hooks/useAccountId';
import { useAccountVisibility } from '@/mastodon/hooks/useAccountVisibility';
import { selectTimelineByKey } from '@/mastodon/selectors/timelines';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
@@ -34,6 +37,7 @@ import {
usePinnedStatusIds,
} from './pinned_statuses';
import classes from './styles.module.scss';
import { TagSuggestions } from './tags_suggestions';
const emptyList = ImmutableList<string>();
@@ -135,6 +139,7 @@ const Prepend: FC<{
accountId: string;
forceEmpty: boolean;
}> = ({ forceEmpty, accountId }) => {
const me = useCurrentAccountId();
if (forceEmpty) {
return <AccountHeader accountId={accountId} hideTabs />;
}
@@ -144,6 +149,7 @@ const Prepend: FC<{
<AccountHeader accountId={accountId} hideTabs />
<AccountFilters />
<FeaturedTags accountId={accountId} />
{me === accountId && <TagSuggestions />}
</>
);
};
@@ -49,8 +49,12 @@
}
}
.tagsWrapper {
.tagsWrapper,
.tagSuggestions {
margin: 0 24px 8px;
}
.tagsWrapper {
display: flex;
flex-wrap: nowrap;
justify-content: flex-start;
@@ -0,0 +1,128 @@
import type { FC } from 'react';
import { useEffect, useCallback, useState } from 'react';
import { FormattedMessage, FormattedList } from 'react-intl';
import { Link } from 'react-router-dom';
import { fetchFeaturedTags } from '@/mastodon/actions/featured_tags';
import { Callout } from '@/mastodon/components/callout';
import { useCurrentAccountId } from '@/mastodon/hooks/useAccountId';
import { useDismissible } from '@/mastodon/hooks/useDismissible';
import {
fetchProfile,
fetchSuggestedTags,
addFeaturedTags,
} from '@/mastodon/reducers/slices/profile_edit';
import { useAppSelector, useAppDispatch } from '@/mastodon/store';
import classes from './styles.module.scss';
const MAX_SUGGESTED_TAGS = 3;
export const TagSuggestions: FC = () => {
const { dismiss, wasDismissed } = useDismissible(
'profile/featured_tag_suggestions',
);
const suggestedTags = useAppSelector((state) =>
state.profileEdit.tagSuggestions?.slice(0, MAX_SUGGESTED_TAGS),
);
const existingTagCount = useAppSelector(
(state) => state.profileEdit.profile?.featuredTags.length,
);
const dispatch = useAppDispatch();
const isLoading = !suggestedTags || existingTagCount === undefined;
useEffect(() => {
if (isLoading) {
void dispatch(fetchProfile());
void dispatch(fetchSuggestedTags());
}
}, [dispatch, isLoading]);
const me = useCurrentAccountId();
const [showSuccessNotice, setSuccessNotice] = useState(false);
const handleAdd = useCallback(() => {
if (!suggestedTags?.length || !me) {
return;
}
const addTags = async () => {
await dispatch(
addFeaturedTags({ names: suggestedTags.map((tag) => tag.name) }),
);
await dispatch(fetchFeaturedTags({ accountId: me }));
setSuccessNotice(true);
dismiss();
};
void addTags();
}, [dismiss, dispatch, me, suggestedTags]);
const handleDismissSuccessNotice = useCallback(() => {
setSuccessNotice(false);
}, []);
if (showSuccessNotice) {
return (
<Callout
variant='subtle'
className={classes.tagSuggestions}
onClose={handleDismissSuccessNotice}
>
<FormattedMessage
id='featured_tags.suggestions.added'
defaultMessage='Manage your featured hashtags at any time under <link>Edit Profile > Featured hashtags</link>.'
values={{
link: (chunks) => <Link to='/profile/featured_tags'>{chunks}</Link>,
}}
/>
</Callout>
);
}
if (
isLoading ||
!suggestedTags.length ||
existingTagCount > 0 ||
wasDismissed
) {
return null;
}
return (
<Callout
id='featured_tags.suggestions'
variant='subtle'
className={classes.tagSuggestions}
onPrimary={handleAdd}
primaryLabel={
<FormattedMessage
id='featured_tags.suggestions.add'
defaultMessage='Add'
/>
}
onSecondary={dismiss}
secondaryLabel={
<FormattedMessage
id='featured_tags.suggestions.dismiss'
defaultMessage='No thanks'
/>
}
>
<FormattedMessage
id='featured_tags.suggestions'
defaultMessage='Lately you’ve posted about {items}. Add these as featured hashtags?'
values={{
items: (
<FormattedList
value={suggestedTags.map(({ name }) => `#${name}`)}
/>
),
}}
/>
</Callout>
);
};
@@ -1,9 +1,4 @@
import { EMOJI_DB_NAME_SHORTCODES, EMOJI_TYPE_CUSTOM } from './constants';
import {
importCustomEmojiData,
importEmojiData,
importLegacyShortcodes,
} from './loader';
addEventListener('message', handleMessage);
self.postMessage('ready'); // After the worker is ready, notify the main thread
@@ -16,6 +11,8 @@ function handleMessage(event: MessageEvent<{ locale: string }>) {
}
async function loadData(locale: string) {
const { importCustomEmojiData, importEmojiData, importLegacyShortcodes } =
await import('./loader');
let importCount: number | undefined;
if (locale === EMOJI_TYPE_CUSTOM) {
importCount = (await importCustomEmojiData())?.length;