Mailspring/app/internal_packages/thread-list/lib/thread-list-columns.jsx

240 lines
7 KiB
JavaScript

const React = require('react');
const {
ListTabular,
RetinaImg,
MailLabelSet,
MailImportantIcon,
InjectedComponent,
InjectedComponentSet,
} = require('mailspring-component-kit');
const { FocusedPerspectiveStore, Utils, DateUtils } = require('mailspring-exports');
const { ThreadArchiveQuickAction, ThreadTrashQuickAction } = require('./thread-list-quick-actions');
const ThreadListParticipants = require('./thread-list-participants');
const ThreadListIcon = require('./thread-list-icon');
// Get and format either last sent or last received timestamp depending on thread-list being viewed
const ThreadListTimestamp = function({ thread }) {
let rawTimestamp = FocusedPerspectiveStore.current().isSent()
? thread.lastMessageSentTimestamp
: thread.lastMessageReceivedTimestamp;
const timestamp = rawTimestamp ? DateUtils.shortTimeString(rawTimestamp) : 'No Date';
return <span className="timestamp">{timestamp}</span>;
};
ThreadListTimestamp.containerRequired = false;
const subject = function(subj) {
if ((subj || '').trim().length === 0) {
return <span className="no-subject">(No Subject)</span>;
} else if (subj.split(/([\uD800-\uDBFF][\uDC00-\uDFFF])/g).length > 1) {
const subjComponents = [];
const subjParts = subj.split(/([\uD800-\uDBFF][\uDC00-\uDFFF])/g);
for (let idx = 0; idx < subjParts.length; idx++) {
const part = subjParts[idx];
if (part.match(/([\uD800-\uDBFF][\uDC00-\uDFFF])/g)) {
subjComponents.push(
<span className="emoji" key={idx}>
{part}
</span>
);
} else {
subjComponents.push(<span key={idx}>{part}</span>);
}
}
return subjComponents;
} else {
return subj;
}
};
const getSnippet = function(thread) {
const messages = thread.__messages || [];
if (messages.length === 0) {
return thread.snippet;
}
for (let ii = messages.length - 1; ii >= 0; ii--) {
if (messages[ii].snippet) return messages[ii].snippet;
}
return null;
};
const c1 = new ListTabular.Column({
name: '★',
resolver: thread => {
return [
<ThreadListIcon key="thread-list-icon" thread={thread} />,
<MailImportantIcon
key="mail-important-icon"
thread={thread}
showIfAvailableForAnyAccount={true}
/>,
<InjectedComponentSet
key="injected-component-set"
inline={true}
containersRequired={false}
matching={{ role: 'ThreadListIcon' }}
className="thread-injected-icons"
exposedProps={{ thread: thread }}
/>,
];
},
});
const c2 = new ListTabular.Column({
name: 'Participants',
width: 200,
resolver: thread => {
const hasDraft = (thread.__messages || []).find(m => m.draft);
if (hasDraft) {
return (
<div style={{ display: 'flex' }}>
<ThreadListParticipants thread={thread} />
<RetinaImg
name="icon-draft-pencil.png"
className="draft-icon"
mode={RetinaImg.Mode.ContentPreserve}
/>
</div>
);
} else {
return <ThreadListParticipants thread={thread} />;
}
},
});
const c3 = new ListTabular.Column({
name: 'Message',
flex: 4,
resolver: thread => {
let attachment = false;
const messages = thread.__messages || [];
const hasAttachments =
thread.attachmentCount > 0 && messages.find(m => Utils.showIconForAttachments(m.files));
if (hasAttachments) {
attachment = <div className="thread-icon thread-icon-attachment" />;
}
return (
<span className="details">
<MailLabelSet thread={thread} />
<span className="subject">{subject(thread.subject)}</span>
<span className="snippet">{getSnippet(thread)}</span>
{attachment}
</span>
);
},
});
const c4 = new ListTabular.Column({
name: 'Date',
resolver: thread => {
return (
<InjectedComponent
className="thread-injected-timestamp"
fallback={ThreadListTimestamp}
exposedProps={{ thread: thread }}
matching={{ role: 'ThreadListTimestamp' }}
/>
);
},
});
const c5 = new ListTabular.Column({
name: 'HoverActions',
resolver: thread => {
return (
<div className="inner">
<InjectedComponentSet
key="injected-component-set"
inline={true}
containersRequired={false}
children={[
<ThreadTrashQuickAction key="thread-trash-quick-action" thread={thread} />,
<ThreadArchiveQuickAction key="thread-archive-quick-action" thread={thread} />,
]}
matching={{ role: 'ThreadListQuickAction' }}
className="thread-injected-quick-actions"
exposedProps={{ thread: thread }}
/>
</div>
);
},
});
const cNarrow = new ListTabular.Column({
name: 'Item',
flex: 1,
resolver: thread => {
let pencil = false;
let attachment = false;
const messages = thread.__messages || [];
const hasAttachments =
thread.attachmentCount > 0 && messages.find(m => Utils.showIconForAttachments(m.files));
if (hasAttachments) {
attachment = <div className="thread-icon thread-icon-attachment" />;
}
const hasDraft = messages.find(m => m.draft);
if (hasDraft) {
pencil = (
<RetinaImg
name="icon-draft-pencil.png"
className="draft-icon"
mode={RetinaImg.Mode.ContentPreserve}
/>
);
}
// TODO We are limiting the amount on injected icons in narrow mode to 1
// until we revisit the UI to accommodate more icons
return (
<div style={{ display: 'flex', alignItems: 'flex-start' }}>
<div className="icons-column">
<ThreadListIcon thread={thread} />
<InjectedComponentSet
inline={true}
matchLimit={1}
direction="column"
containersRequired={false}
key="injected-component-set"
exposedProps={{ thread: thread }}
matching={{ role: 'ThreadListIcon' }}
className="thread-injected-icons"
/>
<MailImportantIcon thread={thread} showIfAvailableForAnyAccount={true} />
</div>
<div className="thread-info-column">
<div className="participants-wrapper">
<ThreadListParticipants thread={thread} />
{pencil}
<span style={{ flex: 1 }} />
{attachment}
<InjectedComponent
key="thread-injected-timestamp"
className="thread-injected-timestamp"
fallback={ThreadListTimestamp}
exposedProps={{ thread: thread }}
matching={{ role: 'ThreadListTimestamp' }}
/>
</div>
<div className="subject">{subject(thread.subject)}</div>
<div className="snippet-and-labels">
<div className="snippet">{getSnippet(thread)}&nbsp;</div>
<div style={{ flex: 1, flexShrink: 1 }} />
<MailLabelSet thread={thread} />
</div>
</div>
</div>
);
},
});
module.exports = {
Narrow: [cNarrow],
Wide: [c1, c2, c3, c4, c5],
};