Added shortcuts
• Added: - shortcuts functionality - shortcuts route, controller, model - shortcut error message for "exists" - shortcut redux - EditShortcutsModal, constant - links to sidebar, sidebar_xs - options to add/remove group, account in GroupOptionsPopover, ProfileOptionsPopover - shortcuts page, feature/list
This commit is contained in:
parent
405ace09da
commit
f92f75d747
95
app/controllers/api/v1/shortcuts_controller.rb
Normal file
95
app/controllers/api/v1/shortcuts_controller.rb
Normal file
@ -0,0 +1,95 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::ShortcutsController < Api::BaseController
|
||||
before_action :require_user!
|
||||
before_action :set_shortcut, except: [:index, :create]
|
||||
|
||||
def index
|
||||
@shortcuts = Shortcut.where(account: current_account).limit(100)
|
||||
|
||||
@onlyGroupIds = @shortcuts.select{ |s| s.shortcut_type == 'group' }.map(&:shortcut_id)
|
||||
@onlyAccountIds = @shortcuts.select{ |s| s.shortcut_type == 'account' }.map(&:shortcut_id)
|
||||
|
||||
@groups = Group.where(id: @onlyGroupIds, is_archived: false).limit(100)
|
||||
@accounts = Account.where(id: @onlyAccountIds).without_suspended.limit(100)
|
||||
|
||||
@final = @shortcuts.map do |s|
|
||||
value = nil
|
||||
title = nil
|
||||
to = nil
|
||||
image = nil
|
||||
|
||||
if s.shortcut_type == 'group'
|
||||
@group = @groups.detect{ |g| g.id == s.shortcut_id }
|
||||
if @group.nil?
|
||||
s.destroy!
|
||||
else
|
||||
value = REST::GroupSerializer.new(@group)
|
||||
end
|
||||
elsif s.shortcut_type == 'account'
|
||||
@account = @accounts.detect{ |a| a.id == s.shortcut_id }
|
||||
if @account.nil?
|
||||
s.destroy!
|
||||
else
|
||||
value = REST::AccountSerializer.new(@account)
|
||||
end
|
||||
end
|
||||
|
||||
r = {
|
||||
id: s.id,
|
||||
created_at: s.created_at,
|
||||
shortcut_id: s.shortcut_id,
|
||||
shortcut_type: s.shortcut_type,
|
||||
shortcut: value,
|
||||
}
|
||||
r
|
||||
end
|
||||
|
||||
render json: @final
|
||||
end
|
||||
|
||||
def show
|
||||
render json: @shortcut, serializer: REST::ShortcutSerializer
|
||||
end
|
||||
|
||||
def create
|
||||
@shortcut = Shortcut.create!(shortcut_params.merge(account: current_account))
|
||||
|
||||
value = nil
|
||||
if @shortcut.shortcut_type == 'group'
|
||||
@group = Group.where(id: @shortcut.shortcut_id, is_archived: false).first
|
||||
value = REST::GroupSerializer.new(@group)
|
||||
elsif @shortcut.shortcut_type == 'account'
|
||||
@account = Account.where(id: @shortcut.shortcut_id).without_suspended.first
|
||||
value = REST::AccountSerializer.new(@account)
|
||||
end
|
||||
|
||||
r = {
|
||||
id: @shortcut.id,
|
||||
created_at: @shortcut.created_at,
|
||||
shortcut_type: @shortcut.shortcut_type,
|
||||
shortcut_id: @shortcut.shortcut_id,
|
||||
shortcut: value,
|
||||
}
|
||||
|
||||
render json: r
|
||||
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
render json: { error: I18n.t('shortcuts.errors.exists') }, status: 422
|
||||
end
|
||||
|
||||
def destroy
|
||||
@shortcut.destroy!
|
||||
render json: { error: false, id: params[:id] }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_shortcut
|
||||
@shortcut = Shortcut.where(account: current_account).find(params[:id])
|
||||
end
|
||||
|
||||
def shortcut_params
|
||||
params.permit(:shortcut_type, :shortcut_id)
|
||||
end
|
||||
end
|
@ -36,7 +36,7 @@ class ReactController < ApplicationController
|
||||
end
|
||||
|
||||
def find_route_matches
|
||||
request.path.match(/\A\/(home|group|groups|list|lists|notifications|tags|compose|follow_requests|admin|account|settings|filters|timeline|blocks|domain_blocks|mutes)/)
|
||||
request.path.match(/\A\/(home|shortcuts|group|groups|list|lists|notifications|tags|compose|follow_requests|admin|account|settings|filters|timeline|blocks|domain_blocks|mutes)/)
|
||||
end
|
||||
|
||||
def find_public_route_matches
|
||||
|
128
app/javascript/gabsocial/actions/shortcuts.js
Normal file
128
app/javascript/gabsocial/actions/shortcuts.js
Normal file
@ -0,0 +1,128 @@
|
||||
import { me } from '../initial_state'
|
||||
import api from '../api'
|
||||
|
||||
export const SHORTCUTS_FETCH_REQUEST = 'SHORTCUTS_FETCH_REQUEST'
|
||||
export const SHORTCUTS_FETCH_SUCCESS = 'SHORTCUTS_FETCH_SUCCESS'
|
||||
export const SHORTCUTS_FETCH_FAIL = 'SHORTCUTS_FETCH_FAIL'
|
||||
|
||||
export const SHORTCUTS_ADD_REQUEST = 'SHORTCUTS_ADD_REQUEST'
|
||||
export const SHORTCUTS_ADD_SUCCESS = 'SHORTCUTS_ADD_SUCCESS'
|
||||
export const SHORTCUTS_ADD_FAIL = 'SHORTCUTS_ADD_FAIL'
|
||||
|
||||
export const SHORTCUTS_REMOVE_REQUEST = 'SHORTCUTS_REMOVE_REQUEST'
|
||||
export const SHORTCUTS_REMOVE_SUCCESS = 'SHORTCUTS_REMOVE_SUCCESS'
|
||||
export const SHORTCUTS_REMOVE_FAIL = 'SHORTCUTS_REMOVE_FAIL'
|
||||
|
||||
export function fetchShortcuts() {
|
||||
return (dispatch, getState) => {
|
||||
if (!me) return
|
||||
|
||||
dispatch(fetchShortcutsRequest())
|
||||
|
||||
api(getState).get('/api/v1/shortcuts').then(response => {
|
||||
dispatch(fetchShortcutsSuccess(response.data))
|
||||
}).catch(error => dispatch(fetchShortcutsFail(error)))
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchShortcutsRequest() {
|
||||
return {
|
||||
type: SHORTCUTS_FETCH_REQUEST,
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchShortcutsSuccess(shortcuts) {
|
||||
return {
|
||||
shortcuts,
|
||||
type: SHORTCUTS_FETCH_SUCCESS,
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchShortcutsFail(error) {
|
||||
return {
|
||||
error,
|
||||
type: SHORTCUTS_FETCH_FAIL,
|
||||
}
|
||||
}
|
||||
|
||||
export function addShortcut(shortcutType, shortcutId) {
|
||||
return (dispatch, getState) => {
|
||||
if (!me) return
|
||||
|
||||
dispatch(addShortcutsRequest())
|
||||
|
||||
api(getState).post('/api/v1/shortcuts', {
|
||||
shortcut_type: shortcutType,
|
||||
shortcut_id: shortcutId,
|
||||
}).then(response => {
|
||||
dispatch(addShortcutsSuccess(response.data))
|
||||
}).catch(error => dispatch(addShortcutsFail(error)))
|
||||
}
|
||||
}
|
||||
|
||||
export function addShortcutsRequest() {
|
||||
return {
|
||||
type: SHORTCUTS_ADD_REQUEST,
|
||||
}
|
||||
}
|
||||
|
||||
export function addShortcutsSuccess(shortcut) {
|
||||
return {
|
||||
shortcut,
|
||||
type: SHORTCUTS_ADD_SUCCESS,
|
||||
}
|
||||
}
|
||||
|
||||
export function addShortcutsFail(error) {
|
||||
return {
|
||||
error,
|
||||
type: SHORTCUTS_ADD_FAIL,
|
||||
}
|
||||
}
|
||||
|
||||
export function removeShortcut(shortcutObjectId, shortcutType, shortcutId) {
|
||||
return (dispatch, getState) => {
|
||||
if (!me) return
|
||||
|
||||
let id
|
||||
if (shortcutObjectId) {
|
||||
shortcutObjectId = id
|
||||
} else if (shortcutType && shortcutId) {
|
||||
const shortcuts = getState().getIn(['shortcuts', 'items'])
|
||||
const shortcut = shortcuts.find((s) => {
|
||||
return s.get('shortcut_id') == shortcutId && s.get('shortcut_type') === shortcutType
|
||||
})
|
||||
if (!!shortcut) {
|
||||
id = shortcut.get('id')
|
||||
}
|
||||
}
|
||||
|
||||
if (!id) return
|
||||
|
||||
dispatch(removeShortcutsRequest())
|
||||
|
||||
api(getState).delete(`/api/v1/shortcuts/${id}`).then(response => {
|
||||
dispatch(removeShortcutsSuccess(response.data.id))
|
||||
}).catch(error => dispatch(removeShortcutsFail(error)))
|
||||
}
|
||||
}
|
||||
|
||||
export function removeShortcutsRequest() {
|
||||
return {
|
||||
type: SHORTCUTS_REMOVE_REQUEST,
|
||||
}
|
||||
}
|
||||
|
||||
export function removeShortcutsSuccess(shortcutId) {
|
||||
return {
|
||||
shortcutId,
|
||||
type: SHORTCUTS_REMOVE_SUCCESS,
|
||||
}
|
||||
}
|
||||
|
||||
export function removeShortcutsFail(error) {
|
||||
return {
|
||||
error,
|
||||
type: SHORTCUTS_REMOVE_FAIL,
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { removeShortcut } from '../../actions/shortcuts'
|
||||
import ModalLayout from './modal_layout'
|
||||
import List from '../list'
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'shortcuts.edit', defaultMessage: 'Edit Shortcuts' },
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
})
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
shortcuts: state.getIn(['shortcuts', 'items']),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onRemoveShortcut(shortcutId) {
|
||||
dispatch(removeShortcut(shortcutId))
|
||||
},
|
||||
})
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
class EditShortcutsModal extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onRemoveShortcut: PropTypes.func.isRequired,
|
||||
shortcuts: ImmutablePropTypes.list,
|
||||
}
|
||||
|
||||
handleOnRemoveShortcut = (shortcutId) => {
|
||||
this.props.onRemoveShortcut(shortcutId)
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
onClose,
|
||||
shortcuts,
|
||||
} = this.props
|
||||
|
||||
const listItems = shortcuts.map((s) => ({
|
||||
title: s.get('title'),
|
||||
image: s.get('image'),
|
||||
actionIcon: 'subtract',
|
||||
onClick: () => this.handleOnRemoveShortcut(s.get('id')),
|
||||
}))
|
||||
|
||||
return (
|
||||
<ModalLayout
|
||||
title={intl.formatMessage(messages.title)}
|
||||
onClose={onClose}
|
||||
width={460}
|
||||
noPadding
|
||||
>
|
||||
<div className={_s.boxShadowNone}>
|
||||
<List
|
||||
scrollKey='shortcuts'
|
||||
emptyMessage='You have no shortcuts'
|
||||
items={listItems}
|
||||
/>
|
||||
</div>
|
||||
</ModalLayout>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -6,9 +6,12 @@ import {
|
||||
MODAL_GROUP_MEMBERS,
|
||||
MODAL_GROUP_REMOVED_ACCOUNTS,
|
||||
} from '../../constants'
|
||||
import {
|
||||
addShortcut,
|
||||
removeShortcut,
|
||||
} from '../../actions/shortcuts'
|
||||
import { openModal } from '../../actions/modal'
|
||||
import { closePopover } from '../../actions/popover'
|
||||
import { me } from '../../initial_state'
|
||||
import PopoverLayout from './popover_layout'
|
||||
import List from '../list'
|
||||
|
||||
@ -16,7 +19,18 @@ const messages = defineMessages({
|
||||
groupMembers: { id: 'group_members', defaultMessage: 'Group members' },
|
||||
removedMembers: { id: 'group_removed_members', defaultMessage: 'Removed accounts' },
|
||||
editGroup: { id: 'edit_group', defaultMessage: 'Edit group' },
|
||||
});
|
||||
add_to_shortcuts: { id: 'account.add_to_shortcuts', defaultMessage: 'Add to shortcuts' },
|
||||
remove_from_shortcuts: { id: 'account.remove_from_shortcuts', defaultMessage: 'Remove from shortcuts' },
|
||||
})
|
||||
|
||||
const mapStateToProps = (state, { group }) => {
|
||||
const groupId = group ? group.get('id') : null
|
||||
const shortcuts = state.getIn(['shortcuts', 'items'])
|
||||
const isShortcut = !!shortcuts.find((s) => {
|
||||
return s.get('shortcut_id') == groupId && s.get('shortcut_type') === 'group'
|
||||
})
|
||||
return { isShortcut }
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
@ -24,38 +38,43 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
dispatch(closePopover())
|
||||
dispatch(openModal(MODAL_GROUP_CREATE, { groupId }))
|
||||
},
|
||||
|
||||
onOpenRemovedMembers(groupId) {
|
||||
dispatch(closePopover())
|
||||
dispatch(openModal(MODAL_GROUP_REMOVED_ACCOUNTS, { groupId }))
|
||||
},
|
||||
|
||||
onOpenGroupMembers(groupId) {
|
||||
dispatch(closePopover())
|
||||
dispatch(openModal(MODAL_GROUP_MEMBERS, { groupId }))
|
||||
},
|
||||
onClosePopover: () => dispatch(closePopover()),
|
||||
onAddShortcut(groupId) {
|
||||
dispatch(addShortcut('group', groupId))
|
||||
},
|
||||
onRemoveShortcut(groupId) {
|
||||
dispatch(removeShortcut(null, 'group', groupId))
|
||||
},
|
||||
|
||||
onClosePopover: () => dispatch(closePopover())
|
||||
|
||||
});
|
||||
})
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
@connect(null, mapDispatchToProps)
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
class GroupOptionsPopover extends ImmutablePureComponent {
|
||||
|
||||
static defaultProps = {
|
||||
group: ImmutablePropTypes.map.isRequired,
|
||||
isAdmin: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
isXS: PropTypes.bool,
|
||||
isShortcut: PropTypes.bool,
|
||||
onAddShortcut: PropTypes.func.isRequired,
|
||||
onRemoveShortcut: PropTypes.func.isRequired,
|
||||
onClosePopover: PropTypes.func.isRequired,
|
||||
onOpenEditGroup: PropTypes.func.isRequired,
|
||||
onOpenGroupMembers: PropTypes.func.isRequired,
|
||||
onOpenRemovedMembers: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
updateOnProps = ['group']
|
||||
|
||||
handleEditGroup = () => {
|
||||
this.props.onOpenEditGroup(this.props.group.get('id'))
|
||||
}
|
||||
@ -72,8 +91,22 @@ class GroupOptionsPopover extends ImmutablePureComponent {
|
||||
this.props.onClosePopover()
|
||||
}
|
||||
|
||||
handleOnToggleShortcut = () => {
|
||||
this.handleOnClosePopover()
|
||||
if (this.props.isShortcut) {
|
||||
this.props.onRemoveShortcut(this.props.group.get('id'))
|
||||
} else {
|
||||
this.props.onAddShortcut(this.props.group.get('id'))
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, isXS } = this.props
|
||||
const {
|
||||
intl,
|
||||
isAdmin,
|
||||
isShortcut,
|
||||
isXS,
|
||||
} = this.props
|
||||
|
||||
const listItems = [
|
||||
{
|
||||
@ -81,24 +114,33 @@ class GroupOptionsPopover extends ImmutablePureComponent {
|
||||
icon: 'group',
|
||||
title: intl.formatMessage(messages.groupMembers),
|
||||
onClick: this.handleOnOpenGroupMembers,
|
||||
isHidden: !isAdmin,
|
||||
},
|
||||
{
|
||||
hideArrow: true,
|
||||
icon: 'block',
|
||||
title: intl.formatMessage(messages.removedMembers),
|
||||
onClick: this.handleOnOpenRemovedMembers,
|
||||
isHidden: !isAdmin,
|
||||
},
|
||||
{
|
||||
hideArrow: true,
|
||||
icon: 'pencil',
|
||||
title: intl.formatMessage(messages.editGroup),
|
||||
onClick: this.handleEditGroup,
|
||||
}
|
||||
isHidden: !isAdmin,
|
||||
},
|
||||
{
|
||||
hideArrow: true,
|
||||
icon: 'star',
|
||||
title: intl.formatMessage(isShortcut ? messages.remove_from_shortcuts : messages.add_to_shortcuts),
|
||||
onClick: this.handleOnToggleShortcut,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<PopoverLayout
|
||||
width={210}
|
||||
width={240}
|
||||
isXS={isXS}
|
||||
onClose={this.handleOnClosePopover}
|
||||
>
|
||||
|
@ -2,23 +2,22 @@ import { defineMessages, injectIntl } from 'react-intl'
|
||||
import {
|
||||
followAccount,
|
||||
unfollowAccount,
|
||||
blockAccount,
|
||||
unblockAccount,
|
||||
unmuteAccount,
|
||||
pinAccount,
|
||||
unpinAccount,
|
||||
} from '../../actions/accounts'
|
||||
import {
|
||||
mentionCompose,
|
||||
} from '../../actions/compose'
|
||||
import { muteAccount } from '../../actions/accounts'
|
||||
import {
|
||||
addShortcut,
|
||||
removeShortcut,
|
||||
} from '../../actions/shortcuts'
|
||||
import { initReport } from '../../actions/reports'
|
||||
import { openModal } from '../../actions/modal'
|
||||
import { closePopover } from '../../actions/popover'
|
||||
import { unfollowModal, autoPlayGif, me, isStaff } from '../../initial_state'
|
||||
import { unfollowModal, me, isStaff } from '../../initial_state'
|
||||
import { makeGetAccount } from '../../selectors'
|
||||
import PopoverLayout from './popover_layout'
|
||||
import Text from '../text'
|
||||
import List from '../list'
|
||||
|
||||
const messages = defineMessages({
|
||||
@ -44,23 +43,27 @@ const messages = defineMessages({
|
||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||
admin_account: { id: 'admin_account', defaultMessage: 'Open moderation interface' },
|
||||
add_to_list: { id: 'lists.account.add', defaultMessage: 'Add to list' },
|
||||
add_or_remove_from_shortcuts: { id: 'account.add_or_remove_from_shortcuts', defaultMessage: 'Add or Remove from shortcuts' },
|
||||
add_to_shortcuts: { id: 'account.add_to_shortcuts', defaultMessage: 'Add to shortcuts' },
|
||||
remove_from_shortcuts: { id: 'account.remove_from_shortcuts', defaultMessage: 'Remove from shortcuts' },
|
||||
accountBlocked: { id: 'account.blocked', defaultMessage: 'Blocked' },
|
||||
accountMuted: { id: 'account.muted', defaultMessage: 'Muted' },
|
||||
});
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getAccount = makeGetAccount();
|
||||
const mapStateToProps = (state, { account }) => {
|
||||
const getAccount = makeGetAccount()
|
||||
const accountId = !!account ? account.get('id') : -1
|
||||
const shortcuts = state.getIn(['shortcuts', 'items'])
|
||||
const isShortcut = !!shortcuts.find((s) => {
|
||||
return s.get('shortcut_id') == accountId && s.get('shortcut_type') === 'account'
|
||||
})
|
||||
|
||||
const mapStateToProps = (state, { account }) => ({
|
||||
account: getAccount(state, !!account ? account.get('id') : -1),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
return {
|
||||
isShortcut,
|
||||
account: getAccount(state, accountId),
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
|
||||
onFollow(account) {
|
||||
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
|
||||
if (unfollowModal) {
|
||||
@ -74,7 +77,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
dispatch(followAccount(account.get('id')))
|
||||
}
|
||||
},
|
||||
|
||||
onBlock(account) {
|
||||
dispatch(closePopover())
|
||||
|
||||
@ -86,12 +88,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
onMention(account) {
|
||||
dispatch(closePopover())
|
||||
dispatch(mentionCompose(account));
|
||||
},
|
||||
|
||||
onRepostToggle(account) {
|
||||
dispatch(closePopover())
|
||||
if (account.getIn(['relationship', 'showing_reblogs'])) {
|
||||
@ -100,12 +100,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
dispatch(followAccount(account.get('id'), true));
|
||||
}
|
||||
},
|
||||
|
||||
onReport(account) {
|
||||
dispatch(closePopover())
|
||||
dispatch(initReport(account));
|
||||
},
|
||||
|
||||
onMute(account) {
|
||||
dispatch(closePopover())
|
||||
if (account.getIn(['relationship', 'muting'])) {
|
||||
@ -116,30 +114,39 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
}))
|
||||
}
|
||||
},
|
||||
|
||||
onAddToList(account) {
|
||||
dispatch(closePopover())
|
||||
dispatch(openModal('LIST_ADD_USER', {
|
||||
accountId: account.get('id'),
|
||||
}));
|
||||
},
|
||||
|
||||
onClosePopover: () => dispatch(closePopover()),
|
||||
|
||||
});
|
||||
|
||||
onAddShortcut(accountId) {
|
||||
dispatch(closePopover())
|
||||
dispatch(addShortcut('account', accountId))
|
||||
},
|
||||
onRemoveShortcut(accountId) {
|
||||
dispatch(closePopover())
|
||||
dispatch(removeShortcut(null, 'account', accountId))
|
||||
},
|
||||
})
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
@connect(makeMapStateToProps, mapDispatchToProps)
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
class ProfileOptionsPopover extends PureComponent {
|
||||
|
||||
static defaultProps = {
|
||||
isXS: PropTypes.bool,
|
||||
isShortcut: PropTypes.bool,
|
||||
}
|
||||
|
||||
makeMenu() {
|
||||
const { account, intl } = this.props;
|
||||
const {
|
||||
account,
|
||||
intl,
|
||||
isShortcut,
|
||||
} = this.props;
|
||||
|
||||
let menu = [];
|
||||
|
||||
@ -208,12 +215,12 @@ class ProfileOptionsPopover extends PureComponent {
|
||||
// onClick: this.handleAddToList
|
||||
// })
|
||||
|
||||
// menu.push({
|
||||
// hideArrow: true,
|
||||
// icon: 'circle',
|
||||
// title: intl.formatMessage(messages.add_or_remove_from_shortcuts),
|
||||
// onClick: this.handleAddToShortcuts
|
||||
// })
|
||||
menu.push({
|
||||
hideArrow: true,
|
||||
icon: 'star',
|
||||
title: intl.formatMessage(isShortcut ? messages.remove_from_shortcuts : messages.add_to_shortcuts),
|
||||
onClick: this.handleToggleShortcuts,
|
||||
})
|
||||
|
||||
if (isStaff) {
|
||||
menu.push({
|
||||
@ -259,8 +266,12 @@ class ProfileOptionsPopover extends PureComponent {
|
||||
this.props.onAddToList(this.props.account);
|
||||
}
|
||||
|
||||
handleAddToShortcuts = () => {
|
||||
// : todo :
|
||||
handleToggleShortcuts = () => {
|
||||
if (this.props.isShortcut) {
|
||||
this.props.onRemoveShortcut(this.props.account.get('id'))
|
||||
} else {
|
||||
this.props.onAddShortcut(this.props.account.get('id'))
|
||||
}
|
||||
}
|
||||
|
||||
handleOnClosePopover = () => {
|
||||
|
@ -1,11 +1,15 @@
|
||||
import { Fragment } from 'react'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { injectIntl, defineMessages } from 'react-intl'
|
||||
import * as Constants from '../constants'
|
||||
import {
|
||||
BREAKPOINT_SMALL,
|
||||
} from '../constants'
|
||||
import Button from './button'
|
||||
import { closeSidebar } from '../actions/sidebar'
|
||||
import { openModal } from '../actions/modal'
|
||||
import { openPopover } from '../actions/popover'
|
||||
import { fetchShortcuts } from '../actions/shortcuts'
|
||||
import { me } from '../initial_state'
|
||||
import { makeGetAccount } from '../selectors'
|
||||
import Responsive from '../features/ui/util/responsive_component'
|
||||
@ -14,6 +18,7 @@ import SidebarSectionItem from './sidebar_section_item'
|
||||
import Heading from './heading'
|
||||
import BackButton from './back_button'
|
||||
import Pills from './pills'
|
||||
import Text from './text'
|
||||
|
||||
const messages = defineMessages({
|
||||
followers: { id: 'account.followers', defaultMessage: 'Followers' },
|
||||
@ -35,10 +40,14 @@ const messages = defineMessages({
|
||||
search: { id: 'tabs_bar.search', defaultMessage: 'Search' },
|
||||
shop: { id: 'tabs_bar.shop', defaultMessage: 'Store - Buy Merch' },
|
||||
donate: { id: 'tabs_bar.donate', defaultMessage: 'Make a Donation' },
|
||||
shortcuts: { id: 'navigation_bar.shortcuts', defaultMessage: 'Shortcuts' },
|
||||
all: { id: 'all', defaultMessage: 'All' },
|
||||
edit: { id: 'edit', defaultMessage: 'Edit' },
|
||||
})
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
account: makeGetAccount()(state, me),
|
||||
shortcuts: state.getIn(['shortcuts', 'items']),
|
||||
moreOpen: state.getIn(['popover', 'popoverType']) === 'SIDEBAR_MORE',
|
||||
notificationCount: state.getIn(['notifications', 'unread']),
|
||||
homeItemsQueueCount: state.getIn(['timelines', 'home', 'totalQueuedItemsCount']),
|
||||
@ -54,6 +63,9 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
onOpenComposeModal() {
|
||||
dispatch(openModal('COMPOSE'))
|
||||
},
|
||||
onFetchShortcuts() {
|
||||
dispatch(fetchShortcuts())
|
||||
},
|
||||
})
|
||||
|
||||
export default
|
||||
@ -67,6 +79,7 @@ class Sidebar extends ImmutablePureComponent {
|
||||
moreOpen: PropTypes.bool,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onOpenComposeModal: PropTypes.func.isRequired,
|
||||
onFetchShortcuts: PropTypes.func.isRequired,
|
||||
openSidebarMorePopover: PropTypes.func.isRequired,
|
||||
notificationCount: PropTypes.number.isRequired,
|
||||
homeItemsQueueCount: PropTypes.number.isRequired,
|
||||
@ -74,6 +87,15 @@ class Sidebar extends ImmutablePureComponent {
|
||||
tabs: PropTypes.array,
|
||||
title: PropTypes.string,
|
||||
showBackBtn: PropTypes.bool,
|
||||
shortcuts: ImmutablePropTypes.list,
|
||||
}
|
||||
|
||||
state = {
|
||||
hoveringShortcuts: false,
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.onFetchShortcuts()
|
||||
}
|
||||
|
||||
handleOpenComposeModal = () => {
|
||||
@ -87,6 +109,14 @@ class Sidebar extends ImmutablePureComponent {
|
||||
})
|
||||
}
|
||||
|
||||
handleMouseEnterShortcuts = () => {
|
||||
this.setState({ hoveringShortcuts: true })
|
||||
}
|
||||
|
||||
handleMouseLeaveShortcuts = () => {
|
||||
this.setState({ hoveringShortcuts: false })
|
||||
}
|
||||
|
||||
setMoreButtonRef = n => {
|
||||
this.moreBtnRef = n
|
||||
}
|
||||
@ -102,14 +132,12 @@ class Sidebar extends ImmutablePureComponent {
|
||||
tabs,
|
||||
title,
|
||||
showBackBtn,
|
||||
shortcuts,
|
||||
} = this.props
|
||||
const { hoveringShortcuts } = this.state
|
||||
|
||||
// : todo :
|
||||
if (!me || !account) return null
|
||||
|
||||
const acct = account.get('acct')
|
||||
const isPro = account.get('is_pro')
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
title: 'Home',
|
||||
@ -144,6 +172,11 @@ class Sidebar extends ImmutablePureComponent {
|
||||
icon: 'explore',
|
||||
to: '/explore',
|
||||
},
|
||||
{
|
||||
title: 'Pro Feed',
|
||||
icon: 'circle',
|
||||
to: '/timeline/pro',
|
||||
},
|
||||
{
|
||||
title: 'More',
|
||||
icon: 'more',
|
||||
@ -153,32 +186,18 @@ class Sidebar extends ImmutablePureComponent {
|
||||
},
|
||||
]
|
||||
|
||||
const shortcutItems = [
|
||||
// {
|
||||
// title: 'Meme Group',
|
||||
// icon: 'group',
|
||||
// to: '/',
|
||||
// count: 0,
|
||||
// },
|
||||
// {
|
||||
// title: '@andrew',
|
||||
// image: 'http://localhost:3000/system/accounts/avatars/000/000/001/original/260e8c96c97834da.jpeg?1562898139',
|
||||
// to: '/',
|
||||
// count: 3,
|
||||
// },
|
||||
]
|
||||
let shortcutItems = []
|
||||
if (!!shortcuts) {
|
||||
shortcuts.forEach((s) => {
|
||||
shortcutItems.push({
|
||||
to: s.get('to'),
|
||||
title: s.get('title'),
|
||||
image: s.get('image'),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const exploreItems = [
|
||||
{
|
||||
title: 'Pro Feed',
|
||||
icon: 'circle',
|
||||
to: '/timeline/pro',
|
||||
},
|
||||
{
|
||||
title: 'Chat',
|
||||
icon: 'chat',
|
||||
href: 'https://chat.gab.com',
|
||||
},
|
||||
{
|
||||
title: 'Apps',
|
||||
icon: 'apps',
|
||||
@ -259,6 +278,41 @@ class Sidebar extends ImmutablePureComponent {
|
||||
)
|
||||
})
|
||||
}
|
||||
{
|
||||
!!shortcutItems.length > 0 &&
|
||||
<Fragment>
|
||||
<SidebarSectionTitle>
|
||||
<div
|
||||
className={[_s.displayFlex, _s.alignItemsCenter, _s.flexRow].join(' ')}
|
||||
onMouseEnter={this.handleMouseEnterShortcuts}
|
||||
onMouseLeave={this.handleMouseLeaveShortcuts}
|
||||
>
|
||||
<span>
|
||||
{intl.formatMessage(messages.shortcuts)}
|
||||
</span>
|
||||
<Button
|
||||
isText
|
||||
to='/shortcuts'
|
||||
color='brand'
|
||||
backgroundColor='none'
|
||||
className={_s.mlAuto}
|
||||
>
|
||||
{
|
||||
hoveringShortcuts &&
|
||||
<Text color='inherit' size='small' weight='medium' align='right'>
|
||||
{intl.formatMessage(messages.all)}
|
||||
</Text>
|
||||
}
|
||||
</Button>
|
||||
</div>
|
||||
</SidebarSectionTitle>
|
||||
{
|
||||
shortcutItems.map((shortcutItem, i) => (
|
||||
<SidebarSectionItem {...shortcutItem} key={`sidebar-item-shortcut-${i}`} />
|
||||
))
|
||||
}
|
||||
</Fragment>
|
||||
}
|
||||
<SidebarSectionTitle>{intl.formatMessage(messages.explore)}</SidebarSectionTitle>
|
||||
{
|
||||
exploreItems.map((exploreItem, i) => (
|
||||
@ -267,7 +321,7 @@ class Sidebar extends ImmutablePureComponent {
|
||||
}
|
||||
</nav>
|
||||
|
||||
<Responsive min={Constants.BREAKPOINT_SMALL}>
|
||||
<Responsive min={BREAKPOINT_SMALL}>
|
||||
<Button
|
||||
isBlock
|
||||
onClick={this.handleOpenComposeModal}
|
||||
@ -277,7 +331,7 @@ class Sidebar extends ImmutablePureComponent {
|
||||
</Button>
|
||||
</Responsive>
|
||||
|
||||
<Responsive max={Constants.BREAKPOINT_SMALL}>
|
||||
<Responsive max={BREAKPOINT_SMALL}>
|
||||
<Button
|
||||
onClick={this.handleOpenComposeModal}
|
||||
className={_s.py15}
|
||||
|
@ -40,6 +40,7 @@ const messages = defineMessages({
|
||||
help: { id: 'getting_started.help', defaultMessage: 'Help' },
|
||||
display: { id: 'display_options', defaultMessage: 'Display Options' },
|
||||
proFeed: { id: 'pro_feed', defaultMessage: 'Pro Feed' },
|
||||
shortcuts: { id: 'shortcuts', defaultMessage: 'Shortcuts' },
|
||||
})
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
@ -101,6 +102,12 @@ class SidebarXS extends ImmutablePureComponent {
|
||||
onClick: this.handleSidebarClose,
|
||||
title: intl.formatMessage(messages.lists),
|
||||
},
|
||||
{
|
||||
icon: 'star',
|
||||
to: '/shortcuts',
|
||||
onClick: this.handleSidebarClose,
|
||||
title: intl.formatMessage(messages.shortcuts),
|
||||
},
|
||||
{
|
||||
icon: 'pro',
|
||||
href: 'https://pro.gab.com',
|
||||
|
@ -41,6 +41,7 @@ export const MODAL_COMPOSE = 'COMPOSE'
|
||||
export const MODAL_CONFIRM = 'CONFIRM'
|
||||
export const MODAL_DISPLAY_OPTIONS = 'DISPLAY_OPTIONS'
|
||||
export const MODAL_EDIT_PROFILE = 'EDIT_PROFILE'
|
||||
export const MODAL_EDIT_SHORTCUTS = 'EDIT_SHORTCUTS'
|
||||
export const MODAL_EMBED = 'EMBED'
|
||||
export const MODAL_GIF_PICKER = 'GIF_PICKER'
|
||||
export const MODAL_GROUP_CREATE = 'GROUP_CREATE'
|
||||
|
62
app/javascript/gabsocial/features/shortcuts.js
Normal file
62
app/javascript/gabsocial/features/shortcuts.js
Normal file
@ -0,0 +1,62 @@
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { fetchShortcuts } from '../actions/shortcuts'
|
||||
import ColumnIndicator from '../components/column_indicator'
|
||||
import List from '../components/list'
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
isError: state.getIn(['shortcuts', 'isError']),
|
||||
isLoading: state.getIn(['shortcuts', 'isLoading']),
|
||||
shortcuts: state.getIn(['shortcuts', 'items']),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onFetchShortcuts() {
|
||||
dispatch(fetchShortcuts())
|
||||
},
|
||||
})
|
||||
|
||||
export default
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
class Shortcuts extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
isError: PropTypes.bool.isRequired,
|
||||
onFetchShortcuts: PropTypes.func.isRequired,
|
||||
shortcuts: ImmutablePropTypes.list,
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.onFetchShortcuts()
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
isLoading,
|
||||
isError,
|
||||
shortcuts,
|
||||
} = this.props
|
||||
|
||||
if (isLoading) {
|
||||
return <ColumnIndicator type='loading' />
|
||||
} else if (isError) {
|
||||
return <ColumnIndicator type='error' message='Error fetching shortcuts' />
|
||||
}
|
||||
|
||||
const listItems = shortcuts.map((s) => ({
|
||||
to: s.get('to'),
|
||||
title: s.get('title'),
|
||||
image: s.get('image'),
|
||||
}))
|
||||
|
||||
return (
|
||||
<List
|
||||
scrollKey='shortcuts'
|
||||
emptyMessage='You have no shortcuts'
|
||||
items={listItems}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -75,7 +75,7 @@ import {
|
||||
PrivacyPolicy,
|
||||
ProTimeline,
|
||||
Search,
|
||||
// Shortcuts,
|
||||
Shortcuts,
|
||||
StatusFeature,
|
||||
StatusLikes,
|
||||
StatusReposts,
|
||||
@ -188,7 +188,7 @@ class SwitchingArea extends PureComponent {
|
||||
|
||||
<WrappedRoute path='/tags/:id' publicRoute page={HashtagPage} component={HashtagTimeline} content={children} componentParams={{ title: 'Hashtag' }} />
|
||||
|
||||
{ /* <WrappedRoute path='/shortcuts' publicRoute page={ShortcutsPage} component={Shortcuts} content={children} /> */ }
|
||||
<WrappedRoute path='/shortcuts' page={ShortcutsPage} component={Shortcuts} content={children} />
|
||||
|
||||
<WrappedRoute path='/lists' exact page={ListsPage} component={ListsDirectory} content={children} />
|
||||
<WrappedRoute path='/lists/create' exact page={ModalPage} component={ListCreate} content={children} componentParams={{ title: 'Create List', page: 'create-list' }} />
|
||||
|
@ -15,6 +15,7 @@ export function DatePickerPopover() { return import(/* webpackChunkName: "compon
|
||||
export function DisplayOptionsModal() { return import(/* webpackChunkName: "components/display_options_modal" */'../../../components/modal/display_options_modal') }
|
||||
export function DMCA() { return import(/* webpackChunkName: "features/about/dmca" */'../../about/dmca') }
|
||||
export function EditProfileModal() { return import(/* webpackChunkName: "components/edit_profile_modal" */'../../../components/modal/edit_profile_modal') }
|
||||
export function EditShortcutsModal() { return import(/* webpackChunkName: "components/edit_shortcuts_modal" */'../../../components/modal/edit_shortcuts_modal') }
|
||||
export function EmbedModal() { return import(/* webpackChunkName: "modals/embed_modal" */'../../../components/modal/embed_modal') }
|
||||
export function EmojiPicker() { return import(/* webpackChunkName: "emoji_picker" */'../../../components/emoji/emoji_picker') }
|
||||
export function EmojiPickerPopover() { return import(/* webpackChunkName: "components/emoji_picker_popover" */'../../../components/popover/emoji_picker_popover') }
|
||||
@ -64,6 +65,7 @@ export function ProfileOptionsPopover() { return import(/* webpackChunkName: "co
|
||||
export function ProUpgradeModal() { return import(/* webpackChunkName: "components/pro_upgrade_modal" */'../../../components/modal/pro_upgrade_modal') }
|
||||
export function ReportModal() { return import(/* webpackChunkName: "modals/report_modal" */'../../../components/modal/report_modal') }
|
||||
export function Search() { return import(/*webpackChunkName: "features/search" */'../../search') }
|
||||
export function Shortcuts() { return import(/*webpackChunkName: "features/shortcuts" */'../../shortcuts') }
|
||||
export function Status() { return import(/* webpackChunkName: "components/status" */'../../../components/status') }
|
||||
export function StatusFeature() { return import(/* webpackChunkName: "features/status" */'../../status') }
|
||||
export function SearchPopover() { return import(/* webpackChunkName: "components/search_popover" */'../../../components/popover/search_popover') }
|
||||
|
@ -1,34 +1,58 @@
|
||||
import { Fragment } from 'react'
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import { openModal } from '../actions/modal'
|
||||
import GroupSidebarPanel from '../components/panel/groups_panel'
|
||||
import { MODAL_EDIT_SHORTCUTS } from '../constants'
|
||||
import PageTitle from '../features/ui/util/page_title'
|
||||
import LinkFooter from '../components/link_footer'
|
||||
import WhoToFollowPanel from '../components/panel/who_to_follow_panel'
|
||||
import ProgressPanel from '../components/panel/progress_panel'
|
||||
import UserPanel from '../components/panel/user_panel'
|
||||
import TrendsPanel from '../components/panel/trends_panel'
|
||||
import DefaultLayout from '../layouts/default_layout'
|
||||
|
||||
const messages = defineMessages({
|
||||
shortcuts: { id: 'shortcuts', defaultMessage: 'Shortcuts' },
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onOpenEditShortcutsModal() {
|
||||
dispatch(openModal(MODAL_EDIT_SHORTCUTS))
|
||||
},
|
||||
})
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
@connect(null, mapDispatchToProps)
|
||||
class ShortcutsPage extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
onOpenEditShortcutsModal: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
handleOnOpenEditShortcutsModal = () => {
|
||||
this.props.onOpenEditShortcutsModal()
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children } = this.props
|
||||
const { intl, children } = this.props
|
||||
|
||||
const title = intl.formatMessage(messages.shortcuts)
|
||||
|
||||
return (
|
||||
<DefaultLayout
|
||||
title='Shortcuts'
|
||||
actions={[]}
|
||||
layout={(
|
||||
<Fragment>
|
||||
<UserPanel />
|
||||
<ProgressPanel />
|
||||
<TrendsPanel />
|
||||
<WhoToFollowPanel />
|
||||
<GroupSidebarPanel />
|
||||
<LinkFooter />
|
||||
</Fragment>
|
||||
)}
|
||||
title={title}
|
||||
page='shortcuts'
|
||||
actions={[
|
||||
{
|
||||
icon: 'cog',
|
||||
onClick: this.handleOnOpenEditShortcutsModal,
|
||||
},
|
||||
]}
|
||||
layout={[
|
||||
<TrendsPanel key='shortcuts-page-trends-panel' />,
|
||||
<WhoToFollowPanel key='shortcuts-page-wtf-panel' />,
|
||||
<LinkFooter key='shortcuts-page-link-footer' />,
|
||||
]}
|
||||
>
|
||||
<PageTitle path={title} />
|
||||
{children}
|
||||
</DefaultLayout>
|
||||
)
|
||||
|
@ -30,6 +30,7 @@ import reports from './reports'
|
||||
import search from './search'
|
||||
import settings from './settings'
|
||||
import shop from './shop'
|
||||
import shortcuts from './shortcuts'
|
||||
import sidebar from './sidebar'
|
||||
import statuses from './statuses'
|
||||
import status_lists from './status_lists'
|
||||
@ -72,6 +73,7 @@ const reducers = {
|
||||
search,
|
||||
settings,
|
||||
shop,
|
||||
shortcuts,
|
||||
sidebar,
|
||||
statuses,
|
||||
status_lists,
|
||||
|
75
app/javascript/gabsocial/reducers/shortcuts.js
Normal file
75
app/javascript/gabsocial/reducers/shortcuts.js
Normal file
@ -0,0 +1,75 @@
|
||||
import {
|
||||
SHORTCUTS_FETCH_REQUEST,
|
||||
SHORTCUTS_FETCH_SUCCESS,
|
||||
SHORTCUTS_FETCH_FAIL,
|
||||
SHORTCUTS_ADD_SUCCESS,
|
||||
SHORTCUTS_REMOVE_SUCCESS,
|
||||
} from '../actions/shortcuts'
|
||||
import { importFetchedAccount } from '../actions/importer'
|
||||
import { importGroup } from '../actions/groups'
|
||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
items: ImmutableList(),
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
})
|
||||
|
||||
const normalizeShortcut = (shortcut) => {
|
||||
if (shortcut.shortcut_type === 'account') {
|
||||
importFetchedAccount(shortcut.shortcut)
|
||||
return {
|
||||
id: shortcut.id,
|
||||
shortcut_type: 'account',
|
||||
shortcut_id: shortcut.shortcut_id,
|
||||
title: shortcut.shortcut.acct,
|
||||
image: shortcut.shortcut.avatar_static,
|
||||
to: `/${shortcut.shortcut.acct}`,
|
||||
}
|
||||
} else if (shortcut.shortcut_type === 'group') {
|
||||
importGroup(shortcut.shortcut)
|
||||
return {
|
||||
id: shortcut.id,
|
||||
shortcut_type: 'group',
|
||||
shortcut_id: shortcut.shortcut_id,
|
||||
title: shortcut.shortcut.title,
|
||||
image: shortcut.shortcut.cover_image_url,
|
||||
to: `/groups/${shortcut.shortcut.id}`,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeShortcuts = (shortcuts) => {
|
||||
return fromJS(shortcuts.map((shortcut) => {
|
||||
return normalizeShortcut(shortcut)
|
||||
}))
|
||||
}
|
||||
|
||||
export default function shortcutsReducer(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case SHORTCUTS_FETCH_REQUEST:
|
||||
return state.withMutations((map) => {
|
||||
map.set('isLoading', true)
|
||||
map.set('isError', false)
|
||||
})
|
||||
case SHORTCUTS_FETCH_SUCCESS:
|
||||
return state.withMutations((map) => {
|
||||
map.set('items', normalizeShortcuts(action.shortcuts))
|
||||
map.set('isLoading', false)
|
||||
map.set('isError', false)
|
||||
})
|
||||
case SHORTCUTS_FETCH_FAIL:
|
||||
return state.withMutations((map) => {
|
||||
map.set('isLoading', false)
|
||||
map.set('isError', true)
|
||||
})
|
||||
case SHORTCUTS_ADD_SUCCESS:
|
||||
return state.update('items', list => list.push(fromJS(normalizeShortcut(action.shortcut))))
|
||||
case SHORTCUTS_REMOVE_SUCCESS:
|
||||
return state.update('items', list => list.filterNot((item) => {
|
||||
return `${item.get('id')}` === `${action.shortcutId}`
|
||||
}))
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
@ -3,14 +3,25 @@
|
||||
#
|
||||
# Table name: shortcuts
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# account_id :bigint(8) not null
|
||||
# shortcut_id :bigint(8) not null
|
||||
# shortcut_type :string not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime
|
||||
# id :bigint(8) not null, primary key
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint(8) not null
|
||||
# shortcut_id :bigint(8) not null
|
||||
# shortcut_type :string default(""), not null
|
||||
#
|
||||
|
||||
class Shortcut < ApplicationRecord
|
||||
|
||||
# enum shortcut_type: {
|
||||
# account: 'account',
|
||||
# group: 'group'
|
||||
# }
|
||||
|
||||
belongs_to :account
|
||||
|
||||
PER_ACCOUNT_LIMIT = 50
|
||||
|
||||
validates_each :account_id, on: :create do |record, _attr, value|
|
||||
record.errors.add(:base, I18n.t('shortcuts.errors.limit')) if Shortcut.where(account_id: value).count >= PER_ACCOUNT_LIMIT
|
||||
end
|
||||
end
|
||||
|
10
app/serializers/rest/shortcut_serializer.rb
Normal file
10
app/serializers/rest/shortcut_serializer.rb
Normal file
@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class REST::ShortcutSerializer < ActiveModel::Serializer
|
||||
attributes :id, :account_id, :created_at, :shortcut_type, :shortcut_id
|
||||
|
||||
def id
|
||||
object.id.to_s
|
||||
end
|
||||
|
||||
end
|
@ -837,6 +837,7 @@ en:
|
||||
shortcuts:
|
||||
errors:
|
||||
limit: You have reached the maximum amount of shortcuts
|
||||
exists: You already have a shortcut for that
|
||||
sessions:
|
||||
activity: Last activity
|
||||
browser: Browser
|
||||
|
@ -795,6 +795,7 @@ en_GB:
|
||||
shortcuts:
|
||||
errors:
|
||||
limit: You have reached the maximum amount of shortcuts
|
||||
exists: You already have a shortcut for that
|
||||
sessions:
|
||||
activity: Last activity
|
||||
browser: Browser
|
||||
|
@ -359,6 +359,7 @@ Rails.application.routes.draw do
|
||||
resources :reports, only: [:create]
|
||||
resources :filters, only: [:index, :create, :show, :update, :destroy]
|
||||
resources :endorsements, only: [:index]
|
||||
resources :shortcuts, only: [:index, :create, :show, :destroy]
|
||||
|
||||
namespace :apps do
|
||||
get :verify_credentials, to: 'credentials#show'
|
||||
|
Loading…
Reference in New Issue
Block a user