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:
mgabdev
2020-07-21 22:24:26 -05:00
parent 405ace09da
commit f92f75d747
20 changed files with 705 additions and 107 deletions

View File

@@ -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>
)
}
}

View File

@@ -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}
>

View File

@@ -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 = () => {

View File

@@ -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}

View File

@@ -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',