mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-10-06 03:14:39 +08:00
245 lines
7.2 KiB
TypeScript
245 lines
7.2 KiB
TypeScript
import React from 'react';
|
|
import {
|
|
ListTabular,
|
|
RetinaImg,
|
|
MailLabelSet,
|
|
MailImportantIcon,
|
|
InjectedComponent,
|
|
InjectedComponentSet,
|
|
} from 'mailspring-component-kit';
|
|
|
|
import { localized, FocusedPerspectiveStore, Utils, DateUtils } from 'mailspring-exports';
|
|
|
|
import { ThreadArchiveQuickAction, ThreadTrashQuickAction } from './thread-list-quick-actions';
|
|
import ThreadListParticipants from './thread-list-participants';
|
|
import ThreadListIcon from './thread-list-icon';
|
|
|
|
// Get and format either last sent or last received timestamp depending on thread-list being viewed
|
|
const ThreadListTimestamp = function({ thread }) {
|
|
const rawTimestamp = FocusedPerspectiveStore.current().isSent()
|
|
? thread.lastMessageSentTimestamp
|
|
: thread.lastMessageReceivedTimestamp;
|
|
const timestamp = rawTimestamp ? DateUtils.shortTimeString(rawTimestamp) : localized('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">{localized('(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: JSX.Element = null;
|
|
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" dir="auto">
|
|
{subject(thread.subject)}
|
|
</span>
|
|
<span className="snippet" dir="auto">
|
|
{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: JSX.Element = null;
|
|
let attachment: JSX.Element = null;
|
|
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" dir="auto">
|
|
{subject(thread.subject)}
|
|
</div>
|
|
<div className="snippet-and-labels">
|
|
<div className="snippet" dir="auto">
|
|
{getSnippet(thread)}
|
|
</div>
|
|
<div style={{ flex: 1, flexShrink: 1 }} />
|
|
<MailLabelSet thread={thread} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
},
|
|
});
|
|
|
|
export const Narrow = [cNarrow];
|
|
export const Wide = [c1, c2, c3, c4, c5];
|