Merge commit 'aa04efb92a47cf27c1aafb2aeb21584e91c526ac' into glitch-soc/merge-upstream

Conflicts:
- `app/models/user_settings.rb`:
  Not a real conflict, upstream added a setting on a line adjacent to a
  glitch-soc-only line.
  Added upstream's new setting.
This commit is contained in:
Claire committed 2025-05-14 13:38:23 +02:00
commit 5321a553d5
133 files changed
+957 -272

No files matched your search

@@ -59,6 +59,7 @@ import {
import { getAccountHidden } from 'mastodon/selectors/accounts';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
import { FamiliarFollowers } from './familiar_followers';
import { MemorialNote } from './memorial_note';
import { MovedNote } from './moved_note';
@@ -1022,6 +1023,7 @@ export const AccountHeader: React.FC<{
/>
</NavLink>
</div>
{signedIn && <FamiliarFollowers accountId={accountId} />}
</div>
)}
</div>
@@ -0,0 +1,84 @@
import { useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { fetchAccountsFamiliarFollowers } from '@/mastodon/actions/accounts_familiar_followers';
import { AvatarGroup } from '@/mastodon/components/avatar_group';
import type { Account } from '@/mastodon/models/account';
import { getAccountFamiliarFollowers } from '@/mastodon/selectors/accounts';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
const AccountLink: React.FC<{ account?: Account }> = ({ account }) => (
<Link to={`/@${account?.username}`} data-hover-card-account={account?.id}>
{account?.display_name}
</Link>
);
const FamiliarFollowersReadout: React.FC<{ familiarFollowers: Account[] }> = ({
familiarFollowers,
}) => {
const messageData = {
name1: <AccountLink account={familiarFollowers.at(0)} />,
name2: <AccountLink account={familiarFollowers.at(1)} />,
othersCount: familiarFollowers.length - 2,
};
if (familiarFollowers.length === 1) {
return (
<FormattedMessage
id='account.familiar_followers_one'
defaultMessage='Followed by {name1}'
values={messageData}
/>
);
} else if (familiarFollowers.length === 2) {
return (
<FormattedMessage
id='account.familiar_followers_two'
defaultMessage='Followed by {name1} and {name2}'
values={messageData}
/>
);
} else {
return (
<FormattedMessage
id='account.familiar_followers_many'
defaultMessage='Followed by {name1}, {name2}, and {othersCount, plural, one {# other} other {# others}}'
values={messageData}
/>
);
}
};
export const FamiliarFollowers: React.FC<{ accountId: string }> = ({
accountId,
}) => {
const dispatch = useAppDispatch();
const familiarFollowers = useAppSelector((state) =>
getAccountFamiliarFollowers(state, accountId),
);
const hasNoData = familiarFollowers === null;
useEffect(() => {
if (hasNoData) {
void dispatch(fetchAccountsFamiliarFollowers({ id: accountId }));
}
}, [dispatch, accountId, hasNoData]);
if (hasNoData || familiarFollowers.length === 0) {
return null;
}
return (
<div className='account__header__familiar-followers'>
<AvatarGroup
compact
accountIds={familiarFollowers.slice(0, 3).map((account) => account.id)}
/>
<FamiliarFollowersReadout familiarFollowers={familiarFollowers} />
</div>
);
};
@@ -12,14 +12,10 @@ import Overlay from 'react-overlays/Overlay';
import MoodIcon from '@/material-icons/400-20px/mood.svg?react';
import { IconButton } from 'mastodon/components/icon_button';
import emojiCompressed from 'mastodon/features/emoji/emoji_compressed';
import { assetHost } from 'mastodon/utils/config';
import { buildCustomEmojis, categoriesFromEmojis } from '../../emoji/emoji';
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
const nimblePickerData = emojiCompressed[5];
const messages = defineMessages({
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' },
@@ -40,19 +36,11 @@ let EmojiPicker, Emoji; // load asynchronously
const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
const backgroundImageFn = () => `${assetHost}/emoji/sheet_15_1.png`;
const notFoundFn = () => (
<div className='emoji-mart-no-results'>
<Emoji
data={nimblePickerData}
emoji='sleuth_or_spy'
set='twitter'
size={32}
sheetSize={32}
sheetColumns={62}
sheetRows={62}
backgroundImageFn={backgroundImageFn}
/>
<div className='emoji-mart-no-results-label'>
@@ -110,12 +98,12 @@ class ModifierPickerMenu extends PureComponent {
return (
<div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}>
<button type='button' onClick={this.handleClick} data-index={1}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} /></button>
<button type='button' onClick={this.handleClick} data-index={2}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} /></button>
<button type='button' onClick={this.handleClick} data-index={3}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} /></button>
<button type='button' onClick={this.handleClick} data-index={4}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} /></button>
<button type='button' onClick={this.handleClick} data-index={5}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} /></button>
<button type='button' onClick={this.handleClick} data-index={6}><Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} /></button>
<button type='button' onClick={this.handleClick} data-index={1}><Emoji emoji='fist' size={22} skin={1} /></button>
<button type='button' onClick={this.handleClick} data-index={2}><Emoji emoji='fist' size={22} skin={2} /></button>
<button type='button' onClick={this.handleClick} data-index={3}><Emoji emoji='fist' size={22} skin={3} /></button>
<button type='button' onClick={this.handleClick} data-index={4}><Emoji emoji='fist' size={22} skin={4} /></button>
<button type='button' onClick={this.handleClick} data-index={5}><Emoji emoji='fist' size={22} skin={5} /></button>
<button type='button' onClick={this.handleClick} data-index={6}><Emoji emoji='fist' size={22} skin={6} /></button>
</div>
);
}
@@ -150,7 +138,7 @@ class ModifierPicker extends PureComponent {
return (
<div className='emoji-picker-dropdown__modifiers'>
<Emoji data={nimblePickerData} sheetColumns={62} sheetRows={62} emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} />
<Emoji emoji='fist' size={22} skin={modifier} onClick={this.handleClick} />
<ModifierPickerMenu active={active} onSelect={this.handleSelect} onClose={this.props.onClose} />
</div>
);
@@ -286,16 +274,11 @@ class EmojiPickerMenuImpl extends PureComponent {
return (
<div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
<EmojiPicker
data={nimblePickerData}
sheetColumns={62}
sheetRows={62}
perLine={8}
emojiSize={22}
sheetSize={32}
custom={buildCustomEmojis(custom_emojis)}
color=''
emoji=''
set='twitter'
title={title}
i18n={this.getI18n()}
onClick={this.handleClick}
@@ -304,7 +287,6 @@ class EmojiPickerMenuImpl extends PureComponent {
skin={skinTone}
showPreview={false}
showSkinTones={false}
backgroundImageFn={backgroundImageFn}
notFound={notFoundFn}
autoFocus={this.state.readyToFocus}
emojiTooltip
@@ -45,7 +45,6 @@ type EmojiCompressed = [
Category[],
Data['aliases'],
EmojisWithoutShortCodes,
Data,
];
/*
@@ -132,6 +132,5 @@ module.exports = JSON.parse(JSON.stringify([
emojiMartData.skins,
emojiMartData.categories,
emojiMartData.aliases,
emojisWithoutShortCodes,
emojiMartData
emojisWithoutShortCodes
]));
@@ -1,7 +0,0 @@
import Emoji from 'emoji-mart/dist-es/components/emoji/nimble-emoji';
import Picker from 'emoji-mart/dist-es/components/picker/nimble-picker';
export {
Picker,
Emoji,
};
@@ -0,0 +1,53 @@
import type { EmojiProps, PickerProps } from 'emoji-mart';
import EmojiRaw from 'emoji-mart/dist-es/components/emoji/nimble-emoji';
import PickerRaw from 'emoji-mart/dist-es/components/picker/nimble-picker';
import { assetHost } from 'mastodon/utils/config';
import EmojiData from './emoji_data.json';
const backgroundImageFnDefault = () => `${assetHost}/emoji/sheet_15_1.png`;
const Emoji = ({
set = 'twitter',
sheetSize = 32,
sheetColumns = 62,
sheetRows = 62,
backgroundImageFn = backgroundImageFnDefault,
...props
}: EmojiProps) => {
return (
<EmojiRaw
data={EmojiData}
set={set}
sheetSize={sheetSize}
sheetColumns={sheetColumns}
sheetRows={sheetRows}
backgroundImageFn={backgroundImageFn}
{...props}
/>
);
};
const Picker = ({
set = 'twitter',
sheetSize = 32,
sheetColumns = 62,
sheetRows = 62,
backgroundImageFn = backgroundImageFnDefault,
...props
}: PickerProps) => {
return (
<PickerRaw
data={EmojiData}
set={set}
sheetSize={sheetSize}
sheetColumns={sheetColumns}
sheetRows={sheetRows}
backgroundImageFn={backgroundImageFn}
{...props}
/>
);
};
export { Picker, Emoji };
@@ -1,31 +0,0 @@
import { Link } from 'react-router-dom';
import { Avatar } from 'mastodon/components/avatar';
import { NOTIFICATIONS_GROUP_MAX_AVATARS } from 'mastodon/models/notification_group';
import { useAppSelector } from 'mastodon/store';
const AvatarWrapper: React.FC<{ accountId: string }> = ({ accountId }) => {
const account = useAppSelector((state) => state.accounts.get(accountId));
if (!account) return null;
return (
<Link
to={`/@${account.acct}`}
title={`@${account.acct}`}
data-hover-card-account={account.id}
>
<Avatar account={account} size={28} />
</Link>
);
};
export const AvatarGroup: React.FC<{ accountIds: string[] }> = ({
accountIds,
}) => (
<div className='notification-group__avatar-group'>
{accountIds.slice(0, NOTIFICATIONS_GROUP_MAX_AVATARS).map((accountId) => (
<AvatarWrapper key={accountId} accountId={accountId} />
))}
</div>
);
@@ -7,12 +7,13 @@ import { HotKeys } from 'react-hotkeys';
import { replyComposeById } from 'mastodon/actions/compose';
import { navigateToStatus } from 'mastodon/actions/statuses';
import { AvatarGroup } from 'mastodon/components/avatar_group';
import type { IconProp } from 'mastodon/components/icon';
import { Icon } from 'mastodon/components/icon';
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
import { NOTIFICATIONS_GROUP_MAX_AVATARS } from 'mastodon/models/notification_group';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
import { AvatarGroup } from './avatar_group';
import { DisplayedName } from './displayed_name';
import { EmbeddedStatus } from './embedded_status';
@@ -98,7 +99,12 @@ export const NotificationGroupWithStatus: React.FC<{
<div className='notification-group__main'>
<div className='notification-group__main__header'>
<div className='notification-group__main__header__wrapper'>
<AvatarGroup accountIds={accountIds} />
<AvatarGroup
accountIds={accountIds.slice(
0,
NOTIFICATIONS_GROUP_MAX_AVATARS,
)}
/>
{actions && (
<div className='notification-group__actions'>{actions}</div>