Change bookmarks and favourites to be tabs on the same screen in web UI

This commit is contained in:
Eugen Rochko 2023-05-11 15:33:23 +02:00
parent 5241f7b2fd
commit 17db7451a0
10 changed files with 262 additions and 42 deletions

View file

@ -24,10 +24,9 @@ const messages = defineMessages({
community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
explore: { id: 'navigation_bar.explore', defaultMessage: 'Explore' },
direct: { id: 'navigation_bar.direct', defaultMessage: 'Private mentions' },
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
saved: { id: 'navigation_bar.saved', defaultMessage: 'Saved posts' },
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
@ -111,8 +110,7 @@ class GettingStarted extends ImmutablePureComponent {
<ColumnSubheading key='header-personal' text={intl.formatMessage(messages.personal)} />,
<ColumnLink key='home' icon='home' text={intl.formatMessage(messages.home_timeline)} to='/home' />,
<ColumnLink key='direct' icon='at' text={intl.formatMessage(messages.direct)} to='/conversations' />,
<ColumnLink key='bookmark' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />,
<ColumnLink key='favourites' icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
<ColumnLink key='saved' icon='bookmark' text={intl.formatMessage(messages.saved)} to='/saved' />,
<ColumnLink key='lists' icon='list-ul' text={intl.formatMessage(messages.lists)} to='/lists' />,
);

View file

@ -0,0 +1,57 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import StatusList from 'mastodon/components/status_list';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from 'mastodon/actions/bookmarks';
import { debounce } from 'lodash';
const mapStateToProps = state => ({
statusIds: state.getIn(['status_lists', 'bookmarks', 'items']),
isLoading: state.getIn(['status_lists', 'bookmarks', 'isLoading'], true),
hasMore: !!state.getIn(['status_lists', 'bookmarks', 'next']),
});
class Bookmarks extends React.PureComponent {
static propTypes = {
statusIds: ImmutablePropTypes.list,
isLoading: PropTypes.bool,
hasMore: PropTypes.bool,
multiColumn: PropTypes.bool,
dispatch: PropTypes.func.isRequired,
};
componentDidMount () {
const { dispatch } = this.props;
dispatch(fetchBookmarkedStatuses());
}
handleLoadMore = debounce(() => {
const { dispatch } = this.props;
dispatch(expandBookmarkedStatuses());
}, 300, { leading: true });
render () {
const { isLoading, hasMore, statusIds, multiColumn } = this.props;
const emptyMessage = <FormattedMessage id='empty_column.bookmarked_statuses' defaultMessage="You don't have any bookmarked posts yet. When you bookmark one, it will show up here." />;
return (
<StatusList
trackScroll
statusIds={statusIds}
scrollKey='bookmarked_statuses'
hasMore={hasMore}
isLoading={isLoading}
onLoadMore={this.handleLoadMore}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
/>
);
}
}
export default connect(mapStateToProps)(Bookmarks);

View file

@ -0,0 +1,57 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import StatusList from 'mastodon/components/status_list';
import { FormattedMessage } from 'react-intl';
import { connect } from 'react-redux';
import { fetchFavouritedStatuses, expandFavouritedStatuses } from 'mastodon/actions/favourites';
import { debounce } from 'lodash';
const mapStateToProps = state => ({
statusIds: state.getIn(['status_lists', 'favourites', 'items']),
isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true),
hasMore: !!state.getIn(['status_lists', 'favourites', 'next']),
});
class Favourites extends React.PureComponent {
static propTypes = {
statusIds: ImmutablePropTypes.list,
isLoading: PropTypes.bool,
hasMore: PropTypes.bool,
multiColumn: PropTypes.bool,
dispatch: PropTypes.func.isRequired,
};
componentDidMount () {
const { dispatch } = this.props;
dispatch(fetchFavouritedStatuses());
}
handleLoadMore = debounce(() => {
const { dispatch } = this.props;
dispatch(expandFavouritedStatuses());
}, 300, { leading: true });
render () {
const { isLoading, hasMore, statusIds, multiColumn } = this.props;
const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite posts yet. When you favourite one, it will show up here." />;
return (
<StatusList
trackScroll
statusIds={statusIds}
scrollKey='favourited_statuses'
hasMore={hasMore}
isLoading={isLoading}
onLoadMore={this.handleLoadMore}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
/>
);
}
}
export default connect(mapStateToProps)(Favourites);

View file

@ -0,0 +1,76 @@
import React from 'react';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import { NavLink, Switch, Route, Redirect } from 'react-router-dom';
import Favourites from './favourites';
import Bookmarks from './bookmarks';
import { Helmet } from 'react-helmet';
const messages = defineMessages({
title: { id: 'saved.title', defaultMessage: 'Saved posts' },
});
class SavedStatuses extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,
identity: PropTypes.object,
};
static propTypes = {
intl: PropTypes.object.isRequired,
multiColumn: PropTypes.bool,
isSearching: PropTypes.bool,
};
handleHeaderClick = () => {
this.column.scrollTop();
};
setRef = c => {
this.column = c;
};
render() {
const { intl, multiColumn } = this.props;
return (
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
<ColumnHeader
icon={'bookmark'}
title={intl.formatMessage(messages.title)}
onClick={this.handleHeaderClick}
multiColumn={multiColumn}
/>
<div className='scrollable scrollable--flex'>
<div className='account__section-headline'>
<NavLink exact to='/saved/favourites'>
<FormattedMessage tagName='div' id='favourites.title' defaultMessage='Favourites' />
</NavLink>
<NavLink exact to='/saved/bookmarks'>
<FormattedMessage tagName='div' id='bookmarks.title' defaultMessage='Bookmarks' />
</NavLink>
</div>
<Switch>
<Redirect from='/saved' to='/saved/favourites' exact />
<Route path='/saved/favourites' component={Favourites} />
<Route path='/saved/bookmarks' component={Bookmarks} />
</Switch>
</div>
<Helmet>
<title>{intl.formatMessage(messages.title)}</title>
<meta name='robots' content='noindex' />
</Helmet>
</Column>
);
}
}
export default injectIntl(SavedStatuses);

