Chats: typescript
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
ae396544a7
commit
b5ae9adf63
|
@ -1,61 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import { injectIntl, defineMessages } from 'react-intl';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import Toggle from 'react-toggle';
|
|
||||||
|
|
||||||
import { changeSetting, getSettings } from 'soapbox/actions/settings';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
switchOn: { id: 'chats.audio_toggle_on', defaultMessage: 'Audio notification on' },
|
|
||||||
switchOff: { id: 'chats.audio_toggle_off', defaultMessage: 'Audio notification off' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
return {
|
|
||||||
checked: getSettings(state).getIn(['chats', 'sound'], false),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
toggleAudio(setting) {
|
|
||||||
dispatch(changeSetting(['chats', 'sound'], setting));
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
|
||||||
@injectIntl
|
|
||||||
class AudioToggle extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
checked: PropTypes.bool.isRequired,
|
|
||||||
toggleAudio: PropTypes.func,
|
|
||||||
showLabel: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleToggleAudio = () => {
|
|
||||||
this.props.toggleAudio(!this.props.checked);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { intl, checked, showLabel } = this.props;
|
|
||||||
const id = 'chats-audio-toggle';
|
|
||||||
const label = intl.formatMessage(checked ? messages.switchOff : messages.switchOn);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='audio-toggle react-toggle--mini'>
|
|
||||||
<div className='setting-toggle' aria-label={label}>
|
|
||||||
<Toggle
|
|
||||||
id={id}
|
|
||||||
checked={checked}
|
|
||||||
onChange={this.handleToggleAudio}
|
|
||||||
onKeyDown={this.onKeyDown}
|
|
||||||
/>
|
|
||||||
{showLabel && (<label htmlFor={id} className='setting-toggle__label'>{label}</label>)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import Toggle from 'react-toggle';
|
||||||
|
|
||||||
|
import { changeSetting, getSettings } from 'soapbox/actions/settings';
|
||||||
|
import { useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
switchOn: { id: 'chats.audio_toggle_on', defaultMessage: 'Audio notification on' },
|
||||||
|
switchOff: { id: 'chats.audio_toggle_off', defaultMessage: 'Audio notification off' },
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IAudioToggle {
|
||||||
|
showLabel?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const AudioToggle: React.FC<IAudioToggle> = ({ showLabel }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const checked = useAppSelector(state => !!getSettings(state).getIn(['chats', 'sound']));
|
||||||
|
|
||||||
|
const handleToggleAudio = () => {
|
||||||
|
dispatch(changeSetting(['chats', 'sound'], !checked));
|
||||||
|
};
|
||||||
|
|
||||||
|
const id = 'chats-audio-toggle';
|
||||||
|
const label = intl.formatMessage(checked ? messages.switchOff : messages.switchOn);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='audio-toggle react-toggle--mini'>
|
||||||
|
<div className='setting-toggle' aria-label={label}>
|
||||||
|
<Toggle
|
||||||
|
id={id}
|
||||||
|
checked={checked}
|
||||||
|
onChange={handleToggleAudio}
|
||||||
|
// onKeyDown={this.onKeyDown}
|
||||||
|
/>
|
||||||
|
{showLabel && (<label htmlFor={id} className='setting-toggle__label'>{label}</label>)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AudioToggle;
|
|
@ -1,87 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
import Icon from 'soapbox/components/icon';
|
|
||||||
import emojify from 'soapbox/features/emoji/emoji';
|
|
||||||
import { makeGetChat } from 'soapbox/selectors';
|
|
||||||
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
|
||||||
|
|
||||||
import Avatar from '../../../components/avatar';
|
|
||||||
import DisplayName from '../../../components/display_name';
|
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
|
||||||
const getChat = makeGetChat();
|
|
||||||
|
|
||||||
const mapStateToProps = (state, { chatId }) => {
|
|
||||||
const chat = state.getIn(['chats', 'items', chatId]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
chat: chat ? getChat(state, chat.toJS()) : undefined,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return mapStateToProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default @connect(makeMapStateToProps)
|
|
||||||
class Chat extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
chatId: PropTypes.string.isRequired,
|
|
||||||
chat: ImmutablePropTypes.map,
|
|
||||||
onClick: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleClick = () => {
|
|
||||||
this.props.onClick(this.props.chat);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { chat } = this.props;
|
|
||||||
if (!chat) return null;
|
|
||||||
const account = chat.get('account');
|
|
||||||
const unreadCount = chat.get('unread');
|
|
||||||
const content = chat.getIn(['last_message', 'content']);
|
|
||||||
const attachment = chat.getIn(['last_message', 'attachment']);
|
|
||||||
const image = attachment && attachment.getIn(['pleroma', 'mime_type'], '').startsWith('image/');
|
|
||||||
const parsedContent = content ? emojify(content) : '';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='account'>
|
|
||||||
<button className='floating-link' onClick={this.handleClick} />
|
|
||||||
<div className='account__wrapper'>
|
|
||||||
<div key={account.get('id')} className='account__display-name'>
|
|
||||||
<div className='account__avatar-wrapper'>
|
|
||||||
<Avatar account={account} size={36} />
|
|
||||||
</div>
|
|
||||||
<DisplayName account={account} />
|
|
||||||
{attachment && (
|
|
||||||
<Icon
|
|
||||||
className='chat__attachment-icon'
|
|
||||||
src={image ? require('@tabler/icons/icons/photo.svg') : require('@tabler/icons/icons/paperclip.svg')}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{content ? (
|
|
||||||
<span
|
|
||||||
className='chat__last-message'
|
|
||||||
dangerouslySetInnerHTML={{ __html: parsedContent }}
|
|
||||||
/>
|
|
||||||
) : attachment && (
|
|
||||||
<span
|
|
||||||
className='chat__last-message attachment'
|
|
||||||
>
|
|
||||||
{image ? <FormattedMessage id='chats.attachment_image' defaultMessage='Image' /> : <FormattedMessage id='chats.attachment' defaultMessage='Attachment' />}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
{unreadCount > 0 && <i className='icon-with-badge__badge'>{shortNumberFormat(unreadCount)}</i>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import Avatar from 'soapbox/components/avatar';
|
||||||
|
import DisplayName from 'soapbox/components/display_name';
|
||||||
|
import Icon from 'soapbox/components/icon';
|
||||||
|
import emojify from 'soapbox/features/emoji/emoji';
|
||||||
|
import { useAppSelector } from 'soapbox/hooks';
|
||||||
|
import { makeGetChat } from 'soapbox/selectors';
|
||||||
|
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
||||||
|
|
||||||
|
import type { Account as AccountEntity, Chat as ChatEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
|
const getChat = makeGetChat();
|
||||||
|
|
||||||
|
interface IChat {
|
||||||
|
chatId: string,
|
||||||
|
onClick: (chat: any) => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
const Chat: React.FC<IChat> = ({ chatId, onClick }) => {
|
||||||
|
const chat = useAppSelector((state) => {
|
||||||
|
const chat = state.chats.getIn(['items', chatId]);
|
||||||
|
return chat ? getChat(state, (chat as any).toJS()) : undefined;
|
||||||
|
}) as ChatEntity;
|
||||||
|
|
||||||
|
const account = chat.account as AccountEntity;
|
||||||
|
if (!chat || !account) return null;
|
||||||
|
const unreadCount = chat.unread;
|
||||||
|
const content = chat.getIn(['last_message', 'content']);
|
||||||
|
const attachment = chat.getIn(['last_message', 'attachment']);
|
||||||
|
const image = attachment && (attachment as any).getIn(['pleroma', 'mime_type'], '').startsWith('image/');
|
||||||
|
const parsedContent = content ? emojify(content) : '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='account'>
|
||||||
|
<button className='floating-link' onClick={() => onClick(chat)} />
|
||||||
|
<div className='account__wrapper'>
|
||||||
|
<div key={account.id} className='account__display-name'>
|
||||||
|
<div className='account__avatar-wrapper'>
|
||||||
|
<Avatar account={account} size={36} />
|
||||||
|
</div>
|
||||||
|
<DisplayName account={account} />
|
||||||
|
{attachment && (
|
||||||
|
<Icon
|
||||||
|
className='chat__attachment-icon'
|
||||||
|
src={image ? require('@tabler/icons/icons/photo.svg') : require('@tabler/icons/icons/paperclip.svg')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{content ? (
|
||||||
|
<span
|
||||||
|
className='chat__last-message'
|
||||||
|
dangerouslySetInnerHTML={{ __html: parsedContent }}
|
||||||
|
/>
|
||||||
|
) : attachment && (
|
||||||
|
<span
|
||||||
|
className='chat__last-message attachment'
|
||||||
|
>
|
||||||
|
{image ? <FormattedMessage id='chats.attachment_image' defaultMessage='Image' /> : <FormattedMessage id='chats.attachment' defaultMessage='Attachment' />}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{unreadCount > 0 && <i className='icon-with-badge__badge'>{shortNumberFormat(unreadCount)}</i>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Chat;
|
|
@ -1,99 +0,0 @@
|
||||||
import { debounce } from 'lodash';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
|
|
||||||
import { expandChats } from 'soapbox/actions/chats';
|
|
||||||
import ScrollableList from 'soapbox/components/scrollable_list';
|
|
||||||
import PlaceholderChat from 'soapbox/features/placeholder/components/placeholder_chat';
|
|
||||||
|
|
||||||
import Chat from './chat';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
emptyMessage: { id: 'chat_panels.main_window.empty', defaultMessage: 'No chats found. To start a chat, visit a user\'s profile' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const getSortedChatIds = chats => (
|
|
||||||
chats
|
|
||||||
.toList()
|
|
||||||
.sort(chatDateComparator)
|
|
||||||
.map(chat => chat.get('id'))
|
|
||||||
);
|
|
||||||
|
|
||||||
const chatDateComparator = (chatA, chatB) => {
|
|
||||||
// Sort most recently updated chats at the top
|
|
||||||
const a = new Date(chatA.get('updated_at'));
|
|
||||||
const b = new Date(chatB.get('updated_at'));
|
|
||||||
|
|
||||||
if (a === b) return 0;
|
|
||||||
if (a > b) return -1;
|
|
||||||
if (a < b) return 1;
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
const sortedChatIdsSelector = createSelector(
|
|
||||||
[getSortedChatIds],
|
|
||||||
chats => chats,
|
|
||||||
);
|
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
chatIds: sortedChatIdsSelector(state.getIn(['chats', 'items'])),
|
|
||||||
hasMore: !!state.getIn(['chats', 'next']),
|
|
||||||
isLoading: state.getIn(['chats', 'loading']),
|
|
||||||
});
|
|
||||||
|
|
||||||
return mapStateToProps;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default @connect(makeMapStateToProps)
|
|
||||||
@injectIntl
|
|
||||||
class ChatList extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
dispatch: PropTypes.func.isRequired,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
chatIds: ImmutablePropTypes.list,
|
|
||||||
onClickChat: PropTypes.func,
|
|
||||||
onRefresh: PropTypes.func,
|
|
||||||
hasMore: PropTypes.func,
|
|
||||||
isLoading: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleLoadMore = debounce(() => {
|
|
||||||
this.props.dispatch(expandChats());
|
|
||||||
}, 300, { leading: true });
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { intl, chatIds, hasMore, isLoading } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollableList
|
|
||||||
className='chat-list'
|
|
||||||
scrollKey='awaiting-approval'
|
|
||||||
emptyMessage={intl.formatMessage(messages.emptyMessage)}
|
|
||||||
hasMore={hasMore}
|
|
||||||
isLoading={isLoading}
|
|
||||||
showLoading={isLoading && chatIds.size === 0}
|
|
||||||
onLoadMore={this.handleLoadMore}
|
|
||||||
onRefresh={this.props.onRefresh}
|
|
||||||
placeholderComponent={PlaceholderChat}
|
|
||||||
placeholderCount={20}
|
|
||||||
>
|
|
||||||
{chatIds.map(chatId => (
|
|
||||||
<div key={chatId} className='chat-list-item'>
|
|
||||||
<Chat
|
|
||||||
chatId={chatId}
|
|
||||||
onClick={this.props.onClickChat}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</ScrollableList>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
import { Map as ImmutableMap } from 'immutable';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
import { expandChats } from 'soapbox/actions/chats';
|
||||||
|
import ScrollableList from 'soapbox/components/scrollable_list';
|
||||||
|
import PlaceholderChat from 'soapbox/features/placeholder/components/placeholder_chat';
|
||||||
|
import { useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
import Chat from './chat';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
emptyMessage: { id: 'chat_panels.main_window.empty', defaultMessage: 'No chats found. To start a chat, visit a user\'s profile' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleLoadMore = debounce((dispatch) => {
|
||||||
|
dispatch(expandChats());
|
||||||
|
}, 300, { leading: true });
|
||||||
|
|
||||||
|
const getSortedChatIds = (chats: ImmutableMap<string, any>) => (
|
||||||
|
chats
|
||||||
|
.toList()
|
||||||
|
.sort(chatDateComparator)
|
||||||
|
.map(chat => chat.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
const chatDateComparator = (chatA: { updated_at: string }, chatB: { updated_at: string }) => {
|
||||||
|
// Sort most recently updated chats at the top
|
||||||
|
const a = new Date(chatA.updated_at);
|
||||||
|
const b = new Date(chatB.updated_at);
|
||||||
|
|
||||||
|
if (a === b) return 0;
|
||||||
|
if (a > b) return -1;
|
||||||
|
if (a < b) return 1;
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sortedChatIdsSelector = createSelector(
|
||||||
|
[getSortedChatIds],
|
||||||
|
chats => chats,
|
||||||
|
);
|
||||||
|
|
||||||
|
interface IChatList {
|
||||||
|
onClickChat: (chat: any) => void,
|
||||||
|
onRefresh: () => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChatList: React.FC<IChatList> = ({ onClickChat, onRefresh }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
const chatIds = useAppSelector(state => sortedChatIdsSelector(state.chats.get('items')));
|
||||||
|
const hasMore = useAppSelector(state => !!state.chats.get('next'));
|
||||||
|
const isLoading = useAppSelector(state => state.chats.get('isLoading'));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollableList
|
||||||
|
className='chat-list'
|
||||||
|
scrollKey='awaiting-approval'
|
||||||
|
emptyMessage={intl.formatMessage(messages.emptyMessage)}
|
||||||
|
hasMore={hasMore}
|
||||||
|
isLoading={isLoading}
|
||||||
|
showLoading={isLoading && chatIds.size === 0}
|
||||||
|
onLoadMore={() => handleLoadMore(dispatch)}
|
||||||
|
onRefresh={onRefresh}
|
||||||
|
placeholderComponent={PlaceholderChat}
|
||||||
|
placeholderCount={20}
|
||||||
|
>
|
||||||
|
{chatIds.map((chatId: string) => (
|
||||||
|
<div key={chatId} className='chat-list-item'>
|
||||||
|
<Chat
|
||||||
|
chatId={chatId}
|
||||||
|
onClick={onClickChat}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</ScrollableList>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChatList;
|
|
@ -1,66 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { withRouter } from 'react-router-dom';
|
|
||||||
|
|
||||||
import { fetchChats, launchChat } from 'soapbox/actions/chats';
|
|
||||||
import AccountSearch from 'soapbox/components/account_search';
|
|
||||||
import AudioToggle from 'soapbox/features/chats/components/audio_toggle';
|
|
||||||
|
|
||||||
import { Column } from '../../components/ui';
|
|
||||||
|
|
||||||
import ChatList from './components/chat_list';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
title: { id: 'column.chats', defaultMessage: 'Chats' },
|
|
||||||
searchPlaceholder: { id: 'chats.search_placeholder', defaultMessage: 'Start a chat with…' },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default @connect()
|
|
||||||
@injectIntl
|
|
||||||
@withRouter
|
|
||||||
class ChatIndex extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
dispatch: PropTypes.func.isRequired,
|
|
||||||
history: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSuggestion = accountId => {
|
|
||||||
this.props.dispatch(launchChat(accountId, this.props.history, true));
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClickChat = (chat) => {
|
|
||||||
this.props.history.push(`/chats/${chat.get('id')}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
handleRefresh = () => {
|
|
||||||
const { dispatch } = this.props;
|
|
||||||
return dispatch(fetchChats());
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { intl } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Column label={intl.formatMessage(messages.title)}>
|
|
||||||
<div className='column__switch'>
|
|
||||||
<AudioToggle />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<AccountSearch
|
|
||||||
placeholder={intl.formatMessage(messages.searchPlaceholder)}
|
|
||||||
onSelected={this.handleSuggestion}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ChatList
|
|
||||||
onClickChat={this.handleClickChat}
|
|
||||||
onRefresh={this.handleRefresh}
|
|
||||||
/>
|
|
||||||
</Column>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { fetchChats, launchChat } from 'soapbox/actions/chats';
|
||||||
|
import AccountSearch from 'soapbox/components/account_search';
|
||||||
|
import AudioToggle from 'soapbox/features/chats/components/audio_toggle';
|
||||||
|
|
||||||
|
import { Column } from '../../components/ui';
|
||||||
|
|
||||||
|
import ChatList from './components/chat_list';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
title: { id: 'column.chats', defaultMessage: 'Chats' },
|
||||||
|
searchPlaceholder: { id: 'chats.search_placeholder', defaultMessage: 'Start a chat with…' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const ChatIndex: React.FC = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const handleSuggestion = (accountId: string) => {
|
||||||
|
dispatch(launchChat(accountId, history, true));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClickChat = (chat: { id: string }) => {
|
||||||
|
history.push(`/chats/${chat.id}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
return dispatch(fetchChats());
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column label={intl.formatMessage(messages.title)}>
|
||||||
|
<div className='column__switch'>
|
||||||
|
<AudioToggle />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AccountSearch
|
||||||
|
placeholder={intl.formatMessage(messages.searchPlaceholder)}
|
||||||
|
onSelected={handleSuggestion}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ChatList
|
||||||
|
onClickChat={handleClickChat}
|
||||||
|
onRefresh={handleRefresh}
|
||||||
|
/>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ChatIndex;
|
|
@ -26,7 +26,7 @@ const mapDispatchToProps = dispatch => ({
|
||||||
},
|
},
|
||||||
|
|
||||||
onOpenModal: media => {
|
onOpenModal: media => {
|
||||||
dispatch(openModal('MEDIA', { media: ImmutableList.of(media), index: 0, onClose: console.log }));
|
dispatch(openModal('MEDIA', { media: ImmutableList.of(media), index: 0 }));
|
||||||
},
|
},
|
||||||
|
|
||||||
onSubmit(router) {
|
onSubmit(router) {
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { Map as ImmutableMap, Record as ImmutableRecord, fromJS } from 'immutable';
|
||||||
|
|
||||||
|
import type { ReducerAccount } from 'soapbox/reducers/accounts';
|
||||||
|
import type { Account, EmbeddedEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
|
export const ChatRecord = ImmutableRecord({
|
||||||
|
account: null as EmbeddedEntity<Account | ReducerAccount>,
|
||||||
|
id: '',
|
||||||
|
unread: 0,
|
||||||
|
last_message: '' as string || null,
|
||||||
|
updated_at: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const normalizeChat = (chat: Record<string, any>) => {
|
||||||
|
return ChatRecord(
|
||||||
|
ImmutableMap(fromJS(chat)),
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,29 @@
|
||||||
|
import {
|
||||||
|
List as ImmutableList,
|
||||||
|
Map as ImmutableMap,
|
||||||
|
Record as ImmutableRecord,
|
||||||
|
fromJS,
|
||||||
|
} from 'immutable';
|
||||||
|
|
||||||
|
import type { Attachment, Card, Emoji } from 'soapbox/types/entities';
|
||||||
|
|
||||||
|
export const ChatMessageRecord = ImmutableRecord({
|
||||||
|
account_id: '',
|
||||||
|
attachment: null as Attachment | null,
|
||||||
|
card: null as Card | null,
|
||||||
|
chat_id: '',
|
||||||
|
content: '',
|
||||||
|
created_at: new Date(),
|
||||||
|
emojis: ImmutableList<Emoji>(),
|
||||||
|
id: '',
|
||||||
|
unread: false,
|
||||||
|
|
||||||
|
deleting: false,
|
||||||
|
pending: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const normalizeChatMessage = (chatMessage: Record<string, any>) => {
|
||||||
|
return ChatMessageRecord(
|
||||||
|
ImmutableMap(fromJS(chatMessage)),
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,6 +1,8 @@
|
||||||
export { AccountRecord, FieldRecord, normalizeAccount } from './account';
|
export { AccountRecord, FieldRecord, normalizeAccount } from './account';
|
||||||
export { AttachmentRecord, normalizeAttachment } from './attachment';
|
export { AttachmentRecord, normalizeAttachment } from './attachment';
|
||||||
export { CardRecord, normalizeCard } from './card';
|
export { CardRecord, normalizeCard } from './card';
|
||||||
|
export { ChatRecord, normalizeChat } from './chat';
|
||||||
|
export { ChatMessageRecord, normalizeChatMessage } from './chat_message';
|
||||||
export { EmojiRecord, normalizeEmoji } from './emoji';
|
export { EmojiRecord, normalizeEmoji } from './emoji';
|
||||||
export { InstanceRecord, normalizeInstance } from './instance';
|
export { InstanceRecord, normalizeInstance } from './instance';
|
||||||
export { MentionRecord, normalizeMention } from './mention';
|
export { MentionRecord, normalizeMention } from './mention';
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable';
|
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||||
|
import { AnyAction } from 'redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CHATS_FETCH_SUCCESS,
|
CHATS_FETCH_SUCCESS,
|
||||||
|
@ -10,41 +11,46 @@ import {
|
||||||
} from 'soapbox/actions/chats';
|
} from 'soapbox/actions/chats';
|
||||||
import { STREAMING_CHAT_UPDATE } from 'soapbox/actions/streaming';
|
import { STREAMING_CHAT_UPDATE } from 'soapbox/actions/streaming';
|
||||||
|
|
||||||
const initialState = ImmutableMap();
|
type APIEntity = Record<string, any>;
|
||||||
|
type APIEntities = Array<APIEntity>;
|
||||||
|
|
||||||
const idComparator = (a, b) => {
|
type State = ImmutableMap<string, ImmutableOrderedSet<string>>;
|
||||||
|
|
||||||
|
const initialState: State = ImmutableMap();
|
||||||
|
|
||||||
|
const idComparator = (a: string, b: string) => {
|
||||||
if (a < b) return -1;
|
if (a < b) return -1;
|
||||||
if (a > b) return 1;
|
if (a > b) return 1;
|
||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateList = (state, chatId, messageIds) => {
|
const updateList = (state: State, chatId: string, messageIds: string[]) => {
|
||||||
const ids = state.get(chatId, ImmutableOrderedSet());
|
const ids = state.get(chatId, ImmutableOrderedSet());
|
||||||
const newIds = ids.union(messageIds).sort(idComparator);
|
const newIds = (ids.union(messageIds) as ImmutableOrderedSet<string>).sort(idComparator);
|
||||||
return state.set(chatId, newIds);
|
return state.set(chatId, newIds);
|
||||||
};
|
};
|
||||||
|
|
||||||
const importMessage = (state, chatMessage) => {
|
const importMessage = (state: State, chatMessage: APIEntity) => {
|
||||||
return updateList(state, chatMessage.chat_id, [chatMessage.id]);
|
return updateList(state, chatMessage.chat_id, [chatMessage.id]);
|
||||||
};
|
};
|
||||||
|
|
||||||
const importMessages = (state, chatMessages) => (
|
const importMessages = (state: State, chatMessages: APIEntities) => (
|
||||||
state.withMutations(map =>
|
state.withMutations(map =>
|
||||||
chatMessages.forEach(chatMessage =>
|
chatMessages.forEach(chatMessage =>
|
||||||
importMessage(map, chatMessage)))
|
importMessage(map, chatMessage)))
|
||||||
);
|
);
|
||||||
|
|
||||||
const importLastMessages = (state, chats) =>
|
const importLastMessages = (state: State, chats: APIEntities) =>
|
||||||
state.withMutations(mutable =>
|
state.withMutations(mutable =>
|
||||||
chats.forEach(chat => {
|
chats.forEach(chat => {
|
||||||
if (chat.last_message) importMessage(mutable, chat.last_message);
|
if (chat.last_message) importMessage(mutable, chat.last_message);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const replaceMessage = (state, chatId, oldId, newId) => {
|
const replaceMessage = (state: State, chatId: string, oldId: string, newId: string) => {
|
||||||
return state.update(chatId, chat => chat.delete(oldId).add(newId).sort(idComparator));
|
return state.update(chatId, chat => chat!.delete(oldId).add(newId).sort(idComparator));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function chatMessageLists(state = initialState, action) {
|
export default function chatMessageLists(state = initialState, action: AnyAction) {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case CHAT_MESSAGE_SEND_REQUEST:
|
case CHAT_MESSAGE_SEND_REQUEST:
|
||||||
return updateList(state, action.chatId, [action.uuid]);
|
return updateList(state, action.chatId, [action.uuid]);
|
||||||
|
@ -58,11 +64,11 @@ export default function chatMessageLists(state = initialState, action) {
|
||||||
else
|
else
|
||||||
return state;
|
return state;
|
||||||
case CHAT_MESSAGES_FETCH_SUCCESS:
|
case CHAT_MESSAGES_FETCH_SUCCESS:
|
||||||
return updateList(state, action.chatId, action.chatMessages.map(chat => chat.id));
|
return updateList(state, action.chatId, action.chatMessages.map((chat: APIEntity) => chat.id));
|
||||||
case CHAT_MESSAGE_SEND_SUCCESS:
|
case CHAT_MESSAGE_SEND_SUCCESS:
|
||||||
return replaceMessage(state, action.chatId, action.uuid, action.chatMessage.id);
|
return replaceMessage(state, action.chatId, action.uuid, action.chatMessage.id);
|
||||||
case CHAT_MESSAGE_DELETE_SUCCESS:
|
case CHAT_MESSAGE_DELETE_SUCCESS:
|
||||||
return state.update(action.chatId, chat => chat.delete(action.messageId));
|
return state.update(action.chatId, chat => chat!.delete(action.messageId));
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||||
|
import { AnyAction } from 'redux';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CHATS_FETCH_SUCCESS,
|
CHATS_FETCH_SUCCESS,
|
||||||
|
@ -10,25 +11,32 @@ import {
|
||||||
CHAT_MESSAGE_DELETE_SUCCESS,
|
CHAT_MESSAGE_DELETE_SUCCESS,
|
||||||
} from 'soapbox/actions/chats';
|
} from 'soapbox/actions/chats';
|
||||||
import { STREAMING_CHAT_UPDATE } from 'soapbox/actions/streaming';
|
import { STREAMING_CHAT_UPDATE } from 'soapbox/actions/streaming';
|
||||||
|
import { normalizeChatMessage } from 'soapbox/normalizers';
|
||||||
|
|
||||||
const initialState = ImmutableMap();
|
type ChatMessageRecord = ReturnType<typeof normalizeChatMessage>;
|
||||||
|
type APIEntity = Record<string, any>;
|
||||||
|
type APIEntities = Array<APIEntity>;
|
||||||
|
|
||||||
const importMessage = (state, message) => {
|
type State = ImmutableMap<string, ChatMessageRecord>;
|
||||||
return state.set(message.get('id'), message);
|
|
||||||
|
const importMessage = (state: State, message: APIEntity) => {
|
||||||
|
return state.set(message.id, normalizeChatMessage(message));
|
||||||
};
|
};
|
||||||
|
|
||||||
const importMessages = (state, messages) =>
|
const importMessages = (state: State, messages: APIEntities) =>
|
||||||
state.withMutations(mutable =>
|
state.withMutations(mutable =>
|
||||||
messages.forEach(message => importMessage(mutable, message)));
|
messages.forEach(message => importMessage(mutable, message)));
|
||||||
|
|
||||||
const importLastMessages = (state, chats) =>
|
const importLastMessages = (state: State, chats: APIEntities) =>
|
||||||
state.withMutations(mutable =>
|
state.withMutations(mutable =>
|
||||||
chats.forEach(chat => {
|
chats.forEach(chat => {
|
||||||
if (chat.get('last_message'))
|
if (chat.last_message)
|
||||||
importMessage(mutable, chat.get('last_message'));
|
importMessage(mutable, chat.last_message);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export default function chatMessages(state = initialState, action) {
|
const initialState: State = ImmutableMap();
|
||||||
|
|
||||||
|
export default function chatMessages(state = initialState, action: AnyAction) {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case CHAT_MESSAGE_SEND_REQUEST:
|
case CHAT_MESSAGE_SEND_REQUEST:
|
||||||
return importMessage(state, fromJS({
|
return importMessage(state, fromJS({
|
||||||
|
@ -41,16 +49,16 @@ export default function chatMessages(state = initialState, action) {
|
||||||
}));
|
}));
|
||||||
case CHATS_FETCH_SUCCESS:
|
case CHATS_FETCH_SUCCESS:
|
||||||
case CHATS_EXPAND_SUCCESS:
|
case CHATS_EXPAND_SUCCESS:
|
||||||
return importLastMessages(state, fromJS(action.chats));
|
return importLastMessages(state, action.chats);
|
||||||
case CHAT_MESSAGES_FETCH_SUCCESS:
|
case CHAT_MESSAGES_FETCH_SUCCESS:
|
||||||
return importMessages(state, fromJS(action.chatMessages));
|
return importMessages(state, action.chatMessages);
|
||||||
case CHAT_MESSAGE_SEND_SUCCESS:
|
case CHAT_MESSAGE_SEND_SUCCESS:
|
||||||
return importMessage(state, fromJS(action.chatMessage)).delete(action.uuid);
|
return importMessage(state, fromJS(action.chatMessage)).delete(action.uuid);
|
||||||
case STREAMING_CHAT_UPDATE:
|
case STREAMING_CHAT_UPDATE:
|
||||||
return importLastMessages(state, fromJS([action.chat]));
|
return importLastMessages(state, [action.chat]);
|
||||||
case CHAT_MESSAGE_DELETE_REQUEST:
|
case CHAT_MESSAGE_DELETE_REQUEST:
|
||||||
return state.update(action.messageId, chatMessage =>
|
return state.update(action.messageId, chatMessage =>
|
||||||
chatMessage.set('pending', true).set('deleting', true));
|
chatMessage!.set('pending', true).set('deleting', true));
|
||||||
case CHAT_MESSAGE_DELETE_SUCCESS:
|
case CHAT_MESSAGE_DELETE_SUCCESS:
|
||||||
return state.delete(action.messageId);
|
return state.delete(action.messageId);
|
||||||
default:
|
default:
|
|
@ -1,58 +0,0 @@
|
||||||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
|
||||||
|
|
||||||
import {
|
|
||||||
CHATS_FETCH_SUCCESS,
|
|
||||||
CHATS_FETCH_REQUEST,
|
|
||||||
CHATS_EXPAND_SUCCESS,
|
|
||||||
CHATS_EXPAND_REQUEST,
|
|
||||||
CHAT_FETCH_SUCCESS,
|
|
||||||
CHAT_READ_SUCCESS,
|
|
||||||
CHAT_READ_REQUEST,
|
|
||||||
} from 'soapbox/actions/chats';
|
|
||||||
import { STREAMING_CHAT_UPDATE } from 'soapbox/actions/streaming';
|
|
||||||
|
|
||||||
const normalizeChat = (chat, normalOldChat) => {
|
|
||||||
const normalChat = { ...chat };
|
|
||||||
const { account, last_message: lastMessage } = chat;
|
|
||||||
|
|
||||||
if (account) normalChat.account = account.id;
|
|
||||||
if (lastMessage) normalChat.last_message = lastMessage.id;
|
|
||||||
|
|
||||||
return normalChat;
|
|
||||||
};
|
|
||||||
|
|
||||||
const importChat = (state, chat) => state.setIn(['items', chat.id], fromJS(normalizeChat(chat)));
|
|
||||||
|
|
||||||
const importChats = (state, chats, next) =>
|
|
||||||
state.withMutations(mutable => {
|
|
||||||
if (next !== undefined) mutable.set('next', next);
|
|
||||||
chats.forEach(chat => importChat(mutable, chat));
|
|
||||||
mutable.set('loading', false);
|
|
||||||
});
|
|
||||||
|
|
||||||
const initialState = ImmutableMap({
|
|
||||||
next: null,
|
|
||||||
isLoading: false,
|
|
||||||
items: ImmutableMap({}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function chats(state = initialState, action) {
|
|
||||||
switch(action.type) {
|
|
||||||
case CHATS_FETCH_REQUEST:
|
|
||||||
case CHATS_EXPAND_REQUEST:
|
|
||||||
return state.set('loading', true);
|
|
||||||
case CHATS_FETCH_SUCCESS:
|
|
||||||
case CHATS_EXPAND_SUCCESS:
|
|
||||||
return importChats(state, action.chats, action.next);
|
|
||||||
case STREAMING_CHAT_UPDATE:
|
|
||||||
return importChats(state, [action.chat]);
|
|
||||||
case CHAT_FETCH_SUCCESS:
|
|
||||||
return importChats(state, [action.chat]);
|
|
||||||
case CHAT_READ_REQUEST:
|
|
||||||
return state.setIn([action.chatId, 'unread'], 0);
|
|
||||||
case CHAT_READ_SUCCESS:
|
|
||||||
return importChats(state, [action.chat]);
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { Map as ImmutableMap, Record as ImmutableRecord } from 'immutable';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CHATS_FETCH_SUCCESS,
|
||||||
|
CHATS_FETCH_REQUEST,
|
||||||
|
CHATS_EXPAND_SUCCESS,
|
||||||
|
CHATS_EXPAND_REQUEST,
|
||||||
|
CHAT_FETCH_SUCCESS,
|
||||||
|
CHAT_READ_SUCCESS,
|
||||||
|
CHAT_READ_REQUEST,
|
||||||
|
} from 'soapbox/actions/chats';
|
||||||
|
import { STREAMING_CHAT_UPDATE } from 'soapbox/actions/streaming';
|
||||||
|
import { normalizeChat } from 'soapbox/normalizers';
|
||||||
|
import { normalizeId } from 'soapbox/utils/normalizers';
|
||||||
|
|
||||||
|
import type { AnyAction } from 'redux';
|
||||||
|
|
||||||
|
type ChatRecord = ReturnType<typeof normalizeChat>;
|
||||||
|
type APIEntity = Record<string, any>;
|
||||||
|
type APIEntities = Array<APIEntity>;
|
||||||
|
|
||||||
|
export interface ReducerChat extends ChatRecord {
|
||||||
|
account: string | null,
|
||||||
|
last_message: string | null,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReducerRecord = ImmutableRecord({
|
||||||
|
next: null as string | null,
|
||||||
|
isLoading: false,
|
||||||
|
items: ImmutableMap<ReducerChat>({}),
|
||||||
|
});
|
||||||
|
|
||||||
|
type State = ReturnType<typeof ReducerRecord>;
|
||||||
|
|
||||||
|
const minifyChat = (chat: ChatRecord): ReducerChat => {
|
||||||
|
return chat.mergeWith((o, n) => n || o, {
|
||||||
|
account: normalizeId(chat.getIn(['account', 'id'])),
|
||||||
|
last_message: normalizeId(chat.getIn(['last_message', 'id'])),
|
||||||
|
}) as ReducerChat;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fixChat = (chat: APIEntity): ReducerChat => {
|
||||||
|
return normalizeChat(chat).withMutations(chat => {
|
||||||
|
minifyChat(chat);
|
||||||
|
}) as ReducerChat;
|
||||||
|
};
|
||||||
|
|
||||||
|
const importChat = (state: State, chat: APIEntity) => state.setIn(['items', chat.id], fixChat(chat));
|
||||||
|
|
||||||
|
const importChats = (state: State, chats: APIEntities, next?: string) =>
|
||||||
|
state.withMutations(mutable => {
|
||||||
|
if (next !== undefined) mutable.set('next', next);
|
||||||
|
chats.forEach(chat => importChat(mutable, chat));
|
||||||
|
mutable.set('isLoading', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function chats(state: State = ReducerRecord(), action: AnyAction): State {
|
||||||
|
switch(action.type) {
|
||||||
|
case CHATS_FETCH_REQUEST:
|
||||||
|
case CHATS_EXPAND_REQUEST:
|
||||||
|
return state.set('isLoading', true);
|
||||||
|
case CHATS_FETCH_SUCCESS:
|
||||||
|
case CHATS_EXPAND_SUCCESS:
|
||||||
|
return importChats(state, action.chats, action.next);
|
||||||
|
case STREAMING_CHAT_UPDATE:
|
||||||
|
return importChats(state, [action.chat]);
|
||||||
|
case CHAT_FETCH_SUCCESS:
|
||||||
|
return importChats(state, [action.chat]);
|
||||||
|
case CHAT_READ_REQUEST:
|
||||||
|
return state.setIn([action.chatId, 'unread'], 0);
|
||||||
|
case CHAT_READ_SUCCESS:
|
||||||
|
return importChats(state, [action.chat]);
|
||||||
|
default:
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,7 @@ import { validId } from 'soapbox/utils/auth';
|
||||||
import ConfigDB from 'soapbox/utils/config_db';
|
import ConfigDB from 'soapbox/utils/config_db';
|
||||||
import { shouldFilter } from 'soapbox/utils/timelines';
|
import { shouldFilter } from 'soapbox/utils/timelines';
|
||||||
|
|
||||||
|
import type { ReducerChat } from 'soapbox/reducers/chats';
|
||||||
import type { RootState } from 'soapbox/store';
|
import type { RootState } from 'soapbox/store';
|
||||||
import type { Notification } from 'soapbox/types/entities';
|
import type { Notification } from 'soapbox/types/entities';
|
||||||
|
|
||||||
|
@ -241,16 +242,18 @@ type APIChat = { id: string, last_message: string };
|
||||||
export const makeGetChat = () => {
|
export const makeGetChat = () => {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
[
|
[
|
||||||
(state: RootState, { id }: APIChat) => state.chats.getIn(['items', id]),
|
(state: RootState, { id }: APIChat) => state.chats.getIn(['items', id]) as ReducerChat,
|
||||||
(state: RootState, { id }: APIChat) => state.accounts.get(state.chats.getIn(['items', id, 'account'])),
|
(state: RootState, { id }: APIChat) => state.accounts.get(state.chats.getIn(['items', id, 'account'])),
|
||||||
(state: RootState, { last_message }: APIChat) => state.chat_messages.get(last_message),
|
(state: RootState, { last_message }: APIChat) => state.chat_messages.get(last_message),
|
||||||
],
|
],
|
||||||
|
|
||||||
(chat, account, lastMessage: string) => {
|
(chat, account, lastMessage) => {
|
||||||
if (!chat) return null;
|
if (!chat || !account) return null;
|
||||||
|
|
||||||
return chat.withMutations((map: ImmutableMap<string, any>) => {
|
return chat.withMutations((map) => {
|
||||||
|
// @ts-ignore
|
||||||
map.set('account', account);
|
map.set('account', account);
|
||||||
|
// @ts-ignore
|
||||||
map.set('last_message', lastMessage);
|
map.set('last_message', lastMessage);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,6 +2,8 @@ import {
|
||||||
AccountRecord,
|
AccountRecord,
|
||||||
AttachmentRecord,
|
AttachmentRecord,
|
||||||
CardRecord,
|
CardRecord,
|
||||||
|
ChatRecord,
|
||||||
|
ChatMessageRecord,
|
||||||
EmojiRecord,
|
EmojiRecord,
|
||||||
FieldRecord,
|
FieldRecord,
|
||||||
InstanceRecord,
|
InstanceRecord,
|
||||||
|
@ -16,6 +18,8 @@ import type { Record as ImmutableRecord } from 'immutable';
|
||||||
|
|
||||||
type Attachment = ReturnType<typeof AttachmentRecord>;
|
type Attachment = ReturnType<typeof AttachmentRecord>;
|
||||||
type Card = ReturnType<typeof CardRecord>;
|
type Card = ReturnType<typeof CardRecord>;
|
||||||
|
type Chat = ReturnType<typeof ChatRecord>;
|
||||||
|
type ChatMessage = ReturnType<typeof ChatMessageRecord>;
|
||||||
type Emoji = ReturnType<typeof EmojiRecord>;
|
type Emoji = ReturnType<typeof EmojiRecord>;
|
||||||
type Field = ReturnType<typeof FieldRecord>;
|
type Field = ReturnType<typeof FieldRecord>;
|
||||||
type Instance = ReturnType<typeof InstanceRecord>;
|
type Instance = ReturnType<typeof InstanceRecord>;
|
||||||
|
@ -44,6 +48,8 @@ export {
|
||||||
Account,
|
Account,
|
||||||
Attachment,
|
Attachment,
|
||||||
Card,
|
Card,
|
||||||
|
Chat,
|
||||||
|
ChatMessage,
|
||||||
Emoji,
|
Emoji,
|
||||||
Field,
|
Field,
|
||||||
Instance,
|
Instance,
|
||||||
|
|
Loading…
Reference in New Issue