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
|
@ -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
|
end
|
||||||
|
|
||||||
def find_route_matches
|
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
|
end
|
||||||
|
|
||||||
def find_public_route_matches
|
def find_public_route_matches
|
||||||
|
|
|
@ -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_MEMBERS,
|
||||||
MODAL_GROUP_REMOVED_ACCOUNTS,
|
MODAL_GROUP_REMOVED_ACCOUNTS,
|
||||||
} from '../../constants'
|
} from '../../constants'
|
||||||
|
import {
|
||||||
|
addShortcut,
|
||||||
|
removeShortcut,
|
||||||
|
} from '../../actions/shortcuts'
|
||||||
import { openModal } from '../../actions/modal'
|
import { openModal } from '../../actions/modal'
|
||||||
import { closePopover } from '../../actions/popover'
|
import { closePopover } from '../../actions/popover'
|
||||||
import { me } from '../../initial_state'
|
|
||||||
import PopoverLayout from './popover_layout'
|
import PopoverLayout from './popover_layout'
|
||||||
import List from '../list'
|
import List from '../list'
|
||||||
|
|
||||||
|
@ -16,7 +19,18 @@ const messages = defineMessages({
|
||||||
groupMembers: { id: 'group_members', defaultMessage: 'Group members' },
|
groupMembers: { id: 'group_members', defaultMessage: 'Group members' },
|
||||||
removedMembers: { id: 'group_removed_members', defaultMessage: 'Removed accounts' },
|
removedMembers: { id: 'group_removed_members', defaultMessage: 'Removed accounts' },
|
||||||
editGroup: { id: 'edit_group', defaultMessage: 'Edit group' },
|
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) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
|
||||||
|
@ -24,38 +38,43 @@ const mapDispatchToProps = (dispatch) => ({
|
||||||
dispatch(closePopover())
|
dispatch(closePopover())
|
||||||
dispatch(openModal(MODAL_GROUP_CREATE, { groupId }))
|
dispatch(openModal(MODAL_GROUP_CREATE, { groupId }))
|
||||||
},
|
},
|
||||||
|
|
||||||
onOpenRemovedMembers(groupId) {
|
onOpenRemovedMembers(groupId) {
|
||||||
dispatch(closePopover())
|
dispatch(closePopover())
|
||||||
dispatch(openModal(MODAL_GROUP_REMOVED_ACCOUNTS, { groupId }))
|
dispatch(openModal(MODAL_GROUP_REMOVED_ACCOUNTS, { groupId }))
|
||||||
},
|
},
|
||||||
|
|
||||||
onOpenGroupMembers(groupId) {
|
onOpenGroupMembers(groupId) {
|
||||||
dispatch(closePopover())
|
dispatch(closePopover())
|
||||||
dispatch(openModal(MODAL_GROUP_MEMBERS, { groupId }))
|
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
|
export default
|
||||||
@injectIntl
|
@injectIntl
|
||||||
@connect(null, mapDispatchToProps)
|
@connect(mapStateToProps, mapDispatchToProps)
|
||||||
class GroupOptionsPopover extends ImmutablePureComponent {
|
class GroupOptionsPopover extends ImmutablePureComponent {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
group: ImmutablePropTypes.map.isRequired,
|
group: ImmutablePropTypes.map.isRequired,
|
||||||
|
isAdmin: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
isXS: PropTypes.bool,
|
isXS: PropTypes.bool,
|
||||||
|
isShortcut: PropTypes.bool,
|
||||||
|
onAddShortcut: PropTypes.func.isRequired,
|
||||||
|
onRemoveShortcut: PropTypes.func.isRequired,
|
||||||
onClosePopover: PropTypes.func.isRequired,
|
onClosePopover: PropTypes.func.isRequired,
|
||||||
onOpenEditGroup: PropTypes.func.isRequired,
|
onOpenEditGroup: PropTypes.func.isRequired,
|
||||||
onOpenGroupMembers: PropTypes.func.isRequired,
|
onOpenGroupMembers: PropTypes.func.isRequired,
|
||||||
onOpenRemovedMembers: PropTypes.func.isRequired,
|
onOpenRemovedMembers: PropTypes.func.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
updateOnProps = ['group']
|
|
||||||
|
|
||||||
handleEditGroup = () => {
|
handleEditGroup = () => {
|
||||||
this.props.onOpenEditGroup(this.props.group.get('id'))
|
this.props.onOpenEditGroup(this.props.group.get('id'))
|
||||||
}
|
}
|
||||||
|
@ -72,8 +91,22 @@ class GroupOptionsPopover extends ImmutablePureComponent {
|
||||||
this.props.onClosePopover()
|
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() {
|
render() {
|
||||||
const { intl, isXS } = this.props
|
const {
|
||||||
|
intl,
|
||||||
|
isAdmin,
|
||||||
|
isShortcut,
|
||||||
|
isXS,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
const listItems = [
|
const listItems = [
|
||||||
{
|
{
|
||||||
|
@ -81,24 +114,33 @@ class GroupOptionsPopover extends ImmutablePureComponent {
|
||||||
icon: 'group',
|
icon: 'group',
|
||||||
title: intl.formatMessage(messages.groupMembers),
|
title: intl.formatMessage(messages.groupMembers),
|
||||||
onClick: this.handleOnOpenGroupMembers,
|
onClick: this.handleOnOpenGroupMembers,
|
||||||
|
isHidden: !isAdmin,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hideArrow: true,
|
hideArrow: true,
|
||||||
icon: 'block',
|
icon: 'block',
|
||||||
title: intl.formatMessage(messages.removedMembers),
|
title: intl.formatMessage(messages.removedMembers),
|
||||||
onClick: this.handleOnOpenRemovedMembers,
|
onClick: this.handleOnOpenRemovedMembers,
|
||||||
|
isHidden: !isAdmin,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
hideArrow: true,
|
hideArrow: true,
|
||||||
icon: 'pencil',
|
icon: 'pencil',
|
||||||
title: intl.formatMessage(messages.editGroup),
|
title: intl.formatMessage(messages.editGroup),
|
||||||
onClick: this.handleEditGroup,
|
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 (
|
return (
|
||||||
<PopoverLayout
|
<PopoverLayout
|
||||||
width={210}
|
width={240}
|
||||||
isXS={isXS}
|
isXS={isXS}
|
||||||
onClose={this.handleOnClosePopover}
|
onClose={this.handleOnClosePopover}
|
||||||
>
|
>
|
||||||
|
|
|
@ -2,23 +2,22 @@ import { defineMessages, injectIntl } from 'react-intl'
|
||||||
import {
|
import {
|
||||||
followAccount,
|
followAccount,
|
||||||
unfollowAccount,
|
unfollowAccount,
|
||||||
blockAccount,
|
|
||||||
unblockAccount,
|
unblockAccount,
|
||||||
unmuteAccount,
|
unmuteAccount,
|
||||||
pinAccount,
|
|
||||||
unpinAccount,
|
|
||||||
} from '../../actions/accounts'
|
} from '../../actions/accounts'
|
||||||
import {
|
import {
|
||||||
mentionCompose,
|
mentionCompose,
|
||||||
} from '../../actions/compose'
|
} from '../../actions/compose'
|
||||||
import { muteAccount } from '../../actions/accounts'
|
import {
|
||||||
|
addShortcut,
|
||||||
|
removeShortcut,
|
||||||
|
} from '../../actions/shortcuts'
|
||||||
import { initReport } from '../../actions/reports'
|
import { initReport } from '../../actions/reports'
|
||||||
import { openModal } from '../../actions/modal'
|
import { openModal } from '../../actions/modal'
|
||||||
import { closePopover } from '../../actions/popover'
|
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 { makeGetAccount } from '../../selectors'
|
||||||
import PopoverLayout from './popover_layout'
|
import PopoverLayout from './popover_layout'
|
||||||
import Text from '../text'
|
|
||||||
import List from '../list'
|
import List from '../list'
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
@ -44,23 +43,27 @@ const messages = defineMessages({
|
||||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||||
admin_account: { id: 'admin_account', defaultMessage: 'Open moderation interface' },
|
admin_account: { id: 'admin_account', defaultMessage: 'Open moderation interface' },
|
||||||
add_to_list: { id: 'lists.account.add', defaultMessage: 'Add to list' },
|
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' },
|
accountBlocked: { id: 'account.blocked', defaultMessage: 'Blocked' },
|
||||||
accountMuted: { id: 'account.muted', defaultMessage: 'Muted' },
|
accountMuted: { id: 'account.muted', defaultMessage: 'Muted' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
const mapStateToProps = (state, { account }) => {
|
||||||
const getAccount = makeGetAccount();
|
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 }) => ({
|
return {
|
||||||
account: getAccount(state, !!account ? account.get('id') : -1),
|
isShortcut,
|
||||||
});
|
account: getAccount(state, accountId),
|
||||||
|
}
|
||||||
return mapStateToProps;
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
|
|
||||||
onFollow(account) {
|
onFollow(account) {
|
||||||
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
|
if (account.getIn(['relationship', 'following']) || account.getIn(['relationship', 'requested'])) {
|
||||||
if (unfollowModal) {
|
if (unfollowModal) {
|
||||||
|
@ -74,7 +77,6 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
dispatch(followAccount(account.get('id')))
|
dispatch(followAccount(account.get('id')))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onBlock(account) {
|
onBlock(account) {
|
||||||
dispatch(closePopover())
|
dispatch(closePopover())
|
||||||
|
|
||||||
|
@ -86,12 +88,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onMention(account) {
|
onMention(account) {
|
||||||
dispatch(closePopover())
|
dispatch(closePopover())
|
||||||
dispatch(mentionCompose(account));
|
dispatch(mentionCompose(account));
|
||||||
},
|
},
|
||||||
|
|
||||||
onRepostToggle(account) {
|
onRepostToggle(account) {
|
||||||
dispatch(closePopover())
|
dispatch(closePopover())
|
||||||
if (account.getIn(['relationship', 'showing_reblogs'])) {
|
if (account.getIn(['relationship', 'showing_reblogs'])) {
|
||||||
|
@ -100,12 +100,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
dispatch(followAccount(account.get('id'), true));
|
dispatch(followAccount(account.get('id'), true));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onReport(account) {
|
onReport(account) {
|
||||||
dispatch(closePopover())
|
dispatch(closePopover())
|
||||||
dispatch(initReport(account));
|
dispatch(initReport(account));
|
||||||
},
|
},
|
||||||
|
|
||||||
onMute(account) {
|
onMute(account) {
|
||||||
dispatch(closePopover())
|
dispatch(closePopover())
|
||||||
if (account.getIn(['relationship', 'muting'])) {
|
if (account.getIn(['relationship', 'muting'])) {
|
||||||
|
@ -116,30 +114,39 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onAddToList(account) {
|
onAddToList(account) {
|
||||||
dispatch(closePopover())
|
dispatch(closePopover())
|
||||||
dispatch(openModal('LIST_ADD_USER', {
|
dispatch(openModal('LIST_ADD_USER', {
|
||||||
accountId: account.get('id'),
|
accountId: account.get('id'),
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
onClosePopover: () => dispatch(closePopover()),
|
onClosePopover: () => dispatch(closePopover()),
|
||||||
|
onAddShortcut(accountId) {
|
||||||
});
|
dispatch(closePopover())
|
||||||
|
dispatch(addShortcut('account', accountId))
|
||||||
|
},
|
||||||
|
onRemoveShortcut(accountId) {
|
||||||
|
dispatch(closePopover())
|
||||||
|
dispatch(removeShortcut(null, 'account', accountId))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
export default
|
export default
|
||||||
@injectIntl
|
@injectIntl
|
||||||
@connect(makeMapStateToProps, mapDispatchToProps)
|
@connect(mapStateToProps, mapDispatchToProps)
|
||||||
class ProfileOptionsPopover extends PureComponent {
|
class ProfileOptionsPopover extends PureComponent {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
isXS: PropTypes.bool,
|
isXS: PropTypes.bool,
|
||||||
|
isShortcut: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
makeMenu() {
|
makeMenu() {
|
||||||
const { account, intl } = this.props;
|
const {
|
||||||
|
account,
|
||||||
|
intl,
|
||||||
|
isShortcut,
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
let menu = [];
|
let menu = [];
|
||||||
|
|
||||||
|
@ -208,12 +215,12 @@ class ProfileOptionsPopover extends PureComponent {
|
||||||
// onClick: this.handleAddToList
|
// onClick: this.handleAddToList
|
||||||
// })
|
// })
|
||||||
|
|
||||||
// menu.push({
|
menu.push({
|
||||||
// hideArrow: true,
|
hideArrow: true,
|
||||||
// icon: 'circle',
|
icon: 'star',
|
||||||
// title: intl.formatMessage(messages.add_or_remove_from_shortcuts),
|
title: intl.formatMessage(isShortcut ? messages.remove_from_shortcuts : messages.add_to_shortcuts),
|
||||||
// onClick: this.handleAddToShortcuts
|
onClick: this.handleToggleShortcuts,
|
||||||
// })
|
})
|
||||||
|
|
||||||
if (isStaff) {
|
if (isStaff) {
|
||||||
menu.push({
|
menu.push({
|
||||||
|
@ -259,8 +266,12 @@ class ProfileOptionsPopover extends PureComponent {
|
||||||
this.props.onAddToList(this.props.account);
|
this.props.onAddToList(this.props.account);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAddToShortcuts = () => {
|
handleToggleShortcuts = () => {
|
||||||
// : todo :
|
if (this.props.isShortcut) {
|
||||||
|
this.props.onRemoveShortcut(this.props.account.get('id'))
|
||||||
|
} else {
|
||||||
|
this.props.onAddShortcut(this.props.account.get('id'))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOnClosePopover = () => {
|
handleOnClosePopover = () => {
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
|
import { Fragment } from 'react'
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
import { injectIntl, defineMessages } from 'react-intl'
|
import { injectIntl, defineMessages } from 'react-intl'
|
||||||
import * as Constants from '../constants'
|
import {
|
||||||
|
BREAKPOINT_SMALL,
|
||||||
|
} from '../constants'
|
||||||
import Button from './button'
|
import Button from './button'
|
||||||
import { closeSidebar } from '../actions/sidebar'
|
import { closeSidebar } from '../actions/sidebar'
|
||||||
import { openModal } from '../actions/modal'
|
import { openModal } from '../actions/modal'
|
||||||
import { openPopover } from '../actions/popover'
|
import { openPopover } from '../actions/popover'
|
||||||
|
import { fetchShortcuts } from '../actions/shortcuts'
|
||||||
import { me } from '../initial_state'
|
import { me } from '../initial_state'
|
||||||
import { makeGetAccount } from '../selectors'
|
import { makeGetAccount } from '../selectors'
|
||||||
import Responsive from '../features/ui/util/responsive_component'
|
import Responsive from '../features/ui/util/responsive_component'
|
||||||
|
@ -14,6 +18,7 @@ import SidebarSectionItem from './sidebar_section_item'
|
||||||
import Heading from './heading'
|
import Heading from './heading'
|
||||||
import BackButton from './back_button'
|
import BackButton from './back_button'
|
||||||
import Pills from './pills'
|
import Pills from './pills'
|
||||||
|
import Text from './text'
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
followers: { id: 'account.followers', defaultMessage: 'Followers' },
|
followers: { id: 'account.followers', defaultMessage: 'Followers' },
|
||||||
|
@ -35,10 +40,14 @@ const messages = defineMessages({
|
||||||
search: { id: 'tabs_bar.search', defaultMessage: 'Search' },
|
search: { id: 'tabs_bar.search', defaultMessage: 'Search' },
|
||||||
shop: { id: 'tabs_bar.shop', defaultMessage: 'Store - Buy Merch' },
|
shop: { id: 'tabs_bar.shop', defaultMessage: 'Store - Buy Merch' },
|
||||||
donate: { id: 'tabs_bar.donate', defaultMessage: 'Make a Donation' },
|
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) => ({
|
const mapStateToProps = (state) => ({
|
||||||
account: makeGetAccount()(state, me),
|
account: makeGetAccount()(state, me),
|
||||||
|
shortcuts: state.getIn(['shortcuts', 'items']),
|
||||||
moreOpen: state.getIn(['popover', 'popoverType']) === 'SIDEBAR_MORE',
|
moreOpen: state.getIn(['popover', 'popoverType']) === 'SIDEBAR_MORE',
|
||||||
notificationCount: state.getIn(['notifications', 'unread']),
|
notificationCount: state.getIn(['notifications', 'unread']),
|
||||||
homeItemsQueueCount: state.getIn(['timelines', 'home', 'totalQueuedItemsCount']),
|
homeItemsQueueCount: state.getIn(['timelines', 'home', 'totalQueuedItemsCount']),
|
||||||
|
@ -54,6 +63,9 @@ const mapDispatchToProps = (dispatch) => ({
|
||||||
onOpenComposeModal() {
|
onOpenComposeModal() {
|
||||||
dispatch(openModal('COMPOSE'))
|
dispatch(openModal('COMPOSE'))
|
||||||
},
|
},
|
||||||
|
onFetchShortcuts() {
|
||||||
|
dispatch(fetchShortcuts())
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
export default
|
export default
|
||||||
|
@ -67,6 +79,7 @@ class Sidebar extends ImmutablePureComponent {
|
||||||
moreOpen: PropTypes.bool,
|
moreOpen: PropTypes.bool,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
onOpenComposeModal: PropTypes.func.isRequired,
|
onOpenComposeModal: PropTypes.func.isRequired,
|
||||||
|
onFetchShortcuts: PropTypes.func.isRequired,
|
||||||
openSidebarMorePopover: PropTypes.func.isRequired,
|
openSidebarMorePopover: PropTypes.func.isRequired,
|
||||||
notificationCount: PropTypes.number.isRequired,
|
notificationCount: PropTypes.number.isRequired,
|
||||||
homeItemsQueueCount: PropTypes.number.isRequired,
|
homeItemsQueueCount: PropTypes.number.isRequired,
|
||||||
|
@ -74,6 +87,15 @@ class Sidebar extends ImmutablePureComponent {
|
||||||
tabs: PropTypes.array,
|
tabs: PropTypes.array,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
showBackBtn: PropTypes.bool,
|
showBackBtn: PropTypes.bool,
|
||||||
|
shortcuts: ImmutablePropTypes.list,
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
hoveringShortcuts: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.onFetchShortcuts()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOpenComposeModal = () => {
|
handleOpenComposeModal = () => {
|
||||||
|
@ -87,6 +109,14 @@ class Sidebar extends ImmutablePureComponent {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleMouseEnterShortcuts = () => {
|
||||||
|
this.setState({ hoveringShortcuts: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseLeaveShortcuts = () => {
|
||||||
|
this.setState({ hoveringShortcuts: false })
|
||||||
|
}
|
||||||
|
|
||||||
setMoreButtonRef = n => {
|
setMoreButtonRef = n => {
|
||||||
this.moreBtnRef = n
|
this.moreBtnRef = n
|
||||||
}
|
}
|
||||||
|
@ -102,14 +132,12 @@ class Sidebar extends ImmutablePureComponent {
|
||||||
tabs,
|
tabs,
|
||||||
title,
|
title,
|
||||||
showBackBtn,
|
showBackBtn,
|
||||||
|
shortcuts,
|
||||||
} = this.props
|
} = this.props
|
||||||
|
const { hoveringShortcuts } = this.state
|
||||||
|
|
||||||
// : todo :
|
|
||||||
if (!me || !account) return null
|
if (!me || !account) return null
|
||||||
|
|
||||||
const acct = account.get('acct')
|
|
||||||
const isPro = account.get('is_pro')
|
|
||||||
|
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{
|
{
|
||||||
title: 'Home',
|
title: 'Home',
|
||||||
|
@ -144,6 +172,11 @@ class Sidebar extends ImmutablePureComponent {
|
||||||
icon: 'explore',
|
icon: 'explore',
|
||||||
to: '/explore',
|
to: '/explore',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Pro Feed',
|
||||||
|
icon: 'circle',
|
||||||
|
to: '/timeline/pro',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'More',
|
title: 'More',
|
||||||
icon: 'more',
|
icon: 'more',
|
||||||
|
@ -153,32 +186,18 @@ class Sidebar extends ImmutablePureComponent {
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const shortcutItems = [
|
let shortcutItems = []
|
||||||
// {
|
if (!!shortcuts) {
|
||||||
// title: 'Meme Group',
|
shortcuts.forEach((s) => {
|
||||||
// icon: 'group',
|
shortcutItems.push({
|
||||||
// to: '/',
|
to: s.get('to'),
|
||||||
// count: 0,
|
title: s.get('title'),
|
||||||
// },
|
image: s.get('image'),
|
||||||
// {
|
})
|
||||||
// title: '@andrew',
|
})
|
||||||
// image: 'http://localhost:3000/system/accounts/avatars/000/000/001/original/260e8c96c97834da.jpeg?1562898139',
|
}
|
||||||
// to: '/',
|
|
||||||
// count: 3,
|
|
||||||
// },
|
|
||||||
]
|
|
||||||
|
|
||||||
const exploreItems = [
|
const exploreItems = [
|
||||||
{
|
|
||||||
title: 'Pro Feed',
|
|
||||||
icon: 'circle',
|
|
||||||
to: '/timeline/pro',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Chat',
|
|
||||||
icon: 'chat',
|
|
||||||
href: 'https://chat.gab.com',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: 'Apps',
|
title: 'Apps',
|
||||||
icon: '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>
|
<SidebarSectionTitle>{intl.formatMessage(messages.explore)}</SidebarSectionTitle>
|
||||||
{
|
{
|
||||||
exploreItems.map((exploreItem, i) => (
|
exploreItems.map((exploreItem, i) => (
|
||||||
|
@ -267,7 +321,7 @@ class Sidebar extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<Responsive min={Constants.BREAKPOINT_SMALL}>
|
<Responsive min={BREAKPOINT_SMALL}>
|
||||||
<Button
|
<Button
|
||||||
isBlock
|
isBlock
|
||||||
onClick={this.handleOpenComposeModal}
|
onClick={this.handleOpenComposeModal}
|
||||||
|
@ -277,7 +331,7 @@ class Sidebar extends ImmutablePureComponent {
|
||||||
</Button>
|
</Button>
|
||||||
</Responsive>
|
</Responsive>
|
||||||
|
|
||||||
<Responsive max={Constants.BREAKPOINT_SMALL}>
|
<Responsive max={BREAKPOINT_SMALL}>
|
||||||
<Button
|
<Button
|
||||||
onClick={this.handleOpenComposeModal}
|
onClick={this.handleOpenComposeModal}
|
||||||
className={_s.py15}
|
className={_s.py15}
|
||||||
|
|
|
@ -40,6 +40,7 @@ const messages = defineMessages({
|
||||||
help: { id: 'getting_started.help', defaultMessage: 'Help' },
|
help: { id: 'getting_started.help', defaultMessage: 'Help' },
|
||||||
display: { id: 'display_options', defaultMessage: 'Display Options' },
|
display: { id: 'display_options', defaultMessage: 'Display Options' },
|
||||||
proFeed: { id: 'pro_feed', defaultMessage: 'Pro Feed' },
|
proFeed: { id: 'pro_feed', defaultMessage: 'Pro Feed' },
|
||||||
|
shortcuts: { id: 'shortcuts', defaultMessage: 'Shortcuts' },
|
||||||
})
|
})
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = (state) => ({
|
||||||
|
@ -101,6 +102,12 @@ class SidebarXS extends ImmutablePureComponent {
|
||||||
onClick: this.handleSidebarClose,
|
onClick: this.handleSidebarClose,
|
||||||
title: intl.formatMessage(messages.lists),
|
title: intl.formatMessage(messages.lists),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: 'star',
|
||||||
|
to: '/shortcuts',
|
||||||
|
onClick: this.handleSidebarClose,
|
||||||
|
title: intl.formatMessage(messages.shortcuts),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
icon: 'pro',
|
icon: 'pro',
|
||||||
href: 'https://pro.gab.com',
|
href: 'https://pro.gab.com',
|
||||||
|
|
|
@ -41,6 +41,7 @@ export const MODAL_COMPOSE = 'COMPOSE'
|
||||||
export const MODAL_CONFIRM = 'CONFIRM'
|
export const MODAL_CONFIRM = 'CONFIRM'
|
||||||
export const MODAL_DISPLAY_OPTIONS = 'DISPLAY_OPTIONS'
|
export const MODAL_DISPLAY_OPTIONS = 'DISPLAY_OPTIONS'
|
||||||
export const MODAL_EDIT_PROFILE = 'EDIT_PROFILE'
|
export const MODAL_EDIT_PROFILE = 'EDIT_PROFILE'
|
||||||
|
export const MODAL_EDIT_SHORTCUTS = 'EDIT_SHORTCUTS'
|
||||||
export const MODAL_EMBED = 'EMBED'
|
export const MODAL_EMBED = 'EMBED'
|
||||||
export const MODAL_GIF_PICKER = 'GIF_PICKER'
|
export const MODAL_GIF_PICKER = 'GIF_PICKER'
|
||||||
export const MODAL_GROUP_CREATE = 'GROUP_CREATE'
|
export const MODAL_GROUP_CREATE = 'GROUP_CREATE'
|
||||||
|
|
|
@ -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,
|
PrivacyPolicy,
|
||||||
ProTimeline,
|
ProTimeline,
|
||||||
Search,
|
Search,
|
||||||
// Shortcuts,
|
Shortcuts,
|
||||||
StatusFeature,
|
StatusFeature,
|
||||||
StatusLikes,
|
StatusLikes,
|
||||||
StatusReposts,
|
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='/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' 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' }} />
|
<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 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 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 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 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 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') }
|
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 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 ReportModal() { return import(/* webpackChunkName: "modals/report_modal" */'../../../components/modal/report_modal') }
|
||||||
export function Search() { return import(/*webpackChunkName: "features/search" */'../../search') }
|
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 Status() { return import(/* webpackChunkName: "components/status" */'../../../components/status') }
|
||||||
export function StatusFeature() { return import(/* webpackChunkName: "features/status" */'../../status') }
|
export function StatusFeature() { return import(/* webpackChunkName: "features/status" */'../../status') }
|
||||||
export function SearchPopover() { return import(/* webpackChunkName: "components/search_popover" */'../../../components/popover/search_popover') }
|
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 { 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 LinkFooter from '../components/link_footer'
|
||||||
import WhoToFollowPanel from '../components/panel/who_to_follow_panel'
|
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 TrendsPanel from '../components/panel/trends_panel'
|
||||||
import DefaultLayout from '../layouts/default_layout'
|
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
|
export default
|
||||||
|
@injectIntl
|
||||||
|
@connect(null, mapDispatchToProps)
|
||||||
class ShortcutsPage extends PureComponent {
|
class ShortcutsPage extends PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
onOpenEditShortcutsModal: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnOpenEditShortcutsModal = () => {
|
||||||
|
this.props.onOpenEditShortcutsModal()
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { children } = this.props
|
const { intl, children } = this.props
|
||||||
|
|
||||||
|
const title = intl.formatMessage(messages.shortcuts)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaultLayout
|
<DefaultLayout
|
||||||
title='Shortcuts'
|
title={title}
|
||||||
actions={[]}
|
page='shortcuts'
|
||||||
layout={(
|
actions={[
|
||||||
<Fragment>
|
{
|
||||||
<UserPanel />
|
icon: 'cog',
|
||||||
<ProgressPanel />
|
onClick: this.handleOnOpenEditShortcutsModal,
|
||||||
<TrendsPanel />
|
},
|
||||||
<WhoToFollowPanel />
|
]}
|
||||||
<GroupSidebarPanel />
|
layout={[
|
||||||
<LinkFooter />
|
<TrendsPanel key='shortcuts-page-trends-panel' />,
|
||||||
</Fragment>
|
<WhoToFollowPanel key='shortcuts-page-wtf-panel' />,
|
||||||
)}
|
<LinkFooter key='shortcuts-page-link-footer' />,
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
|
<PageTitle path={title} />
|
||||||
{children}
|
{children}
|
||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
)
|
)
|
||||||
|
|
|
@ -30,6 +30,7 @@ import reports from './reports'
|
||||||
import search from './search'
|
import search from './search'
|
||||||
import settings from './settings'
|
import settings from './settings'
|
||||||
import shop from './shop'
|
import shop from './shop'
|
||||||
|
import shortcuts from './shortcuts'
|
||||||
import sidebar from './sidebar'
|
import sidebar from './sidebar'
|
||||||
import statuses from './statuses'
|
import statuses from './statuses'
|
||||||
import status_lists from './status_lists'
|
import status_lists from './status_lists'
|
||||||
|
@ -72,6 +73,7 @@ const reducers = {
|
||||||
search,
|
search,
|
||||||
settings,
|
settings,
|
||||||
shop,
|
shop,
|
||||||
|
shortcuts,
|
||||||
sidebar,
|
sidebar,
|
||||||
statuses,
|
statuses,
|
||||||
status_lists,
|
status_lists,
|
||||||
|
|
|
@ -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
|
# Table name: shortcuts
|
||||||
#
|
#
|
||||||
# id :bigint(8) not null, primary key
|
# id :bigint(8) not null, primary key
|
||||||
# account_id :bigint(8) not null
|
# created_at :datetime not null
|
||||||
# shortcut_id :bigint(8) not null
|
# updated_at :datetime not null
|
||||||
# shortcut_type :string not null
|
# account_id :bigint(8) not null
|
||||||
# created_at :datetime not null
|
# shortcut_id :bigint(8) not null
|
||||||
# updated_at :datetime
|
# shortcut_type :string default(""), not null
|
||||||
#
|
#
|
||||||
|
|
||||||
class Shortcut < ApplicationRecord
|
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
|
end
|
||||||
|
|
|
@ -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:
|
shortcuts:
|
||||||
errors:
|
errors:
|
||||||
limit: You have reached the maximum amount of shortcuts
|
limit: You have reached the maximum amount of shortcuts
|
||||||
|
exists: You already have a shortcut for that
|
||||||
sessions:
|
sessions:
|
||||||
activity: Last activity
|
activity: Last activity
|
||||||
browser: Browser
|
browser: Browser
|
||||||
|
|
|
@ -795,6 +795,7 @@ en_GB:
|
||||||
shortcuts:
|
shortcuts:
|
||||||
errors:
|
errors:
|
||||||
limit: You have reached the maximum amount of shortcuts
|
limit: You have reached the maximum amount of shortcuts
|
||||||
|
exists: You already have a shortcut for that
|
||||||
sessions:
|
sessions:
|
||||||
activity: Last activity
|
activity: Last activity
|
||||||
browser: Browser
|
browser: Browser
|
||||||
|
|
|
@ -359,6 +359,7 @@ Rails.application.routes.draw do
|
||||||
resources :reports, only: [:create]
|
resources :reports, only: [:create]
|
||||||
resources :filters, only: [:index, :create, :show, :update, :destroy]
|
resources :filters, only: [:index, :create, :show, :update, :destroy]
|
||||||
resources :endorsements, only: [:index]
|
resources :endorsements, only: [:index]
|
||||||
|
resources :shortcuts, only: [:index, :create, :show, :destroy]
|
||||||
|
|
||||||
namespace :apps do
|
namespace :apps do
|
||||||
get :verify_credentials, to: 'credentials#show'
|
get :verify_credentials, to: 'credentials#show'
|
||||||
|
|
Loading…
Reference in New Issue