Merge commit '3aed93711c0118ab68fa09f4d3ac2635cb3344b8' into glitch-soc/merge-upstream

This commit is contained in:
Claire committed 2025-06-11 22:52:21 +02:00
commit 86a7a1ef3b
65 files changed
+1919 -1198

No files matched your search

@@ -1,81 +0,0 @@
import { useMemo } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
import { openModal } from 'mastodon/actions/modal';
import { Dropdown } from 'mastodon/components/dropdown_menu';
import { useAppDispatch } from 'mastodon/store';
const messages = defineMessages({
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
preferences: {
id: 'navigation_bar.preferences',
defaultMessage: 'Preferences',
},
follow_requests: {
id: 'navigation_bar.follow_requests',
defaultMessage: 'Follow requests',
},
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
followed_tags: {
id: 'navigation_bar.followed_tags',
defaultMessage: 'Followed hashtags',
},
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
domain_blocks: {
id: 'navigation_bar.domain_blocks',
defaultMessage: 'Blocked domains',
},
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
});
export const ActionBar: React.FC = () => {
const dispatch = useAppDispatch();
const intl = useIntl();
const menu = useMemo(() => {
const handleLogoutClick = () => {
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT', modalProps: {} }));
};
return [
{
text: intl.formatMessage(messages.edit_profile),
href: '/settings/profile',
},
{
text: intl.formatMessage(messages.preferences),
href: '/settings/preferences',
},
null,
{
text: intl.formatMessage(messages.follow_requests),
to: '/follow_requests',
},
{ text: intl.formatMessage(messages.favourites), to: '/favourites' },
{ text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' },
{ text: intl.formatMessage(messages.lists), to: '/lists' },
{
text: intl.formatMessage(messages.followed_tags),
to: '/followed_tags',
},
null,
{ text: intl.formatMessage(messages.mutes), to: '/mutes' },
{ text: intl.formatMessage(messages.blocks), to: '/blocks' },
{
text: intl.formatMessage(messages.domain_blocks),
to: '/domain_blocks',
},
{ text: intl.formatMessage(messages.filters), href: '/filters' },
null,
{ text: intl.formatMessage(messages.logout), action: handleLogoutClick },
];
}, [intl, dispatch]);
return <Dropdown items={menu} icon='bars' iconComponent={MoreHorizIcon} />;
};
@@ -2,34 +2,44 @@ import { useCallback } from 'react';
import { useIntl, defineMessages } from 'react-intl';
import { useSelector, useDispatch } from 'react-redux';
import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import { cancelReplyCompose } from 'mastodon/actions/compose';
import { Account } from 'mastodon/components/account';
import { IconButton } from 'mastodon/components/icon_button';
import { me } from 'mastodon/initial_state';
import { ActionBar } from './action_bar';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
const messages = defineMessages({
cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' },
});
export const NavigationBar = () => {
const dispatch = useDispatch();
export const NavigationBar: React.FC = () => {
const dispatch = useAppDispatch();
const intl = useIntl();
const isReplying = useSelector(state => !!state.getIn(['compose', 'in_reply_to']));
const isReplying = useAppSelector(
(state) => !!state.compose.get('in_reply_to'),
);
const handleCancelClick = useCallback(() => {
dispatch(cancelReplyCompose());
}, [dispatch]);
if (!me) {
return null;
}
return (
<div className='navigation-bar'>
<Account id={me} minimal />
{isReplying ? <IconButton title={intl.formatMessage(messages.cancel)} iconComponent={CloseIcon} onClick={handleCancelClick} /> : <ActionBar />}
{isReplying && (
<IconButton
title={intl.formatMessage(messages.cancel)}
icon=''
iconComponent={CloseIcon}
onClick={handleCancelClick}
/>
)}
</div>
);
};
@@ -4,7 +4,6 @@ import { animated, useSpring } from '@react-spring/web';
import UploadFileIcon from '@/material-icons/400-24px/upload_file.svg?react';
import { Icon } from 'mastodon/components/icon';
import { reduceMotion } from 'mastodon/initial_state';
interface UploadProgressProps {
active: boolean;
@@ -20,7 +19,7 @@ export const UploadProgress: React.FC<UploadProgressProps> = ({
const styles = useSpring({
from: { width: '0%' },
to: { width: `${progress}%` },
immediate: reduceMotion || !active, // If this is not active, update the UI immediately.
immediate: !active, // If this is not active, update the UI immediately.
});
if (!active) {
return null;
@@ -1,138 +0,0 @@
import PropTypes from 'prop-types';
import { PureComponent } from 'react';
import { injectIntl, defineMessages } from 'react-intl';
import { Helmet } from 'react-helmet';
import { Link } from 'react-router-dom';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { connect } from 'react-redux';
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
import LogoutIcon from '@/material-icons/400-24px/logout.svg?react';
import MenuIcon from '@/material-icons/400-24px/menu.svg?react';
import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react';
import { openModal } from 'mastodon/actions/modal';
import Column from 'mastodon/components/column';
import { Icon } from 'mastodon/components/icon';
import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
import { changeComposing, mountCompose, unmountCompose } from '../../actions/compose';
import { mascot } from '../../initial_state';
import { isMobile } from '../../is_mobile';
import { Search } from './components/search';
import ComposeFormContainer from './containers/compose_form_container';
const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new post' },
});
const mapStateToProps = (state) => ({
columns: state.getIn(['settings', 'columns']),
});
class Compose extends PureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
columns: ImmutablePropTypes.list.isRequired,
multiColumn: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
componentDidMount () {
const { dispatch } = this.props;
dispatch(mountCompose());
}
componentWillUnmount () {
const { dispatch } = this.props;
dispatch(unmountCompose());
}
handleLogoutClick = e => {
const { dispatch } = this.props;
e.preventDefault();
e.stopPropagation();
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT' }));
return false;
};
onFocus = () => {
this.props.dispatch(changeComposing(true));
};
onBlur = () => {
this.props.dispatch(changeComposing(false));
};
render () {
const { multiColumn, intl } = this.props;
if (multiColumn) {
const { columns } = this.props;
return (
<div className='drawer' role='region' aria-label={intl.formatMessage(messages.compose)}>
<nav className='drawer__header'>
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><Icon id='bars' icon={MenuIcon} /></Link>
{!columns.some(column => column.get('id') === 'HOME') && (
<Link to='/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' icon={HomeIcon} /></Link>
)}
{!columns.some(column => column.get('id') === 'NOTIFICATIONS') && (
<Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><Icon id='bell' icon={NotificationsIcon} /></Link>
)}
{!columns.some(column => column.get('id') === 'COMMUNITY') && (
<Link to='/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' icon={PeopleIcon} /></Link>
)}
{!columns.some(column => column.get('id') === 'PUBLIC') && (
<Link to='/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' icon={PublicIcon} /></Link>
)}
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><Icon id='cog' icon={SettingsIcon} /></a>
<a href='/auth/sign_out' className='drawer__tab' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)} onClick={this.handleLogoutClick}><Icon id='sign-out' icon={LogoutIcon} /></a>
</nav>
{multiColumn && <Search /> }
<div className='drawer__pager'>
<div className='drawer__inner' onFocus={this.onFocus}>
<ComposeFormContainer autoFocus={!isMobile(window.innerWidth)} />
<div className='drawer__inner__mastodon'>
<img alt='' draggable='false' src={mascot || elephantUIPlane} />
</div>
</div>
</div>
</div>
);
}
return (
<Column onFocus={this.onFocus}>
<ComposeFormContainer />
<Helmet>
<meta name='robots' content='noindex' />
</Helmet>
</Column>
);
}
}
export default connect(mapStateToProps)(injectIntl(Compose));
@@ -0,0 +1,200 @@
import { useEffect, useCallback } from 'react';
import { useIntl, defineMessages } from 'react-intl';
import { Helmet } from 'react-helmet';
import { Link } from 'react-router-dom';
import type { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import elephantUIPlane from '@/images/elephant_ui_plane.svg';
import EditIcon from '@/material-icons/400-24px/edit_square.svg?react';
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
import LogoutIcon from '@/material-icons/400-24px/logout.svg?react';
import MenuIcon from '@/material-icons/400-24px/menu.svg?react';
import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react';
import { mountCompose, unmountCompose } from 'mastodon/actions/compose';
import { openModal } from 'mastodon/actions/modal';
import { Column } from 'mastodon/components/column';
import { ColumnHeader } from 'mastodon/components/column_header';
import { Icon } from 'mastodon/components/icon';
import { mascot } from 'mastodon/initial_state';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
import { Search } from './components/search';
import ComposeFormContainer from './containers/compose_form_container';
const messages = defineMessages({
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
notifications: {
id: 'tabs_bar.notifications',
defaultMessage: 'Notifications',
},
public: {
id: 'navigation_bar.public_timeline',
defaultMessage: 'Federated timeline',
},
community: {
id: 'navigation_bar.community_timeline',
defaultMessage: 'Local timeline',
},
preferences: {
id: 'navigation_bar.preferences',
defaultMessage: 'Preferences',
},
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new post' },
});
type ColumnMap = ImmutableMap<'id' | 'uuid' | 'params', string>;
const Compose: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
const intl = useIntl();
const dispatch = useAppDispatch();
const columns = useAppSelector(
(state) =>
(state.settings as ImmutableMap<string, unknown>).get(
'columns',
) as ImmutableList<ColumnMap>,
);
useEffect(() => {
dispatch(mountCompose());
return () => {
dispatch(unmountCompose());
};
}, [dispatch]);
const handleLogoutClick = useCallback(
(e: React.MouseEvent) => {
e.preventDefault();
e.stopPropagation();
dispatch(openModal({ modalType: 'CONFIRM_LOG_OUT', modalProps: {} }));
return false;
},
[dispatch],
);
if (multiColumn) {
return (
<div
className='drawer'
role='region'
aria-label={intl.formatMessage(messages.compose)}
>
<nav className='drawer__header'>
<Link
to='/getting-started'
className='drawer__tab'
title={intl.formatMessage(messages.start)}
aria-label={intl.formatMessage(messages.start)}
>
<Icon id='bars' icon={MenuIcon} />
</Link>
{!columns.some((column) => column.get('id') === 'HOME') && (
<Link
to='/home'
className='drawer__tab'
title={intl.formatMessage(messages.home_timeline)}
aria-label={intl.formatMessage(messages.home_timeline)}
>
<Icon id='home' icon={HomeIcon} />
</Link>
)}
{!columns.some((column) => column.get('id') === 'NOTIFICATIONS') && (
<Link
to='/notifications'
className='drawer__tab'
title={intl.formatMessage(messages.notifications)}
aria-label={intl.formatMessage(messages.notifications)}
>
<Icon id='bell' icon={NotificationsIcon} />
</Link>
)}
{!columns.some((column) => column.get('id') === 'COMMUNITY') && (
<Link
to='/public/local'
className='drawer__tab'
title={intl.formatMessage(messages.community)}
aria-label={intl.formatMessage(messages.community)}
>
<Icon id='users' icon={PeopleIcon} />
</Link>
)}
{!columns.some((column) => column.get('id') === 'PUBLIC') && (
<Link
to='/public'
className='drawer__tab'
title={intl.formatMessage(messages.public)}
aria-label={intl.formatMessage(messages.public)}
>
<Icon id='globe' icon={PublicIcon} />
</Link>
)}
<a
href='/settings/preferences'
className='drawer__tab'
title={intl.formatMessage(messages.preferences)}
aria-label={intl.formatMessage(messages.preferences)}
>
<Icon id='cog' icon={SettingsIcon} />
</a>
<a
href='/auth/sign_out'
className='drawer__tab'
title={intl.formatMessage(messages.logout)}
aria-label={intl.formatMessage(messages.logout)}
onClick={handleLogoutClick}
>
<Icon id='sign-out' icon={LogoutIcon} />
</a>
</nav>
<Search singleColumn={false} />
<div className='drawer__pager'>
<div className='drawer__inner'>
<ComposeFormContainer />
<div className='drawer__inner__mastodon'>
<img alt='' draggable='false' src={mascot ?? elephantUIPlane} />
</div>
</div>
</div>
</div>
);
}
return (
<Column
bindToDocument={!multiColumn}
label={intl.formatMessage(messages.compose)}
>
<ColumnHeader
icon='pencil'
iconComponent={EditIcon}
title={intl.formatMessage(messages.compose)}
multiColumn={multiColumn}
showBackButton
/>
<div className='scrollable'>
<ComposeFormContainer />
</div>
<Helmet>
<meta name='robots' content='noindex' />
</Helmet>
</Column>
);
};
// eslint-disable-next-line import/no-default-export
export default Compose;