Change bookmarks and favourites to be tabs on the same screen in web UI
This commit is contained in:
parent
5241f7b2fd
commit
17db7451a0
10 changed files with 262 additions and 42 deletions
|
@ -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' />,
|
||||
);
|
||||
|
||||
|
|
|
@ -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);
|
|
@ -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);
|
76
app/javascript/mastodon/features/saved_statuses/index.jsx
Normal file
76
app/javascript/mastodon/features/saved_statuses/index.jsx
Normal 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);
|
|
@ -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 />
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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');
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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}",
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue