Progress, Deck done
This commit is contained in:
parent
8f94ffad9c
commit
04053c0e31
@ -1,16 +1,23 @@
|
||||
import throttle from 'lodash.throttle'
|
||||
import debounce from 'lodash.debounce'
|
||||
import api, { getLinks } from '../api'
|
||||
import { importFetchedAccounts } from './importer'
|
||||
import { me } from '../initial_state'
|
||||
|
||||
export const CHAT_CONVERSATION_CREATE_SEARCH_ACCOUNTS_SUCCESS = 'CHAT_CONVERSATION_CREATE_SEARCH_ACCOUNTS_SUCCESS'
|
||||
|
||||
export const CLEAR_CHAT_CONVERSATION_CREATE_SEARCH_ACCOUNTS = 'CLEAR_CHAT_CONVERSATION_CREATE_SEARCH_ACCOUNTS'
|
||||
|
||||
export const SET_CHAT_CONVERSATION_SELECTED = 'SET_CHAT_CONVERSATION_SELECTED'
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export const fetchChatConversationAccountSuggestions = (query) => throttle((dispatch, getState) => {
|
||||
export const fetchChatConversationAccountSuggestions = (query) => (dispatch, getState) => {
|
||||
if (!query) return
|
||||
debouncedFetchChatConversationAccountSuggestions(query, dispatch, getState)
|
||||
}
|
||||
|
||||
export const debouncedFetchChatConversationAccountSuggestions = debounce((query, dispatch, getState) => {
|
||||
if (!query) return
|
||||
|
||||
api(getState).get('/api/v1/accounts/search', {
|
||||
@ -25,13 +32,20 @@ export const fetchChatConversationAccountSuggestions = (query) => throttle((disp
|
||||
}).catch((error) => {
|
||||
//
|
||||
})
|
||||
}, 200, { leading: true, trailing: true })
|
||||
}, 650, { leading: true })
|
||||
|
||||
const fetchChatConversationAccountSuggestionsSuccess = (accounts) => ({
|
||||
type: CHAT_CONVERSATION_CREATE_SEARCH_ACCOUNTS_SUCCESS,
|
||||
accounts,
|
||||
})
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export const clearChatConversationAccountSuggestions = () => (dispatch) => {
|
||||
dispatch({ type: CLEAR_CHAT_CONVERSATION_CREATE_SEARCH_ACCOUNTS })
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -41,6 +41,7 @@ export const COMPOSE_QUOTE = 'COMPOSE_QUOTE'
|
||||
export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL'
|
||||
export const COMPOSE_MENTION = 'COMPOSE_MENTION'
|
||||
export const COMPOSE_RESET = 'COMPOSE_RESET'
|
||||
export const COMPOSE_GROUP_SET = 'COMPOSE_GROUP_SET'
|
||||
|
||||
export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST'
|
||||
export const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS'
|
||||
@ -762,6 +763,14 @@ export const changeExpiresAt = (value) => ({
|
||||
value,
|
||||
})
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export const changeComposeGroupId = (groupId) => ({
|
||||
type: COMPOSE_GROUP_SET,
|
||||
groupId,
|
||||
})
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
@ -1,5 +1,8 @@
|
||||
import debounce from 'lodash.debounce'
|
||||
import api from '../api'
|
||||
import { me } from '../initial_state'
|
||||
import { saveSettings } from './settings'
|
||||
import { importFetchedAccounts } from './importer'
|
||||
|
||||
export const DECK_CONNECT = 'DECK_CONNECT'
|
||||
export const DECK_DISCONNECT = 'DECK_DISCONNECT'
|
||||
@ -8,6 +11,9 @@ export const DECK_SET_COLUMN_AT_INDEX = 'DECK_SET_COLUMN_AT_INDEX'
|
||||
export const DECK_DELETE_COLUMN_AT_INDEX = 'DECK_DELETE_COLUMN_AT_INDEX'
|
||||
export const DECK_CHANGE_COLUMN_AT_INDEX = 'DECK_CHANGE_COLUMN_AT_INDEX'
|
||||
|
||||
export const DECK_SEARCH_USERS_SUCCESS = 'DECK_SEARCH_USERS_SUCCESS'
|
||||
export const DECK_SEARCH_USERS_CLEAR = 'DECK_SEARCH_USERS_CLEAR'
|
||||
|
||||
export const deckConnect = () => ({
|
||||
type: DECK_CONNECT,
|
||||
})
|
||||
@ -41,3 +47,40 @@ export const updateDeckColumnAtIndex = (oldIndex, newIndex) => (dispatch) => {
|
||||
})
|
||||
dispatch(saveSettings())
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export const fetchDeckAccountSuggestions = (query) => (dispatch, getState) => {
|
||||
if (!query) return
|
||||
debouncedFetchDeckAccountSuggestions(query, dispatch, getState)
|
||||
}
|
||||
|
||||
const debouncedFetchDeckAccountSuggestions = debounce((query, dispatch, getState) => {
|
||||
if (!query) return
|
||||
|
||||
api(getState).get('/api/v1/accounts/search', {
|
||||
params: {
|
||||
q: query,
|
||||
resolve: false,
|
||||
limit: 4,
|
||||
},
|
||||
}).then((response) => {
|
||||
dispatch(importFetchedAccounts(response.data))
|
||||
dispatch(fetchDeckAccountSuggestionsSuccess(response.data))
|
||||
}).catch((error) => {
|
||||
//
|
||||
})
|
||||
}, 650, { leading: true })
|
||||
|
||||
const fetchDeckAccountSuggestionsSuccess = (accounts) => ({
|
||||
type: DECK_SEARCH_USERS_SUCCESS,
|
||||
accounts,
|
||||
})
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export const clearDeckAccountSuggestions = () => (dispatch) => {
|
||||
dispatch({ type: DECK_SEARCH_USERS_CLEAR })
|
||||
}
|
@ -1,8 +1,82 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { connect } from 'react-redux'
|
||||
import { fetchAccount } from '../actions/accounts'
|
||||
import DeckColumnHeader from './deck_column_header'
|
||||
import Avatar from './avatar'
|
||||
import DisplayName from './display_name'
|
||||
import {
|
||||
FONT_SIZES,
|
||||
DEFAULT_FONT_SIZE,
|
||||
FONT_SIZES_EXTRA_SMALL,
|
||||
FONT_SIZES_SMALL,
|
||||
FONT_SIZES_NORMAL,
|
||||
FONT_SIZES_MEDIUM,
|
||||
FONT_SIZES_LARGE,
|
||||
FONT_SIZES_EXTRA_LARGE
|
||||
} from '../constants'
|
||||
|
||||
class DeckColumn extends React.PureComponent {
|
||||
class DeckColumn extends ImmutablePureComponent {
|
||||
|
||||
state = {
|
||||
width: 360, // default
|
||||
refreshBool: false,
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setWidth()
|
||||
|
||||
const { accountId, account } = this.props
|
||||
if (!!accountId || !account) {
|
||||
this.props.onFetchAccount(accountId)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (prevProps.fontSize !== this.props.fontSize) {
|
||||
this.setWidth()
|
||||
}
|
||||
}
|
||||
|
||||
setWidth = () => {
|
||||
const { fontSize } = this.props
|
||||
|
||||
let width = 360
|
||||
switch (FONT_SIZES[fontSize]) {
|
||||
case FONT_SIZES_EXTRA_SMALL:
|
||||
width = 310
|
||||
break;
|
||||
case FONT_SIZES_SMALL:
|
||||
width = 320
|
||||
break;
|
||||
case FONT_SIZES_NORMAL:
|
||||
width = 335
|
||||
break;
|
||||
case FONT_SIZES_MEDIUM:
|
||||
width = 350
|
||||
break;
|
||||
case FONT_SIZES_LARGE:
|
||||
width = 365
|
||||
break;
|
||||
case FONT_SIZES_EXTRA_LARGE:
|
||||
width = 385
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
this.setState({ width })
|
||||
}
|
||||
|
||||
handleOnRefresh = () => {
|
||||
//hacky
|
||||
this.setState({ refreshBool: true })
|
||||
setTimeout(() => {
|
||||
this.setState({ refreshBool: false })
|
||||
}, 100);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
@ -12,20 +86,38 @@ class DeckColumn extends React.PureComponent {
|
||||
children,
|
||||
index,
|
||||
noButtons,
|
||||
noRefresh,
|
||||
account,
|
||||
} = this.props
|
||||
const { width, refreshBool } = this.state
|
||||
|
||||
let newTitle = title
|
||||
if (!!account) {
|
||||
|
||||
newTitle = (
|
||||
<div className={[_s.d, _s.flexRow, _s.aiCenter].join(' ')}>
|
||||
<Avatar account={account} noHover size={30} />
|
||||
<div className={[_s.d, _s.ml10].join(' ')}>
|
||||
<DisplayName account={account} noUsername isInline noHover />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={[_s.d, _s.w360PX, _s.px2, _s.bgSecondary, _s.h100VH].join(' ')}>
|
||||
<div className={[_s.d, _s.px2, _s.bgSecondary, _s.h100VH].join(' ')} style={{ width: `${width}px` }}>
|
||||
<div className={[_s.d, _s.w100PC, _s.bgPrimary, _s.h100VH].join(' ')}>
|
||||
<DeckColumnHeader
|
||||
title={title}
|
||||
title={newTitle}
|
||||
subtitle={subtitle}
|
||||
icon={icon}
|
||||
index={index}
|
||||
noButtons={noButtons}
|
||||
noRefresh={noRefresh}
|
||||
onRefresh={this.handleOnRefresh}
|
||||
/>
|
||||
<div className={[_s.d, _s.w100PC, _s.overflowYScroll, _s.boxShadowNone, _s.posAbs, _s.top60PX, _s.left0, _s.right0, _s.bottom0].join(' ')}>
|
||||
{children}
|
||||
{ !refreshBool && children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -34,12 +126,32 @@ class DeckColumn extends React.PureComponent {
|
||||
|
||||
}
|
||||
|
||||
const mapStateToProps = (state, { accountId }) => {
|
||||
const account = !!accountId ? state.getIn(['accounts', accountId]) : null
|
||||
|
||||
return {
|
||||
account,
|
||||
fontSize: state.getIn(['settings', 'displayOptions', 'fontSize'], DEFAULT_FONT_SIZE),
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onFetchAccount(accountId) {
|
||||
dispatch(fetchAccount(accountId))
|
||||
}
|
||||
})
|
||||
|
||||
DeckColumn.propTypes = {
|
||||
title: PropTypes.string,
|
||||
subtitle: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
index: PropTypes.number,
|
||||
noButtons: PropTypes.bool,
|
||||
noRefresh: PropTypes.bool,
|
||||
onRefresh: PropTypes.func,
|
||||
accountId: PropTypes.string,
|
||||
account: ImmutablePropTypes.map,
|
||||
onFetchAccount: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default DeckColumn
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DeckColumn)
|
@ -13,7 +13,8 @@ class DeckColumnHeader extends React.PureComponent {
|
||||
}
|
||||
|
||||
handleClickRefresh = () => {
|
||||
|
||||
const { onRefresh } = this.props
|
||||
if (!!onRefresh) onRefresh()
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -23,34 +24,36 @@ class DeckColumnHeader extends React.PureComponent {
|
||||
icon,
|
||||
children,
|
||||
noButtons,
|
||||
noRefresh,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div data-sort-header className={[_s.d, _s.w100PC, _s.flexRow, _s.aiCenter, _s.h60PX, _s.px15, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary, _s.bgPrimary].join(' ')}>
|
||||
{
|
||||
!!icon &&
|
||||
<div data-sort-header className={[_s.d, _s.flexRow, _s.mr15, _s.cursorEWResize].join(' ')}>
|
||||
<span className={[_s.d, _s.w1PX, _s.h24PX, _s.mr2, _s.bgSecondary].join(' ')} />
|
||||
<span className={[_s.d, _s.w1PX, _s.h24PX, _s.mr2, _s.bgSecondary].join(' ')} />
|
||||
<span className={[_s.d, _s.w1PX, _s.h24PX, _s.bgSecondary].join(' ')} />
|
||||
</div>
|
||||
}
|
||||
{ !!icon && <Icon id={icon} className={_s.cPrimary} size='18px' /> }
|
||||
<div className={[_s.d, _s.flexRow, _s.aiEnd, _s.ml15].join(' ')}>
|
||||
<div data-sort-header className={[_s.d, _s.flexRow, _s.mr15, _s.cursorEWResize].join(' ')}>
|
||||
<span className={[_s.d, _s.w1PX, _s.h24PX, _s.mr2, _s.bgSecondary].join(' ')} />
|
||||
<span className={[_s.d, _s.w1PX, _s.h24PX, _s.mr2, _s.bgSecondary].join(' ')} />
|
||||
<span className={[_s.d, _s.w1PX, _s.h24PX, _s.bgSecondary].join(' ')} />
|
||||
</div>
|
||||
|
||||
{ !!icon && <Icon id={icon} className={[_s.cPrimary, _s.mr15].join(' ')} size='18px' /> }
|
||||
<div className={[_s.d, _s.flexRow, _s.aiEnd].join(' ')}>
|
||||
{ !!title && <Text size='extraLarge' weight='medium'>{title}</Text> }
|
||||
{ !!subtitle && <Text className={_s.ml5} color='secondary'>{subtitle}</Text> }
|
||||
</div>
|
||||
{
|
||||
!!title && !noButtons &&
|
||||
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.mlAuto, _s.jcCenter].join(' ')}>
|
||||
<Button
|
||||
isNarrow
|
||||
noClasses
|
||||
onClick={this.handleClickRefresh}
|
||||
className={[_s.d, _s.mr5, _s.cursorPointer, _s.outlineNone, _s.bgTransparent, _s.px5, _s.py5].join(' ')}
|
||||
iconClassName={_s.cSecondary}
|
||||
icon='repost'
|
||||
/>
|
||||
{
|
||||
!noRefresh &&
|
||||
<Button
|
||||
isNarrow
|
||||
noClasses
|
||||
onClick={this.handleClickRefresh}
|
||||
className={[_s.d, _s.mr5, _s.cursorPointer, _s.outlineNone, _s.bgTransparent, _s.px5, _s.py5].join(' ')}
|
||||
iconClassName={_s.cSecondary}
|
||||
icon='repost'
|
||||
/>
|
||||
}
|
||||
<Button
|
||||
isNarrow
|
||||
noClasses
|
||||
@ -73,6 +76,8 @@ DeckColumnHeader.propTypes = {
|
||||
icon: PropTypes.string,
|
||||
index: PropTypes.number,
|
||||
noButtons: PropTypes.bool,
|
||||
noRefresh: PropTypes.bool,
|
||||
onRefresh: PropTypes.func,
|
||||
}
|
||||
|
||||
export default connect()(DeckColumnHeader)
|
@ -7,13 +7,19 @@ import { openModal } from '../../actions/modal'
|
||||
import { setDeckColumnAtIndex } from '../../actions/deck'
|
||||
import { getOrderedLists, getListOfGroups } from '../../selectors'
|
||||
import { fetchLists } from '../../actions/lists'
|
||||
import {
|
||||
fetchDeckAccountSuggestions,
|
||||
clearDeckAccountSuggestions,
|
||||
} from '../../actions/deck'
|
||||
import { fetchGroupsByTab } from '../../actions/groups'
|
||||
import { MODAL_DECK_COLUMN_ADD } from '../../constants'
|
||||
import Account from '../account'
|
||||
import Heading from '../heading'
|
||||
import Button from '../button'
|
||||
import Block from '../block'
|
||||
import Input from '../input'
|
||||
import List from '../list'
|
||||
import Text from '../text'
|
||||
|
||||
class DeckColumnAddOptionsModal extends ImmutablePureComponent {
|
||||
|
||||
@ -50,17 +56,24 @@ class DeckColumnAddOptionsModal extends ImmutablePureComponent {
|
||||
this.setState({ hashtagValue: '' })
|
||||
}
|
||||
|
||||
handleAddUser = (userId) => {
|
||||
this.setState({ usernameValue: '' })
|
||||
this.props.onClearDeckAccountSuggestions()
|
||||
if (!!userId) this.handleAdd(`user.${userId}`)
|
||||
}
|
||||
|
||||
onChangeHashtagValue = (hashtagValue) => {
|
||||
this.setState({ hashtagValue })
|
||||
}
|
||||
|
||||
onChangeUsernameValue = (usernameValue) => {
|
||||
this.setState({ usernameValue })
|
||||
this.props.onFetchUserSuggestions(usernameValue)
|
||||
}
|
||||
|
||||
getContentForColumn = () => {
|
||||
const { column, lists, groups, accounts } = this.props
|
||||
const { hashtagValue } = this.state
|
||||
const { column, lists, groups, suggestionsIds } = this.props
|
||||
const { hashtagValue, usernameValue } = this.state
|
||||
|
||||
if (column === 'hashtag') {
|
||||
return (
|
||||
@ -107,17 +120,39 @@ class DeckColumnAddOptionsModal extends ImmutablePureComponent {
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
} else if (column === 'group') {
|
||||
} else if (column === 'user') {
|
||||
return (
|
||||
<div className={[_s.d, _s.px15, _s.py10].join(' ')}>
|
||||
<Input
|
||||
type='text'
|
||||
value={usernameValue}
|
||||
placeholder=''
|
||||
id='user-deck'
|
||||
title='Enter username'
|
||||
onChange={this.onChangeUsernameValue}
|
||||
/>
|
||||
<div className={[_s.d, _s.width100PC].join(' ')}>
|
||||
<div className={[_s.d, _s.px15, _s.py10].join(' ')}>
|
||||
<Input
|
||||
type='text'
|
||||
value={usernameValue}
|
||||
placeholder=''
|
||||
id='user-deck'
|
||||
title='Enter username'
|
||||
onChange={this.onChangeUsernameValue}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={[_s.d, _s.pt10].join(' ')}>
|
||||
<div className={[_s.d].join(' ')}>
|
||||
<Text weight='bold' size='small' color='secondary' className={[_s.d, _s.px15, _s.ml15, _s.mt5, _s.mb15].join(' ')}>
|
||||
Search results ({suggestionsIds.size})
|
||||
</Text>
|
||||
{
|
||||
suggestionsIds &&
|
||||
suggestionsIds.map((accountId) => (
|
||||
<Account
|
||||
compact
|
||||
key={`create-deck-user-${accountId}`}
|
||||
id={accountId}
|
||||
onActionClick={() => this.handleAddUser(accountId)}
|
||||
actionIcon='add'
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -175,7 +210,7 @@ class DeckColumnAddOptionsModal extends ImmutablePureComponent {
|
||||
const mapStateToProps = (state) => ({
|
||||
lists: getOrderedLists(state),
|
||||
groups: getListOfGroups(state, { type: 'member' }),
|
||||
accounts: [],
|
||||
suggestionsIds: state.getIn(['deck', 'accountSuggestions']),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
@ -191,6 +226,12 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
onOpenDeckColumnAddModal() {
|
||||
dispatch(openModal(MODAL_DECK_COLUMN_ADD))
|
||||
},
|
||||
onFetchUserSuggestions(query) {
|
||||
dispatch(fetchDeckAccountSuggestions(query))
|
||||
},
|
||||
onClearDeckAccountSuggestions() {
|
||||
dispatch(clearDeckAccountSuggestions())
|
||||
}
|
||||
})
|
||||
|
||||
DeckColumnAddOptionsModal.propTypes = {
|
||||
|
@ -1,74 +1,152 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { connect } from 'react-redux'
|
||||
import { closePopover } from '../../actions/popover'
|
||||
import { getListOfGroups } from '../../selectors'
|
||||
import { fetchGroupsByTab } from '../../actions/groups'
|
||||
import { changeComposeGroupId } from '../../actions/compose'
|
||||
import PopoverLayout from './popover_layout'
|
||||
import List from '../list'
|
||||
import Button from '../button'
|
||||
import Text from '../text'
|
||||
|
||||
class ComposePostDesinationPopover extends React.PureComponent {
|
||||
class ComposePostDesinationPopover extends ImmutablePureComponent {
|
||||
|
||||
state = {
|
||||
isGroupsSelected: false,
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (this.props.composeGroupId) {
|
||||
this.setState({ isGroupsSelected: true })
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
if (prevProps.composeGroupId !== this.props.composeGroupId) {
|
||||
this.setState({ isGroupsSelected: !!this.props.composeGroupId })
|
||||
}
|
||||
}
|
||||
|
||||
handleOnClosePopover = () => {
|
||||
this.props.onClosePopover()
|
||||
}
|
||||
|
||||
selectDestination = (destination) => {
|
||||
const isGroupsSelected = destination === 'group'
|
||||
this.setState({ isGroupsSelected })
|
||||
if (isGroupsSelected) {
|
||||
this.props.onFetchMemberGroups()
|
||||
} else {
|
||||
this.handleSelectGroup(null)
|
||||
}
|
||||
}
|
||||
|
||||
handleSelectGroup = (groupId) => {
|
||||
this.props.onChangeComposeGroupId(groupId)
|
||||
this.handleOnClosePopover()
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
isXS,
|
||||
} = this.props
|
||||
const { isXS, groups, composeGroupId } = this.props
|
||||
const { isGroupsSelected } = this.state
|
||||
|
||||
// TIMELINE
|
||||
// GROUP - MY GROUPS
|
||||
|
||||
const items = [
|
||||
const mainItems = [
|
||||
{
|
||||
hideArrow: true,
|
||||
title: 'Timeline',
|
||||
onClick: () => this.handleOnDelete(),
|
||||
isActive: !isGroupsSelected,
|
||||
onClick: () => this.selectDestination('home'),
|
||||
},
|
||||
{
|
||||
title: 'Group',
|
||||
onClick: () => this.handleOnReport(),
|
||||
isActive: isGroupsSelected,
|
||||
onClick: () => this.selectDestination('group'),
|
||||
},
|
||||
]
|
||||
|
||||
const groupItems = !!groups ? groups.map((group) => ({
|
||||
hideArrow: true,
|
||||
onClick: () => this.handleSelectGroup(group.get('id')),
|
||||
title: group.get('title'),
|
||||
isActive: group.get('id') === composeGroupId,
|
||||
})) : []
|
||||
|
||||
return (
|
||||
<PopoverLayout
|
||||
width={180}
|
||||
width={isGroupsSelected ? 320 : 180}
|
||||
isXS={isXS}
|
||||
onClose={this.handleOnClosePopover}
|
||||
>
|
||||
<div className={[_s.d]}>
|
||||
<Text className={[_s.d, _s.px15, _s.py10, _s.bgSecondary].join(' ')}>Post to:</Text>
|
||||
<List items={items} />
|
||||
</div>
|
||||
<div>
|
||||
<Text className={[_s.d, _s.px15, _s.py10, _s.bgSecondary].join(' ')}>
|
||||
<Button
|
||||
isText
|
||||
icon='back'
|
||||
/>
|
||||
Select group:
|
||||
</Text>
|
||||
<List items={items} />
|
||||
</div>
|
||||
{
|
||||
!isGroupsSelected &&
|
||||
<div className={[_s.d, _s.w100PC].join(' ')}>
|
||||
<Text className={[_s.d, _s.px15, _s.py10, _s.bgSecondary].join(' ')}>Post to:</Text>
|
||||
<List items={mainItems} />
|
||||
</div>
|
||||
}
|
||||
{
|
||||
isGroupsSelected &&
|
||||
<div className={[_s.d, _s.w100PC].join(' ')}>
|
||||
<div className={[_s.d, _s.flexRow, _s.bgSecondary].join(' ')}>
|
||||
<Button
|
||||
isText
|
||||
icon='back'
|
||||
color='primary'
|
||||
backgroundColor='none'
|
||||
className={[_s.aiCenter, _s.jcCenter, _s.pl15, _s.pr5].join(' ')}
|
||||
onClick={() => this.selectDestination('home')}
|
||||
/>
|
||||
<Text className={[_s.d, _s.pl5, _s.py10].join(' ')}>
|
||||
Select group:
|
||||
</Text>
|
||||
</div>
|
||||
<div className={[_s.d, _s.w100PC, _s.overflowYScroll, _s.maxH340PX].join(' ')}>
|
||||
<List
|
||||
scrollKey='groups-post-destination-add'
|
||||
showLoading={groups.size === 0}
|
||||
emptyMessage="You are not a member of any groups yet."
|
||||
items={groupItems}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</PopoverLayout>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
//
|
||||
})
|
||||
const mapStateToProps = (state) => {
|
||||
const composeGroupId = state.getIn(['compose', 'group_id'])
|
||||
|
||||
return {
|
||||
composeGroupId,
|
||||
composeGroup: state.getIn(['groups', composeGroupId]),
|
||||
groups: getListOfGroups(state, { type: 'member' }),
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onClosePopover: () => dispatch(closePopover()),
|
||||
onClosePopover() {
|
||||
dispatch(closePopover())
|
||||
},
|
||||
onFetchMemberGroups() {
|
||||
dispatch(fetchGroupsByTab('member'))
|
||||
},
|
||||
onChangeComposeGroupId(groupId) {
|
||||
dispatch(changeComposeGroupId(groupId))
|
||||
}
|
||||
})
|
||||
|
||||
ComposePostDesinationPopover.propTypes = {
|
||||
isXS: PropTypes.bool,
|
||||
onClosePopover: PropTypes.func.isRequired,
|
||||
onFetchMemberGroups: PropTypes.func.isRequired,
|
||||
onChangeComposeGroupId: PropTypes.func.isRequired,
|
||||
groups: ImmutablePropTypes.list,
|
||||
composeGroup: ImmutablePropTypes.map,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ComposePostDesinationPopover)
|
@ -3,7 +3,9 @@ import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import { closePopover } from '../../actions/popover'
|
||||
import { openModal } from '../../actions/modal'
|
||||
import { meUsername } from '../../initial_state'
|
||||
import { MODAL_DISPLAY_OPTIONS } from '../../constants'
|
||||
import PopoverLayout from './popover_layout'
|
||||
import List from '../list'
|
||||
|
||||
@ -13,6 +15,11 @@ class NavSettingsPopover extends React.PureComponent {
|
||||
this.props.onClosePopover()
|
||||
}
|
||||
|
||||
handleOpenDisplayOptions = () => {
|
||||
this.props.onOpenDisplayOptions()
|
||||
this.handleOnClosePopover()
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, isXS } = this.props
|
||||
|
||||
@ -29,6 +36,10 @@ class NavSettingsPopover extends React.PureComponent {
|
||||
to: `/${meUsername}`,
|
||||
onClick: this.handleOnClosePopover,
|
||||
},
|
||||
{
|
||||
title: 'Display options',
|
||||
onClick: this.handleOpenDisplayOptions,
|
||||
},
|
||||
{
|
||||
title: intl.formatMessage(messages.help),
|
||||
href: 'https://help.gab.com',
|
||||
@ -60,6 +71,9 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
onClosePopover() {
|
||||
dispatch(closePopover())
|
||||
},
|
||||
onOpenDisplayOptions() {
|
||||
dispatch(openModal(MODAL_DISPLAY_OPTIONS))
|
||||
}
|
||||
})
|
||||
|
||||
NavSettingsPopover.propTypes = {
|
||||
|
@ -175,7 +175,6 @@ export const TRENDS_RSS_SOURCES = [
|
||||
{'id':'5daf66772fea4d3ba000883b','title':'Gateway Pundit'},
|
||||
{'id':'5dafa767300c0e2601330386','title':'RT'},
|
||||
{'id':'5dafa88b786f593d02078d35','title':'ABC News'},
|
||||
{'id':'5e1e0a479d78d445de6a32bd','title':'Aljazeera'},
|
||||
{'id':'5e1e0a7dc46f1d5487be1806','title':'Yahoo News'},
|
||||
{'id':'5e1e0ae5c46f1d5487be1902','title':'NBC'},
|
||||
{'id':'5e1e0b849d78d445de6a35c7','title':'LA Times'},
|
||||
@ -183,11 +182,8 @@ export const TRENDS_RSS_SOURCES = [
|
||||
{'id':'5e52dfc91f94b1111db105ed','title':'National File'},
|
||||
{'id':'5e56dcff1f94b1111db95a75','title':'WND'},
|
||||
{'id':'5e6423d39f964d7a761997f8','title':'Mediaite'},
|
||||
{'id':'5e716016a994095d6ca9b907','title':'CNBC'},
|
||||
{'id':'5e7160cb40c78e3a4af7a5bb','title':'FiveThirtyEight'},
|
||||
{'id':'5e7160f7a994095d6ca9bbee','title':'Redstate'},
|
||||
{'id':'5e71613ca994095d6ca9bcbb','title':'Vice'},
|
||||
{'id':'5e716155a994095d6ca9bd03','title':'Politico'},
|
||||
{'id':'5e7161f3a994095d6ca9bea6','title':'TMZ'},
|
||||
{'id':'5e8275900d86876052a853ae','title':'CD Media'},
|
||||
]
|
@ -95,9 +95,8 @@ const messages = defineMessages({
|
||||
|
||||
const emptyList = ImmutableList()
|
||||
|
||||
const mapStateToProps = (state, { account, commentsOnly = false }) => {
|
||||
const accountId = !!account ? account.getIn(['id'], null) : -1
|
||||
|
||||
const mapStateToProps = (state, { id, account, commentsOnly = false }) => {
|
||||
const accountId = !!id ? id : !!account ? account.getIn(['id'], null) : -1
|
||||
const path = commentsOnly ? `${accountId}:comments_only` : accountId
|
||||
|
||||
return {
|
||||
|
@ -1,7 +1,10 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { fetchChatConversationAccountSuggestions } from '../actions/chats'
|
||||
import {
|
||||
fetchChatConversationAccountSuggestions,
|
||||
clearChatConversationAccountSuggestions,
|
||||
} from '../actions/chats'
|
||||
import { createChatConversation } from '../actions/chat_conversations'
|
||||
import Account from '../components/account'
|
||||
import Button from '../components/button'
|
||||
@ -22,6 +25,11 @@ class ChatConversationCreate extends React.PureComponent {
|
||||
|
||||
handleOnCreateChatConversation = (accountId) => {
|
||||
this.props.onCreateChatConversation(accountId)
|
||||
this.props.onClearChatConversationAccountSuggestions()
|
||||
|
||||
if (this.props.isModal && !!this.props.onCloseModal) {
|
||||
this.props.onCloseModal()
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -47,7 +55,7 @@ class ChatConversationCreate extends React.PureComponent {
|
||||
suggestionsIds.map((accountId) => (
|
||||
<Account
|
||||
compact
|
||||
key={`remove-from-list-${accountId}`}
|
||||
key={`chat-conversation-account-create-${accountId}`}
|
||||
id={accountId}
|
||||
onActionClick={() => this.handleOnCreateChatConversation(accountId)}
|
||||
actionIcon='add'
|
||||
@ -67,12 +75,15 @@ const mapStateToProps = (state) => ({
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onChange: (value) => {
|
||||
onChange(value) {
|
||||
dispatch(fetchChatConversationAccountSuggestions(value))
|
||||
},
|
||||
onCreateChatConversation: (accountId) => {
|
||||
onCreateChatConversation(accountId) {
|
||||
dispatch(createChatConversation(accountId))
|
||||
},
|
||||
onClearChatConversationAccountSuggestions() {
|
||||
dispatch(clearChatConversationAccountSuggestions())
|
||||
}
|
||||
})
|
||||
|
||||
ChatConversationCreate.propTypes = {
|
||||
|
@ -30,10 +30,18 @@ class ComposeDestinationHeader extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { account, isModal, formLocation } = this.props
|
||||
const {
|
||||
account,
|
||||
isModal,
|
||||
composeGroup,
|
||||
formLocation,
|
||||
} = this.props
|
||||
|
||||
const isIntroduction = formLocation === 'introduction'
|
||||
const title = 'Post to timeline'
|
||||
|
||||
let groupTitle = !!composeGroup ? composeGroup.get('title') : ''
|
||||
groupTitle = groupTitle.length > 32 ? `${groupTitle.substring(0, 32).trim()}...` : groupTitle
|
||||
const title = !!composeGroup ? `Post to ${groupTitle}` : 'Post to timeline'
|
||||
|
||||
return (
|
||||
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.bgPrimary, _s.w100PC, _s.h40PX, _s.pr15].join(' ')}>
|
||||
@ -76,6 +84,15 @@ class ComposeDestinationHeader extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const composeGroupId = state.getIn(['compose', 'group_id'])
|
||||
|
||||
return {
|
||||
composeGroupId,
|
||||
composeGroup: state.getIn(['groups', composeGroupId]),
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onOpenModal() {
|
||||
dispatch(openModal(MODAL_COMPOSE))
|
||||
@ -96,4 +113,4 @@ ComposeDestinationHeader.propTypes = {
|
||||
formLocation: PropTypes.string,
|
||||
}
|
||||
|
||||
export default connect(null, mapDispatchToProps)(ComposeDestinationHeader)
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ComposeDestinationHeader)
|
@ -134,6 +134,11 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
|
||||
componentDidMount() {
|
||||
document.addEventListener('click', this.handleClick, false)
|
||||
|
||||
const { groupId } = this.props
|
||||
if (groupId) {
|
||||
this.props.onChangeComposeGroupId(groupId)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
changeComposeSpoilerText,
|
||||
uploadCompose,
|
||||
changeScheduledAt,
|
||||
changeComposeGroupId,
|
||||
} from '../../../actions/compose'
|
||||
import { openModal } from '../../../actions/modal'
|
||||
import { MODAL_COMPOSE } from '../../../constants'
|
||||
@ -132,6 +133,10 @@ const mapDispatchToProps = (dispatch, { isStandalone }) => ({
|
||||
openComposeModal() {
|
||||
dispatch(openModal(MODAL_COMPOSE))
|
||||
},
|
||||
|
||||
onChangeComposeGroupId(groupId) {
|
||||
dispatch(changeComposeGroupId(groupId))
|
||||
}
|
||||
})
|
||||
|
||||
function mergeProps(stateProps, dispatchProps, ownProps) {
|
||||
|
@ -73,9 +73,9 @@ class Deck extends React.PureComponent {
|
||||
getDeckColumn = (deckColumn, index) => {
|
||||
if (!deckColumn || !this.props.isPro) return null
|
||||
|
||||
let Component = null
|
||||
let Component, noRefresh, accountId, icon = null
|
||||
let componentParams = {}
|
||||
let title, subtitle, icon = ''
|
||||
let title, subtitle = ''
|
||||
|
||||
switch (deckColumn) {
|
||||
case 'notifications':
|
||||
@ -92,6 +92,7 @@ class Deck extends React.PureComponent {
|
||||
title = 'Compose'
|
||||
icon = 'pencil'
|
||||
Component = Compose
|
||||
noRefresh = true
|
||||
break
|
||||
case 'likes':
|
||||
title = 'Likes'
|
||||
@ -127,7 +128,11 @@ class Deck extends React.PureComponent {
|
||||
|
||||
if (!Component) {
|
||||
if (deckColumn.indexOf('user.') > -1) {
|
||||
|
||||
const userAccountId = deckColumn.replace('user.', '')
|
||||
title = 'User'
|
||||
Component = AccountTimeline
|
||||
componentParams = { id: userAccountId }
|
||||
accountId = userAccountId
|
||||
} else if (deckColumn.indexOf('list.') > -1) {
|
||||
const listId = deckColumn.replace('list.', '')
|
||||
title = 'List'
|
||||
@ -162,7 +167,14 @@ class Deck extends React.PureComponent {
|
||||
index={index}
|
||||
sortIndex={index}
|
||||
>
|
||||
<DeckColumn title={title} subtitle={subtitle} icon={icon} index={index}>
|
||||
<DeckColumn
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
icon={icon}
|
||||
index={index}
|
||||
noRefresh={noRefresh}
|
||||
accountId={accountId}
|
||||
>
|
||||
<WrappedBundle component={Component} componentParams={componentParams} />
|
||||
</DeckColumn>
|
||||
</SortableItem>
|
||||
|
@ -182,9 +182,7 @@ class SlideFirstPost extends React.PureComponent {
|
||||
<Text size='large' className={_s.pb10}>Now let's make your very first Gab post! Please introduce yourself to the Gab community. How did you hear about Gab? What are you interested in?</Text>
|
||||
<br />
|
||||
|
||||
<Divider />
|
||||
|
||||
<div className={[_s.d, _s.mt15, _s.boxShadowBlock, _s.radiusSmall].join(' ')}>
|
||||
<div className={[_s.d, _s.boxShadowBlock, _s.overflowHidden, _s.radiusSmall].join(' ')}>
|
||||
<ComposeFormContainer
|
||||
formLocation='introduction'
|
||||
groupId={GAB_COM_INTRODUCE_YOURSELF_GROUP_ID}
|
||||
|
@ -141,7 +141,9 @@ class ChatMessageItem extends ImmutablePureComponent {
|
||||
<div className={[_s.d, _s.w100PC, _s.pb15].join(' ')}>
|
||||
|
||||
<div className={messageContainerClasses}>
|
||||
<Avatar account={chatMessage.get('account')} size={38} />
|
||||
<NavLink to={`/${chatMessage.getIn(['account', 'username'])}`}>
|
||||
<Avatar account={chatMessage.get('account')} size={38} />
|
||||
</NavLink>
|
||||
<div className={messageInnerContainerClasses}>
|
||||
<div className={[_s.py5, _s.dangerousContent, _s.cPrimary].join(' ')} dangerouslySetInnerHTML={content} />
|
||||
</div>
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
import { me } from '../initial_state'
|
||||
import {
|
||||
CHAT_CONVERSATION_CREATE_SEARCH_ACCOUNTS_SUCCESS,
|
||||
CLEAR_CHAT_CONVERSATION_CREATE_SEARCH_ACCOUNTS,
|
||||
SET_CHAT_CONVERSATION_SELECTED,
|
||||
} from '../actions/chats'
|
||||
import {
|
||||
@ -29,6 +30,8 @@ export default function chats(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case CHAT_CONVERSATION_CREATE_SEARCH_ACCOUNTS_SUCCESS:
|
||||
return state.set('createChatConversationSuggestionIds', ImmutableList(action.accounts.map((item) => item.id)))
|
||||
case CLEAR_CHAT_CONVERSATION_CREATE_SEARCH_ACCOUNTS:
|
||||
return state.set('createChatConversationSuggestionIds', ImmutableList())
|
||||
case SET_CHAT_CONVERSATION_SELECTED:
|
||||
return state.set('selectedChatConversationId', action.chatConversationId)
|
||||
case CHAT_CONVERSATION_REQUESTED_COUNT_FETCH_SUCCESS:
|
||||
|
@ -39,6 +39,7 @@ import {
|
||||
COMPOSE_EXPIRES_AT_CHANGE,
|
||||
COMPOSE_RICH_TEXT_EDITOR_CONTROLS_VISIBILITY,
|
||||
COMPOSE_CLEAR,
|
||||
COMPOSE_GROUP_SET,
|
||||
} from '../actions/compose';
|
||||
import { TIMELINE_DELETE } from '../actions/timelines';
|
||||
import { STORE_HYDRATE } from '../actions/store';
|
||||
@ -62,6 +63,7 @@ const initialState = ImmutableMap({
|
||||
preselectDate: null,
|
||||
in_reply_to: null,
|
||||
quote_of_id: null,
|
||||
group_id: null,
|
||||
is_composing: false,
|
||||
is_submitting: false,
|
||||
is_changing_upload: false,
|
||||
@ -119,6 +121,7 @@ function clearAll(state) {
|
||||
map.set('expires_at', null);
|
||||
map.set('rte_controls_visible', false);
|
||||
map.set('gif', false);
|
||||
map.set('group_id', null);
|
||||
});
|
||||
};
|
||||
|
||||
@ -305,6 +308,7 @@ export default function compose(state = initialState, action) {
|
||||
map.set('id', null);
|
||||
map.set('quote_of_id', null);
|
||||
map.set('in_reply_to', null);
|
||||
map.set('group_id', null);
|
||||
map.set('text', '');
|
||||
map.set('spoiler', false);
|
||||
map.set('spoiler_text', '');
|
||||
@ -414,6 +418,8 @@ export default function compose(state = initialState, action) {
|
||||
return state.withMutations(map => {
|
||||
map.set('rte_controls_visible', action.open || !state.get('rte_controls_visible'));
|
||||
});
|
||||
case COMPOSE_GROUP_SET:
|
||||
return state.set('group_id', action.groupId);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
@ -1,11 +1,17 @@
|
||||
import {
|
||||
DECK_CONNECT,
|
||||
DECK_DISCONNECT,
|
||||
DECK_SEARCH_USERS_CLEAR,
|
||||
DECK_SEARCH_USERS_SUCCESS,
|
||||
} from '../actions/deck'
|
||||
import { Map as ImmutableMap } from 'immutable'
|
||||
import {
|
||||
Map as ImmutableMap,
|
||||
List as ImmutableList,
|
||||
} from 'immutable'
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
connected: false,
|
||||
accountSuggestions: ImmutableList(),
|
||||
})
|
||||
|
||||
export default function deck(state = initialState, action) {
|
||||
@ -14,6 +20,10 @@ export default function deck(state = initialState, action) {
|
||||
return state.set('connected', true)
|
||||
case DECK_DISCONNECT:
|
||||
return state.set('connected', false)
|
||||
case DECK_SEARCH_USERS_SUCCESS:
|
||||
return state.set('accountSuggestions', ImmutableList(action.accounts.map((item) => item.id)))
|
||||
case DECK_SEARCH_USERS_CLEAR:
|
||||
return state.set('accountSuggestions', ImmutableList())
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user