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

Conflicts:
- `app/helpers/theme_helper.rb`:
  Upstream simplified `theme_style_tags`. Our version is different because of
  the different theming system.
  Adapted upstream's changes.
- `app/views/layouts/error.html.haml`:
  Theming system.
This commit is contained in:
Claire committed 2026-01-16 23:22:19 +01:00
commit 7839c8d9af
20 files changed
+456 -214

No files matched your search

@@ -0,0 +1,5 @@
import { isClientFeatureEnabled } from '@/mastodon/utils/environment';
export function isRedesignEnabled() {
return isClientFeatureEnabled('profile_redesign');
}
@@ -1,18 +1,16 @@
import { useCallback } from 'react';
import { useIntl, FormattedMessage } from 'react-intl';
import { useIntl } from 'react-intl';
import classNames from 'classnames';
import { Helmet } from 'react-helmet';
import { AccountBio } from '@/mastodon/components/account_bio';
import { AccountFields } from '@/mastodon/components/account_fields';
import { DisplayName } from '@/mastodon/components/display_name';
import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context';
import LockIcon from '@/material-icons/400-24px/lock.svg?react';
import { openModal } from 'mastodon/actions/modal';
import { Avatar } from 'mastodon/components/avatar';
import { FormattedDateWrapper } from 'mastodon/components/formatted_date';
import { Icon } from 'mastodon/components/icon';
import { AccountNote } from 'mastodon/features/account/components/account_note';
import { DomainPill } from 'mastodon/features/account/components/domain_pill';
@@ -25,10 +23,11 @@ import { useAppSelector, useAppDispatch } from 'mastodon/store';
import { AccountBadges } from './badges';
import { AccountButtons } from './buttons';
import { FamiliarFollowers } from './familiar_followers';
import { AccountHeaderFields } from './fields';
import { AccountInfo } from './info';
import { AccountLinks } from './links';
import { MemorialNote } from './memorial_note';
import { MovedNote } from './moved_note';
import { AccountNumberFields } from './number_fields';
import { AccountTabs } from './tabs';
const titleFromAccount = (account: Account) => {
@@ -192,32 +191,10 @@ export const AccountHeader: React.FC<{
className='account__header__content'
/>
<div className='account__header__fields'>
<dl>
<dt>
<FormattedMessage
id='account.joined_short'
defaultMessage='Joined'
/>
</dt>
<dd>
<FormattedDateWrapper
value={account.created_at}
year='numeric'
month='short'
day='2-digit'
/>
</dd>
</dl>
<AccountFields
fields={account.fields}
emojis={account.emojis}
/>
</div>
<AccountHeaderFields accountId={accountId} />
</div>
<AccountLinks accountId={accountId} />
<AccountNumberFields accountId={accountId} />
</div>
)}
</div>
@@ -0,0 +1,97 @@
import { useCallback, useMemo } from 'react';
import type { FC } from 'react';
import { FormattedMessage } from 'react-intl';
import { openModal } from '@/mastodon/actions/modal';
import { AccountFields } from '@/mastodon/components/account_fields';
import { EmojiHTML } from '@/mastodon/components/emoji/html';
import { FormattedDateWrapper } from '@/mastodon/components/formatted_date';
import { MiniCardList } from '@/mastodon/components/mini_card/list';
import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
import { useAccount } from '@/mastodon/hooks/useAccount';
import type { Account } from '@/mastodon/models/account';
import { useAppDispatch } from '@/mastodon/store';
import { isRedesignEnabled } from '../common';
import classes from './redesign.module.scss';
export const AccountHeaderFields: FC<{ accountId: string }> = ({
accountId,
}) => {
const account = useAccount(accountId);
if (!account) {
return null;
}
if (isRedesignEnabled()) {
return <RedesignAccountHeaderFields account={account} />;
}
return (
<div className='account__header__fields'>
<dl>
<dt>
<FormattedMessage id='account.joined_short' defaultMessage='Joined' />
</dt>
<dd>
<FormattedDateWrapper
value={account.created_at}
year='numeric'
month='short'
day='2-digit'
/>
</dd>
</dl>
<AccountFields fields={account.fields} emojis={account.emojis} />
</div>
);
};
const RedesignAccountHeaderFields: FC<{ account: Account }> = ({ account }) => {
const htmlHandlers = useElementHandledLink();
const cards = useMemo(
() =>
account.fields.toArray().map(({ value_emojified, name_emojified }) => ({
label: (
<EmojiHTML
htmlString={name_emojified}
extraEmojis={account.emojis}
className='translate'
as='span'
{...htmlHandlers}
/>
),
value: (
<EmojiHTML
as='span'
htmlString={value_emojified}
extraEmojis={account.emojis}
{...htmlHandlers}
/>
),
})),
[account.emojis, account.fields, htmlHandlers],
);
const dispatch = useAppDispatch();
const handleOverflowClick = useCallback(() => {
dispatch(
openModal({
modalType: 'ACCOUNT_FIELDS',
modalProps: { accountId: account.id },
}),
);
}, [account.id, dispatch]);
return (
<MiniCardList
cards={cards}
className={classes.fieldList}
onOverflowClick={handleOverflowClick}
/>
);
};
@@ -0,0 +1,80 @@
import type { FC } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { DisplayName } from '@/mastodon/components/display_name';
import { AnimateEmojiProvider } from '@/mastodon/components/emoji/context';
import { EmojiHTML } from '@/mastodon/components/emoji/html';
import { IconButton } from '@/mastodon/components/icon_button';
import { LoadingIndicator } from '@/mastodon/components/loading_indicator';
import { useElementHandledLink } from '@/mastodon/components/status/handled_link';
import { useAccount } from '@/mastodon/hooks/useAccount';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import classes from './redesign.module.scss';
export const AccountFieldsModal: FC<{
accountId: string;
onClose: () => void;
}> = ({ accountId, onClose }) => {
const intl = useIntl();
const account = useAccount(accountId);
const htmlHandlers = useElementHandledLink();
if (!account) {
return (
<div className='modal-root__modal dialog-modal'>
<LoadingIndicator />
</div>
);
}
return (
<div className='modal-root__modal dialog-modal'>
<div className='dialog-modal__header'>
<IconButton
icon='close'
className={classes.modalCloseButton}
onClick={onClose}
iconComponent={CloseIcon}
title={intl.formatMessage({
id: 'account_fields_modal.close',
defaultMessage: 'Close',
})}
/>
<span className={`${classes.modalTitle} dialog-modal__header__title`}>
<FormattedMessage
id='account_fields_modal.title'
defaultMessage="{name}'s info"
values={{
name: <DisplayName account={account} variant='simple' />,
}}
/>
</span>
</div>
<div className='dialog-modal__content'>
<AnimateEmojiProvider>
<dl className={classes.modalFieldsList}>
{account.fields.map((field, index) => (
<div key={index} className={classes.modalFieldItem}>
<EmojiHTML
as='dt'
htmlString={field.name_emojified}
extraEmojis={account.emojis}
className='translate'
{...htmlHandlers}
/>
<EmojiHTML
as='dd'
htmlString={field.value_emojified}
extraEmojis={account.emojis}
{...htmlHandlers}
/>
</div>
))}
</dl>
</AnimateEmojiProvider>
</div>
</div>
);
};
@@ -1,58 +0,0 @@
import type { FC } from 'react';
import { useIntl } from 'react-intl';
import { NavLink } from 'react-router-dom';
import {
FollowersCounter,
FollowingCounter,
StatusesCounter,
} from '@/mastodon/components/counters';
import { ShortNumber } from '@/mastodon/components/short_number';
import { useAccount } from '@/mastodon/hooks/useAccount';
export const AccountLinks: FC<{ accountId: string }> = ({ accountId }) => {
const intl = useIntl();
const account = useAccount(accountId);
if (!account) {
return null;
}
return (
<div className='account__header__extra__links'>
<NavLink
to={`/@${account.acct}`}
title={intl.formatNumber(account.statuses_count)}
>
<ShortNumber
value={account.statuses_count}
renderer={StatusesCounter}
/>
</NavLink>
<NavLink
exact
to={`/@${account.acct}/following`}
title={intl.formatNumber(account.following_count)}
>
<ShortNumber
value={account.following_count}
renderer={FollowingCounter}
/>
</NavLink>
<NavLink
exact
to={`/@${account.acct}/followers`}
title={intl.formatNumber(account.followers_count)}
>
<ShortNumber
value={account.followers_count}
renderer={FollowersCounter}
/>
</NavLink>
</div>
);
};
@@ -0,0 +1,94 @@
import type { FC } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import classNames from 'classnames';
import { NavLink } from 'react-router-dom';
import {
FollowersCounter,
FollowingCounter,
StatusesCounter,
} from '@/mastodon/components/counters';
import { FormattedDateWrapper } from '@/mastodon/components/formatted_date';
import { ShortNumber } from '@/mastodon/components/short_number';
import { useAccount } from '@/mastodon/hooks/useAccount';
import { isRedesignEnabled } from '../common';
import classes from './redesign.module.scss';
export const AccountNumberFields: FC<{ accountId: string }> = ({
accountId,
}) => {
const intl = useIntl();
const account = useAccount(accountId);
if (!account) {
return null;
}
return (
<div
className={classNames(
'account__header__extra__links',
isRedesignEnabled() && classes.fieldNumbersWrapper,
)}
>
{!isRedesignEnabled() && (
<NavLink
to={`/@${account.acct}`}
title={intl.formatNumber(account.statuses_count)}
>
<ShortNumber
value={account.statuses_count}
renderer={StatusesCounter}
/>
</NavLink>
)}
<NavLink
exact
to={`/@${account.acct}/following`}
title={intl.formatNumber(account.following_count)}
>
<ShortNumber
value={account.following_count}
renderer={FollowingCounter}
/>
</NavLink>
<NavLink
exact
to={`/@${account.acct}/followers`}
title={intl.formatNumber(account.followers_count)}
>
<ShortNumber
value={account.followers_count}
renderer={FollowersCounter}
/>
</NavLink>
{isRedesignEnabled() && (
<NavLink exact to={`/@${account.acct}`}>
<FormattedMessage
id='account.joined_long'
defaultMessage='Joined on {date}'
values={{
date: (
<strong>
<FormattedDateWrapper
value={account.created_at}
year='numeric'
month='short'
day='2-digit'
/>
</strong>
),
}}
/>
</NavLink>
)}
</div>
);
};
@@ -0,0 +1,47 @@
.fieldList {
margin-top: 16px;
}
.fieldNumbersWrapper {
a {
font-weight: unset;
}
}
.modalCloseButton {
padding: 8px;
border-radius: 50%;
border: 1px solid var(--color-border-primary);
}
.modalTitle {
flex-grow: 1;
text-align: center;
}
.modalFieldsList {
padding: 24px;
}
.modalFieldItem {
&:not(:first-child) {
padding-top: 12px;
}
&:not(:last-child)::after {
content: '';
display: block;
border-bottom: 1px solid var(--color-border-primary);
margin-top: 12px;
}
dt {
color: var(--color-text-secondary);
font-size: 13px;
}
dd {
font-weight: 600;
font-size: 15px;
}
}
@@ -85,6 +85,7 @@ export const MODAL_COMPONENTS = {
'IGNORE_NOTIFICATIONS': IgnoreNotificationsModal,
'ANNUAL_REPORT': AnnualReportModal,
'COMPOSE_PRIVACY': () => Promise.resolve({ default: VisibilityModal }),
'ACCOUNT_FIELDS': () => import('mastodon/features/account_timeline/components/fields_modal.tsx').then(module => ({ default: module.AccountFieldsModal })),
};
export default class ModalRoot extends PureComponent {