M => +1 -1
@@ 3,7 3,7 @@ import { Utils, DOMUtils, Account, AccountStore } from 'mailspring-exports';
import { OutlineView, ScrollRegion, Flexbox } from 'mailspring-component-kit';
import AccountSwitcher from './account-switcher';
import SidebarStore from '../sidebar-store';
import { ISidebarSection, ISidebarItem } from '../types';
import { ISidebarSection } from '../types';
interface AccountSidebarState {
accounts: Account[];
M => +3 -1
@@ 177,7 177,7 @@ class SidebarSection {
}
static forUserCategories(
account,
account: Account,
{ title, collapsible }: { title?: string; collapsible?: boolean } = {}
): ISidebarSection {
let onCollapseToggled;
@@ 239,12 239,14 @@ class SidebarSection {
if (collapsible) {
onCollapseToggled = toggleSectionCollapsed;
}
const titleColor = account.color;
return {
title,
iconName,
items,
collapsed,
titleColor,
onCollapseToggled,
onItemCreated(displayName) {
if (!displayName) {
M => +5 -5
@@ 15,7 15,7 @@ import SidebarSection from './sidebar-section';
import * as SidebarActions from './sidebar-actions';
import * as AccountCommands from './account-commands';
import { Disposable } from 'event-kit';
import { ISidebarItem, ISidebarSection } from './types';
import { ISidebarSection } from './types';
const Sections = {
Standard: 'Standard',
@@ 27,9 27,9 @@ class SidebarStore extends MailspringStore {
Standard: ISidebarSection;
User: ISidebarSection[];
} = {
Standard: { title: '', items: [] },
User: [],
};
Standard: { title: '', items: [] },
User: [],
};
configSubscription: Disposable;
constructor() {
@@ 151,7 151,7 @@ class SidebarStore extends MailspringStore {
const multiAccount = accounts.length > 1;
this._sections[Sections.Standard] = SidebarSection.standardSectionForAccounts(accounts);
this._sections[Sections.User] = accounts.map(function(acc) {
this._sections[Sections.User] = accounts.map(function (acc) {
const opts: { title?: string; collapsible?: boolean } = {};
if (multiAccount) {
opts.title = acc.label;
M => +1 -0
@@ 27,6 27,7 @@ export interface ISidebarSection {
items: ISidebarItem[];
iconName?: string;
collapsed?: boolean;
titleColor?: string;
onCollapseToggled?: () => void;
onItemCreated?: (displayName) => void;
}
M app/internal_packages/composer/lib/account-contact-field.tsx => app/internal_packages/composer/lib/account-contact-field.tsx +39 -6
@@ 1,4 1,4 @@
-import React from 'react';
+import React, { CSSProperties } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import {
@@ 19,6 19,7 @@ interface AccountContactFieldProps {
draft: Message;
onChange: (val: { from: Contact[]; cc: Contact[]; bcc: Contact[] }) => void;
}
+
export default class AccountContactField extends React.Component<AccountContactFieldProps> {
static displayName = 'AccountContactField';
@@ 58,6 59,18 @@ export default class AccountContactField extends React.Component<AccountContactF
const label = this.props.value.toString();
const multipleAccounts = this.props.accounts.length > 1;
const hasAliases = this.props.accounts[0] && this.props.accounts[0].aliases.length > 0;
+ const account = AccountStore.accountForEmail(this.props.value.email);
+ let style: CSSProperties = {
+ }
+ if (account && account.color) {
+ style = {
+ ...style,
+ borderLeftColor: account.color,
+ paddingLeft: '8px',
+ borderLeftWidth: '8px',
+ borderLeftStyle: 'solid',
+ }
+ }
if (multipleAccounts || hasAliases) {
return (
@@ 66,28 79,48 @@ export default class AccountContactField extends React.Component<AccountContactF
this._dropdownComponent = cm;
}}
bordered={false}
- primaryItem={<span>{label}</span>}
+ primaryItem={<span style={style}>{label}</span>}
menu={this._renderAccounts(this.props.accounts)}
/>
);
}
- return this._renderAccountSpan(label);
+ return this._renderAccountSpan(label, style);
}
- _renderAccountSpan = label => {
+ _renderAccountSpan = (label, style) => {
+ style = {
+ ...style,
+ position: 'relative',
+ top: 13,
+ left: '0.5em',
+ }
+
return (
- <span className="from-single-name" style={{ position: 'relative', top: 13, left: '0.5em' }}>
+ <span className="from-single-name" style={style}>
{label}
</span>
);
};
_renderMenuItem = contact => {
+ const account = AccountStore.accountForId(contact.accountId)
+ let style: CSSProperties = {}
+ if (account && account.color) {
+ style = {
+ ...style,
+ borderLeftColor: account.color,
+ paddingLeft: '8px',
+ borderLeftWidth: '8px',
+ borderLeftStyle: 'solid',
+ }
+ }
const className = classnames({
contact: true,
'is-alias': contact.isAlias,
});
- return <span className={className}>{contact.toString()}</span>;
+ return <div className={className} style={style}>
+ {contact.toString()}
+ </div>;
};
_renderAccounts(accounts) {
M app/internal_packages/contacts/lib/ContactList.tsx => app/internal_packages/contacts/lib/ContactList.tsx +16 -6
@@ 1,5 1,5 @@
-import React from 'react';
-import { Contact, localized, CanvasUtils } from 'mailspring-exports';
+import React, { CSSProperties } from 'react';
+import { Contact, localized, CanvasUtils, AccountStore } from 'mailspring-exports';
import {
FocusContainer,
MultiselectList,
@@ 16,9 16,20 @@ const ContactColumn = new ListTabular.Column({
flex: 1,
resolver: (contact: Contact) => {
// until we revisit the UI to accommodate more icons
+ const account = AccountStore.accountForId(contact.accountId);
+ let style: CSSProperties = {}
+ if (account && account.color) {
+ style = {
+ height: '50%',
+ paddingLeft: '4px',
+ borderLeftWidth: '4px',
+ borderLeftColor: account.color,
+ borderLeftStyle: 'solid',
+ }
+ }
return (
<div style={{ display: 'flex', alignItems: 'flex-start' }}>
- <div className="subject" dir="auto">
+ <div style={style} className="subject" dir="auto">
{contact.name}
</div>
</div>
@@ 109,9 120,8 @@ const ContactListSearchWithData = (props: ContactListSearchWithDataProps) => {
type="text"
ref={this._searchEl}
value={props.search}
- placeholder={`${localized('Search')} ${
- props.perspective.type === 'unified' ? 'All Contacts' : props.perspective.label
- }`}
+ placeholder={`${localized('Search')} ${props.perspective.type === 'unified' ? 'All Contacts' : props.perspective.label
+ }`}
onChange={e => props.setSearch(e.currentTarget.value)}
/>
{props.search.length > 0 && (
M app/internal_packages/preferences/lib/tabs/preferences-account-details.tsx => app/internal_packages/preferences/lib/tabs/preferences-account-details.tsx +31 -4
@@ 57,7 57,7 @@ class PreferencesAccountDetails extends Component<
{
account: Account;
}
-> {
+ > {
static propTypes = {
account: PropTypes.object,
onAccountUpdated: PropTypes.func.isRequired,
@@ 107,7 107,7 @@ class PreferencesAccountDetails extends Component<
this.props.onAccountUpdated(this.props.account, this.state.account);
};
- _setState = (updates, callback = () => {}) => {
+ _setState = (updates, callback = () => { }) => {
const account = Object.assign(this.state.account.clone(), updates);
this.setState({ account }, callback);
};
@@ 169,6 169,21 @@ class PreferencesAccountDetails extends Component<
ipcRenderer.send('command', 'application:show-contacts', {});
};
+ _onSetColor = (colorChanged) => {
+ // TODO: Ensure that the account color is updated in all places where it is displayed:
+ // - internal_packages/composer/lib/account-contict-field.tsx
+ // - internal_packages/contacts/lib/ContactsList.tsx
+ // - internal_packages/preferecnes/lib/preferences-account-list.tsx
+ // - internal/packages/thread-list/lib/thread-lib-participants.tsx
+ // - src/components/outline-view.tsx
+ this._setState(colorChanged)
+ }
+
+ _onResetColor = () => {
+ this.state.account.color = '';
+ this._saveChanges();
+ }
+
_onContactSupport = () => {
shell.openExternal('https://support.getmailspring.com/hc/en-us/requests/new');
};
@@ 316,8 331,20 @@ class PreferencesAccountDetails extends Component<
</select>
</div>
) : (
- undefined
- )}
+ undefined
+ )}
+ <h6>{localized('Account Color')}</h6>
+ <div style={{ display: 'flex', alignItems: 'flex-end' }}>
+ <input
+ type="color"
+ value={account.color}
+ onBlur={this._saveChanges}
+ onChange={e => this._onSetColor({ color: e.target.value })}
+ />
+ <div className="btn" style={{ marginLeft: 6 }} onClick={this._onResetColor}>
+ {localized('Reset Account Color')}
+ </div>
+ </div>
<h6>{localized('Account Settings')}</h6>
<div className="btn" onClick={this._onManageContacts}>
{localized('Manage Contacts')}
M app/internal_packages/preferences/lib/tabs/preferences-account-list.tsx => app/internal_packages/preferences/lib/tabs/preferences-account-list.tsx +8 -2
@@ 1,4 1,4 @@
-import React, { Component } from 'react';
+import React, { Component, CSSProperties } from 'react';
import { localized, Account } from 'mailspring-exports';
import { RetinaImg, Flexbox, EditableList } from 'mailspring-component-kit';
import classnames from 'classnames';
@@ 42,9 42,15 @@ class PreferencesAccountList extends Component<PreferencesAccountListProps> {
const label = account.label;
const accountSub = `${account.name || localized('No name provided')} <${account.emailAddress}>`;
const syncError = account.hasSyncStateError();
+ let style: CSSProperties = {}
+ if (account.color) {
+ style = { borderLeftColor: account.color, borderLeftWidth: '8px', borderLeftStyle: 'solid' }
+ } else {
+ style = { marginLeft: '8px' }
+ }
return (
- <div className={classnames({ account: true, 'sync-error': syncError })} key={account.id}>
+ <div style={style} className={classnames({ account: true, 'sync-error': syncError })} key={account.id}>
<Flexbox direction="row" style={{ alignItems: 'middle' }}>
<div style={{ textAlign: 'center' }}>
<RetinaImg
M app/internal_packages/thread-list/lib/thread-list-participants.tsx => app/internal_packages/thread-list/lib/thread-list-participants.tsx +5 -3
@@ 1,5 1,6 @@
import React from 'react';
import { PropTypes, Utils } from 'mailspring-exports';
+import { AccountColorBar } from 'mailspring-component-kit';
import { ThreadWithMessagesMetadata } from './types';
class ThreadListParticipants extends React.Component<{ thread: ThreadWithMessagesMetadata }> {
@@ 18,6 19,7 @@ class ThreadListParticipants extends React.Component<{ thread: ThreadWithMessage
const items = this.getTokens();
return (
<div className="participants" dir="auto">
+ <AccountColorBar accountId={this.props.thread.accountId} />
{this.renderSpans(items)}
</div>
);
@@ 28,7 30,7 @@ class ThreadListParticipants extends React.Component<{ thread: ThreadWithMessage
let accumulated = null;
let accumulatedUnread = false;
- const flush = function() {
+ const flush = function () {
if (accumulated) {
spans.push(
<span key={spans.length} className={`unread-${accumulatedUnread}`}>
@@ 40,7 42,7 @@ class ThreadListParticipants extends React.Component<{ thread: ThreadWithMessage
accumulatedUnread = false;
};
- const accumulate = function(text, unread?: boolean) {
+ const accumulate = function (text, unread?: boolean) {
if (accumulated && unread && accumulatedUnread !== unread) {
flush();
}
@@ 147,7 149,7 @@ class ThreadListParticipants extends React.Component<{ thread: ThreadWithMessage
if (
list.length === 0 &&
(this.props.thread.participants != null ? this.props.thread.participants.length : undefined) >
- 0
+ 0
) {
list.push({ contact: this.props.thread.participants[0], unread: false });
}
M app/lang/de.json => app/lang/de.json +3 -0
@@ 17,6 17,7 @@
"About Mailspring": "Über Mailspring",
"Accept": "Annehmen",
"Account": "Konto",
+ "Account Color": "Kontofarbe",
"Account Details": "Accountdetails",
"Account Label": "Konto-Label",
"Account Settings": "Konten-Einstellungen",
@@ 364,6 365,7 @@
"Make sure you have `libsecret` installed and a keyring is present. ": "Stellen Sie sicher, dass `libsecret` installiert ist und ein Schlüsselring vorhanden ist.",
"Manage": "Verwalten",
"Manage Accounts": "Konten verwalten",
+ "Manage Contacts": "Kontakte verwalten",
"Manage Billing": "Bezahlung verwalten",
"Manage Templates...": "Vorlagen verwalten...",
"Manually": "Manuell",
@@ 537,6 539,7 @@
"Reply Rate": "Antwortquote",
"Reply to": "Antwort an",
"Reset": "Zurücksetzen",
+ "Reset Account Color": "Kontofarbe zurücksetzen",
"Reset Accounts and Settings": "Konten und Einstellungen zurücksetzen",
"Reset Cache": "Cache zurücksetzen",
"Reset Configuration": "Konfiguration zurücksetzen",
A app/src/components/account-color-bar.tsx => app/src/components/account-color-bar.tsx +49 -0
@@ 0,0 1,49 @@
+import { AccountStore } from 'mailspring-exports';
+import React from 'react';
+
+class AccountColorBar extends React.Component<{ accountId: string }, { color: string | null }> {
+
+ static displayName = 'AccountColorBar';
+
+ unsubscribe?: () => void;
+
+ constructor(props) {
+ super(props);
+ this.state = { color: this.getColor(props) };
+ }
+
+ getColor = (props = this.props) => {
+ const account = AccountStore.accountForId(props.accountId);
+ return account ? account.color : null;
+ }
+
+ componentDidMount() {
+ this.unsubscribe = AccountStore.listen(() => {
+ const nextColor = this.getColor();
+ if (this.state.color !== nextColor) this.setState({ color: nextColor });
+ });
+ }
+
+ componentWillUnmount() {
+ this.unsubscribe && this.unsubscribe();
+ }
+
+ render() {
+ return this.state.color ? (
+ <span
+ style={{
+ height: '50%',
+ paddingLeft: '4px',
+ borderLeftWidth: '4px',
+ borderLeftColor: this.state.color,
+ borderLeftStyle: 'solid',
+ }}
+ />
+ ) : (
+ <span />
+ );
+ }
+
+}
+
+export default AccountColorBar;<
\ No newline at end of file
M app/src/components/list-tabular-item.tsx => app/src/components/list-tabular-item.tsx +1 -2
@@ 86,8 86,7 @@ export default class ListTabularItem extends React.Component<ListTabularItemProp
return (this.props.columns || []).map(column => {
if (names[column.name]) {
console.warn(
- `ListTabular: Columns do not have distinct names, will cause React error! \`${
- column.name
+ `ListTabular: Columns do not have distinct names, will cause React error! \`${column.name
}\` twice.`
);
}
M app/src/components/outline-view.tsx => app/src/components/outline-view.tsx +15 -2
@@ 1,5 1,5 @@
import { Utils, localized } from 'mailspring-exports';
-import React, { Component } from 'react';
+import React, { Component, CSSProperties } from 'react';
import { DropZone } from './drop-zone';
import { RetinaImg } from './retina-img';
import OutlineViewItem from './outline-view-item';
@@ 34,6 34,7 @@ interface OutlineViewProps {
items: IOutlineViewItem[];
iconName?: string;
collapsed?: boolean;
+ titleColor?: string;
onCollapseToggled?: (props: OutlineViewProps) => void;
onItemCreated?: (displayName) => void;
}
@@ 63,6 64,7 @@ interface OutlineViewState {
* OutlineViewItem}s
* @param {boolean} props.collapsed - Whether the OutlineView is collapsed or
* not
+ * @param {string} props.titleColor - Colored bar that is displayed to highlight the title
* @param {props.onItemCreated} props.onItemCreated
* @param {props.onCollapseToggled} props.onCollapseToggled
* @class OutlineView
@@ 83,6 85,7 @@ export class OutlineView extends Component<OutlineViewProps, OutlineViewState> {
*/
static propTypes = {
title: PropTypes.string,
+ titleColor: PropTypes.string,
iconName: PropTypes.string,
items: PropTypes.array,
collapsed: PropTypes.bool,
@@ 181,6 184,16 @@ export class OutlineView extends Component<OutlineViewProps, OutlineViewState> {
_renderHeading(allowCreate, collapsed, collapsible) {
const collapseLabel = collapsed ? localized('Show') : localized('Hide');
+ let style: CSSProperties = {}
+ if (this.props.titleColor) {
+ style = {
+ height: '50%',
+ paddingLeft: '4px',
+ borderLeftWidth: '4px',
+ borderLeftColor: this.props.titleColor,
+ borderLeftStyle: 'solid',
+ }
+ }
return (
<DropZone
className="heading"
@@ 188,7 201,7 @@ export class OutlineView extends Component<OutlineViewProps, OutlineViewState> {
onDragStateChange={this._onDragStateChange}
shouldAcceptDrop={() => true}
>
- <span className="text" title={this.props.title}>
+ <span style={style} className="text" title={this.props.title}>
{this.props.title}
</span>
{allowCreate ? this._renderCreateButton() : null}
M app/src/flux/models/account.ts => app/src/flux/models/account.ts +6 -0
@@ 81,6 81,10 @@ export class Account extends ModelWithMetadata {
authedAt: Attributes.DateTime({
modelKey: 'authedAt',
}),
+
+ color: Attributes.String({
+ modelKey: 'color',
+ }),
};
public name: string;
@@ 108,6 112,7 @@ export class Account extends ModelWithMetadata {
public defaultAlias: string;
public syncState: string;
public syncError: string;
+ public color: string;
constructor(args) {
super(args);
@@ 119,6 124,7 @@ export class Account extends ModelWithMetadata {
type: 'bcc',
value: '',
};
+ this.color = this.color || '';
}
toJSON() {
M app/src/global/mailspring-component-kit.d.ts => app/src/global/mailspring-component-kit.d.ts +1 -0
@@ 65,6 65,7 @@ export * from '../components/scroll-region';
export * from '../components/resizable-region';
export * from '../components/mail-label';
+export const AccountColorBar: typeof import('../components/account-color-bar').default;
export const MailLabelSet: typeof import('../components/mail-label-set').default;
export const MailImportantIcon: typeof import('../components/mail-important-icon').default;
M app/src/global/mailspring-component-kit.js => app/src/global/mailspring-component-kit.js +1 -0
@@ 123,6 123,7 @@ lazyLoad('ComposerSupport', 'composer-editor/composer-support');
lazyLoad('ScrollRegion', 'scroll-region');
lazyLoad('ResizableRegion', 'resizable-region');
+lazyLoad('AccountColorBar', 'account-color-bar');
lazyLoadFrom('MailLabel', 'mail-label');
lazyLoadFrom('LabelColorizer', 'mail-label');
lazyLoad('MailLabelSet', 'mail-label-set');