Wip: bookmark statuses view and adder
This commit is contained in:
parent
f6bdd9b6de
commit
87490a3220
30 changed files with 616 additions and 24 deletions
|
@ -0,0 +1,94 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::BookmarkCategories::StatusesController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:show]
|
||||||
|
before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:show]
|
||||||
|
|
||||||
|
before_action :require_user!
|
||||||
|
before_action :set_bookmark_category
|
||||||
|
|
||||||
|
after_action :insert_pagination_headers, only: :show
|
||||||
|
|
||||||
|
def show
|
||||||
|
@statuses = load_statuses
|
||||||
|
render json: @statuses, each_serializer: REST::StatusSerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
ApplicationRecord.transaction do
|
||||||
|
bookmark_category_statuses.each do |status|
|
||||||
|
Bookmark.find_or_create_by!(account: current_account, status: status)
|
||||||
|
@bookmark_category.statuses << status
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
render_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
BookmarkCategoryStatus.where(bookmark_category: @bookmark_category, status_id: status_ids).destroy_all
|
||||||
|
render_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_bookmark_category
|
||||||
|
@bookmark_category = current_account.bookmark_categories.find(params[:bookmark_category_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def load_statuses
|
||||||
|
if unlimited?
|
||||||
|
@bookmark_category.statuses.includes(:status_stat).all
|
||||||
|
else
|
||||||
|
@bookmark_category.statuses.includes(:status_stat).paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def bookmark_category_statuses
|
||||||
|
Status.find(status_ids)
|
||||||
|
end
|
||||||
|
|
||||||
|
def status_ids
|
||||||
|
Array(resource_params[:status_ids])
|
||||||
|
end
|
||||||
|
|
||||||
|
def resource_params
|
||||||
|
params.permit(status_ids: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def insert_pagination_headers
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
|
||||||
|
def next_path
|
||||||
|
return if unlimited?
|
||||||
|
|
||||||
|
api_v1_bookmark_category_statuses_url pagination_params(max_id: pagination_max_id) if records_continue?
|
||||||
|
end
|
||||||
|
|
||||||
|
def prev_path
|
||||||
|
return if unlimited?
|
||||||
|
|
||||||
|
api_v1_bookmark_category_statuses_url pagination_params(since_id: pagination_since_id) unless @statuses.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_max_id
|
||||||
|
@statuses.last.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_since_id
|
||||||
|
@statuses.first.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def records_continue?
|
||||||
|
@statuses.size == limit_param(DEFAULT_STATUSES_LIMIT)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_params(core_params)
|
||||||
|
params.slice(:limit).permit(:limit).merge(core_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def unlimited?
|
||||||
|
params[:limit] == '0'
|
||||||
|
end
|
||||||
|
end
|
47
app/controllers/api/v1/bookmark_categories_controller.rb
Normal file
47
app/controllers/api/v1/bookmark_categories_controller.rb
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::BookmarkCategoriesController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :read, :'read:lists' }, only: [:index, :show]
|
||||||
|
before_action -> { doorkeeper_authorize! :write, :'write:lists' }, except: [:index, :show]
|
||||||
|
|
||||||
|
before_action :require_user!
|
||||||
|
before_action :set_bookmark_category, except: [:index, :create]
|
||||||
|
|
||||||
|
rescue_from ArgumentError do |e|
|
||||||
|
render json: { error: e.to_s }, status: 422
|
||||||
|
end
|
||||||
|
|
||||||
|
def index
|
||||||
|
@bookmark_categories = BookmarkCategory.where(account: current_account).all
|
||||||
|
render json: @bookmark_categories, each_serializer: REST::BookmarkCategorySerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
render json: @bookmark_category, serializer: REST::BookmarkCategorySerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@bookmark_category = BookmarkCategory.create!(bookmark_category_params.merge(account: current_account))
|
||||||
|
render json: @bookmark_category, serializer: REST::BookmarkCategorySerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@bookmark_category.update!(bookmark_category_params)
|
||||||
|
render json: @bookmark_category, serializer: REST::BookmarkCategorySerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@bookmark_category.destroy!
|
||||||
|
render_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_bookmark_category
|
||||||
|
@bookmark_category = BookmarkCategory.where(account: current_account).find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def bookmark_category_params
|
||||||
|
params.permit(:title)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,18 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::Statuses::BookmarkCategoriesController < Api::BaseController
|
||||||
|
before_action -> { doorkeeper_authorize! :read, :'read:lists' }
|
||||||
|
before_action :require_user!
|
||||||
|
before_action :set_status
|
||||||
|
|
||||||
|
def index
|
||||||
|
@statuses = @status.deleted_at.present? ? [] : @status.joined_bookmark_categories.where(account: current_account)
|
||||||
|
render json: @statuses, each_serializer: REST::BookmarkCategorySerializer
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_status
|
||||||
|
@status = Status.find(params[:status_id])
|
||||||
|
end
|
||||||
|
end
|
|
@ -150,10 +150,10 @@ export const createBookmarkCategoryFail = error => ({
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const updateBookmarkCategory = (id, title, shouldReset, isExclusive, replies_policy) => (dispatch, getState) => {
|
export const updateBookmarkCategory = (id, title, shouldReset) => (dispatch, getState) => {
|
||||||
dispatch(updateBookmarkCategoryRequest(id));
|
dispatch(updateBookmarkCategoryRequest(id));
|
||||||
|
|
||||||
api(getState).put(`/api/v1/bookmark_categories/${id}`, { title, replies_policy, exclusive: typeof isExclusive === 'undefined' ? undefined : !!isExclusive }).then(({ data }) => {
|
api(getState).put(`/api/v1/bookmark_categories/${id}`, { title }).then(({ data }) => {
|
||||||
dispatch(updateBookmarkCategorySuccess(data));
|
dispatch(updateBookmarkCategorySuccess(data));
|
||||||
|
|
||||||
if (shouldReset) {
|
if (shouldReset) {
|
||||||
|
|
|
@ -37,6 +37,7 @@ const messages = defineMessages({
|
||||||
favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
|
favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
|
||||||
emojiReaction: { id: 'status.emoji_reaction', defaultMessage: 'Stamp' },
|
emojiReaction: { id: 'status.emoji_reaction', defaultMessage: 'Stamp' },
|
||||||
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
||||||
|
bookmarkCategory: { id: 'status.bookmark_category', defaultMessage: 'Bookmark category' },
|
||||||
removeBookmark: { id: 'status.remove_bookmark', defaultMessage: 'Remove bookmark' },
|
removeBookmark: { id: 'status.remove_bookmark', defaultMessage: 'Remove bookmark' },
|
||||||
open: { id: 'status.open', defaultMessage: 'Expand this status' },
|
open: { id: 'status.open', defaultMessage: 'Expand this status' },
|
||||||
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
|
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
|
||||||
|
@ -92,6 +93,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
onMuteConversation: PropTypes.func,
|
onMuteConversation: PropTypes.func,
|
||||||
onPin: PropTypes.func,
|
onPin: PropTypes.func,
|
||||||
onBookmark: PropTypes.func,
|
onBookmark: PropTypes.func,
|
||||||
|
onBookmarkCategoryAdder: PropTypes.func,
|
||||||
onFilter: PropTypes.func,
|
onFilter: PropTypes.func,
|
||||||
onAddFilter: PropTypes.func,
|
onAddFilter: PropTypes.func,
|
||||||
onInteractionModal: PropTypes.func,
|
onInteractionModal: PropTypes.func,
|
||||||
|
@ -167,6 +169,10 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
this.props.onBookmark(this.props.status);
|
this.props.onBookmark(this.props.status);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleBookmarkCategoryAdderClick = () => {
|
||||||
|
this.props.onBookmarkCategoryAdder(this.props.status);
|
||||||
|
};
|
||||||
|
|
||||||
handleDeleteClick = () => {
|
handleDeleteClick = () => {
|
||||||
this.props.onDelete(this.props.status, this.context.router.history);
|
this.props.onDelete(this.props.status, this.context.router.history);
|
||||||
};
|
};
|
||||||
|
@ -300,6 +306,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });
|
menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick });
|
||||||
|
menu.push({ text: intl.formatMessage(messages.bookmarkCategory), action: this.handleBookmarkCategoryAdderClick });
|
||||||
|
|
||||||
if (writtenByMe && pinnableStatus) {
|
if (writtenByMe && pinnableStatus) {
|
||||||
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
|
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
|
||||||
|
|
|
@ -142,6 +142,15 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onBookmarkCategoryAdder (status) {
|
||||||
|
dispatch(openModal({
|
||||||
|
modalType: 'BOOKMARK_CATEGORY_ADDER',
|
||||||
|
modalProps: {
|
||||||
|
statusId: status.get('id'),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
onPin (status) {
|
onPin (status) {
|
||||||
if (status.get('pinned')) {
|
if (status.get('pinned')) {
|
||||||
dispatch(unpin(status));
|
dispatch(unpin(status));
|
||||||
|
|
|
@ -17,11 +17,12 @@ import ScrollableList from 'mastodon/components/scrollable_list';
|
||||||
import ColumnLink from 'mastodon/features/ui/components/column_link';
|
import ColumnLink from 'mastodon/features/ui/components/column_link';
|
||||||
import ColumnSubheading from 'mastodon/features/ui/components/column_subheading';
|
import ColumnSubheading from 'mastodon/features/ui/components/column_subheading';
|
||||||
|
|
||||||
import NewListForm from './components/new_list_form';
|
import NewListForm from './components/new_bookmark_category_form';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'column.bookmark_categories', defaultMessage: 'Bookmark categories' },
|
heading: { id: 'column.bookmark_categories', defaultMessage: 'Bookmark categories' },
|
||||||
subheading: { id: 'bookmark_categories_ex.subheading', defaultMessage: 'Your categories' },
|
subheading: { id: 'bookmark_categories.subheading', defaultMessage: 'Your categories' },
|
||||||
|
allBookmarks: { id: 'bookmark_categories.all_bookmarks', defaultMessage: 'All bookmarks' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const getOrderedCategories = createSelector([state => state.get('bookmark_categories')], categories => {
|
const getOrderedCategories = createSelector([state => state.get('bookmark_categories')], categories => {
|
||||||
|
@ -69,6 +70,7 @@ class BookmarkCategories extends ImmutablePureComponent {
|
||||||
|
|
||||||
<NewListForm />
|
<NewListForm />
|
||||||
|
|
||||||
|
<ColumnLink to='/bookmarks' icon='bookmark' text={intl.formatMessage(messages.allBookmarks)} />,
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='bookmark_categories'
|
scrollKey='bookmark_categories'
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
import { injectIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { Avatar } from '../../../components/avatar';
|
||||||
|
import { DisplayName } from '../../../components/display_name';
|
||||||
|
import { makeGetAccount } from '../../../selectors';
|
||||||
|
|
||||||
|
const makeMapStateToProps = () => {
|
||||||
|
const getAccount = makeGetAccount();
|
||||||
|
|
||||||
|
const mapStateToProps = (state, { accountId }) => ({
|
||||||
|
account: getAccount(state, accountId),
|
||||||
|
});
|
||||||
|
|
||||||
|
return mapStateToProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Account extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
account: ImmutablePropTypes.map.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { account } = this.props;
|
||||||
|
return (
|
||||||
|
<div className='account'>
|
||||||
|
<div className='account__wrapper'>
|
||||||
|
<div className='account__display-name'>
|
||||||
|
<div className='account__avatar-wrapper'><Avatar account={account} size={36} /></div>
|
||||||
|
<DisplayName account={account} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(makeMapStateToProps)(injectIntl(Account));
|
|
@ -0,0 +1,72 @@
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { Icon } from 'mastodon/components/icon';
|
||||||
|
|
||||||
|
import { removeFromBookmarkCategoryAdder, addToBookmarkCategoryAdder } from '../../../actions/bookmark_categories';
|
||||||
|
import { IconButton } from '../../../components/icon_button';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
remove: { id: 'bookmark_categories.status.remove', defaultMessage: 'Remove from bookmark category' },
|
||||||
|
add: { id: 'bookmark_categories.status.add', defaultMessage: 'Add to bookmark category' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const MapStateToProps = (state, { bookmarkCategoryId, added }) => ({
|
||||||
|
bookmarkCategory: state.get('bookmark_categories').get(bookmarkCategoryId),
|
||||||
|
added: typeof added === 'undefined' ? state.getIn(['bookmarkCategoryAdder', 'bookmarkCategories', 'items']).includes(bookmarkCategoryId) : added,
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch, { bookmarkCategoryId }) => ({
|
||||||
|
onRemove: () => dispatch(removeFromBookmarkCategoryAdder(bookmarkCategoryId)),
|
||||||
|
onAdd: () => dispatch(addToBookmarkCategoryAdder(bookmarkCategoryId)),
|
||||||
|
});
|
||||||
|
|
||||||
|
class BookmarkCategory extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
bookmarkCategory: ImmutablePropTypes.map.isRequired,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
onRemove: PropTypes.func.isRequired,
|
||||||
|
onAdd: PropTypes.func.isRequired,
|
||||||
|
added: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
added: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { bookmarkCategory, intl, onRemove, onAdd, added } = this.props;
|
||||||
|
|
||||||
|
let button;
|
||||||
|
|
||||||
|
if (added) {
|
||||||
|
button = <IconButton icon='times' title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
|
||||||
|
} else {
|
||||||
|
button = <IconButton icon='plus' title={intl.formatMessage(messages.add)} onClick={onAdd} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='list'>
|
||||||
|
<div className='list__wrapper'>
|
||||||
|
<div className='list__display-name'>
|
||||||
|
<Icon id='user-bookmarkCategory' className='column-link__icon' fixedWidth />
|
||||||
|
{bookmarkCategory.get('title')}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='account__relationship'>
|
||||||
|
{button}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(MapStateToProps, mapDispatchToProps)(injectIntl(BookmarkCategory));
|
|
@ -0,0 +1,77 @@
|
||||||
|
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 { setupBookmarkCategoryAdder, resetBookmarkCategoryAdder } from '../../actions/bookmark_categories';
|
||||||
|
import NewBookmarkCategoryForm from '../bookmark_categories/components/new_bookmark_category_form';
|
||||||
|
|
||||||
|
// import Account from './components/account';
|
||||||
|
import BookmarkCategory from './components/bookmark_category';
|
||||||
|
|
||||||
|
const getOrderedBookmarkCategories = createSelector([state => state.get('bookmark_categories')], bookmarkCategories => {
|
||||||
|
if (!bookmarkCategories) {
|
||||||
|
return bookmarkCategories;
|
||||||
|
}
|
||||||
|
|
||||||
|
return bookmarkCategories.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
bookmarkCategoryIds: getOrderedBookmarkCategories(state).map(bookmarkCategory=>bookmarkCategory.get('id')),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => ({
|
||||||
|
onInitialize: statusId => dispatch(setupBookmarkCategoryAdder(statusId)),
|
||||||
|
onReset: () => dispatch(resetBookmarkCategoryAdder()),
|
||||||
|
});
|
||||||
|
|
||||||
|
class BookmarkCategoryAdder extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
statusId: PropTypes.string.isRequired,
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
onInitialize: PropTypes.func.isRequired,
|
||||||
|
onReset: PropTypes.func.isRequired,
|
||||||
|
bookmarkCategoryIds: ImmutablePropTypes.list.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
const { onInitialize, statusId } = this.props;
|
||||||
|
onInitialize(statusId);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount () {
|
||||||
|
const { onReset } = this.props;
|
||||||
|
onReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { bookmarkCategoryIds } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='modal-root__modal list-adder'>
|
||||||
|
{/*
|
||||||
|
<div className='list-adder__account'>
|
||||||
|
<Account accountId={accountId} />
|
||||||
|
</div>
|
||||||
|
*/}
|
||||||
|
|
||||||
|
<NewBookmarkCategoryForm />
|
||||||
|
|
||||||
|
|
||||||
|
<div className='list-adder__lists'>
|
||||||
|
{bookmarkCategoryIds.map(BookmarkCategoryId => <BookmarkCategory key={BookmarkCategoryId} bookmarkCategoryId={BookmarkCategoryId} />)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(BookmarkCategoryAdder));
|
|
@ -99,8 +99,8 @@ class BookmarkCategoryStatuses extends ImmutablePureComponent {
|
||||||
return (
|
return (
|
||||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}>
|
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}>
|
||||||
<ColumnHeader
|
<ColumnHeader
|
||||||
icon='bookmark_ex'
|
icon='bookmark'
|
||||||
title={intl.formatMessage(messages.heading)}
|
title={bookmarkCategory.get('title')}
|
||||||
onPin={this.handlePin}
|
onPin={this.handlePin}
|
||||||
onMove={this.handleMove}
|
onMove={this.handleMove}
|
||||||
onClick={this.handleHeaderClick}
|
onClick={this.handleHeaderClick}
|
||||||
|
|
|
@ -72,6 +72,7 @@ class ActionBar extends PureComponent {
|
||||||
onEmojiReact: PropTypes.func.isRequired,
|
onEmojiReact: PropTypes.func.isRequired,
|
||||||
onReference: PropTypes.func.isRequired,
|
onReference: PropTypes.func.isRequired,
|
||||||
onBookmark: PropTypes.func.isRequired,
|
onBookmark: PropTypes.func.isRequired,
|
||||||
|
onBookmarkCategoryAdder: PropTypes.func.isRequired,
|
||||||
onDelete: PropTypes.func.isRequired,
|
onDelete: PropTypes.func.isRequired,
|
||||||
onEdit: PropTypes.func.isRequired,
|
onEdit: PropTypes.func.isRequired,
|
||||||
onDirect: PropTypes.func.isRequired,
|
onDirect: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -374,6 +374,15 @@ class Status extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handleBookmarkCategoryAdderClick = (status) => {
|
||||||
|
this.props.dispatch(openModal({
|
||||||
|
modalType: 'BOOKMARK_CATEGORY_ADDER',
|
||||||
|
modalProps: {
|
||||||
|
statusId: status.get('id'),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
handleDeleteClick = (status, history, withRedraft = false) => {
|
handleDeleteClick = (status, history, withRedraft = false) => {
|
||||||
const { dispatch, intl } = this.props;
|
const { dispatch, intl } = this.props;
|
||||||
|
|
||||||
|
@ -737,6 +746,7 @@ class Status extends ImmutablePureComponent {
|
||||||
onReblogForceModal={this.handleReblogForceModalClick}
|
onReblogForceModal={this.handleReblogForceModalClick}
|
||||||
onReference={this.handleReference}
|
onReference={this.handleReference}
|
||||||
onBookmark={this.handleBookmarkClick}
|
onBookmark={this.handleBookmarkClick}
|
||||||
|
onBookmarkCategoryAdder={this.handleBookmarkCategoryAdderClick}
|
||||||
onDelete={this.handleDeleteClick}
|
onDelete={this.handleDeleteClick}
|
||||||
onEdit={this.handleEditClick}
|
onEdit={this.handleEditClick}
|
||||||
onDirect={this.handleDirectClick}
|
onDirect={this.handleDirectClick}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
AntennaAdder,
|
AntennaAdder,
|
||||||
CircleEditor,
|
CircleEditor,
|
||||||
CircleAdder,
|
CircleAdder,
|
||||||
|
BookmarkCategoryAdder,
|
||||||
CompareHistoryModal,
|
CompareHistoryModal,
|
||||||
FilterModal,
|
FilterModal,
|
||||||
InteractionModal,
|
InteractionModal,
|
||||||
|
@ -55,6 +56,7 @@ export const MODAL_COMPONENTS = {
|
||||||
'LIST_ADDER': ListAdder,
|
'LIST_ADDER': ListAdder,
|
||||||
'ANTENNA_ADDER': AntennaAdder,
|
'ANTENNA_ADDER': AntennaAdder,
|
||||||
'CIRCLE_ADDER': CircleAdder,
|
'CIRCLE_ADDER': CircleAdder,
|
||||||
|
'BOOKMARK_CATEGORY_ADDER': BookmarkCategoryAdder,
|
||||||
'COMPARE_HISTORY': CompareHistoryModal,
|
'COMPARE_HISTORY': CompareHistoryModal,
|
||||||
'FILTER': FilterModal,
|
'FILTER': FilterModal,
|
||||||
'SUBSCRIBED_LANGUAGES': SubscribedLanguagesModal,
|
'SUBSCRIBED_LANGUAGES': SubscribedLanguagesModal,
|
||||||
|
|
|
@ -130,6 +130,10 @@ export function BookmarkCategoryStatuses () {
|
||||||
return import(/* webpackChunkName: "features/bookmark_category_statuses" */'../../bookmark_category_statuses');
|
return import(/* webpackChunkName: "features/bookmark_category_statuses" */'../../bookmark_category_statuses');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function BookmarkCategoryAdder () {
|
||||||
|
return import(/* webpackChunkName: "features/bookmark_category_adder" */'../../bookmark_category_adder');
|
||||||
|
}
|
||||||
|
|
||||||
export function Blocks () {
|
export function Blocks () {
|
||||||
return import(/* webpackChunkName: "features/blocks" */'../../blocks');
|
return import(/* webpackChunkName: "features/blocks" */'../../blocks');
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,9 @@ import {
|
||||||
BOOKMARK_CATEGORY_STATUSES_EXPAND_SUCCESS,
|
BOOKMARK_CATEGORY_STATUSES_EXPAND_SUCCESS,
|
||||||
BOOKMARK_CATEGORY_STATUSES_EXPAND_FAIL,
|
BOOKMARK_CATEGORY_STATUSES_EXPAND_FAIL,
|
||||||
} from '../actions/bookmark_categories';
|
} from '../actions/bookmark_categories';
|
||||||
|
import {
|
||||||
|
UNBOOKMARK_SUCCESS,
|
||||||
|
} from '../actions/interactions';
|
||||||
|
|
||||||
const initialState = ImmutableMap();
|
const initialState = ImmutableMap();
|
||||||
|
|
||||||
|
@ -27,8 +30,8 @@ const normalizeBookmarkCategories = (state, bookmarkCategories) => {
|
||||||
return state;
|
return state;
|
||||||
};
|
};
|
||||||
|
|
||||||
const normalizeBookmarkCategoryStatuses = (state, bookmaryCategoryId, statuses, next) => {
|
const normalizeBookmarkCategoryStatuses = (state, bookmarkCategoryId, statuses, next) => {
|
||||||
return state.updateIn([bookmaryCategoryId, 'items'], listMap => listMap.withMutations(map => {
|
return state.update(bookmarkCategoryId, listMap => listMap.withMutations(map => {
|
||||||
map.set('next', next);
|
map.set('next', next);
|
||||||
map.set('loaded', true);
|
map.set('loaded', true);
|
||||||
map.set('isLoading', false);
|
map.set('isLoading', false);
|
||||||
|
@ -37,13 +40,20 @@ const normalizeBookmarkCategoryStatuses = (state, bookmaryCategoryId, statuses,
|
||||||
};
|
};
|
||||||
|
|
||||||
const appendToBookmarkCategoryStatuses = (state, bookmarkCategoryId, statuses, next) => {
|
const appendToBookmarkCategoryStatuses = (state, bookmarkCategoryId, statuses, next) => {
|
||||||
return state.updateIn([bookmarkCategoryId, 'items'], listMap => listMap.withMutations(map => {
|
return state.update(bookmarkCategoryId, listMap => listMap.withMutations(map => {
|
||||||
map.set('next', next);
|
map.set('next', next);
|
||||||
map.set('isLoading', false);
|
map.set('isLoading', false);
|
||||||
map.set('items', map.get('items').union(statuses.map(item => item.id)));
|
map.set('items', map.get('items').union(statuses.map(item => item.id)));
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeStatusFromAllBookmarkCategories = (state, status) => {
|
||||||
|
state.toList().forEach((bookmarkCategory) => {
|
||||||
|
state = state.updateIn([bookmarkCategory.get('id'), 'items'], items => items.delete(status.get('id')));
|
||||||
|
});
|
||||||
|
return state;
|
||||||
|
};
|
||||||
|
|
||||||
export default function bookmarkCategories(state = initialState, action) {
|
export default function bookmarkCategories(state = initialState, action) {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case BOOKMARK_CATEGORY_FETCH_SUCCESS:
|
case BOOKMARK_CATEGORY_FETCH_SUCCESS:
|
||||||
|
@ -65,6 +75,8 @@ export default function bookmarkCategories(state = initialState, action) {
|
||||||
return normalizeBookmarkCategoryStatuses(state, action.id, action.statuses, action.next);
|
return normalizeBookmarkCategoryStatuses(state, action.id, action.statuses, action.next);
|
||||||
case BOOKMARK_CATEGORY_STATUSES_EXPAND_SUCCESS:
|
case BOOKMARK_CATEGORY_STATUSES_EXPAND_SUCCESS:
|
||||||
return appendToBookmarkCategoryStatuses(state, action.id, action.statuses, action.next);
|
return appendToBookmarkCategoryStatuses(state, action.id, action.statuses, action.next);
|
||||||
|
case UNBOOKMARK_SUCCESS:
|
||||||
|
return removeStatusFromAllBookmarkCategories(state, action.status);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
48
app/javascript/mastodon/reducers/bookmark_category_adder.js
Normal file
48
app/javascript/mastodon/reducers/bookmark_category_adder.js
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BOOKMARK_CATEGORY_ADDER_RESET,
|
||||||
|
BOOKMARK_CATEGORY_ADDER_SETUP,
|
||||||
|
BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_REQUEST,
|
||||||
|
BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_SUCCESS,
|
||||||
|
BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_FAIL,
|
||||||
|
BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS,
|
||||||
|
BOOKMARK_CATEGORY_EDITOR_REMOVE_SUCCESS,
|
||||||
|
} from '../actions/bookmark_categories';
|
||||||
|
|
||||||
|
const initialState = ImmutableMap({
|
||||||
|
statusId: null,
|
||||||
|
|
||||||
|
bookmarkCategories: ImmutableMap({
|
||||||
|
items: ImmutableList(),
|
||||||
|
loaded: false,
|
||||||
|
isLoading: false,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function bookmarkCategoryAdderReducer(state = initialState, action) {
|
||||||
|
switch(action.type) {
|
||||||
|
case BOOKMARK_CATEGORY_ADDER_RESET:
|
||||||
|
return initialState;
|
||||||
|
case BOOKMARK_CATEGORY_ADDER_SETUP:
|
||||||
|
return state.withMutations(map => {
|
||||||
|
map.set('statusId', action.status.get('id'));
|
||||||
|
});
|
||||||
|
case BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_REQUEST:
|
||||||
|
return state.setIn(['bookmarkCategories', 'isLoading'], true);
|
||||||
|
case BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_FAIL:
|
||||||
|
return state.setIn(['bookmarkCategories', 'isLoading'], false);
|
||||||
|
case BOOKMARK_CATEGORY_ADDER_BOOKMARK_CATEGORIES_FETCH_SUCCESS:
|
||||||
|
return state.update('bookmarkCategories', bookmarkCategories => bookmarkCategories.withMutations(map => {
|
||||||
|
map.set('isLoading', false);
|
||||||
|
map.set('loaded', true);
|
||||||
|
map.set('items', ImmutableList(action.bookmarkCategories.map(item => item.id)));
|
||||||
|
}));
|
||||||
|
case BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS:
|
||||||
|
return state.updateIn(['bookmarkCategories', 'items'], bookmarkCategory => bookmarkCategory.unshift(action.bookmarkCategoryId));
|
||||||
|
case BOOKMARK_CATEGORY_EDITOR_REMOVE_SUCCESS:
|
||||||
|
return state.updateIn(['bookmarkCategories', 'items'], bookmarkCategory => bookmarkCategory.filterNot(item => item === action.bookmarkCategoryId));
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,12 +10,10 @@ import {
|
||||||
BOOKMARK_CATEGORY_EDITOR_RESET,
|
BOOKMARK_CATEGORY_EDITOR_RESET,
|
||||||
BOOKMARK_CATEGORY_EDITOR_SETUP,
|
BOOKMARK_CATEGORY_EDITOR_SETUP,
|
||||||
BOOKMARK_CATEGORY_EDITOR_TITLE_CHANGE,
|
BOOKMARK_CATEGORY_EDITOR_TITLE_CHANGE,
|
||||||
BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS,
|
|
||||||
BOOKMARK_CATEGORY_EDITOR_REMOVE_SUCCESS,
|
|
||||||
} from '../actions/bookmark_categories';
|
} from '../actions/bookmark_categories';
|
||||||
|
|
||||||
const initialState = ImmutableMap({
|
const initialState = ImmutableMap({
|
||||||
bookmaryCategoryId: null,
|
bookmarkCategoryId: null,
|
||||||
isSubmitting: false,
|
isSubmitting: false,
|
||||||
isChanged: false,
|
isChanged: false,
|
||||||
title: '',
|
title: '',
|
||||||
|
@ -33,15 +31,15 @@ const initialState = ImmutableMap({
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function bookmaryCategoryEditorReducer(state = initialState, action) {
|
export default function bookmarkCategoryEditorReducer(state = initialState, action) {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case BOOKMARK_CATEGORY_EDITOR_RESET:
|
case BOOKMARK_CATEGORY_EDITOR_RESET:
|
||||||
return initialState;
|
return initialState;
|
||||||
case BOOKMARK_CATEGORY_EDITOR_SETUP:
|
case BOOKMARK_CATEGORY_EDITOR_SETUP:
|
||||||
return state.withMutations(map => {
|
return state.withMutations(map => {
|
||||||
map.set('bookmaryCategoryId', action.bookmaryCategory.get('id'));
|
map.set('bookmarkCategoryId', action.bookmarkCategory.get('id'));
|
||||||
map.set('title', action.bookmaryCategory.get('title'));
|
map.set('title', action.bookmarkCategory.get('title'));
|
||||||
map.set('isExclusive', action.bookmaryCategory.get('is_exclusive'));
|
map.set('isExclusive', action.bookmarkCategory.get('is_exclusive'));
|
||||||
map.set('isSubmitting', false);
|
map.set('isSubmitting', false);
|
||||||
});
|
});
|
||||||
case BOOKMARK_CATEGORY_EDITOR_TITLE_CHANGE:
|
case BOOKMARK_CATEGORY_EDITOR_TITLE_CHANGE:
|
||||||
|
@ -62,12 +60,8 @@ export default function bookmaryCategoryEditorReducer(state = initialState, acti
|
||||||
case BOOKMARK_CATEGORY_UPDATE_SUCCESS:
|
case BOOKMARK_CATEGORY_UPDATE_SUCCESS:
|
||||||
return state.withMutations(map => {
|
return state.withMutations(map => {
|
||||||
map.set('isSubmitting', false);
|
map.set('isSubmitting', false);
|
||||||
map.set('bookmaryCategoryId', action.bookmaryCategory.id);
|
map.set('bookmarkCategoryId', action.bookmarkCategory.id);
|
||||||
});
|
});
|
||||||
case BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS:
|
|
||||||
return state.updateIn(['accounts', 'items'], bookmaryCategory => bookmaryCategory.unshift(action.accountId));
|
|
||||||
case BOOKMARK_CATEGORY_EDITOR_REMOVE_SUCCESS:
|
|
||||||
return state.updateIn(['accounts', 'items'], bookmaryCategory => bookmaryCategory.filterNot(item => item === action.accountId));
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import antennaEditor from './antenna_editor';
|
||||||
import antennas from './antennas';
|
import antennas from './antennas';
|
||||||
import blocks from './blocks';
|
import blocks from './blocks';
|
||||||
import bookmark_categories from './bookmark_categories';
|
import bookmark_categories from './bookmark_categories';
|
||||||
|
import bookmarkCategoryAdder from './bookmark_category_adder';
|
||||||
import bookmarkCategoryEditor from './bookmark_category_editor';
|
import bookmarkCategoryEditor from './bookmark_category_editor';
|
||||||
import boosts from './boosts';
|
import boosts from './boosts';
|
||||||
import circleAdder from './circle_adder';
|
import circleAdder from './circle_adder';
|
||||||
|
@ -93,6 +94,7 @@ const reducers = {
|
||||||
circleAdder,
|
circleAdder,
|
||||||
bookmark_categories,
|
bookmark_categories,
|
||||||
bookmarkCategoryEditor,
|
bookmarkCategoryEditor,
|
||||||
|
bookmarkCategoryAdder,
|
||||||
filters,
|
filters,
|
||||||
conversations,
|
conversations,
|
||||||
suggestions,
|
suggestions,
|
||||||
|
|
|
@ -4,6 +4,9 @@ import {
|
||||||
ACCOUNT_BLOCK_SUCCESS,
|
ACCOUNT_BLOCK_SUCCESS,
|
||||||
ACCOUNT_MUTE_SUCCESS,
|
ACCOUNT_MUTE_SUCCESS,
|
||||||
} from '../actions/accounts';
|
} from '../actions/accounts';
|
||||||
|
import {
|
||||||
|
BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS,
|
||||||
|
} from '../actions/bookmark_categories';
|
||||||
import {
|
import {
|
||||||
BOOKMARKED_STATUSES_FETCH_REQUEST,
|
BOOKMARKED_STATUSES_FETCH_REQUEST,
|
||||||
BOOKMARKED_STATUSES_FETCH_SUCCESS,
|
BOOKMARKED_STATUSES_FETCH_SUCCESS,
|
||||||
|
@ -98,11 +101,15 @@ const appendToList = (state, listType, statuses, next) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const prependOneToList = (state, listType, status) => {
|
const prependOneToList = (state, listType, status) => {
|
||||||
|
return prependOneToListById(state, listType, status.get('id'));
|
||||||
|
};
|
||||||
|
|
||||||
|
const prependOneToListById = (state, listType, statusId) => {
|
||||||
return state.updateIn([listType, 'items'], (list) => {
|
return state.updateIn([listType, 'items'], (list) => {
|
||||||
if (list.includes(status.get('id'))) {
|
if (list.includes(statusId)) {
|
||||||
return list;
|
return list;
|
||||||
} else {
|
} else {
|
||||||
return ImmutableOrderedSet([status.get('id')]).union(list);
|
return ImmutableOrderedSet([statusId]).union(list);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -163,6 +170,8 @@ export default function statusLists(state = initialState, action) {
|
||||||
return removeOneFromList(state, 'emoji_reactions', action.status);
|
return removeOneFromList(state, 'emoji_reactions', action.status);
|
||||||
case BOOKMARK_SUCCESS:
|
case BOOKMARK_SUCCESS:
|
||||||
return prependOneToList(state, 'bookmarks', action.status);
|
return prependOneToList(state, 'bookmarks', action.status);
|
||||||
|
case BOOKMARK_CATEGORY_EDITOR_ADD_SUCCESS:
|
||||||
|
return prependOneToListById(state, 'bookmarks', action.statusId);
|
||||||
case UNBOOKMARK_SUCCESS:
|
case UNBOOKMARK_SUCCESS:
|
||||||
return removeOneFromList(state, 'bookmarks', action.status);
|
return removeOneFromList(state, 'bookmarks', action.status);
|
||||||
case PINNED_STATUSES_FETCH_SUCCESS:
|
case PINNED_STATUSES_FETCH_SUCCESS:
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||||
|
|
||||||
|
import {
|
||||||
|
BOOKMARK_CATEGORY_EDITOR_ADD_REQUEST,
|
||||||
|
BOOKMARK_CATEGORY_EDITOR_ADD_FAIL,
|
||||||
|
} from '../actions/bookmark_categories';
|
||||||
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
|
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
|
||||||
import { normalizeStatusTranslation } from '../actions/importer/normalizer';
|
import { normalizeStatusTranslation } from '../actions/importer/normalizer';
|
||||||
import {
|
import {
|
||||||
|
@ -111,6 +115,10 @@ export default function statuses(state = initialState, action) {
|
||||||
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], true);
|
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], true);
|
||||||
case BOOKMARK_FAIL:
|
case BOOKMARK_FAIL:
|
||||||
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], false);
|
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], false);
|
||||||
|
case BOOKMARK_CATEGORY_EDITOR_ADD_REQUEST:
|
||||||
|
return state.get(action.statusId) === undefined ? state : state.setIn([action.statusId, 'bookmarked'], true);
|
||||||
|
case BOOKMARK_CATEGORY_EDITOR_ADD_FAIL:
|
||||||
|
return state.get(action.statusId) === undefined ? state : state.setIn([action.statusId, 'bookmarked'], false);
|
||||||
case UNBOOKMARK_REQUEST:
|
case UNBOOKMARK_REQUEST:
|
||||||
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], false);
|
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'bookmarked'], false);
|
||||||
case UNBOOKMARK_FAIL:
|
case UNBOOKMARK_FAIL:
|
||||||
|
|
29
app/models/bookmark_category.rb
Normal file
29
app/models/bookmark_category.rb
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: bookmark_categories
|
||||||
|
#
|
||||||
|
# id :bigint(8) not null, primary key
|
||||||
|
# account_id :bigint(8) not null
|
||||||
|
# title :string default(""), not null
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
|
||||||
|
class BookmarkCategory < ApplicationRecord
|
||||||
|
include Paginable
|
||||||
|
|
||||||
|
PER_CATEGORY_LIMIT = 20
|
||||||
|
|
||||||
|
belongs_to :account
|
||||||
|
|
||||||
|
has_many :bookmark_category_statuses, inverse_of: :bookmark_category, dependent: :destroy
|
||||||
|
has_many :statuses, through: :bookmark_category_statuses
|
||||||
|
|
||||||
|
validates :title, presence: true
|
||||||
|
|
||||||
|
validates_each :account_id, on: :create do |record, _attr, value|
|
||||||
|
record.errors.add(:base, I18n.t('bookmark_categories.errors.limit')) if BookmarkCategory.where(account_id: value).count >= PER_CATEGORY_LIMIT
|
||||||
|
end
|
||||||
|
end
|
34
app/models/bookmark_category_status.rb
Normal file
34
app/models/bookmark_category_status.rb
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: bookmark_category_statuses
|
||||||
|
#
|
||||||
|
# id :bigint(8) not null, primary key
|
||||||
|
# bookmark_category_id :bigint(8) not null
|
||||||
|
# status_id :bigint(8) not null
|
||||||
|
# bookmark_id :bigint(8)
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
|
||||||
|
class BookmarkCategoryStatus < ApplicationRecord
|
||||||
|
belongs_to :bookmark_category
|
||||||
|
belongs_to :status
|
||||||
|
belongs_to :bookmark
|
||||||
|
|
||||||
|
validates :status_id, uniqueness: { scope: :bookmark_category_id }
|
||||||
|
validate :validate_relationship
|
||||||
|
|
||||||
|
before_validation :set_bookmark
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_bookmark
|
||||||
|
self.bookmark = Bookmark.find_by!(account_id: bookmark_category.account_id, status_id: status_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_relationship
|
||||||
|
errors.add(:account_id, 'bookmark relationship missing') if bookmark_id.blank?
|
||||||
|
end
|
||||||
|
end
|
|
@ -15,6 +15,9 @@ module AccountAssociations
|
||||||
has_many :favourites, inverse_of: :account, dependent: :destroy
|
has_many :favourites, inverse_of: :account, dependent: :destroy
|
||||||
has_many :emoji_reactions, inverse_of: :account, dependent: :destroy
|
has_many :emoji_reactions, inverse_of: :account, dependent: :destroy
|
||||||
has_many :bookmarks, inverse_of: :account, dependent: :destroy
|
has_many :bookmarks, inverse_of: :account, dependent: :destroy
|
||||||
|
has_many :bookmark_categories, inverse_of: :account, dependent: :destroy
|
||||||
|
has_many :circles, inverse_of: :account, dependent: :destroy
|
||||||
|
has_many :antennas, inverse_of: :account, dependent: :destroy
|
||||||
has_many :mentions, inverse_of: :account, dependent: :destroy
|
has_many :mentions, inverse_of: :account, dependent: :destroy
|
||||||
has_many :notifications, inverse_of: :account, dependent: :destroy
|
has_many :notifications, inverse_of: :account, dependent: :destroy
|
||||||
has_many :conversations, class_name: 'AccountConversation', dependent: :destroy, inverse_of: :account
|
has_many :conversations, class_name: 'AccountConversation', dependent: :destroy, inverse_of: :account
|
||||||
|
|
|
@ -84,6 +84,8 @@ class Status < ApplicationRecord
|
||||||
has_many :referenced_by_status_objects, foreign_key: 'target_status_id', class_name: 'StatusReference', inverse_of: :target_status, dependent: :destroy
|
has_many :referenced_by_status_objects, foreign_key: 'target_status_id', class_name: 'StatusReference', inverse_of: :target_status, dependent: :destroy
|
||||||
has_many :referenced_by_statuses, through: :referenced_by_status_objects, class_name: 'Status', source: :status
|
has_many :referenced_by_statuses, through: :referenced_by_status_objects, class_name: 'Status', source: :status
|
||||||
has_many :capability_tokens, class_name: 'StatusCapabilityToken', inverse_of: :status, dependent: :destroy
|
has_many :capability_tokens, class_name: 'StatusCapabilityToken', inverse_of: :status, dependent: :destroy
|
||||||
|
has_many :bookmark_category_relationships, class_name: 'BookmarkCategoryStatus', inverse_of: :status, dependent: :destroy
|
||||||
|
has_many :joined_bookmark_categories, class_name: 'BookmarkCategory', through: :bookmark_category_relationships, source: :bookmark_category
|
||||||
|
|
||||||
has_and_belongs_to_many :tags
|
has_and_belongs_to_many :tags
|
||||||
has_and_belongs_to_many :preview_cards
|
has_and_belongs_to_many :preview_cards
|
||||||
|
|
9
app/serializers/rest/bookmark_category_serializer.rb
Normal file
9
app/serializers/rest/bookmark_category_serializer.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class REST::BookmarkCategorySerializer < ActiveModel::Serializer
|
||||||
|
attributes :id, :title
|
||||||
|
|
||||||
|
def id
|
||||||
|
object.id.to_s
|
||||||
|
end
|
||||||
|
end
|
|
@ -13,6 +13,7 @@ namespace :api, format: false do
|
||||||
resources :emoji_reactioned_by, controller: :emoji_reactioned_by_accounts, only: :index
|
resources :emoji_reactioned_by, controller: :emoji_reactioned_by_accounts, only: :index
|
||||||
resources :emoji_reactioned_by_slim, controller: :emoji_reactioned_by_accounts_slim, only: :index
|
resources :emoji_reactioned_by_slim, controller: :emoji_reactioned_by_accounts_slim, only: :index
|
||||||
resources :referred_by, controller: :referred_by_statuses, only: :index
|
resources :referred_by, controller: :referred_by_statuses, only: :index
|
||||||
|
resources :bookmark_categories, only: :index
|
||||||
resource :reblog, only: :create
|
resource :reblog, only: :create
|
||||||
post :unreblog, to: 'reblogs#destroy'
|
post :unreblog, to: 'reblogs#destroy'
|
||||||
|
|
||||||
|
@ -228,6 +229,10 @@ namespace :api, format: false do
|
||||||
resource :accounts, only: [:show, :create, :destroy], controller: 'circles/accounts'
|
resource :accounts, only: [:show, :create, :destroy], controller: 'circles/accounts'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :bookmark_categories, only: [:index, :create, :show, :update, :destroy] do
|
||||||
|
resource :statuses, only: [:show, :create, :destroy], controller: 'bookmark_categories/statuses'
|
||||||
|
end
|
||||||
|
|
||||||
namespace :featured_tags do
|
namespace :featured_tags do
|
||||||
get :suggestions, to: 'suggestions#index'
|
get :suggestions, to: 'suggestions#index'
|
||||||
end
|
end
|
||||||
|
|
27
db/migrate/20230826023400_create_bookmark_categories.rb
Normal file
27
db/migrate/20230826023400_create_bookmark_categories.rb
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
|
||||||
|
|
||||||
|
class CreateBookmarkCategories < ActiveRecord::Migration[7.0]
|
||||||
|
include Mastodon::MigrationHelpers
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def change
|
||||||
|
create_table :bookmark_categories do |t|
|
||||||
|
t.belongs_to :account, null: false, foreign_key: { on_delete: :cascade }
|
||||||
|
t.string :title, null: false, default: ''
|
||||||
|
t.datetime :created_at, null: false
|
||||||
|
t.datetime :updated_at, null: false
|
||||||
|
end
|
||||||
|
create_table :bookmark_category_statuses do |t|
|
||||||
|
t.belongs_to :bookmark_category, null: false, foreign_key: { on_delete: :cascade }
|
||||||
|
t.belongs_to :status, null: false, foreign_key: { on_delete: :cascade }
|
||||||
|
t.belongs_to :bookmark, null: true, foreign_key: { on_delete: :cascade }
|
||||||
|
t.datetime :created_at, null: false
|
||||||
|
t.datetime :updated_at, null: false
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :bookmark_category_statuses, [:bookmark_category_id, :status_id], unique: true, algorithm: :concurrently, name: 'index_bc_statuses'
|
||||||
|
end
|
||||||
|
end
|
26
db/schema.rb
26
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.0].define(version: 2023_08_22_041804) do
|
ActiveRecord::Schema[7.0].define(version: 2023_08_26_023400) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
|
@ -361,6 +361,26 @@ ActiveRecord::Schema[7.0].define(version: 2023_08_22_041804) do
|
||||||
t.index ["target_account_id"], name: "index_blocks_on_target_account_id"
|
t.index ["target_account_id"], name: "index_blocks_on_target_account_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "bookmark_categories", force: :cascade do |t|
|
||||||
|
t.bigint "account_id", null: false
|
||||||
|
t.string "title", default: "", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["account_id"], name: "index_bookmark_categories_on_account_id"
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table "bookmark_category_statuses", force: :cascade do |t|
|
||||||
|
t.bigint "bookmark_category_id", null: false
|
||||||
|
t.bigint "status_id", null: false
|
||||||
|
t.bigint "bookmark_id"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["bookmark_category_id", "status_id"], name: "index_bc_statuses", unique: true
|
||||||
|
t.index ["bookmark_category_id"], name: "index_bookmark_category_statuses_on_bookmark_category_id"
|
||||||
|
t.index ["bookmark_id"], name: "index_bookmark_category_statuses_on_bookmark_id"
|
||||||
|
t.index ["status_id"], name: "index_bookmark_category_statuses_on_status_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "bookmarks", force: :cascade do |t|
|
create_table "bookmarks", force: :cascade do |t|
|
||||||
t.bigint "account_id", null: false
|
t.bigint "account_id", null: false
|
||||||
t.bigint "status_id", null: false
|
t.bigint "status_id", null: false
|
||||||
|
@ -1359,6 +1379,10 @@ ActiveRecord::Schema[7.0].define(version: 2023_08_22_041804) do
|
||||||
add_foreign_key "backups", "users", on_delete: :nullify
|
add_foreign_key "backups", "users", on_delete: :nullify
|
||||||
add_foreign_key "blocks", "accounts", column: "target_account_id", name: "fk_9571bfabc1", on_delete: :cascade
|
add_foreign_key "blocks", "accounts", column: "target_account_id", name: "fk_9571bfabc1", on_delete: :cascade
|
||||||
add_foreign_key "blocks", "accounts", name: "fk_4269e03e65", on_delete: :cascade
|
add_foreign_key "blocks", "accounts", name: "fk_4269e03e65", on_delete: :cascade
|
||||||
|
add_foreign_key "bookmark_categories", "accounts", on_delete: :cascade
|
||||||
|
add_foreign_key "bookmark_category_statuses", "bookmark_categories", on_delete: :cascade
|
||||||
|
add_foreign_key "bookmark_category_statuses", "bookmarks", on_delete: :cascade
|
||||||
|
add_foreign_key "bookmark_category_statuses", "statuses", on_delete: :cascade
|
||||||
add_foreign_key "bookmarks", "accounts", on_delete: :cascade
|
add_foreign_key "bookmarks", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "bookmarks", "statuses", on_delete: :cascade
|
add_foreign_key "bookmarks", "statuses", on_delete: :cascade
|
||||||
add_foreign_key "bulk_import_rows", "bulk_imports", on_delete: :cascade
|
add_foreign_key "bulk_import_rows", "bulk_imports", on_delete: :cascade
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue