1
0
Fork 0
forked from gitea/nas

Merge remote-tracking branch 'parent/main' into upstream-20240308

This commit is contained in:
KMY 2024-03-12 09:27:01 +09:00
commit 8e94ed2cec
204 changed files with 5112 additions and 1998 deletions

View file

@ -1,28 +1,31 @@
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { NavLink } from 'react-router-dom';
import { useRouteMatch, NavLink } from 'react-router-dom';
import { Icon } from 'mastodon/components/icon';
const ColumnLink = ({ icon, iconComponent, text, to, href, method, badge, transparent, children, ...other }) => {
const ColumnLink = ({ icon, activeIcon, iconComponent, activeIconComponent, text, to, href, method, badge, transparent, children, ...other }) => {
const match = useRouteMatch(to);
const className = classNames('column-link', { 'column-link--transparent': transparent });
const badgeElement = typeof badge !== 'undefined' ? <span className='column-link__badge'>{badge}</span> : null;
const iconElement = (typeof icon === 'string' || iconComponent) ? <Icon id={icon} icon={iconComponent} className='column-link__icon' /> : icon;
const activeIconElement = activeIcon ?? (activeIconComponent ? <Icon id={icon} icon={activeIconComponent} className='column-link__icon' /> : iconElement);
const active = match?.isExact;
const childElement = typeof children !== 'undefined' ? <p>{children}</p> : null;
if (href) {
return (
<a href={href} className={className} data-method={method} title={text} {...other}>
{iconElement}
{active ? activeIconElement : iconElement}
<span>{text}</span>
{badgeElement}
</a>
);
} else {
return (
<NavLink to={to} className={className} title={text} {...other}>
{iconElement}
<NavLink to={to} className={className} title={text} exact {...other}>
{active ? activeIconElement : iconElement}
<span>{text}</span>
{badgeElement}
{childElement}
@ -34,6 +37,8 @@ const ColumnLink = ({ icon, iconComponent, text, to, href, method, badge, transp
ColumnLink.propTypes = {
icon: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,
iconComponent: PropTypes.func,
activeIcon: PropTypes.node,
activeIconComponent: PropTypes.func,
text: PropTypes.string.isRequired,
to: PropTypes.string,
href: PropTypes.string,

View file

@ -1,55 +0,0 @@
import PropTypes from 'prop-types';
import { Component } from 'react';
import { injectIntl, defineMessages } from 'react-intl';
import { List as ImmutableList } from 'immutable';
import { connect } from 'react-redux';
import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
import { fetchFollowRequests } from 'mastodon/actions/accounts';
import { IconWithBadge } from 'mastodon/components/icon_with_badge';
import ColumnLink from 'mastodon/features/ui/components/column_link';
const messages = defineMessages({
text: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
});
const mapStateToProps = state => ({
count: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size,
});
class FollowRequestsColumnLink extends Component {
static propTypes = {
dispatch: PropTypes.func.isRequired,
count: PropTypes.number.isRequired,
intl: PropTypes.object.isRequired,
};
componentDidMount () {
const { dispatch } = this.props;
dispatch(fetchFollowRequests());
}
render () {
const { count, intl } = this.props;
if (count === 0) {
return null;
}
return (
<ColumnLink
transparent
to='/follow_requests'
icon={<IconWithBadge className='column-link__icon' id='user-plus' icon={PersonAddIcon} count={count} />}
text={intl.formatMessage(messages.text)}
/>
);
}
}
export default injectIntl(connect(mapStateToProps)(FollowRequestsColumnLink));

View file

@ -1,10 +1,9 @@
import PropTypes from 'prop-types';
import { useEffect } from 'react';
import { createSelector } from '@reduxjs/toolkit';
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import ListAltActiveIcon from '@/material-icons/400-24px/list_alt-fill.svg?react';
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
import { fetchAntennas } from 'mastodon/actions/antennas';
@ -28,47 +27,31 @@ const getOrderedAntennas = createSelector([state => state.get('antennas')], ante
return antennas.toList().filter(item => !!item && !item.get('insert_feeds')).sort((a, b) => a.get('title').localeCompare(b.get('title'))).take(8);
});
const mapStateToProps = state => ({
lists: getOrderedLists(state),
antennas: getOrderedAntennas(state),
});
export const ListPanel = () => {
const dispatch = useDispatch();
const lists = useSelector(state => getOrderedLists(state));
const antennas = useSelector(state => getOrderedAntennas(state));
class ListPanel extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
lists: ImmutablePropTypes.list,
antennas: ImmutablePropTypes.list,
};
componentDidMount () {
const { dispatch } = this.props;
useEffect(() => {
dispatch(fetchLists());
dispatch(fetchAntennas());
}, [dispatch]);
const size = (lists ? lists.size : 0) + (antennas ? antennas.size : 0);
if (size === 0) {
return null;
}
render () {
const { lists, antennas } = this.props;
const size = (lists ? lists.size : 0) + (antennas ? antennas.size : 0);
return (
<div className='list-panel'>
<hr />
if (size === 0) {
return null;
}
return (
<div className='list-panel'>
<hr />
{lists && lists.map(list => (
<ColumnLink icon='list-ul' iconComponent={ListAltIcon} key={list.get('id')} strict text={list.get('title')} to={`/lists/${list.get('id')}`} transparent />
))}
{antennas && antennas.take(8 - (lists ? lists.size : 0)).map(antenna => (
<ColumnLink icon='wifi' iconComponent={AntennaIcon} key={antenna.get('id')} strict text={antenna.get('title')} to={`/antennast/${antenna.get('id')}`} transparent />
))}
</div>
);
}
}
export default connect(mapStateToProps)(ListPanel);
{lists && lists.map(list => (
<ColumnLink icon='list-ul' key={list.get('id')} iconComponent={ListAltIcon} activeIconComponent={ListAltActiveIcon} text={list.get('title')} to={`/lists/${list.get('id')}`} transparent />
))}
{antennas && antennas.map(antenna => (
<ColumnLink icon='wifi' key={antenna.get('id')} iconComponent={AntennaIcon} activeIconComponent={AntennaIcon} text={antenna.get('title')} to={`/antennast/${antenna.get('id')}`} transparent />
))}
</div>
);
};

View file

@ -1,23 +1,35 @@
import PropTypes from 'prop-types';
import { Component } from 'react';
import { Component, useEffect } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import { defineMessages, injectIntl, useIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import CirclesIcon from '@/material-icons/400-24px/account_circle-fill.svg?react';
import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?react';
import BookmarksIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
import BookmarksActiveIcon from '@/material-icons/400-24px/bookmarks-fill.svg?react';
import BookmarksIcon from '@/material-icons/400-24px/bookmarks.svg?react';
import ExploreIcon from '@/material-icons/400-24px/explore.svg?react';
import PeopleIcon from '@/material-icons/400-24px/group.svg?react';
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
import HomeActiveIcon from '@/material-icons/400-24px/home-fill.svg?react';
import HomeIcon from '@/material-icons/400-24px/home.svg?react';
import ListAltActiveIcon from '@/material-icons/400-24px/list_alt-fill.svg?react';
import ListAltIcon from '@/material-icons/400-24px/list_alt.svg?react';
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
import NotificationsActiveIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
import NotificationsIcon from '@/material-icons/400-24px/notifications.svg?react';
import PersonAddActiveIcon from '@/material-icons/400-24px/person_add-fill.svg?react';
import PersonAddIcon from '@/material-icons/400-24px/person_add.svg?react';
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
import SearchIcon from '@/material-icons/400-24px/search.svg?react';
import SettingsIcon from '@/material-icons/400-24px/settings-fill.svg?react';
import StarIcon from '@/material-icons/400-24px/star-fill.svg?react';
import SettingsIcon from '@/material-icons/400-24px/settings.svg?react';
import StarActiveIcon from '@/material-icons/400-24px/star-fill.svg?react';
import StarIcon from '@/material-icons/400-24px/star.svg?react';
import AntennaIcon from '@/material-icons/400-24px/wifi.svg?react';
import { fetchFollowRequests } from 'mastodon/actions/accounts';
import { IconWithBadge } from 'mastodon/components/icon_with_badge';
import { WordmarkLogo } from 'mastodon/components/logo';
import { NavigationPortal } from 'mastodon/components/navigation_portal';
import { enableDtlMenu, timelinePreview, trendsEnabled, dtlTag, enableLocalTimeline, isHideItem } from 'mastodon/initial_state';
@ -25,9 +37,7 @@ import { transientSingleColumn } from 'mastodon/is_mobile';
import ColumnLink from './column_link';
import DisabledAccountBanner from './disabled_account_banner';
import FollowRequestsColumnLink from './follow_requests_column_link';
import ListPanel from './list_panel';
import NotificationsCounterIcon from './notifications_counter_icon';
import { ListPanel } from './list_panel';
import SignInBanner from './sign_in_banner';
const messages = defineMessages({
@ -49,8 +59,48 @@ const messages = defineMessages({
search: { id: 'navigation_bar.search', defaultMessage: 'Search' },
advancedInterface: { id: 'navigation_bar.advanced_interface', defaultMessage: 'Open in advanced web interface' },
openedInClassicInterface: { id: 'navigation_bar.opened_in_classic_interface', defaultMessage: 'Posts, accounts, and other specific pages are opened by default in the classic web interface.' },
followRequests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
});
const NotificationsLink = () => {
const count = useSelector(state => state.getIn(['notifications', 'unread']));
const intl = useIntl();
return (
<ColumnLink
transparent
to='/notifications'
icon={<IconWithBadge icon={NotificationsIcon} count={count} className='column-link__icon' />}
activeIcon={<IconWithBadge icon={NotificationsActiveIcon} count={count} className='column-link__icon' />}
text={intl.formatMessage(messages.notifications)}
/>
);
};
const FollowRequestsLink = () => {
const count = useSelector(state => state.getIn(['user_lists', 'follow_requests', 'items'])?.size ?? 0);
const intl = useIntl();
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchFollowRequests());
}, [dispatch]);
if (count === 0) {
return null;
}
return (
<ColumnLink
transparent
to='/follow_requests'
icon={<IconWithBadge icon={PersonAddIcon} count={count} className='column-link__icon' />}
activeIcon={<IconWithBadge icon={PersonAddActiveIcon} count={count} className='column-link__icon' />}
text={intl.formatMessage(messages.followRequests)}
/>
);
};
class NavigationPanel extends Component {
static contextTypes = {
@ -104,8 +154,8 @@ class NavigationPanel extends Component {
{signedIn && (
<>
<ColumnLink transparent to='/home' icon='home' iconComponent={HomeIcon} text={intl.formatMessage(messages.home)} />
<ColumnLink transparent to='/notifications' icon={<NotificationsCounterIcon className='column-link__icon' />} text={intl.formatMessage(messages.notifications)} />
<ColumnLink transparent to='/home' icon='home' iconComponent={HomeIcon} activeIconComponent={HomeActiveIcon} text={intl.formatMessage(messages.home)} />
<NotificationsLink />
</>
)}
@ -136,10 +186,10 @@ class NavigationPanel extends Component {
{signedIn && (
<>
<ColumnLink transparent to='/lists' icon='list-ul' iconComponent={ListAltIcon} text={intl.formatMessage(messages.lists)} />
<ColumnLink transparent to='/lists' icon='list-ul' iconComponent={ListAltIcon} activeIconComponent={ListAltActiveIcon} text={intl.formatMessage(messages.lists)} />
<ColumnLink transparent to='/antennasw' icon='wifi' iconComponent={AntennaIcon} text={intl.formatMessage(messages.antennas)} isActive={this.isAntennasActive} />
<ColumnLink transparent to='/circles' icon='user-circle' iconComponent={CirclesIcon} text={intl.formatMessage(messages.circles)} />
<FollowRequestsColumnLink />
<FollowRequestsLink />
<ColumnLink transparent to='/conversations' icon='at' iconComponent={AlternateEmailIcon} text={intl.formatMessage(messages.direct)} />
</>
)}
@ -148,8 +198,8 @@ class NavigationPanel extends Component {
{signedIn && (
<>
<ColumnLink transparent to='/bookmark_categories' icon='bookmarks' iconComponent={BookmarksIcon} text={intl.formatMessage(messages.bookmarks)} />
{ !isHideItem('favourite_menu') && <ColumnLink transparent to='/favourites' icon='star' iconComponent={StarIcon} text={intl.formatMessage(messages.favourites)} /> }
<ColumnLink transparent to='/bookmark_categories' icon='bookmarks' iconComponent={BookmarksIcon} activeIconComponent={BookmarksActiveIcon} text={intl.formatMessage(messages.bookmarks)} />
{ !isHideItem('favourite_menu') && <ColumnLink transparent to='/favourites' icon='star' iconComponent={StarIcon} activeIconComponent={StarActiveIcon} text={intl.formatMessage(messages.favourites)} /> }
<hr />
<ColumnLink transparent href='/settings/preferences' icon='cog' iconComponent={SettingsIcon} text={intl.formatMessage(messages.preferences)} />

View file

@ -1,13 +0,0 @@
import { connect } from 'react-redux';
import NotificationsIcon from '@/material-icons/400-24px/notifications-fill.svg?react';
import { IconWithBadge } from 'mastodon/components/icon_with_badge';
const mapStateToProps = state => ({
count: state.getIn(['notifications', 'unread']),
id: 'bell',
icon: NotificationsIcon,
});
export default connect(mapStateToProps)(IconWithBadge);

View file

@ -52,6 +52,8 @@ import {
HashtagTimeline,
AntennaTimeline,
Notifications,
NotificationRequests,
NotificationRequest,
FollowRequests,
FavouritedStatuses,
EmojiReactedStatuses,
@ -93,7 +95,6 @@ const mapStateToProps = state => ({
hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0,
hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < 4,
dropdownMenuIsOpen: state.dropdownMenu.openId !== null,
firstLaunch: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION,
username: state.getIn(['accounts', me, 'username']),
});
@ -221,7 +222,9 @@ class SwitchingColumnsArea extends PureComponent {
<WrappedRoute path='/lists/:id' component={ListTimeline} content={children} />
<WrappedRoute path='/antennasw/:id' component={AntennaSetting} content={children} />
<WrappedRoute path='/antennast/:id' component={AntennaTimeline} content={children} />
<WrappedRoute path='/notifications' component={Notifications} content={children} />
<WrappedRoute path='/notifications' component={Notifications} content={children} exact />
<WrappedRoute path='/notifications/requests' component={NotificationRequests} content={children} exact />
<WrappedRoute path='/notifications/requests/:id' component={NotificationRequest} content={children} exact />
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
<WrappedRoute path='/emoji_reactions' component={EmojiReactedStatuses} content={children} />
@ -292,7 +295,6 @@ class UI extends PureComponent {
hasMediaAttachments: PropTypes.bool,
canUploadMore: PropTypes.bool,
intl: PropTypes.object.isRequired,
dropdownMenuIsOpen: PropTypes.bool,
layout: PropTypes.string.isRequired,
firstLaunch: PropTypes.bool,
username: PropTypes.string,
@ -589,7 +591,7 @@ class UI extends PureComponent {
render () {
const { draggingOver } = this.state;
const { children, isComposing, location, dropdownMenuIsOpen, layout } = this.props;
const { children, isComposing, location, layout } = this.props;
const handlers = {
help: this.handleHotkeyToggleHelp,
@ -616,7 +618,7 @@ class UI extends PureComponent {
return (
<HotKeys keyMap={keyMap} handlers={handlers} ref={this.setHotkeysRef} attach={window} focused>
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef} style={{ pointerEvents: dropdownMenuIsOpen ? 'none' : null }}>
<div className={classNames('ui', { 'is-composing': isComposing })} ref={this.setRef}>
<Header />
<SwitchingColumnsArea location={location} singleColumn={layout === 'mobile' || layout === 'single-column'}>

View file

@ -257,3 +257,11 @@ export function About () {
export function PrivacyPolicy () {
return import(/*webpackChunkName: "features/privacy_policy" */'../../privacy_policy');
}
export function NotificationRequests () {
return import(/*webpackChunkName: "features/notifications/requests" */'../../notifications/requests');
}
export function NotificationRequest () {
return import(/*webpackChunkName: "features/notifications/request" */'../../notifications/request');
}