A app/javascript/flavours/glitch/actions/bookmark_folders.js => app/javascript/flavours/glitch/actions/bookmark_folders.js +146 -0
@@ 0,0 1,146 @@
+import api from '../api';
+
+export const BOOKMARK_FOLDERS_FETCH_REQUEST = 'BOOKMARK_FOLDERS_FETCH_REQUEST';
+export const BOOKMARK_FOLDERS_FETCH_SUCCESS = 'BOOKMARK_FOLDERS_FETCH_SUCCESS';
+export const BOOKMARK_FOLDERS_FETCH_FAIL = 'BOOKMARK_FOLDERS_FETCH_FAIL';
+
+export const BOOKMARK_FOLDER_CREATE_REQUEST = 'BOOKMARK_FOLDER_CREATE_REQUEST';
+export const BOOKMARK_FOLDER_CREATE_SUCCESS = 'BOOKMARK_FOLDER_CREATE_SUCCESS';
+export const BOOKMARK_FOLDER_CREATE_FAIL = 'BOOKMARK_FOLDER_CREATE_FAIL';
+
+export const BOOKMARK_FOLDER_UPDATE_REQUEST = 'BOOKMARK_FOLDER_UPDATE_REQUEST';
+export const BOOKMARK_FOLDER_UPDATE_SUCCESS = 'BOOKMARK_FOLDER_UPDATE_SUCCESS';
+export const BOOKMARK_FOLDER_UPDATE_FAIL = 'BOOKMARK_FOLDER_UPDATE_FAIL';
+
+export const BOOKMARK_FOLDER_DELETE_REQUEST = 'BOOKMARK_FOLDER_DELETE_REQUEST';
+export const BOOKMARK_FOLDER_DELETE_SUCCESS = 'BOOKMARK_FOLDER_DELETE_SUCCESS';
+export const BOOKMARK_FOLDER_DELETE_FAIL = 'BOOKMARK_FOLDER_DELETE_FAIL';
+
+export const BOOKMARK_FOLDER_EDITOR_NAME_CHANGE = 'BOOKMARK_FOLDER_EDITOR_NAME_CHANGE';
+export const BOOKMARK_FOLDER_EDITOR_RESET = 'BOOKMARK_FOLDER_EDITOR_RESET';
+export const BOOKMARK_FOLDER_EDITOR_SETUP = 'BOOKMARK_FOLDER_EDITOR_SETUP';
+
+export const fetchBookmarkFolders = () => (dispatch, getState) => {
+ dispatch(fetchBookmarkFoldersRequest());
+
+ api(getState).get('/api/v1/bookmark_folders')
+ .then(({ data }) => dispatch(fetchBookmarkFoldersSuccess(data)))
+ .catch(err => dispatch(fetchBookmarkFoldersFail(err)));
+};
+
+export const fetchBookmarkFoldersRequest = () => ({
+ type: BOOKMARK_FOLDERS_FETCH_REQUEST,
+});
+
+export const fetchBookmarkFoldersSuccess = folders => ({
+ type: BOOKMARK_FOLDERS_FETCH_SUCCESS,
+ folders,
+});
+
+export const fetchBookmarkFoldersFail = error => ({
+ type: BOOKMARK_FOLDERS_FETCH_FAIL,
+ error,
+});
+
+export const createBookmarkFolder = (name, shouldReset) => (dispatch, getState) => {
+ dispatch(createBookmarkFolderRequest());
+
+ api(getState).post('/api/v1/bookmark_folders', { name }).then(({ data }) => {
+ dispatch(createBookmarkFolderSuccess(data));
+
+ if (shouldReset) {
+ dispatch(resetBookmarkFolderEditor());
+ }
+ }).catch(err => dispatch(createBookmarkFolderFail(err)));
+};
+
+export const createBookmarkFolderRequest = () => ({
+ type: BOOKMARK_FOLDER_CREATE_REQUEST,
+});
+
+export const createBookmarkFolderSuccess = folder => ({
+ type: BOOKMARK_FOLDER_CREATE_SUCCESS,
+ folder,
+});
+
+export const createBookmarkFolderFail = error => ({
+ type: BOOKMARK_FOLDER_CREATE_FAIL,
+ error,
+});
+
+export const updateBookmarkFolder = (id, name, shouldReset) => (dispatch, getState) => {
+ dispatch(updateBookmarkFolderRequest(id));
+
+ api(getState).put(`/api/v1/bookmark_folders/${id}`, { name }).then(({ data }) => {
+ dispatch(updateBookmarkFolderSuccess(data));
+
+ if (shouldReset) {
+ dispatch(resetBookmarkFolderEditor());
+ }
+ }).catch(err => dispatch(updateBookmarkFolderFail(id, err)));
+};
+
+export const updateBookmarkFolderRequest = id => ({
+ type: BOOKMARK_FOLDER_UPDATE_REQUEST,
+ id,
+});
+
+export const updateBookmarkFolderSuccess = folder => ({
+ type: BOOKMARK_FOLDER_UPDATE_SUCCESS,
+ folder,
+});
+
+export const updateBookmarkFolderFail = (id, error) => ({
+ type: BOOKMARK_FOLDER_UPDATE_FAIL,
+ id,
+ error,
+});
+
+export const deleteBookmarkFolder = id => (dispatch, getState) => {
+ dispatch(deleteBookmarkFolderRequest(id));
+
+ api(getState).delete(`/api/v1/bookmark_folders/${id}`)
+ .then(() => dispatch(deleteBookmarkFolderSuccess(id)))
+ .catch(err => dispatch(deleteBookmarkFolderFail(id, err)));
+};
+
+export const deleteBookmarkFolderRequest = id => ({
+ type: BOOKMARK_FOLDER_DELETE_REQUEST,
+ id,
+});
+
+export const deleteBookmarkFolderSuccess = id => ({
+ type: BOOKMARK_FOLDER_DELETE_SUCCESS,
+ id,
+});
+
+export const deleteBookmarkFolderFail = (id, error) => ({
+ type: BOOKMARK_FOLDER_DELETE_FAIL,
+ id,
+ error,
+});
+
+export const submitBookmarkFolderEditor = shouldReset => (dispatch, getState) => {
+ const folderId = getState().getIn(['bookmarkFolderEditor', 'folderId']);
+ const name = getState().getIn(['bookmarkFolderEditor', 'name']);
+
+ if (folderId === null) {
+ dispatch(createBookmarkFolder(name, shouldReset));
+ } else {
+ dispatch(updateBookmarkFolder(folderId, name, shouldReset));
+ }
+};
+
+export const resetBookmarkFolderEditor = () => ({
+ type: BOOKMARK_FOLDER_EDITOR_RESET,
+});
+
+export const setupBookmarkFolderEditor = folderId => (dispatch, getState) => dispatch({
+ type: BOOKMARK_FOLDER_EDITOR_SETUP,
+ folder: getState().getIn(['bookmarkFolders', folderId]),
+});
+
+export const changeBookmarkFolderEditorName = value => ({
+ type: BOOKMARK_FOLDER_EDITOR_NAME_CHANGE,
+ value,
+});
M app/javascript/flavours/glitch/actions/bookmarks.js => app/javascript/flavours/glitch/actions/bookmarks.js +28 -18
@@ 10,82 10,92 @@ export const BOOKMARKED_STATUSES_EXPAND_REQUEST = 'BOOKMARKED_STATUSES_EXPAND_RE
export const BOOKMARKED_STATUSES_EXPAND_SUCCESS = 'BOOKMARKED_STATUSES_EXPAND_SUCCESS';
export const BOOKMARKED_STATUSES_EXPAND_FAIL = 'BOOKMARKED_STATUSES_EXPAND_FAIL';
-export function fetchBookmarkedStatuses() {
+export function fetchBookmarkedStatuses(folderId) {
return (dispatch, getState) => {
- if (getState().getIn(['status_lists', 'bookmarks', 'isLoading'])) {
+ const key = folderId ? `bookmarks:${folderId}`: 'bookmarks';
+
+ if (getState().getIn(['status_lists', key, 'isLoading'])) {
return;
}
- dispatch(fetchBookmarkedStatusesRequest());
+ dispatch(fetchBookmarkedStatusesRequest(folderId));
- api(getState).get('/api/v1/bookmarks').then(response => {
+ api(getState).get('/api/v1/bookmarks', { params: { folder_id: folderId } }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
- dispatch(fetchBookmarkedStatusesSuccess(response.data, next ? next.uri : null));
+ dispatch(fetchBookmarkedStatusesSuccess(response.data, next ? next.uri : null, folderId));
}).catch(error => {
- dispatch(fetchBookmarkedStatusesFail(error));
+ dispatch(fetchBookmarkedStatusesFail(error, folderId));
});
};
}
-export function fetchBookmarkedStatusesRequest() {
+export function fetchBookmarkedStatusesRequest(folderId) {
return {
type: BOOKMARKED_STATUSES_FETCH_REQUEST,
+ folderId,
};
}
-export function fetchBookmarkedStatusesSuccess(statuses, next) {
+export function fetchBookmarkedStatusesSuccess(statuses, next, folderId) {
return {
type: BOOKMARKED_STATUSES_FETCH_SUCCESS,
statuses,
next,
+ folderId,
};
}
-export function fetchBookmarkedStatusesFail(error) {
+export function fetchBookmarkedStatusesFail(error, folderId) {
return {
type: BOOKMARKED_STATUSES_FETCH_FAIL,
error,
+ folderId,
};
}
-export function expandBookmarkedStatuses() {
+export function expandBookmarkedStatuses(folderId) {
return (dispatch, getState) => {
- const url = getState().getIn(['status_lists', 'bookmarks', 'next'], null);
+ const key = folderId ? `bookmarks:${folderId}`: 'bookmarks';
+
+ const url = getState().getIn(['status_lists', key, 'next'], null);
- if (url === null || getState().getIn(['status_lists', 'bookmarks', 'isLoading'])) {
+ if (url === null || getState().getIn(['status_lists', key, 'isLoading'])) {
return;
}
- dispatch(expandBookmarkedStatusesRequest());
+ dispatch(expandBookmarkedStatusesRequest(folderId));
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
- dispatch(expandBookmarkedStatusesSuccess(response.data, next ? next.uri : null));
+ dispatch(expandBookmarkedStatusesSuccess(response.data, next ? next.uri : null, folderId));
}).catch(error => {
- dispatch(expandBookmarkedStatusesFail(error));
+ dispatch(expandBookmarkedStatusesFail(error, folderId));
});
};
}
-export function expandBookmarkedStatusesRequest() {
+export function expandBookmarkedStatusesRequest(folderId) {
return {
type: BOOKMARKED_STATUSES_EXPAND_REQUEST,
+ folderId,
};
}
-export function expandBookmarkedStatusesSuccess(statuses, next) {
+export function expandBookmarkedStatusesSuccess(statuses, next, folderId) {
return {
type: BOOKMARKED_STATUSES_EXPAND_SUCCESS,
statuses,
next,
+ folderId,
};
}
-export function expandBookmarkedStatusesFail(error) {
+export function expandBookmarkedStatusesFail(error, folderId) {
return {
type: BOOKMARKED_STATUSES_EXPAND_FAIL,
error,
+ folderId,
};
}
M app/javascript/flavours/glitch/actions/interactions.js => app/javascript/flavours/glitch/actions/interactions.js +27 -4
@@ 1,7 1,11 @@
+import { defineMessages } from 'react-intl';
+
import api, { getLinks } from '../api';
import { fetchRelationships } from './accounts';
+import { showAlert } from './alerts';
import { importFetchedAccounts, importFetchedStatus } from './importer';
+import { openModal } from './modal';
export const REBLOG_REQUEST = 'REBLOG_REQUEST';
export const REBLOG_SUCCESS = 'REBLOG_SUCCESS';
@@ 51,6 55,14 @@ export const UNBOOKMARK_REQUEST = 'UNBOOKMARKED_REQUEST';
export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS';
export const UNBOOKMARK_FAIL = 'UNBOOKMARKED_FAIL';
+const messages = defineMessages({
+ bookmarkAdded: { id: 'status.bookmarked', defaultMessage: 'Bookmark added.' },
+ folderChanged: { id: 'status.bookmark_folder_changed', defaultMessage: 'Changed folder' },
+ view: { id: 'toast.view', defaultMessage: 'View' },
+ selectFolder: { id: 'status.bookmark.select_folder', defaultMessage: 'Select folder' },
+
+});
+
export function reblog(status, visibility) {
return function (dispatch, getState) {
dispatch(reblogRequest(status));
@@ 193,13 205,23 @@ export function unfavouriteFail(status, error) {
};
}
-export function bookmark(status) {
+export function bookmark(status, folder_id, routerHistory) {
return function (dispatch, getState) {
dispatch(bookmarkRequest(status));
- api(getState).post(`/api/v1/statuses/${status.get('id')}/bookmark`).then(function (response) {
+ return api(getState).post(`/api/v1/statuses/${status.get('id')}/bookmark`, { folder_id }).then(function (response) {
dispatch(importFetchedStatus(response.data));
- dispatch(bookmarkSuccess(status));
+ dispatch(bookmarkSuccess(status, folder_id));
+
+ dispatch(showAlert({
+ message: folder_id !== undefined ? messages.folderChanged : messages.bookmarkAdded,
+ action: folder_id !== undefined ? messages.view : messages.selectFolder,
+ dismissAfter: 10000,
+ onClick: () => folder_id !== undefined ? routerHistory.push(`/bookmarks/${folder_id}`) : dispatch(openModal({
+ modalType: 'SELECT_BOOKMARK_FOLDER',
+ modalProps: { statusId: status.get('id') },
+ })),
+ }));
}).catch(function (error) {
dispatch(bookmarkFail(status, error));
});
@@ 226,10 248,11 @@ export function bookmarkRequest(status) {
};
}
-export function bookmarkSuccess(status) {
+export function bookmarkSuccess(status, folderId) {
return {
type: BOOKMARK_SUCCESS,
status: status,
+ folderId,
};
}
M app/javascript/flavours/glitch/components/status.jsx => app/javascript/flavours/glitch/components/status.jsx +4 -2
@@ 96,11 96,13 @@ class Status extends ImmutablePureComponent {
onToggleHidden: PropTypes.func,
onTranslate: PropTypes.func,
onInteractionModal: PropTypes.func,
+ onChangeBookmarkFolder: PropTypes.func,
muted: PropTypes.bool,
hidden: PropTypes.bool,
unread: PropTypes.bool,
prepend: PropTypes.string,
withDismiss: PropTypes.bool,
+ fromBookmarks: PropTypes.bool,
onMoveUp: PropTypes.func,
onMoveDown: PropTypes.func,
getScrollPosition: PropTypes.func,
@@ 442,8 444,8 @@ class Status extends ImmutablePureComponent {
this.props.onReblog(this.props.status, e);
};
- handleHotkeyBookmark = e => {
- this.props.onBookmark(this.props.status, e);
+ handleHotkeyBookmark = () => {
+ this.props.onBookmark(this.props.status, undefined, this.context.router.history);
};
handleHotkeyMention = e => {
M app/javascript/flavours/glitch/components/status_action_bar.jsx => app/javascript/flavours/glitch/components/status_action_bar.jsx +15 -3
@@ 48,6 48,7 @@ const messages = defineMessages({
edited: { id: 'status.edited', defaultMessage: 'Edited {date}' },
filter: { id: 'status.filter', defaultMessage: 'Filter this post' },
openOriginalPage: { id: 'account.open_original_page', defaultMessage: 'Open original page' },
+ changeBookmarkFolder: { id: 'status.bookmark.change_folder', defaultMessage: 'Change bookmark folder' },
});
class StatusActionBar extends ImmutablePureComponent {
@@ 75,9 76,11 @@ class StatusActionBar extends ImmutablePureComponent {
onFilter: PropTypes.func,
onAddFilter: PropTypes.func,
onInteractionModal: PropTypes.func,
+ onChangeBookmarkFolder: PropTypes.func,
withDismiss: PropTypes.bool,
withCounters: PropTypes.bool,
showReplyCount: PropTypes.bool,
+ fromBookmarks: PropTypes.bool,
scrollKey: PropTypes.string,
intl: PropTypes.object.isRequired,
};
@@ 127,8 130,8 @@ class StatusActionBar extends ImmutablePureComponent {
}
};
- handleBookmarkClick = (e) => {
- this.props.onBookmark(this.props.status, e);
+ handleBookmarkClick = () => {
+ this.props.onBookmark(this.props.status, undefined, this.context.router.history);
};
handleDeleteClick = () => {
@@ 197,8 200,12 @@ class StatusActionBar extends ImmutablePureComponent {
this.props.onAddFilter(this.props.status);
};
+ handleChangeBookmarkFolder = () => {
+ this.props.onChangeBookmarkFolder(this.props.status);
+ };
+
render () {
- const { status, intl, withDismiss, withCounters, showReplyCount, scrollKey } = this.props;
+ const { status, intl, withDismiss, withCounters, showReplyCount, fromBookmarks, scrollKey } = this.props;
const { permissions, signedIn } = this.context.identity;
const mutingConversation = status.get('muted');
@@ 212,6 219,11 @@ class StatusActionBar extends ImmutablePureComponent {
let replyIcon;
let replyTitle;
+ if (status.get('bookmarked') && fromBookmarks) {
+ menu.push({ text: intl.formatMessage(messages.changeBookmarkFolder), action: this.handleChangeBookmarkFolder });
+ menu.push(null);
+ }
+
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
if (publicStatus && isRemote) {
M app/javascript/flavours/glitch/components/status_list.jsx => app/javascript/flavours/glitch/components/status_list.jsx +3 -0
@@ 28,6 28,7 @@ export default class StatusList extends ImmutablePureComponent {
emptyMessage: PropTypes.node,
alwaysPrepend: PropTypes.bool,
withCounters: PropTypes.bool,
+ fromBookmarks: PropTypes.bool,
timelineId: PropTypes.string.isRequired,
lastId: PropTypes.string,
regex: PropTypes.string,
@@ 107,6 108,7 @@ export default class StatusList extends ImmutablePureComponent {
contextType={timelineId}
scrollKey={this.props.scrollKey}
withCounters={this.props.withCounters}
+ fromBookmarks={this.props.fromBookmarks}
/>
))
) : null;
@@ 122,6 124,7 @@ export default class StatusList extends ImmutablePureComponent {
contextType={timelineId}
scrollKey={this.props.scrollKey}
withCounters={this.props.withCounters}
+ fromBookmarks={this.props.fromBookmarks}
/>
)).concat(scrollableContent);
}
M app/javascript/flavours/glitch/containers/status_container.js => app/javascript/flavours/glitch/containers/status_container.js +9 -2
@@ 135,11 135,11 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
});
},
- onBookmark (status) {
+ onBookmark (status, folderId, router) {
if (status.get('bookmarked')) {
dispatch(unbookmark(status));
} else {
- dispatch(bookmark(status));
+ dispatch(bookmark(status, folderId, router));
}
},
@@ 298,6 298,13 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
}));
},
+ onChangeBookmarkFolder (status) {
+ dispatch(openModal({
+ modalType: 'SELECT_BOOKMARK_FOLDER',
+ modalProps: { statusId: status.get('id') },
+ }));
+ },
+
});
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));
A app/javascript/flavours/glitch/features/bookmark_folder/index.jsx => app/javascript/flavours/glitch/features/bookmark_folder/index.jsx +142 -0
@@ 0,0 1,142 @@
+import PropTypes from 'prop-types';
+
+import { injectIntl, FormattedMessage } from 'react-intl';
+
+import { Helmet } from 'react-helmet';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import { debounce } from 'lodash';
+
+import { fetchBookmarkedStatuses, expandBookmarkedStatuses } from 'flavours/glitch/actions/bookmarks';
+import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
+import ColumnHeader from 'flavours/glitch/components/column_header';
+import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
+import StatusList from 'flavours/glitch/components/status_list';
+import BundleColumnError from 'flavours/glitch/features/ui/components/bundle_column_error';
+import Column from 'flavours/glitch/features/ui/components/column';
+import { getStatusList } from 'flavours/glitch/selectors';
+
+const mapStateToProps = (state, props) => {
+ const key = `bookmarks:${props.params.folderId}`;
+ return {
+ folder: state.getIn(['bookmarkFolders', props.params.folderId]),
+ statusIds: getStatusList(state, key),
+ isLoading: state.getIn(['status_lists', key, 'isLoading'], true),
+ hasMore: !!state.getIn(['status_lists', key, 'next']),
+ };
+};
+
+class BookmarkFolder extends ImmutablePureComponent {
+
+ static propTypes = {
+ dispatch: PropTypes.func.isRequired,
+ statusIds: ImmutablePropTypes.list.isRequired,
+ intl: PropTypes.object.isRequired,
+ columnId: PropTypes.string,
+ multiColumn: PropTypes.bool,
+ hasMore: PropTypes.bool,
+ isLoading: PropTypes.bool,
+ };
+
+ UNSAFE_componentWillMount () {
+ this.props.dispatch(fetchBookmarkedStatuses(this.props.params.folderId));
+ }
+
+ UNSAFE_componentWillReceiveProps (nextProps) {
+ const { folderId } = nextProps.params;
+
+ if (folderId !== this.props.params.folderId) {
+
+ this.props.dispatch(fetchBookmarkedStatuses(folderId));
+
+ }
+ }
+
+ handlePin = () => {
+ const { columnId, dispatch } = this.props;
+
+ if (columnId) {
+ dispatch(removeColumn(columnId));
+ } else {
+ dispatch(addColumn('BOOKMARK_FOLDER', { folderId: this.props.params.folderId }));
+ }
+ };
+
+ handleMove = (dir) => {
+ const { columnId, dispatch } = this.props;
+ dispatch(moveColumn(columnId, dir));
+ };
+
+ handleHeaderClick = () => {
+ this.column.scrollTop();
+ };
+
+ setRef = c => {
+ this.column = c;
+ };
+
+ handleLoadMore = debounce(() => {
+ this.props.dispatch(expandBookmarkedStatuses(this.props.params.folderId));
+ }, 300, { leading: true });
+
+ render () {
+ const { statusIds, columnId, multiColumn, folder, hasMore, isLoading, params } = this.props;
+ const { folderId } = params;
+ const pinned = !!columnId;
+ const name = folder ? folder.get('name') : folderId;
+
+ const emptyMessage = <FormattedMessage id='empty_column.bookmarked_statuses.folder' defaultMessage="You don't have any bookmarked posts in this folder yet. When you ad one, it will show up here." />;
+
+ if (typeof folder === 'undefined') {
+ return (
+ <Column>
+ <div className='scrollable'>
+ <LoadingIndicator />
+ </div>
+ </Column>
+ );
+ } else if (folder === false) {
+ return (
+ <BundleColumnError multiColumn={multiColumn} errorType='routing' />
+ );
+ }
+
+ return (
+ <Column bindToDocument={!multiColumn} ref={this.setRef} label={name}>
+ <ColumnHeader
+ icon='bookmark'
+ title={name}
+ onPin={this.handlePin}
+ onMove={this.handleMove}
+ onClick={this.handleHeaderClick}
+ pinned={pinned}
+ multiColumn={multiColumn}
+ showBackButton
+ />
+
+ <StatusList
+ trackScroll={!pinned}
+ statusIds={statusIds}
+ scrollKey={`bookmarked_statuses:${folderId}-${columnId}`}
+ hasMore={hasMore}
+ isLoading={isLoading}
+ onLoadMore={this.handleLoadMore}
+ emptyMessage={emptyMessage}
+ bindToDocument={!multiColumn}
+ fromBookmarks
+ />
+
+ <Helmet>
+ <title>{name}</title>
+ <meta name='robots' content='noindex' />
+ </Helmet>
+ </Column>
+ );
+ }
+
+}
+
+export default connect(mapStateToProps)(injectIntl(BookmarkFolder));
A app/javascript/flavours/glitch/features/bookmark_folders/index.jsx => app/javascript/flavours/glitch/features/bookmark_folders/index.jsx +96 -0
@@ 0,0 1,96 @@
+import PropTypes from 'prop-types';
+
+import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
+
+import { Helmet } from 'react-helmet';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+
+import { fetchBookmarkFolders } from 'flavours/glitch/actions/bookmark_folders';
+import ColumnBackButtonSlim from 'flavours/glitch/components/column_back_button_slim';
+import { LoadingIndicator } from 'flavours/glitch/components/loading_indicator';
+import ScrollableList from 'flavours/glitch/components/scrollable_list';
+import Column from 'flavours/glitch/features/ui/components/column';
+import ColumnLink from 'flavours/glitch/features/ui/components/column_link';
+import ColumnSubheading from 'flavours/glitch/features/ui/components/column_subheading';
+
+import NewFolderForm from '../select_bookmark_folder_modal/components/new_folder_form';
+
+const messages = defineMessages({
+ heading: { id: 'column.bookmark_folders', defaultMessage: 'Bookmark folders' },
+ subheading: { id: 'bookmark_folders.subheading', defaultMessage: 'Your bookmark folders' },
+});
+
+const getOrderedBookmarkFolders = createSelector([state => state.get('bookmarkFolders')], folders => {
+ if (!folders) {
+ return folders;
+ }
+
+ return folders.toList().filter(item => !!item).sort((a, b) => a.get('name').localeCompare(b.get('name')));
+});
+
+const mapStateToProps = state => ({
+ folders: getOrderedBookmarkFolders(state),
+});
+
+class BookmarkFolders extends ImmutablePureComponent {
+
+ static propTypes = {
+ params: PropTypes.object.isRequired,
+ dispatch: PropTypes.func.isRequired,
+ folders: ImmutablePropTypes.list,
+ intl: PropTypes.object.isRequired,
+ multiColumn: PropTypes.bool,
+ };
+
+ UNSAFE_componentWillMount () {
+ this.props.dispatch(fetchBookmarkFolders());
+ }
+
+ render () {
+ const { intl, folders, multiColumn } = this.props;
+
+ if (!folders) {
+ return (
+ <Column>
+ <LoadingIndicator />
+ </Column>
+ );
+ }
+
+ const children = [
+ <ColumnLink key='all' to='/bookmarks' icon='bookmark' text={<FormattedMessage id='bookmark_folders.all' defaultMessage='All bookmarks' />} />,
+ ];
+
+ for (const folder of folders) {
+ children.push(<ColumnLink key={folder.get('id')} to={`/bookmarks/${folder.get('id')}`} icon='folder' text={folder.get('name')} />)
+ }
+
+ return (
+ <Column bindToDocument={!multiColumn} icon='bars' heading={intl.formatMessage(messages.heading)}>
+ <ColumnBackButtonSlim />
+
+ <NewFolderForm />
+
+ <ColumnSubheading text={intl.formatMessage(messages.subheading)} />
+ <ScrollableList
+ scrollKey='bookmark_folders'
+ bindToDocument={!multiColumn}
+ >
+ {children}
+ </ScrollableList>
+
+ <Helmet>
+ <title>{intl.formatMessage(messages.heading)}</title>
+ <meta name='robots' content='noindex' />
+ </Helmet>
+ </Column>
+ );
+ }
+
+}
+
+export default connect(mapStateToProps)(injectIntl(BookmarkFolders));
M app/javascript/flavours/glitch/features/bookmarked_statuses/index.jsx => app/javascript/flavours/glitch/features/bookmarked_statuses/index.jsx +1 -0
@@ 98,6 98,7 @@ class Bookmarks extends ImmutablePureComponent {
onLoadMore={this.handleLoadMore}
emptyMessage={emptyMessage}
bindToDocument={!multiColumn}
+ fromBookmarks
/>
<Helmet>
M app/javascript/flavours/glitch/features/getting_started/index.jsx => app/javascript/flavours/glitch/features/getting_started/index.jsx +6 -1
@@ 11,6 11,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchFollowRequests } from 'flavours/glitch/actions/accounts';
+import { fetchBookmarkFolders } from 'flavours/glitch/actions/bookmark_folders';
import { fetchLists } from 'flavours/glitch/actions/lists';
import { openModal } from 'flavours/glitch/actions/modal';
import Column from 'flavours/glitch/features/ui/components/column';
@@ 69,6 70,7 @@ const makeMapStateToProps = () => {
const mapDispatchToProps = dispatch => ({
fetchFollowRequests: () => dispatch(fetchFollowRequests()),
fetchLists: () => dispatch(fetchLists()),
+ fetchBookmarkFolders: () => dispatch(fetchBookmarkFolders()),
openSettings: () => dispatch(openModal({
modalType: 'SETTINGS',
modalProps: {},
@@ 102,15 104,17 @@ class GettingStarted extends ImmutablePureComponent {
unreadNotifications: PropTypes.number,
lists: ImmutablePropTypes.list,
fetchLists: PropTypes.func.isRequired,
+ fetchBookmarkFolders: PropTypes.func.isRequired,
openSettings: PropTypes.func.isRequired,
};
UNSAFE_componentWillMount () {
this.props.fetchLists();
+ this.props.fetchBookmarkFolders();
}
componentDidMount () {
- const { fetchFollowRequests } = this.props;
+ const { fetchFollowRequests, fetchBookmarkFolders } = this.props;
const { signedIn } = this.context.identity;
if (!signedIn) {
@@ 118,6 122,7 @@ class GettingStarted extends ImmutablePureComponent {
}
fetchFollowRequests();
+ fetchBookmarkFolders();
}
render () {
A app/javascript/flavours/glitch/features/select_bookmark_folder_modal/components/bookmark_folder.jsx => app/javascript/flavours/glitch/features/select_bookmark_folder_modal/components/bookmark_folder.jsx +71 -0
@@ 0,0 1,71 @@
+import PropTypes from 'prop-types';
+
+import { FormattedMessage, injectIntl } from 'react-intl';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+
+import { bookmark } from 'flavours/glitch/actions/interactions';
+import { closeModal } from 'flavours/glitch/actions/modal';
+import { RadioButton } from 'flavours/glitch/components/radio_button';
+
+const mapStateToProps = (state, { statusId }) => ({
+ status: state.getIn(['statuses', statusId]),
+});
+
+const mapDispatchToProps = (dispatch, { folder }) => ({
+ onChange: (status, router) => {
+ dispatch(bookmark(status, folder ? folder.get('id') : null, router)).then(() => {
+ dispatch(closeModal({
+ modalType: 'SELECT_BOOKMARK_FOLDER',
+ }));
+ }).catch(() => {});
+ },
+});
+
+class BookmarkFolder extends ImmutablePureComponent {
+
+ static contextTypes = {
+ router: PropTypes.object,
+ };
+
+ static propTypes = {
+ status: ImmutablePropTypes.map.isRequired,
+ folder: ImmutablePropTypes.map,
+ statusId: PropTypes.string.isRequired,
+ statusFolderId: PropTypes.string,
+ intl: PropTypes.object.isRequired,
+ onChange: PropTypes.func.isRequired,
+ };
+
+ static defaultProps = {
+ added: false,
+ };
+
+ handleChange = () => {
+ const { onChange, status } = this.props;
+ onChange(status, this.context.router.history);
+ };
+
+ render () {
+ const { folder, statusFolderId } = this.props;
+
+ return (
+ <div className='bookmark-folder'>
+ <div className='bookmark-folder__wrapper'>
+ <RadioButton
+ name='folder_id'
+ value={statusFolderId || ''}
+ label={folder ? folder.get('name') : <FormattedMessage id='bookmark_folders.uncategorized' defaultMessage='Uncategorized' />}
+ checked={statusFolderId === (folder ? folder.get('id') : null)}
+ onChange={this.handleChange}
+ />
+ </div>
+ </div>
+ );
+ }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(BookmarkFolder));
A app/javascript/flavours/glitch/features/select_bookmark_folder_modal/components/new_folder_form.jsx => app/javascript/flavours/glitch/features/select_bookmark_folder_modal/components/new_folder_form.jsx +81 -0
@@ 0,0 1,81 @@
+import PropTypes from 'prop-types';
+import { PureComponent } from 'react';
+
+import { defineMessages, injectIntl } from 'react-intl';
+
+import { connect } from 'react-redux';
+
+import { changeBookmarkFolderEditorName, submitBookmarkFolderEditor } from 'flavours/glitch/actions/bookmark_folders';
+import { IconButton } from 'flavours/glitch/components/icon_button';
+
+const messages = defineMessages({
+ label: { id: 'bookmark_folders.new.name_placeholder', defaultMessage: 'New folder name' },
+ title: { id: 'bookmark_folders.new.create', defaultMessage: 'Add folder' },
+});
+
+const mapStateToProps = state => ({
+ value: state.getIn(['bookmarkFolderEditor', 'name']),
+ disabled: state.getIn(['bookmarkFolderEditor', 'isSubmitting']),
+});
+
+const mapDispatchToProps = dispatch => ({
+ onChange: value => dispatch(changeBookmarkFolderEditorName(value)),
+ onSubmit: () => dispatch(submitBookmarkFolderEditor(true)),
+});
+
+class NewFolderForm extends PureComponent {
+
+ static propTypes = {
+ value: PropTypes.string.isRequired,
+ disabled: PropTypes.bool,
+ intl: PropTypes.object.isRequired,
+ onChange: PropTypes.func.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+ };
+
+ handleChange = e => {
+ this.props.onChange(e.target.value);
+ };
+
+ handleSubmit = e => {
+ e.preventDefault();
+ this.props.onSubmit();
+ };
+
+ handleClick = () => {
+ this.props.onSubmit();
+ };
+
+ render () {
+ const { value, disabled, intl } = this.props;
+
+ const label = intl.formatMessage(messages.label);
+ const title = intl.formatMessage(messages.title);
+
+ return (
+ <form className='column-inline-form' onSubmit={this.handleSubmit}>
+ <label>
+ <span style={{ display: 'none' }}>{label}</span>
+
+ <input
+ className='setting-text'
+ value={value}
+ disabled={disabled}
+ onChange={this.handleChange}
+ placeholder={label}
+ />
+ </label>
+
+ <IconButton
+ disabled={disabled || !value}
+ icon='plus'
+ title={title}
+ onClick={this.handleClick}
+ />
+ </form>
+ );
+ }
+
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(NewFolderForm));
A app/javascript/flavours/glitch/features/select_bookmark_folder_modal/index.jsx => app/javascript/flavours/glitch/features/select_bookmark_folder_modal/index.jsx +73 -0
@@ 0,0 1,73 @@
+import PropTypes from 'prop-types';
+
+import { injectIntl } from 'react-intl';
+
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import ImmutablePureComponent from 'react-immutable-pure-component';
+import { connect } from 'react-redux';
+import { createSelector } from 'reselect';
+
+import { fetchBookmarkFolders } from 'flavours/glitch/actions/bookmark_folders';
+import { makeGetStatus } from 'flavours/glitch/selectors';
+
+import BookmarkFolder from './components/bookmark_folder';
+import NewFolderForm from './components/new_folder_form';
+
+const getOrderedBookmarkFolders = createSelector([state => state.get('bookmarkFolders')], folders => {
+ if (!folders) {
+ return folders;
+ }
+
+ return folders.toList().filter(item => !!item).sort((a, b) => a.get('name').localeCompare(b.get('name')));
+});
+
+const makeMapStateToProps = () => {
+ const getStatus = makeGetStatus();
+
+ const mapStateToProps = (state, { statusId }) => ({
+ folders: getOrderedBookmarkFolders(state),
+ status: getStatus(state, { id: statusId }),
+ });
+
+ return mapStateToProps;
+};
+
+const mapDispatchToProps = (dispatch) => ({
+ onInitialize () {
+ dispatch(fetchBookmarkFolders());
+ }
+});
+
+class SelectBookmarkFolderModal extends ImmutablePureComponent {
+
+ static propTypes = {
+ statusId: PropTypes.string.isRequired,
+ onClose: PropTypes.func.isRequired,
+ intl: PropTypes.object.isRequired,
+ onInitialize: PropTypes.func.isRequired,
+ folders: ImmutablePropTypes.list.isRequired,
+ };
+
+ componentDidMount () {
+ const { onInitialize } = this.props;
+ onInitialize();
+ }
+
+ render () {
+ const { folders, status } = this.props;
+
+ return (
+ <div className='modal-root__modal select-bookmark-folder'>
+ <NewFolderForm />
+
+ <div className='select-bookmark-folder__folders'>
+ <BookmarkFolder statusId={status.get('id')} statusFolderId={status.get('bookmark_folder')} />
+ {folders.map(folder => <BookmarkFolder key={folder.get('id')} folder={folder} statusId={status.get('id')} statusFolderId={status.get('bookmark_folder')} />)}
+ </div>
+ </div>
+ );
+ }
+
+}
+
+export default connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(SelectBookmarkFolderModal));
M app/javascript/flavours/glitch/features/status/components/action_bar.jsx => app/javascript/flavours/glitch/features/status/components/action_bar.jsx +2 -2
@@ 81,8 81,8 @@ class ActionBar extends PureComponent {
this.props.onFavourite(this.props.status, e);
};
- handleBookmarkClick = (e) => {
- this.props.onBookmark(this.props.status, e);
+ handleBookmarkClick = () => {
+ this.props.onBookmark(this.props.status, undefined, this.context.router.history);
};
handleDeleteClick = () => {
M app/javascript/flavours/glitch/features/status/index.jsx => app/javascript/flavours/glitch/features/status/index.jsx +1 -1
@@ 378,7 378,7 @@ class Status extends ImmutablePureComponent {
if (status.get('bookmarked')) {
this.props.dispatch(unbookmark(status));
} else {
- this.props.dispatch(bookmark(status));
+ this.props.dispatch(bookmark(status, undefined, this.context.router.history));
}
};
M app/javascript/flavours/glitch/features/ui/components/columns_area.jsx => app/javascript/flavours/glitch/features/ui/components/columns_area.jsx +2 -0
@@ 21,6 21,7 @@ import {
BookmarkedStatuses,
ListTimeline,
Directory,
+ BookmarkFolder,
} from '../util/async-components';
import BundleColumnError from './bundle_column_error';
@@ 40,6 41,7 @@ const componentMap = {
'DIRECT': DirectTimeline,
'FAVOURITES': FavouritedStatuses,
'BOOKMARKS': BookmarkedStatuses,
+ 'BOOKMARK_FOLDER': BookmarkFolder,
'LIST': ListTimeline,
'DIRECTORY': Directory,
};
M app/javascript/flavours/glitch/features/ui/components/modal_root.jsx => app/javascript/flavours/glitch/features/ui/components/modal_root.jsx +2 -0
@@ 19,6 19,7 @@ import {
InteractionModal,
SubscribedLanguagesModal,
ClosedRegistrationsModal,
+ SelectBookmarkFolderModal,
} from 'flavours/glitch/features/ui/util/async-components';
import { getScrollbarWidth } from 'flavours/glitch/utils/scrollbar';
@@ 64,6 65,7 @@ export const MODAL_COMPONENTS = {
'SUBSCRIBED_LANGUAGES': SubscribedLanguagesModal,
'INTERACTION': InteractionModal,
'CLOSED_REGISTRATIONS': ClosedRegistrationsModal,
+ 'SELECT_BOOKMARK_FOLDER': SelectBookmarkFolderModal,
};
export default class ModalRoot extends PureComponent {
M app/javascript/flavours/glitch/features/ui/containers/notifications_container.js => app/javascript/flavours/glitch/features/ui/containers/notifications_container.js +2 -5
@@ 15,17 15,14 @@ const formatIfNeeded = (intl, message, values) => {
return message;
};
-const mapStateToProps = (state, { intl }) => {
- console.log(getAlerts(state));
- return ({
+const mapStateToProps = (state, { intl }) => ({
notifications: getAlerts(state).map(alert => ({
...alert,
action: formatIfNeeded(intl, alert.action, alert.values),
title: formatIfNeeded(intl, alert.title, alert.values),
message: formatIfNeeded(intl, alert.message, alert.values),
})),
-})
-};
+});
const mapDispatchToProps = (dispatch) => ({
onDismiss (alert) {
M app/javascript/flavours/glitch/features/ui/index.jsx => app/javascript/flavours/glitch/features/ui/index.jsx +4 -0
@@ 64,6 64,8 @@ import {
FollowRecommendations,
About,
PrivacyPolicy,
+ BookmarkFolders,
+ BookmarkFolder,
} from './util/async-components';
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
// Dummy import, to make sure that <Status /> ends up in the application bundle.
@@ 212,7 214,9 @@ class SwitchingColumnsArea extends PureComponent {
<WrappedRoute path='/notifications' component={Notifications} content={children} />
<WrappedRoute path='/favourites' component={FavouritedStatuses} content={children} />
+ <WrappedRoute path='/bookmarks/:folderId' component={BookmarkFolder} content={children} />
<WrappedRoute path='/bookmarks' component={BookmarkedStatuses} content={children} />
+ <WrappedRoute path='/bookmark_folders' component={BookmarkFolders} content={children} />
<WrappedRoute path='/pinned' component={PinnedStatuses} content={children} />
<WrappedRoute path='/start' component={FollowRecommendations} content={children} />
M app/javascript/flavours/glitch/features/ui/util/async-components.js => app/javascript/flavours/glitch/features/ui/util/async-components.js +12 -0
@@ 205,3 205,15 @@ export function About () {
export function PrivacyPolicy () {
return import(/*webpackChunkName: "features/glitch/async/privacy_policy" */'flavours/glitch/features/privacy_policy');
}
+
+export function SelectBookmarkFolderModal () {
+ return import(/*webpackChunkName: "flavours/glitch/async/modals/select_bookmark_folder_modal" */'flavours/glitch/features/select_bookmark_folder_modal');
+}
+
+export function BookmarkFolders () {
+ return import(/*webpackChunkName: "flavours/glitch/async/bookmark_folders" */'flavours/glitch/features/bookmark_folders');
+}
+
+export function BookmarkFolder () {
+ return import(/*webpackChunkName: "flavours/glitch/async/bookmark_folder" */'flavours/glitch/features/bookmark_folder');
+}
A app/javascript/flavours/glitch/reducers/bookmark_folder_editor.js => app/javascript/flavours/glitch/reducers/bookmark_folder_editor.js +54 -0
@@ 0,0 1,54 @@
+import { Map as ImmutableMap } from 'immutable';
+
+import {
+ BOOKMARK_FOLDER_CREATE_REQUEST,
+ BOOKMARK_FOLDER_CREATE_FAIL,
+ BOOKMARK_FOLDER_CREATE_SUCCESS,
+ BOOKMARK_FOLDER_UPDATE_REQUEST,
+ BOOKMARK_FOLDER_UPDATE_FAIL,
+ BOOKMARK_FOLDER_UPDATE_SUCCESS,
+ BOOKMARK_FOLDER_EDITOR_RESET,
+ BOOKMARK_FOLDER_EDITOR_SETUP,
+ BOOKMARK_FOLDER_EDITOR_NAME_CHANGE,
+} from '../actions/bookmark_folders';
+
+const initialState = ImmutableMap({
+ folderId: null,
+ isSubmitting: false,
+ isChanged: false,
+ name: '',
+});
+
+export default function listEditorReducer(state = initialState, action) {
+ switch(action.type) {
+ case BOOKMARK_FOLDER_EDITOR_RESET:
+ return initialState;
+ case BOOKMARK_FOLDER_EDITOR_SETUP:
+ return state.withMutations(map => {
+ map.set('folderId', action.folder.get('id'));
+ map.set('folder', action.folder.get('title'));
+ });
+ case BOOKMARK_FOLDER_EDITOR_NAME_CHANGE:
+ return state.withMutations(map => {
+ map.set('name', action.value);
+ map.set('isChanged', true);
+ });
+ case BOOKMARK_FOLDER_CREATE_REQUEST:
+ case BOOKMARK_FOLDER_UPDATE_REQUEST:
+ return state.withMutations(map => {
+ map.set('isSubmitting', true);
+ map.set('isChanged', false);
+ });
+ case BOOKMARK_FOLDER_CREATE_FAIL:
+ case BOOKMARK_FOLDER_UPDATE_FAIL:
+ return state.set('isSubmitting', false);
+ case BOOKMARK_FOLDER_CREATE_SUCCESS:
+ case BOOKMARK_FOLDER_UPDATE_SUCCESS:
+ return state.withMutations(map => {
+ map.set('isSubmitting', false);
+ map.set('folderId', action.folder.id);
+ });
+ default:
+ return state;
+ }
+}
A app/javascript/flavours/glitch/reducers/bookmark_folders.js => app/javascript/flavours/glitch/reducers/bookmark_folders.js +34 -0
@@ 0,0 1,34 @@
+import { Map as ImmutableMap, fromJS } from 'immutable';
+
+import {
+ BOOKMARK_FOLDERS_FETCH_SUCCESS,
+ BOOKMARK_FOLDER_CREATE_SUCCESS,
+ BOOKMARK_FOLDER_UPDATE_SUCCESS,
+ BOOKMARK_FOLDER_DELETE_SUCCESS,
+} from '../actions/bookmark_folders';
+
+const initialState = ImmutableMap();
+
+const normalizeBookmarkFolder = (state, folder) => state.set(folder.id, fromJS(folder));
+
+const normalizeBookmarkFolders = (state, folders) => {
+ folders.forEach(folder => {
+ state = normalizeBookmarkFolder(state, folder);
+ });
+
+ return state;
+};
+
+export default function bookmarkFolders(state = initialState, action) {
+ switch(action.type) {
+ case BOOKMARK_FOLDER_CREATE_SUCCESS:
+ case BOOKMARK_FOLDER_UPDATE_SUCCESS:
+ return normalizeBookmarkFolder(state, action.folder);
+ case BOOKMARK_FOLDERS_FETCH_SUCCESS:
+ return normalizeBookmarkFolders(state, action.folders);
+ case BOOKMARK_FOLDER_DELETE_SUCCESS:
+ return state.set(action.id, false);
+ default:
+ return state;
+ }
+}
M app/javascript/flavours/glitch/reducers/index.ts => app/javascript/flavours/glitch/reducers/index.ts +4 -0
@@ 10,6 10,8 @@ import accounts_map from './accounts_map';
import alerts from './alerts';
import announcements from './announcements';
import blocks from './blocks';
+import bookmarkFolders from './bookmark_folders';
+import bookmarkFolderEditor from './bookmark_folder_editor';
import boosts from './boosts';
import compose from './compose';
import contexts from './contexts';
@@ 49,6 51,8 @@ import user_lists from './user_lists';
const reducers = {
announcements,
+ bookmarkFolders,
+ bookmarkFolderEditor,
dropdownMenu: dropdownMenuReducer,
timelines,
meta,
M app/javascript/flavours/glitch/reducers/status_lists.js => app/javascript/flavours/glitch/reducers/status_lists.js +36 -6
@@ 96,7 96,33 @@ const removeOneFromList = (state, listType, status) => {
return state.updateIn([listType, 'items'], (list) => list.delete(status.get('id')));
};
+const addBookmark = (state, status, folderId) => {
+ state = prependOneToList(state, 'bookmarks', status);
+ return state.map((list, key) => {
+ if (!key.startsWith('bookmarks:')) return list;
+ return list.update('items', (list) => {
+ if (key === `bookmarks:${folderId}`) {
+ if (list.includes(status.get('id'))) {
+ return list;
+ } else {
+ return ImmutableOrderedSet([status.get('id')]).union(list);
+ }
+ } else {
+ return list.delete(status.get('id'));
+ }
+ });
+ });
+};
+
+const removeBookmark = (state, status) => {
+ return state.map((list, key) => {
+ if (!key.startsWith('bookmarks')) return list;
+ return list.update('items', (list) => list.delete(status.get('id')));
+ });
+};
+
export default function statusLists(state = initialState, action) {
+ let key;
switch(action.type) {
case FAVOURITED_STATUSES_FETCH_REQUEST:
case FAVOURITED_STATUSES_EXPAND_REQUEST:
@@ 110,14 136,18 @@ export default function statusLists(state = initialState, action) {
return appendToList(state, 'favourites', action.statuses, action.next);
case BOOKMARKED_STATUSES_FETCH_REQUEST:
case BOOKMARKED_STATUSES_EXPAND_REQUEST:
- return state.setIn(['bookmarks', 'isLoading'], true);
+ key = action.folderId ? `bookmarks:${action.folderId}`: 'bookmarks';
+ return state.setIn([key, 'isLoading'], true);
case BOOKMARKED_STATUSES_FETCH_FAIL:
case BOOKMARKED_STATUSES_EXPAND_FAIL:
- return state.setIn(['bookmarks', 'isLoading'], false);
+ key = action.folderId ? `bookmarks:${action.folderId}`: 'bookmarks';
+ return state.setIn([key, 'isLoading'], false);
case BOOKMARKED_STATUSES_FETCH_SUCCESS:
- return normalizeList(state, 'bookmarks', action.statuses, action.next);
+ key = action.folderId ? `bookmarks:${action.folderId}`: 'bookmarks';
+ return normalizeList(state, key, action.statuses, action.next);
case BOOKMARKED_STATUSES_EXPAND_SUCCESS:
- return appendToList(state, 'bookmarks', action.statuses, action.next);
+ key = action.folderId ? `bookmarks:${action.folderId}`: 'bookmarks';
+ return appendToList(state, key, action.statuses, action.next);
case TRENDS_STATUSES_FETCH_REQUEST:
case TRENDS_STATUSES_EXPAND_REQUEST:
return state.setIn(['trending', 'isLoading'], true);
@@ 133,9 163,9 @@ export default function statusLists(state = initialState, action) {
case UNFAVOURITE_SUCCESS:
return removeOneFromList(state, 'favourites', action.status);
case BOOKMARK_SUCCESS:
- return prependOneToList(state, 'bookmarks', action.status);
+ return addBookmark(state, action.status, action.folderId);
case UNBOOKMARK_SUCCESS:
- return removeOneFromList(state, 'bookmarks', action.status);
+ return removeBookmark(state, action.status);
case PINNED_STATUSES_FETCH_SUCCESS:
return normalizeList(state, 'pins', action.statuses, action.next);
case PIN_SUCCESS:
M app/javascript/flavours/glitch/selectors/index.js => app/javascript/flavours/glitch/selectors/index.js +2 -2
@@ 1,4 1,4 @@
-import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
+import { List as ImmutableList, Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable';
import { createSelector } from 'reselect';
import { me } from 'flavours/glitch/initial_state';
@@ 129,5 129,5 @@ export const getAccountHidden = createSelector([
});
export const getStatusList = createSelector([
- (state, type) => state.getIn(['status_lists', type, 'items']),
+ (state, type) => state.getIn(['status_lists', type, 'items'], ImmutableOrderedSet()),
], (items) => items.toList());
A app/javascript/flavours/glitch/styles/components/bookmark_folders.scss => app/javascript/flavours/glitch/styles/components/bookmark_folders.scss +12 -0
@@ 0,0 1,12 @@
+.select-bookmark-folder {
+ background: $ui-base-color;
+ flex-direction: column;
+ border-radius: 8px;
+ box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
+ width: 380px;
+ overflow: hidden;
+
+ @media screen and (width <= 420px) {
+ width: 90%;
+ }
+}
M app/javascript/flavours/glitch/styles/components/index.scss => app/javascript/flavours/glitch/styles/components/index.scss +1 -0
@@ 23,3 23,4 @@
@import 'signed_out';
@import 'privacy_policy';
@import 'about';
+@import 'bookmark_folders';