View file

@ -19,8 +19,7 @@ const messages = defineMessages({
local: { id: 'tabs_bar.local_timeline', defaultMessage: 'Local' },
federated: { id: 'tabs_bar.federated_timeline', defaultMessage: 'Federated' },
direct: { id: 'navigation_bar.direct', defaultMessage: 'Private mentions' },
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
saved: { id: 'navigation_bar.saved', defaultMessage: 'Saved posts' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
followsAndFollowers: { id: 'navigation_bar.follows_and_followers', defaultMessage: 'Follows and followers' },
@ -81,8 +80,7 @@ class NavigationPanel extends React.Component {
{signedIn && (
<React.Fragment>
<ColumnLink transparent to='/conversations' icon='at' text={intl.formatMessage(messages.direct)} />
<ColumnLink transparent to='/bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} />
<ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} />
<ColumnLink transparent to='/saved' icon='bookmark' text={intl.formatMessage(messages.saved)} />
<ColumnLink transparent to='/lists' icon='list-ul' text={intl.formatMessage(messages.lists)} />
<ListPanel />

View file

@ -40,8 +40,7 @@ import {
HashtagTimeline,
Notifications,
FollowRequests,
FavouritedStatuses,
BookmarkedStatuses,
SavedStatuses,
FollowedTags,
ListTimeline,
Blocks,
@ -187,9 +186,8 @@ class SwitchingColumnsArea extends React.PureComponent {
<WrappedRoute path='/tags/:id' component={HashtagTimeline} content={children} />
<WrappedRoute path='/lists/:id' component={ListTimeline} content={children} />
<WrappedRoute path='/notifications' component={Notifications} content={children} />
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
<WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} />
<WrappedRoute path='/saved' component={SavedStatuses} content={children} />
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
<WrappedRoute path='/start' exact component={Onboarding} content={children} />

View file

@ -78,6 +78,10 @@ export function Favourites () {
return import(/* webpackChunkName: "features/favourites" */'../../favourites');
}
export function SavedStatuses () {
return import(/* webpackChunkName: "features/saved_statuses" */'../../saved_statuses');
}
export function FollowRequests () {
return import(/* webpackChunkName: "features/follow_requests" */'../../follow_requests');
}

View file

@ -61,28 +61,28 @@
"id": "account.requested"
},
{
"defaultMessage": "Unblock @{name}",
"id": "account.unblock"
"defaultMessage": "Unblock",
"id": "account.unblock_short"
},
{
"defaultMessage": "Unmute @{name}",
"id": "account.unmute"
"defaultMessage": "Unmute",
"id": "account.unmute_short"
},
{
"defaultMessage": "Mute notifications from @{name}",
"id": "account.mute_notifications"
"defaultMessage": "Mute notifications",
"id": "account.mute_notifications_short"
},
{
"defaultMessage": "Unmute notifications from @{name}",
"id": "account.unmute_notifications"
"defaultMessage": "Unmute notifications",
"id": "account.unmute_notifications_short"
},
{
"defaultMessage": "Mute @{name}",
"id": "account.mute"
"defaultMessage": "Mute",
"id": "account.mute_short"
},
{
"defaultMessage": "Block @{name}",
"id": "account.block"
"defaultMessage": "Block",
"id": "account.block_short"
}
],
"path": "app/javascript/mastodon/components/account.json"
@ -2409,10 +2409,6 @@
"defaultMessage": "Private mentions",
"id": "navigation_bar.direct"
},
{
"defaultMessage": "Bookmarks",
"id": "navigation_bar.bookmarks"
},
{
"defaultMessage": "Preferences",
"id": "navigation_bar.preferences"
@ -2422,8 +2418,8 @@
"id": "navigation_bar.follow_requests"
},
{
"defaultMessage": "Favourites",
"id": "navigation_bar.favourites"
"defaultMessage": "Saved posts",
"id": "navigation_bar.saved"
},
{
"defaultMessage": "Blocked users",
@ -3232,11 +3228,11 @@
"id": "onboarding.steps.setup_profile.body"
},
{
"defaultMessage": "Follow {count, plural, one {one person} other {# people}}",
"defaultMessage": "Find at least {count, plural, one {one person} other {# people}} to follow",
"id": "onboarding.steps.follow_people.title"
},
{
"defaultMessage": "You curate your own feed. Lets fill it with interesting people.",
"defaultMessage": "You curate your own home feed. Lets fill it with interesting people.",
"id": "onboarding.steps.follow_people.body"
},
{
@ -3648,6 +3644,41 @@
],
"path": "app/javascript/mastodon/features/report/thanks.json"
},
{
"descriptors": [
{
"defaultMessage": "You don't have any bookmarked posts yet. When you bookmark one, it will show up here.",
"id": "empty_column.bookmarked_statuses"
}
],
"path": "app/javascript/mastodon/features/saved_statuses/bookmarks.json"
},
{
"descriptors": [
{
"defaultMessage": "You don't have any favourite posts yet. When you favourite one, it will show up here.",
"id": "empty_column.favourited_statuses"
}
],
"path": "app/javascript/mastodon/features/saved_statuses/favourites.json"
},
{
"descriptors": [
{
"defaultMessage": "Saved posts",
"id": "saved.title"
},
{
"defaultMessage": "Favourites",
"id": "favourites.title"
},
{
"defaultMessage": "Bookmarks",
"id": "bookmarks.title"
}
],
"path": "app/javascript/mastodon/features/saved_statuses/index.json"
},
{
"descriptors": [
{
@ -4324,12 +4355,8 @@
"id": "navigation_bar.direct"
},
{
"defaultMessage": "Favourites",
"id": "navigation_bar.favourites"
},
{
"defaultMessage": "Bookmarks",
"id": "navigation_bar.bookmarks"
"defaultMessage": "Saved posts",
"id": "navigation_bar.saved"
},
{
"defaultMessage": "Lists",

View file

@ -17,6 +17,7 @@
"account.badges.group": "Group",
"account.block": "Block @{name}",
"account.block_domain": "Block domain {domain}",
"account.block_short": "Block",
"account.blocked": "Blocked",
"account.browse_more_on_origin_server": "Browse more on the original profile",
"account.cancel_follow_request": "Withdraw follow request",
@ -48,7 +49,8 @@
"account.mention": "Mention @{name}",
"account.moved_to": "{name} has indicated that their new account is now:",
"account.mute": "Mute @{name}",
"account.mute_notifications": "Mute notifications from @{name}",
"account.mute_notifications_short": "Mute notifications",
"account.mute_short": "Mute",
"account.muted": "Muted",
"account.open_original_page": "Open original page",
"account.posts": "Posts",
@ -65,7 +67,7 @@
"account.unendorse": "Don't feature on profile",
"account.unfollow": "Unfollow",
"account.unmute": "Unmute @{name}",
"account.unmute_notifications": "Unmute notifications from @{name}",
"account.unmute_notifications_short": "Unmute notifications",
"account.unmute_short": "Unmute",
"account_note.placeholder": "Click to add note",
"admin.dashboard.daily_retention": "User retention rate by day after sign-up",
@ -81,6 +83,7 @@
"attachments_list.unprocessed": "(unprocessed)",
"audio.hide": "Hide audio",
"autosuggest_hashtag.per_week": "{count} per week",
"bookmarks.title": "Bookmarks",
"boost_modal.combo": "You can press {combo} to skip this next time",
"bundle_column_error.copy_stacktrace": "Copy error report",
"bundle_column_error.error.body": "The requested page could not be rendered. It could be due to a bug in our code, or a browser compatibility issue.",
@ -245,6 +248,7 @@
"explore.trending_links": "News",
"explore.trending_statuses": "Posts",
"explore.trending_tags": "Hashtags",
"favourites.title": "Favourites",
"filter_modal.added.context_mismatch_explanation": "This filter category does not apply to the context in which you have accessed this post. If you want the post to be filtered in this context too, you will have to edit the filter.",
"filter_modal.added.context_mismatch_title": "Context mismatch!",
"filter_modal.added.expired_explanation": "This filter category has expired, you will need to change the expiration date for it to apply.",
@ -389,6 +393,7 @@
"navigation_bar.pins": "Pinned posts",
"navigation_bar.preferences": "Preferences",
"navigation_bar.public_timeline": "Federated timeline",
"navigation_bar.saved": "Saved posts",
"navigation_bar.search": "Search",
"navigation_bar.security": "Security",
"not_signed_in_indicator.not_signed_in": "You need to login to access this resource.",
@ -548,6 +553,7 @@
"report_notification.categories.spam": "Spam",
"report_notification.categories.violation": "Rule violation",
"report_notification.open": "Open report",
"saved.title": "Saved posts",
"search.no_recent_searches": "No recent searches",
"search.placeholder": "Search",
"search.quick_action.account_search": "Profiles matching {x}",

View file

@ -15,12 +15,11 @@ Rails.application.routes.draw do
/conversations
/lists/(*any)
/notifications
/favourites
/bookmarks
/pinned
/start
/directory
/explore/(*any)
/saved/(*any)
/search
/publish
/follow_requests