Merge branch 'chats-pagination' into 'develop'

use `/api/v2/pleroma/chats`

See merge request soapbox-pub/soapbox-fe!911
strip-front-mentions
Alex Gleason 2021-12-14 15:13:11 +00:00
commit ff26336f3a
22 zmienionych plików z 201 dodań i 64 usunięć

Wyświetl plik

@ -1,5 +1,6 @@
import api from '../api'; import api, { getLinks } from '../api';
import { getSettings, changeSetting } from 'soapbox/actions/settings'; import { getSettings, changeSetting } from 'soapbox/actions/settings';
import { getFeatures } from 'soapbox/utils/features';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { Map as ImmutableMap } from 'immutable'; import { Map as ImmutableMap } from 'immutable';
@ -7,6 +8,10 @@ export const CHATS_FETCH_REQUEST = 'CHATS_FETCH_REQUEST';
export const CHATS_FETCH_SUCCESS = 'CHATS_FETCH_SUCCESS'; export const CHATS_FETCH_SUCCESS = 'CHATS_FETCH_SUCCESS';
export const CHATS_FETCH_FAIL = 'CHATS_FETCH_FAIL'; export const CHATS_FETCH_FAIL = 'CHATS_FETCH_FAIL';
export const CHATS_EXPAND_REQUEST = 'CHATS_EXPAND_REQUEST';
export const CHATS_EXPAND_SUCCESS = 'CHATS_EXPAND_SUCCESS';
export const CHATS_EXPAND_FAIL = 'CHATS_EXPAND_FAIL';
export const CHAT_MESSAGES_FETCH_REQUEST = 'CHAT_MESSAGES_FETCH_REQUEST'; export const CHAT_MESSAGES_FETCH_REQUEST = 'CHAT_MESSAGES_FETCH_REQUEST';
export const CHAT_MESSAGES_FETCH_SUCCESS = 'CHAT_MESSAGES_FETCH_SUCCESS'; export const CHAT_MESSAGES_FETCH_SUCCESS = 'CHAT_MESSAGES_FETCH_SUCCESS';
export const CHAT_MESSAGES_FETCH_FAIL = 'CHAT_MESSAGES_FETCH_FAIL'; export const CHAT_MESSAGES_FETCH_FAIL = 'CHAT_MESSAGES_FETCH_FAIL';
@ -27,14 +32,61 @@ export const CHAT_MESSAGE_DELETE_REQUEST = 'CHAT_MESSAGE_DELETE_REQUEST';
export const CHAT_MESSAGE_DELETE_SUCCESS = 'CHAT_MESSAGE_DELETE_SUCCESS'; export const CHAT_MESSAGE_DELETE_SUCCESS = 'CHAT_MESSAGE_DELETE_SUCCESS';
export const CHAT_MESSAGE_DELETE_FAIL = 'CHAT_MESSAGE_DELETE_FAIL'; export const CHAT_MESSAGE_DELETE_FAIL = 'CHAT_MESSAGE_DELETE_FAIL';
export function fetchChats() { export function fetchChatsV1() {
return (dispatch, getState) => { return (dispatch, getState) =>
dispatch({ type: CHATS_FETCH_REQUEST }); api(getState).get('/api/v1/pleroma/chats').then((response) => {
return api(getState).get('/api/v1/pleroma/chats').then(({ data }) => { dispatch({ type: CHATS_FETCH_SUCCESS, chats: response.data });
dispatch({ type: CHATS_FETCH_SUCCESS, chats: data });
}).catch(error => { }).catch(error => {
dispatch({ type: CHATS_FETCH_FAIL, error }); dispatch({ type: CHATS_FETCH_FAIL, error });
}); });
}
export function fetchChatsV2() {
return (dispatch, getState) =>
api(getState).get('/api/v2/pleroma/chats').then((response) => {
let next = getLinks(response).refs.find(link => link.rel === 'next');
if (!next && response.data.length) {
next = { uri: `/api/v2/pleroma/chats?max_id=${response.data[response.data.length - 1].id}&offset=0` };
}
dispatch({ type: CHATS_FETCH_SUCCESS, chats: response.data, next: next ? next.uri : null });
}).catch(error => {
dispatch({ type: CHATS_FETCH_FAIL, error });
});
}
export function fetchChats() {
return (dispatch, getState) => {
const state = getState();
const instance = state.get('instance');
const features = getFeatures(instance);
dispatch({ type: CHATS_FETCH_REQUEST });
if (features.chatsV2) {
dispatch(fetchChatsV2());
} else {
dispatch(fetchChatsV1());
}
};
}
export function expandChats() {
return (dispatch, getState) => {
const url = getState().getIn(['chats', 'next']);
if (url === null) {
return;
}
dispatch({ type: CHATS_EXPAND_REQUEST });
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch({ type: CHATS_EXPAND_SUCCESS, chats: response.data, next: next ? next.uri : null });
}).catch(error => {
dispatch({ type: CHATS_EXPAND_FAIL, error });
});
}; };
} }
@ -140,7 +192,7 @@ export function startChat(accountId) {
export function markChatRead(chatId, lastReadId) { export function markChatRead(chatId, lastReadId) {
return (dispatch, getState) => { return (dispatch, getState) => {
const chat = getState().getIn(['chats', chatId]); const chat = getState().getIn(['chats', 'items', chatId]);
if (!lastReadId) lastReadId = chat.get('last_message'); if (!lastReadId) lastReadId = chat.get('last_message');
if (chat.get('unread') < 1) return; if (chat.get('unread') < 1) return;

Wyświetl plik

@ -11,7 +11,7 @@ FaviconService.initFaviconService();
const getNotifTotals = state => { const getNotifTotals = state => {
const notifications = state.getIn(['notifications', 'unread'], 0); const notifications = state.getIn(['notifications', 'unread'], 0);
const chats = state.get('chats').reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0); const chats = state.getIn(['chats', 'items']).reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0);
const reports = state.getIn(['admin', 'openReports']).count(); const reports = state.getIn(['admin', 'openReports']).count();
const approvals = state.getIn(['admin', 'awaitingApproval']).count(); const approvals = state.getIn(['admin', 'awaitingApproval']).count();
return notifications + chats + reports + approvals; return notifications + chats + reports + approvals;

Wyświetl plik

@ -25,7 +25,7 @@ const mapStateToProps = state => {
account, account,
logo: getSoapboxConfig(state).get('logo'), logo: getSoapboxConfig(state).get('logo'),
notificationCount: state.getIn(['notifications', 'unread']), notificationCount: state.getIn(['notifications', 'unread']),
chatsCount: state.get('chats').reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0), chatsCount: state.getIn(['chats', 'items']).reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0),
dashboardCount: reportsCount + approvalCount, dashboardCount: reportsCount + approvalCount,
baseURL: getBaseURL(account), baseURL: getBaseURL(account),
settings: getSettings(state), settings: getSettings(state),

Wyświetl plik

@ -1,6 +1,7 @@
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import classNames from 'classnames';
import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container'; import IntersectionObserverArticleContainer from '../containers/intersection_observer_article_container';
import LoadMore from './load_more'; import LoadMore from './load_more';
import MoreFollows from './more_follows'; import MoreFollows from './more_follows';
@ -45,6 +46,7 @@ class ScrollableList extends PureComponent {
placeholderCount: PropTypes.number, placeholderCount: PropTypes.number,
autoload: PropTypes.bool, autoload: PropTypes.bool,
onRefresh: PropTypes.func, onRefresh: PropTypes.func,
className: PropTypes.string,
}; };
state = { state = {
@ -240,16 +242,22 @@ class ScrollableList extends PureComponent {
} }
renderLoading = () => { renderLoading = () => {
const { prepend, placeholderComponent: Placeholder, placeholderCount } = this.props; const { className, prepend, placeholderComponent: Placeholder, placeholderCount } = this.props;
if (Placeholder && placeholderCount > 0) { if (Placeholder && placeholderCount > 0) {
return Array(placeholderCount).fill().map((_, i) => ( return (
<Placeholder key={i} /> <div className={classNames('slist slist--flex', className)}>
)); <div role='feed' className='item-list'>
{Array(placeholderCount).fill().map((_, i) => (
<Placeholder key={i} />
))}
</div>
</div>
);
} }
return ( return (
<div className='slist slist--flex'> <div className={classNames('slist slist--flex', className)}>
<div role='feed' className='item-list'> <div role='feed' className='item-list'>
{prepend} {prepend}
</div> </div>
@ -262,10 +270,10 @@ class ScrollableList extends PureComponent {
} }
renderEmptyMessage = () => { renderEmptyMessage = () => {
const { prepend, alwaysPrepend, emptyMessage } = this.props; const { className, prepend, alwaysPrepend, emptyMessage } = this.props;
return ( return (
<div className='slist slist--flex' ref={this.setRef}> <div className={classNames('slist slist--flex', className)} ref={this.setRef}>
{alwaysPrepend && prepend} {alwaysPrepend && prepend}
<div className='empty-column-indicator'> <div className='empty-column-indicator'>
@ -276,13 +284,13 @@ class ScrollableList extends PureComponent {
} }
renderFeed = () => { renderFeed = () => {
const { children, scrollKey, isLoading, hasMore, prepend, onLoadMore, onRefresh, placeholderComponent: Placeholder } = this.props; const { className, children, scrollKey, isLoading, hasMore, prepend, onLoadMore, onRefresh, placeholderComponent: Placeholder } = this.props;
const childrenCount = React.Children.count(children); const childrenCount = React.Children.count(children);
const trackScroll = true; //placeholder const trackScroll = true; //placeholder
const loadMore = (hasMore && onLoadMore) ? <LoadMore visible={!isLoading} onClick={this.handleLoadMore} /> : null; const loadMore = (hasMore && onLoadMore) ? <LoadMore visible={!isLoading} onClick={this.handleLoadMore} /> : null;
const feed = ( const feed = (
<div className='slist' ref={this.setRef} onMouseMove={this.handleMouseMove}> <div className={classNames('slist', className)} ref={this.setRef} onMouseMove={this.handleMouseMove}>
<div role='feed' className='item-list'> <div role='feed' className='item-list'>
{prepend} {prepend}

Wyświetl plik

@ -21,7 +21,7 @@ const mapStateToProps = state => {
account: state.getIn(['accounts', me]), account: state.getIn(['accounts', me]),
logo: getSoapboxConfig(state).get('logo'), logo: getSoapboxConfig(state).get('logo'),
notificationCount: state.getIn(['notifications', 'unread']), notificationCount: state.getIn(['notifications', 'unread']),
chatsCount: state.get('chats').reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0), chatsCount: state.getIn(['chats', 'items']).reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0),
dashboardCount: reportsCount + approvalCount, dashboardCount: reportsCount + approvalCount,
features: getFeatures(instance), features: getFeatures(instance),
}; };

Wyświetl plik

@ -17,7 +17,7 @@ import { displayFqn } from 'soapbox/utils/state';
const mapStateToProps = (state, { params }) => { const mapStateToProps = (state, { params }) => {
const getChat = makeGetChat(); const getChat = makeGetChat();
const chat = state.getIn(['chats', params.chatId], ImmutableMap()).toJS(); const chat = state.getIn(['chats', 'items', params.chatId], ImmutableMap()).toJS();
return { return {
me: state.get('me'), me: state.get('me'),

Wyświetl plik

@ -15,7 +15,7 @@ const makeMapStateToProps = () => {
const getChat = makeGetChat(); const getChat = makeGetChat();
const mapStateToProps = (state, { chatId }) => { const mapStateToProps = (state, { chatId }) => {
const chat = state.getIn(['chats', chatId]); const chat = state.getIn(['chats', 'items', chatId]);
return { return {
chat: chat ? getChat(state, chat.toJS()) : undefined, chat: chat ? getChat(state, chat.toJS()) : undefined,

Wyświetl plik

@ -23,7 +23,7 @@ const messages = defineMessages({
const mapStateToProps = (state, { chatId }) => ({ const mapStateToProps = (state, { chatId }) => ({
me: state.get('me'), me: state.get('me'),
chat: state.getIn(['chats', chatId]), chat: state.getIn(['chats', 'items', chatId]),
chatMessageIds: state.getIn(['chat_message_lists', chatId], ImmutableOrderedSet()), chatMessageIds: state.getIn(['chat_message_lists', chatId], ImmutableOrderedSet()),
}); });

Wyświetl plik

@ -2,11 +2,19 @@ import React from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { debounce } from 'lodash';
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'; import Chat from './chat';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
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 => ( const getSortedChatIds = chats => (
chats chats
.toList() .toList()
@ -32,7 +40,9 @@ const makeMapStateToProps = () => {
); );
const mapStateToProps = state => ({ const mapStateToProps = state => ({
chatIds: sortedChatIdsSelector(state.get('chats')), chatIds: sortedChatIdsSelector(state.getIn(['chats', 'items'])),
hasMore: !!state.getIn(['chats', 'next']),
isLoading: state.getIn(['chats', 'loading']),
}); });
return mapStateToProps; return mapStateToProps;
@ -47,28 +57,40 @@ class ChatList extends ImmutablePureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
chatIds: ImmutablePropTypes.list, chatIds: ImmutablePropTypes.list,
onClickChat: PropTypes.func, onClickChat: PropTypes.func,
emptyMessage: PropTypes.node, onRefresh: PropTypes.func,
hasMore: PropTypes.func,
isLoading: PropTypes.bool,
}; };
handleLoadMore = debounce(() => {
this.props.dispatch(expandChats());
}, 300, { leading: true });
render() { render() {
const { chatIds, emptyMessage } = this.props; const { intl, chatIds, hasMore, isLoading } = this.props;
return ( return (
<div className='chat-list'> <ScrollableList
<div className='chat-list__content'> className='chat-list'
{chatIds.count() === 0 && scrollKey='awaiting-approval'
<div className='empty-column-indicator'>{emptyMessage}</div> emptyMessage={intl.formatMessage(messages.emptyMessage)}
} hasMore={hasMore}
{chatIds.map(chatId => ( isLoading={isLoading}
<div key={chatId} className='chat-list-item'> showLoading={isLoading && chatIds.size === 0}
<Chat onLoadMore={this.handleLoadMore}
chatId={chatId} onRefresh={this.props.onRefresh}
onClick={this.props.onClickChat} placeholderComponent={PlaceholderChat}
/> placeholderCount={20}
</div> >
))} {chatIds.map(chatId => (
</div> <div key={chatId} className='chat-list-item'>
</div> <Chat
chatId={chatId}
onClick={this.props.onClickChat}
/>
</div>
))}
</ScrollableList>
); );
} }

Wyświetl plik

@ -20,7 +20,7 @@ const messages = defineMessages({
}); });
const getChatsUnreadCount = state => { const getChatsUnreadCount = state => {
const chats = state.get('chats'); const chats = state.getIn(['chats', 'items']);
return chats.reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0); return chats.reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0);
}; };
@ -30,7 +30,7 @@ const normalizePanes = (chats, panes = ImmutableList()) => (
); );
const makeNormalizeChatPanes = () => createSelector([ const makeNormalizeChatPanes = () => createSelector([
state => state.get('chats'), state => state.getIn(['chats', 'items']),
state => getSettings(state).getIn(['chats', 'panes']), state => getSettings(state).getIn(['chats', 'panes']),
], normalizePanes); ], normalizePanes);
@ -93,7 +93,6 @@ class ChatPanes extends ImmutablePureComponent {
<> <>
<ChatList <ChatList
onClickChat={this.handleClickChat} onClickChat={this.handleClickChat}
emptyMessage={<FormattedMessage id='chat_panels.main_window.empty' defaultMessage="No chats found. To start a chat, visit a user's profile." />}
/> />
<AccountSearch <AccountSearch
placeholder={intl.formatMessage(messages.searchPlaceholder)} placeholder={intl.formatMessage(messages.searchPlaceholder)}

Wyświetl plik

@ -22,7 +22,7 @@ const makeMapStateToProps = () => {
const getChat = makeGetChat(); const getChat = makeGetChat();
const mapStateToProps = (state, { chatId }) => { const mapStateToProps = (state, { chatId }) => {
const chat = state.getIn(['chats', chatId]); const chat = state.getIn(['chats', 'items', chatId]);
return { return {
me: state.get('me'), me: state.get('me'),

Wyświetl plik

@ -4,11 +4,10 @@ import { connect } from 'react-redux';
import Column from '../../components/column'; import Column from '../../components/column';
import ColumnHeader from '../../components/column_header'; import ColumnHeader from '../../components/column_header';
import { fetchChats, launchChat } from 'soapbox/actions/chats'; import { fetchChats, launchChat } from 'soapbox/actions/chats';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import ChatList from './components/chat_list'; import ChatList from './components/chat_list';
import AudioToggle from 'soapbox/features/chats/components/audio_toggle'; import AudioToggle from 'soapbox/features/chats/components/audio_toggle';
import AccountSearch from 'soapbox/components/account_search'; import AccountSearch from 'soapbox/components/account_search';
import PullToRefresh from 'soapbox/components/pull_to_refresh';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'column.chats', defaultMessage: 'Chats' }, title: { id: 'column.chats', defaultMessage: 'Chats' },
@ -60,12 +59,10 @@ class ChatIndex extends React.PureComponent {
onSelected={this.handleSuggestion} onSelected={this.handleSuggestion}
/> />
<PullToRefresh onRefresh={this.handleRefresh}> <ChatList
<ChatList onClickChat={this.handleClickChat}
onClickChat={this.handleClickChat} onRefresh={this.handleRefresh}
emptyMessage={<FormattedMessage id='chat_panels.main_window.empty' defaultMessage="No chats found. To start a chat, visit a user's profile." />} />
/>
</PullToRefresh>
</Column> </Column>
); );
} }

Wyświetl plik

@ -0,0 +1,30 @@
import React from 'react';
import PlaceholderAvatar from './placeholder_avatar';
import PlaceholderDisplayName from './placeholder_display_name';
import { randomIntFromInterval, generateText } from '../utils';
export default class PlaceholderAccount extends React.Component {
render() {
const messageLength = randomIntFromInterval(5, 75);
return (
<div className='chat-list-item chat-list-item--placeholder'>
<div className='account'>
<div className='account__wrapper'>
<div className='account__display-name'>
<div className='account__avatar-wrapper'>
<PlaceholderAvatar size={36} />
</div>
<PlaceholderDisplayName minLength={3} maxLength={25} />
<span className='chat__last-message'>
{generateText(messageLength)}
</span>
</div>
</div>
</div>
</div>
);
}
}

Wyświetl plik

@ -171,7 +171,7 @@ const mapStateToProps = state => {
logo: getSoapboxConfig(state).get('logo'), logo: getSoapboxConfig(state).get('logo'),
features: getFeatures(instance), features: getFeatures(instance),
notificationCount: state.getIn(['notifications', 'unread']), notificationCount: state.getIn(['notifications', 'unread']),
chatsCount: state.get('chats').reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0), chatsCount: state.getIn(['chats', 'items']).reduce((acc, curr) => acc + Math.min(curr.get('unread', 0), 1), 0),
dashboardCount: reportsCount + approvalCount, dashboardCount: reportsCount + approvalCount,
}; };
}; };

Wyświetl plik

@ -3,7 +3,7 @@ import {
ACCOUNTS_IMPORT, ACCOUNTS_IMPORT,
ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP, ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP,
} from '../actions/importer'; } from '../actions/importer';
import { CHATS_FETCH_SUCCESS, CHAT_FETCH_SUCCESS } from 'soapbox/actions/chats'; import { CHATS_FETCH_SUCCESS, CHATS_EXPAND_SUCCESS, CHAT_FETCH_SUCCESS } from 'soapbox/actions/chats';
import { STREAMING_CHAT_UPDATE } from 'soapbox/actions/streaming'; import { STREAMING_CHAT_UPDATE } from 'soapbox/actions/streaming';
import { normalizeAccount as normalizeAccount2 } from 'soapbox/actions/importer/normalizer'; import { normalizeAccount as normalizeAccount2 } from 'soapbox/actions/importer/normalizer';
import { import {
@ -208,6 +208,7 @@ export default function accounts(state = initialState, action) {
username: action.username, username: action.username,
})); }));
case CHATS_FETCH_SUCCESS: case CHATS_FETCH_SUCCESS:
case CHATS_EXPAND_SUCCESS:
return importAccountsFromChats(state, action.chats); return importAccountsFromChats(state, action.chats);
case CHAT_FETCH_SUCCESS: case CHAT_FETCH_SUCCESS:
case STREAMING_CHAT_UPDATE: case STREAMING_CHAT_UPDATE:

Wyświetl plik

@ -1,5 +1,6 @@
import { import {
CHATS_FETCH_SUCCESS, CHATS_FETCH_SUCCESS,
CHATS_EXPAND_SUCCESS,
CHAT_MESSAGES_FETCH_SUCCESS, CHAT_MESSAGES_FETCH_SUCCESS,
CHAT_MESSAGE_SEND_REQUEST, CHAT_MESSAGE_SEND_REQUEST,
CHAT_MESSAGE_SEND_SUCCESS, CHAT_MESSAGE_SEND_SUCCESS,
@ -47,6 +48,7 @@ export default function chatMessageLists(state = initialState, action) {
case CHAT_MESSAGE_SEND_REQUEST: case CHAT_MESSAGE_SEND_REQUEST:
return updateList(state, action.chatId, [action.uuid]); return updateList(state, action.chatId, [action.uuid]);
case CHATS_FETCH_SUCCESS: case CHATS_FETCH_SUCCESS:
case CHATS_EXPAND_SUCCESS:
return importLastMessages(state, action.chats); return importLastMessages(state, action.chats);
case STREAMING_CHAT_UPDATE: case STREAMING_CHAT_UPDATE:
if (action.chat.last_message && if (action.chat.last_message &&

Wyświetl plik

@ -1,5 +1,6 @@
import { import {
CHATS_FETCH_SUCCESS, CHATS_FETCH_SUCCESS,
CHATS_EXPAND_SUCCESS,
CHAT_MESSAGES_FETCH_SUCCESS, CHAT_MESSAGES_FETCH_SUCCESS,
CHAT_MESSAGE_SEND_REQUEST, CHAT_MESSAGE_SEND_REQUEST,
CHAT_MESSAGE_SEND_SUCCESS, CHAT_MESSAGE_SEND_SUCCESS,
@ -38,6 +39,7 @@ export default function chatMessages(state = initialState, action) {
pending: true, pending: true,
})); }));
case CHATS_FETCH_SUCCESS: case CHATS_FETCH_SUCCESS:
case CHATS_EXPAND_SUCCESS:
return importLastMessages(state, fromJS(action.chats)); return importLastMessages(state, fromJS(action.chats));
case CHAT_MESSAGES_FETCH_SUCCESS: case CHAT_MESSAGES_FETCH_SUCCESS:
return importMessages(state, fromJS(action.chatMessages)); return importMessages(state, fromJS(action.chatMessages));

Wyświetl plik

@ -1,5 +1,8 @@
import { import {
CHATS_FETCH_SUCCESS, CHATS_FETCH_SUCCESS,
CHATS_FETCH_REQUEST,
CHATS_EXPAND_SUCCESS,
CHATS_EXPAND_REQUEST,
CHAT_FETCH_SUCCESS, CHAT_FETCH_SUCCESS,
CHAT_READ_SUCCESS, CHAT_READ_SUCCESS,
CHAT_READ_REQUEST, CHAT_READ_REQUEST,
@ -8,17 +11,29 @@ import { STREAMING_CHAT_UPDATE } from 'soapbox/actions/streaming';
import { normalizeChat } from 'soapbox/actions/importer/normalizer'; import { normalizeChat } from 'soapbox/actions/importer/normalizer';
import { Map as ImmutableMap, fromJS } from 'immutable'; import { Map as ImmutableMap, fromJS } from 'immutable';
const importChat = (state, chat) => state.set(chat.id, fromJS(normalizeChat(chat))); const importChat = (state, chat) => state.setIn(['items', chat.id], fromJS(normalizeChat(chat)));
const importChats = (state, chats) => const importChats = (state, chats, next) =>
state.withMutations(mutable => chats.forEach(chat => importChat(mutable, chat))); state.withMutations(mutable => {
if (next !== undefined) mutable.set('next', next);
chats.forEach(chat => importChat(mutable, chat));
mutable.set('loading', false);
});
const initialState = ImmutableMap(); const initialState = ImmutableMap({
next: null,
isLoading: false,
items: ImmutableMap({}),
});
export default function chats(state = initialState, action) { export default function chats(state = initialState, action) {
switch(action.type) { switch(action.type) {
case CHATS_FETCH_REQUEST:
case CHATS_EXPAND_REQUEST:
return state.set('loading', true);
case CHATS_FETCH_SUCCESS: case CHATS_FETCH_SUCCESS:
return importChats(state, action.chats); case CHATS_EXPAND_SUCCESS:
return importChats(state, action.chats, action.next);
case STREAMING_CHAT_UPDATE: case STREAMING_CHAT_UPDATE:
return importChats(state, [action.chat]); return importChats(state, [action.chat]);
case CHAT_FETCH_SUCCESS: case CHAT_FETCH_SUCCESS:

Wyświetl plik

@ -209,8 +209,8 @@ export const getAccountGallery = createSelector([
export const makeGetChat = () => { export const makeGetChat = () => {
return createSelector( return createSelector(
[ [
(state, { id }) => state.getIn(['chats', id]), (state, { id }) => state.getIn(['chats', 'items', id]),
(state, { id }) => state.getIn(['accounts', state.getIn(['chats', id, 'account'])]), (state, { id }) => state.getIn(['accounts', state.getIn(['chats', 'items', id, 'account'])]),
(state, { last_message }) => state.getIn(['chat_messages', last_message]), (state, { last_message }) => state.getIn(['chat_messages', last_message]),
], ],

Wyświetl plik

@ -54,6 +54,7 @@ export const getFeatures = createSelector([
importMutes: v.software === PLEROMA && gte(v.version, '2.2.0'), importMutes: v.software === PLEROMA && gte(v.version, '2.2.0'),
emailList: features.includes('email_list'), emailList: features.includes('email_list'),
chats: v.software === PLEROMA && gte(v.version, '2.1.0'), chats: v.software === PLEROMA && gte(v.version, '2.1.0'),
chatsV2: v.software === PLEROMA && gte(v.version, '2.3.0'),
scopes: v.software === PLEROMA ? 'read write follow push admin' : 'read write follow push', scopes: v.software === PLEROMA ? 'read write follow push admin' : 'read write follow push',
federating: federation.get('enabled', true), // Assume true unless explicitly false federating: federation.get('enabled', true), // Assume true unless explicitly false
richText: v.software === PLEROMA, richText: v.software === PLEROMA,

Wyświetl plik

@ -116,6 +116,10 @@
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
} }
.chat-list {
overflow-y: auto;
}
} }
.audio-toggle .react-toggle-thumb { .audio-toggle .react-toggle-thumb {
@ -219,7 +223,6 @@
} }
.chat-list { .chat-list {
overflow-y: auto;
flex: 1; flex: 1;
&__content { &__content {
@ -233,6 +236,10 @@
align-items: start; align-items: start;
} }
.account {
border-bottom: none;
}
.account__display-name { .account__display-name {
position: relative; position: relative;

Wyświetl plik

@ -46,7 +46,8 @@
} }
.status__content--placeholder, .status__content--placeholder,
.display-name--placeholder { .display-name--placeholder,
.chat-list-item--placeholder .chat__last-message {
letter-spacing: -1px; letter-spacing: -1px;
color: var(--brand-color) !important; color: var(--brand-color) !important;
opacity: 0.1; opacity: 0.1;