import classNames from 'classnames';
import React from 'react';
import ReactDOM from 'react-dom';
import {
localized,
Utils,
Actions,
MessageStore,
Message,
Thread,
SearchableComponentStore,
SearchableComponentMaker,
} from 'mailspring-exports';
import {
Spinner,
RetinaImg,
MailLabelSet,
ScrollRegion,
ScrollPosition,
MailImportantIcon,
KeyCommandsRegion,
InjectedComponentSet,
} from 'mailspring-component-kit';
import FindInThread from './find-in-thread';
import MessageItemContainer from './message-item-container';
import { MessageListScrollTooltip } from './message-list-scroll-tooltip';
import { SubjectLineIcons } from './subject-line-icons';
interface MessageListState {
messages: Message[];
messagesExpandedState: {
[messageId: string]: 'explicit' | 'default';
};
canCollapse: boolean;
hasCollapsedItems: boolean;
currentThread: Thread | null;
loading: boolean;
minified: boolean;
}
const PREF_REPLY_TYPE = 'core.sending.defaultReplyType';
const PREF_RESTRICT_WIDTH = 'core.reading.restrictMaxWidth';
const PREF_DESCENDING_ORDER = 'core.reading.descendingOrderMessageList';
class MessageList extends React.Component<{}, MessageListState> {
static displayName = 'MessageList';
static containerStyles = {
minWidth: 500,
maxWidth: 999999,
};
_unsubscribers = [];
_messageWrapEl: ScrollRegion;
_draftScrollInProgress = false;
MINIFY_THRESHOLD = 3;
constructor(props) {
super(props);
this.state = Object.assign(this._getStateFromStores(), { minified: true });
}
componentDidMount() {
this._unsubscribers = [];
this._unsubscribers.push(MessageStore.listen(this._onChange));
}
shouldComponentUpdate(nextProps, nextState) {
return !Utils.isEqualReact(nextProps, this.props) || !Utils.isEqualReact(nextState, this.state);
}
componentDidUpdate() {
// cannot remove
}
componentWillUnmount() {
for (const unsubscribe of this._unsubscribers) {
unsubscribe();
}
}
_globalKeymapHandlers() {
const handlers = {
'core:reply': () =>
Actions.composeReply({
thread: this.state.currentThread,
message: this._lastMessage(),
type: 'reply',
behavior: 'prefer-existing',
}),
'core:reply-all': () =>
Actions.composeReply({
thread: this.state.currentThread,
message: this._lastMessage(),
type: 'reply-all',
behavior: 'prefer-existing',
}),
'core:forward': () => this._onForward(),
'core:print-thread': () => this._onPrintThread(),
'core:messages-page-up': () => this._onScrollByPage(-1),
'core:messages-page-down': () => this._onScrollByPage(1),
};
if (this.state.canCollapse) {
handlers['message-list:toggle-expanded'] = () => this._onToggleAllMessagesExpanded();
}
return handlers;
}
_getMessageContainer(headerMessageId) {
return this.refs[`message-container-${headerMessageId}`];
}
_onForward = () => {
if (!this.state.currentThread) {
return;
}
Actions.composeForward({
thread: this.state.currentThread,
message: this._lastMessage(),
});
};
_lastMessage() {
return (this.state.messages || []).filter(m => !m.draft).pop();
}
// Returns either "reply" or "reply-all"
_replyType() {
const defaultReplyType = AppEnv.config.get(PREF_REPLY_TYPE);
const lastMessage = this._lastMessage();
if (!lastMessage) {
return 'reply';
}
if (lastMessage.canReplyAll()) {
return defaultReplyType === 'reply-all' ? 'reply-all' : 'reply';
}
return 'reply';
}
_onToggleAllMessagesExpanded = () => {
Actions.toggleAllMessagesExpanded();
};
_onPrintThread = () => {
const node = ReactDOM.findDOMNode(this) as HTMLElement;
Actions.printThread(this.state.currentThread, node.innerHTML);
};
_onPopThreadIn = () => {
if (!this.state.currentThread) {
return;
}
Actions.focusThreadMainWindow(this.state.currentThread);
AppEnv.close();
};
_onPopoutThread = () => {
if (!this.state.currentThread) {
return;
}
Actions.popoutThread(this.state.currentThread);
// This returns the single-pane view to the inbox, and does nothing for
// double-pane view because we're at the root sheet.
Actions.popSheet();
};
_onClickReplyArea = () => {
if (!this.state.currentThread) {
return;
}
Actions.composeReply({
thread: this.state.currentThread,
message: this._lastMessage(),
type: this._replyType(),
behavior: 'prefer-existing-if-pristine',
});
};
_messageElements() {
const { messagesExpandedState, currentThread } = this.state;
const elements = [];
let messages = this._messagesWithMinification(this.state.messages);
const mostRecentMessage = messages[messages.length - 1];
const hasReplyArea = mostRecentMessage && !mostRecentMessage.draft;
// Invert the message list if the descending option is set
if (AppEnv.config.get(PREF_DESCENDING_ORDER)) {
messages = [...messages].reverse();
}
messages.forEach(message => {
if (message.type === 'minifiedBundle') {
elements.push(this._renderMinifiedBundle(message));
return;
}
const collapsed = !messagesExpandedState[message.id];
const isMostRecent = message === mostRecentMessage;
const isBeforeReplyArea = isMostRecent && hasReplyArea;
elements.push(