Progress, Deck done

This commit is contained in:
mgabdev 2020-12-16 17:29:06 -05:00
parent 8f94ffad9c
commit 04053c0e31
20 changed files with 473 additions and 93 deletions

View File

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

View File

@ -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,
})
/**
*
*/

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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