Build ContactBook concept to track which accounts have sync running

This commit is contained in:
Ben Gotow 2019-10-07 14:00:34 -05:00
parent 1f6aab1083
commit 96c6a64e46
19 changed files with 267 additions and 159 deletions

View file

@ -110,7 +110,7 @@ export function registerMenuItems(accounts: Account[], sidebarAccountIds: string
return; return;
} }
const idx = submenu.findIndex(({ type }) => type === 'separator'); const idx = submenu.findIndex(({ id }) => id === 'account-shortcuts-separator');
if (!(idx > 0)) { if (!(idx > 0)) {
return; return;
} }

View file

@ -31,7 +31,11 @@ class AddContactToolbarWithData extends React.Component<AddContactToolbarProps>
tabIndex={-1} tabIndex={-1}
disabled={!enabled} disabled={!enabled}
className={`btn btn-toolbar btn-new-contact ${!enabled && 'btn-disabled'}`} className={`btn btn-toolbar btn-new-contact ${!enabled && 'btn-disabled'}`}
title={localized('New contact in %@', acct ? acct.label : 'account')} title={
acct
? localized('New contact in %@', acct.label)
: localized('Select an account to add a contact.')
}
onClick={enabled ? this.onAdd : undefined} onClick={enabled ? this.onAdd : undefined}
> >
<Icons.NewPerson /> <Icons.NewPerson />

View file

@ -86,6 +86,7 @@ class ContactDetailToolbarWithData extends React.Component<ContactDetailToolbarP
{perspective && perspective.type === 'group' && ( {perspective && perspective.type === 'group' && (
<button <button
tabIndex={-1} tabIndex={-1}
title={localized('Remove from Group')}
className={`btn btn-toolbar ${actionSet.length === 0 && 'btn-disabled'}`} className={`btn btn-toolbar ${actionSet.length === 0 && 'btn-disabled'}`}
onClick={actionSet.length > 0 ? this._onRemoveFromSource : undefined} onClick={actionSet.length > 0 ? this._onRemoveFromSource : undefined}
> >
@ -94,14 +95,15 @@ class ContactDetailToolbarWithData extends React.Component<ContactDetailToolbarP
)} )}
<button <button
tabIndex={-1} tabIndex={-1}
className={`btn btn-toolbar ${actionSet.length === 0 && 'btn-disabled'}`}
title={localized('Delete')} title={localized('Delete')}
className={`btn btn-toolbar ${actionSet.length === 0 && 'btn-disabled'}`}
onClick={actionSet.length > 0 ? this._onDelete : undefined} onClick={actionSet.length > 0 ? this._onDelete : undefined}
> >
<RetinaImg name="toolbar-trash.png" mode={RetinaImg.Mode.ContentIsMask} /> <RetinaImg name="toolbar-trash.png" mode={RetinaImg.Mode.ContentIsMask} />
</button> </button>
<button <button
tabIndex={-1} tabIndex={-1}
title={localized('Edit')}
className={`btn btn-toolbar ${!editable && 'btn-disabled'}`} className={`btn btn-toolbar ${!editable && 'btn-disabled'}`}
onClick={editable ? () => Store.setEditing(actionSet[0].id) : undefined} onClick={editable ? () => Store.setEditing(actionSet[0].id) : undefined}
> >

View file

@ -3,11 +3,13 @@ import {
Account, Account,
AccountStore, AccountStore,
ContactGroup, ContactGroup,
ContactBook,
Rx, Rx,
Actions, Actions,
DestroyContactGroupTask, DestroyContactGroupTask,
SyncbackContactGroupTask, SyncbackContactGroupTask,
ChangeContactGroupMembershipTask, ChangeContactGroupMembershipTask,
localized,
} from 'mailspring-exports'; } from 'mailspring-exports';
import { ContactsPerspective, Store } from './Store'; import { ContactsPerspective, Store } from './Store';
import { import {
@ -16,12 +18,14 @@ import {
OutlineViewItem, OutlineViewItem,
ListensToFluxStore, ListensToFluxStore,
ListensToObservable, ListensToObservable,
IOutlineViewItem,
} from 'mailspring-component-kit'; } from 'mailspring-component-kit';
import { isEqual } from 'underscore'; import { isEqual } from 'underscore';
interface ContactsPerspectivesProps { interface ContactsPerspectivesProps {
accounts: Account[]; accounts: Account[];
groups: ContactGroup[]; groups: ContactGroup[];
books: ContactBook[];
findInMailDisabled: string[]; findInMailDisabled: string[];
selected: ContactsPerspective | null; selected: ContactsPerspective | null;
onSelect: (item: ContactsPerspective | null) => void; onSelect: (item: ContactsPerspective | null) => void;
@ -36,9 +40,115 @@ function perspectiveForGroup(g: ContactGroup): ContactsPerspective {
}; };
} }
interface OutlineViewForAccountProps {
account: Account;
groups: ContactGroup[];
books: ContactBook[];
findInMailDisabled: boolean;
selected: ContactsPerspective | null;
onSelect: (item: ContactsPerspective | null) => void;
}
const OutlineViewForAccount = ({
account,
groups,
books,
selected,
onSelect,
findInMailDisabled,
}: OutlineViewForAccountProps) => {
const items: IOutlineViewItem[] = [];
if (books.length) {
items.push({
id: 'all-contacts',
name: localized('All Contacts'),
iconName: 'person.png',
children: [],
selected: selected && selected.type === 'all',
onSelect: () =>
onSelect({ accountId: account.id, type: 'all', label: localized('All Contacts') }),
shouldAcceptDrop: () => false,
});
for (const group of groups) {
const perspective = perspectiveForGroup(group);
items.push({
id: `${perspective.accountId}-${perspective.label}`,
name: perspective.label,
iconName: 'label.png',
children: [],
selected: isEqual(selected, perspective),
onSelect: () => onSelect(perspective),
onEdited: (item, value: string) => {
Actions.queueTask(SyncbackContactGroupTask.forRenaming(group, value));
},
onDelete: () => {
Actions.queueTask(DestroyContactGroupTask.forRemoving(group));
},
onDrop: (item, { dataTransfer }) => {
const data = JSON.parse(dataTransfer.getData('mailspring-contacts-data'));
const contacts = data.ids.map(i => Store.filteredContacts().find(c => c.id === i));
Actions.queueTask(
ChangeContactGroupMembershipTask.forMoving({
direction: 'add',
contacts,
group,
})
);
},
shouldAcceptDrop: (item, { dataTransfer }) => {
if (!dataTransfer.types.includes('mailspring-contacts-data')) {
return false;
}
if (isEqual(selected, perspective)) {
return false;
}
// We can't inspect the drag payload until drop, so we use a dataTransfer
// type to encode the account IDs of threads currently being dragged.
const accountsType = dataTransfer.types.find(t => t.startsWith('mailspring-accounts='));
const accountIds = (accountsType || '').replace('mailspring-accounts=', '').split(',');
return isEqual(accountIds, [perspective.accountId]);
},
});
}
}
items.push({
id: 'found-in-mail',
name: localized('Found in Mail'),
iconName: 'inbox.png',
children: [],
className: findInMailDisabled ? 'found-in-mail-disabled' : '',
selected: selected && selected.type === 'found-in-mail',
shouldAcceptDrop: () => false,
onSelect: () =>
onSelect({
accountId: account.id,
type: 'found-in-mail',
label: `${localized('Found in Mail')} (${account.label})`,
}),
});
return (
<OutlineView
title={account.label}
items={items}
onItemCreated={
books.length > 0
? name => Actions.queueTask(SyncbackContactGroupTask.forCreating(account.id, name))
: undefined
}
/>
);
};
const ContactsPerspectivesWithData: React.FunctionComponent<ContactsPerspectivesProps> = ({ const ContactsPerspectivesWithData: React.FunctionComponent<ContactsPerspectivesProps> = ({
findInMailDisabled, findInMailDisabled,
groups, groups,
books,
accounts, accounts,
selected, selected,
onSelect, onSelect,
@ -57,87 +167,14 @@ const ContactsPerspectivesWithData: React.FunctionComponent<ContactsPerspectives
/> />
</section> </section>
{accounts.map(a => ( {accounts.map(a => (
<OutlineView <OutlineViewForAccount
key={a.id} key={a.id}
title={a.label} account={a}
onItemCreated={name => Actions.queueTask(SyncbackContactGroupTask.forCreating(a.id, name))} findInMailDisabled={findInMailDisabled.includes(a.id)}
items={[ books={books.filter(b => b.accountId === a.id)}
{ groups={groups.filter(b => b.accountId === a.id)}
id: 'all-contacts', selected={selected && selected.accountId === a.id ? selected : null}
name: 'All Contacts', onSelect={onSelect}
iconName: 'person.png',
children: [],
selected: selected && selected.accountId == a.id && selected.type === 'all',
onSelect: () => onSelect({ accountId: a.id, type: 'all', label: 'All Contacts' }),
shouldAcceptDrop: () => false,
},
...groups
.filter(g => g.accountId === a.id)
.map(group => {
const perspective = perspectiveForGroup(group);
return {
id: `${perspective.accountId}-${perspective.label}`,
name: perspective.label,
iconName: 'label.png',
children: [],
selected: isEqual(selected, perspective),
onSelect: () => onSelect(perspective),
onEdited: (item, value: string) => {
Actions.queueTask(SyncbackContactGroupTask.forRenaming(group, value));
},
onDelete: () => {
Actions.queueTask(DestroyContactGroupTask.forRemoving(group));
},
onDrop: (item, { dataTransfer }) => {
const data = JSON.parse(dataTransfer.getData('mailspring-contacts-data'));
const contacts = data.ids.map(i =>
Store.filteredContacts().find(c => c.id === i)
);
Actions.queueTask(
ChangeContactGroupMembershipTask.forMoving({
direction: 'add',
contacts,
group,
})
);
},
shouldAcceptDrop: (item, { dataTransfer }) => {
if (!dataTransfer.types.includes('mailspring-contacts-data')) {
return false;
}
if (isEqual(selected, perspective)) {
return false;
}
// We can't inspect the drag payload until drop, so we use a dataTransfer
// type to encode the account IDs of threads currently being dragged.
const accountsType = dataTransfer.types.find(t =>
t.startsWith('mailspring-accounts=')
);
const accountIds = (accountsType || '')
.replace('mailspring-accounts=', '')
.split(',');
return isEqual(accountIds, [perspective.accountId]);
},
};
}),
{
id: 'found-in-mail',
name: 'Found in Mail',
iconName: 'inbox.png',
children: [],
className: findInMailDisabled.includes(a.id) ? 'found-in-mail-disabled' : '',
selected: selected && selected.accountId == a.id && selected.type === 'found-in-mail',
shouldAcceptDrop: () => false,
onSelect: () =>
onSelect({
accountId: a.id,
type: 'found-in-mail',
label: `Found in Mail (${a.label})`,
}),
},
]}
/> />
))} ))}
</ScrollRegion> </ScrollRegion>
@ -148,6 +185,7 @@ export const ContactPerspectivesList = ListensToObservable(
stores: [AccountStore, Store], stores: [AccountStore, Store],
getStateFromStores: () => ({ getStateFromStores: () => ({
accounts: AccountStore.accounts(), accounts: AccountStore.accounts(),
books: Store.books(),
groups: Store.groups(), groups: Store.groups(),
selected: Store.perspective(), selected: Store.perspective(),
onSelect: s => Store.setPerspective(s), onSelect: s => Store.setPerspective(s),

View file

@ -1,5 +1,11 @@
import Rx from 'rx-lite'; import Rx from 'rx-lite';
import { DatabaseStore, Contact, ContactGroup, MutableQuerySubscription } from 'mailspring-exports'; import {
DatabaseStore,
Contact,
ContactGroup,
ContactBook,
MutableQuerySubscription,
} from 'mailspring-exports';
import MailspringStore from 'mailspring-store'; import MailspringStore from 'mailspring-store';
import { ListTabular } from 'mailspring-component-kit'; import { ListTabular } from 'mailspring-component-kit';
@ -10,6 +16,7 @@ class ContactsWindowStore extends MailspringStore {
_contacts: Contact[] = []; _contacts: Contact[] = [];
_contactsSubscription: MutableQuerySubscription<Contact>; _contactsSubscription: MutableQuerySubscription<Contact>;
_groups: ContactGroup[] = []; _groups: ContactGroup[] = [];
_books: ContactBook[] = [];
_search: string = ''; _search: string = '';
_filtered: Contact[] | null = null; _filtered: Contact[] | null = null;
_editing: string | 'new' | false = false; _editing: string | 'new' | false = false;
@ -24,6 +31,7 @@ class ContactsWindowStore extends MailspringStore {
.where(Contact.attributes.refs.greaterThan(0)) .where(Contact.attributes.refs.greaterThan(0))
.where(Contact.attributes.hidden.equal(false)); .where(Contact.attributes.hidden.equal(false));
this._contactsSubscription = new MutableQuerySubscription<Contact>(contacts); this._contactsSubscription = new MutableQuerySubscription<Contact>(contacts);
Rx.Observable.fromNamedQuerySubscription('contacts', this._contactsSubscription).subscribe( Rx.Observable.fromNamedQuerySubscription('contacts', this._contactsSubscription).subscribe(
contacts => { contacts => {
this._contacts = contacts as Contact[]; this._contacts = contacts as Contact[];
@ -32,14 +40,24 @@ class ContactsWindowStore extends MailspringStore {
} }
); );
const groups = Rx.Observable.fromQuery(DatabaseStore.findAll<ContactGroup>(ContactGroup)); Rx.Observable.fromQuery(DatabaseStore.findAll<ContactGroup>(ContactGroup)).subscribe(
groups.subscribe(groups => { groups => {
this._groups = groups; this._groups = groups;
this.trigger();
}
);
Rx.Observable.fromQuery(DatabaseStore.findAll<ContactBook>(ContactBook)).subscribe(books => {
this._books = books;
this.trigger(); this.trigger();
}); });
}); });
} }
books() {
return this._books;
}
groups() { groups() {
return this._groups; return this._groups;
} }

View file

@ -8,7 +8,7 @@ import { FoundInMailEnabledBar } from './FoundInMailEnabledBar';
function adjustMenus() { function adjustMenus() {
const contactMenu: typeof AppEnv.menu.template[0] = { const contactMenu: typeof AppEnv.menu.template[0] = {
key: 'Contact', id: 'Contact',
label: localized('Contact'), label: localized('Contact'),
submenu: [ submenu: [
{ {
@ -32,10 +32,8 @@ function adjustMenus() {
], ],
}; };
const template = AppEnv.menu.template.filter( const template = AppEnv.menu.template.filter(item => item.id !== 'Thread' && item.id !== 'View');
item => item.key !== 'Thread' && item.key !== 'View' const editIndex = template.findIndex(item => item.id === 'Edit');
);
const editIndex = template.findIndex(item => item.key === 'Edit');
template.splice(editIndex + 1, 0, contactMenu); template.splice(editIndex + 1, 0, contactMenu);
AppEnv.menu.template = template; AppEnv.menu.template = template;

View file

@ -44,7 +44,8 @@
"window:select-account-8": "mod+9", "window:select-account-8": "mod+9",
"window:sync-mail-now": "f5", "window:sync-mail-now": "f5",
"application:show-main-window": "mod+0",
"contenteditable:underline": "mod+u", "contenteditable:underline": "mod+u",
"contenteditable:bold": "mod+b", "contenteditable:bold": "mod+b",
"contenteditable:italic": "mod+i", "contenteditable:italic": "mod+i",

View file

@ -3,7 +3,7 @@ const { localized } = require('../src/intl');
module.exports = { module.exports = {
menu: [ menu: [
{ {
key: 'Mailspring', id: 'Mailspring',
label: 'Mailspring', label: 'Mailspring',
submenu: [ submenu: [
{ label: localized('About Mailspring'), command: 'application:about' }, { label: localized('About Mailspring'), command: 'application:about' },
@ -42,7 +42,7 @@ module.exports = {
], ],
}, },
{ {
key: 'File', id: 'File',
label: localized('File'), label: localized('File'),
submenu: [ submenu: [
{ label: localized('Sync New Mail Now'), command: 'window:sync-mail-now' }, { label: localized('Sync New Mail Now'), command: 'window:sync-mail-now' },
@ -56,7 +56,7 @@ module.exports = {
}, },
{ {
key: 'Edit', id: 'Edit',
label: localized('Edit'), label: localized('Edit'),
submenu: [ submenu: [
{ label: localized('Undo'), command: 'core:undo' }, { label: localized('Undo'), command: 'core:undo' },
@ -86,7 +86,7 @@ module.exports = {
}, },
{ {
key: 'View', id: 'View',
label: localized('View'), label: localized('View'),
submenu: [ submenu: [
{ {
@ -140,7 +140,7 @@ module.exports = {
}, },
{ {
key: 'Thread', id: 'Thread',
label: localized('Thread'), label: localized('Thread'),
submenu: [ submenu: [
{ label: localized('Reply'), command: 'core:reply' }, { label: localized('Reply'), command: 'core:reply' },
@ -192,7 +192,7 @@ module.exports = {
}, },
{ {
key: 'Developer', id: 'Developer',
label: localized('Developer'), label: localized('Developer'),
submenu: [ submenu: [
{ {
@ -202,7 +202,6 @@ module.exports = {
}, },
{ type: 'separator' }, { type: 'separator' },
{ label: localized('Calendar Preview'), command: 'application:show-calendar' }, { label: localized('Calendar Preview'), command: 'application:show-calendar' },
{ label: localized('Contacts Preview'), command: 'application:show-contacts' },
{ type: 'separator' }, { type: 'separator' },
{ label: localized('Create a Plugin') + '...', command: 'window:create-package' }, { label: localized('Create a Plugin') + '...', command: 'window:create-package' },
{ label: localized('Install a Plugin') + '...', command: 'window:install-package' }, { label: localized('Install a Plugin') + '...', command: 'window:install-package' },
@ -221,13 +220,22 @@ module.exports = {
], ],
}, },
{ {
key: 'Window', id: 'Window',
label: localized('Window'), label: localized('Window'),
submenu: [ submenu: [
{ label: localized('Minimize'), command: 'application:minimize' }, { label: localized('Minimize'), command: 'application:minimize' },
{ label: localized('Zoom'), command: 'application:zoom' }, { label: localized('Zoom'), command: 'application:zoom' },
{ type: 'separator', id: 'window-list-separator' },
{ type: 'separator' }, { type: 'separator' },
{
label: localized('Message Viewer'),
command: 'application:show-main-window',
},
{
label: localized('Contacts'),
command: 'application:show-contacts',
},
{ type: 'separator', id: 'window-list-separator' },
{ type: 'separator', id: 'account-shortcuts-separator' },
{ {
label: localized('Bring All to Front'), label: localized('Bring All to Front'),
command: 'application:bring-all-windows-to-front', command: 'application:bring-all-windows-to-front',
@ -236,7 +244,7 @@ module.exports = {
}, },
{ {
key: 'Help', id: 'Help',
label: localized('Help'), label: localized('Help'),
submenu: [ submenu: [
{ label: localized('Mailspring Help'), command: 'application:view-help' }, { label: localized('Mailspring Help'), command: 'application:view-help' },

View file

@ -3,7 +3,7 @@ const { localized } = require('../src/intl');
module.exports = { module.exports = {
menu: [ menu: [
{ {
key: 'File', id: 'File',
label: localized('File'), label: localized('File'),
submenu: [ submenu: [
{ label: localized('Sync New Mail Now'), command: 'window:sync-mail-now' }, { label: localized('Sync New Mail Now'), command: 'window:sync-mail-now' },
@ -24,7 +24,7 @@ module.exports = {
}, },
{ {
key: 'Edit', id: 'Edit',
label: localized('Edit'), label: localized('Edit'),
submenu: [ submenu: [
{ label: localized('Undo'), command: 'core:undo' }, { label: localized('Undo'), command: 'core:undo' },
@ -58,7 +58,7 @@ module.exports = {
}, },
{ {
key: 'View', id: 'View',
label: localized('View'), label: localized('View'),
submenu: [ submenu: [
{ {
@ -110,7 +110,7 @@ module.exports = {
}, },
{ {
key: 'Thread', id: 'Thread',
label: localized('Thread'), label: localized('Thread'),
submenu: [ submenu: [
{ label: localized('Reply'), command: 'core:reply' }, { label: localized('Reply'), command: 'core:reply' },
@ -161,7 +161,7 @@ module.exports = {
], ],
}, },
{ {
key: 'Developer', id: 'Developer',
label: localized('Developer'), label: localized('Developer'),
submenu: [ submenu: [
{ {
@ -171,7 +171,6 @@ module.exports = {
}, },
{ type: 'separator' }, { type: 'separator' },
{ label: localized('Calendar Preview'), command: 'application:show-calendar' }, { label: localized('Calendar Preview'), command: 'application:show-calendar' },
{ label: localized('Contacts Preview'), command: 'application:show-contacts' },
{ type: 'separator' }, { type: 'separator' },
{ label: localized('Create a Plugin') + '...', command: 'window:create-package' }, { label: localized('Create a Plugin') + '...', command: 'window:create-package' },
{ label: localized('Install a Plugin') + '...', command: 'window:install-package' }, { label: localized('Install a Plugin') + '...', command: 'window:install-package' },
@ -189,16 +188,27 @@ module.exports = {
], ],
}, },
{ {
key: 'Window', id: 'Window',
label: localized('Window'), label: localized('Window'),
submenu: [ submenu: [
{ label: localized('Minimize'), command: 'application:minimize' }, { label: localized('Minimize'), command: 'application:minimize' },
{ label: localized('Zoom'), command: 'application:zoom' }, { label: localized('Zoom'), command: 'application:zoom' },
{ type: 'separator' },
{
label: localized('Message Viewer'),
command: 'application:show-main-window',
accelerator: 'CmdOrCtrl+0',
},
{
label: localized('Contacts'),
command: 'application:show-contacts',
},
{ type: 'separator', id: 'window-list-separator' }, { type: 'separator', id: 'window-list-separator' },
{ type: 'separator', id: 'account-shortcuts-separator' },
], ],
}, },
{ {
key: 'Help', id: 'Help',
label: localized('Help'), label: localized('Help'),
submenu: [ submenu: [
{ label: 'VERSION', enabled: false }, { label: 'VERSION', enabled: false },

View file

@ -3,7 +3,7 @@ const { localized } = require('../src/intl');
module.exports = { module.exports = {
menu: [ menu: [
{ {
key: 'Edit', id: 'Edit',
label: localized('Edit'), label: localized('Edit'),
submenu: [ submenu: [
{ label: localized('Undo'), command: 'core:undo' }, { label: localized('Undo'), command: 'core:undo' },
@ -33,7 +33,7 @@ module.exports = {
}, },
{ {
key: 'View', id: 'View',
label: localized('View'), label: localized('View'),
submenu: [ submenu: [
{ {
@ -87,7 +87,7 @@ module.exports = {
}, },
{ {
key: 'Thread', id: 'Thread',
label: localized('Thread'), label: localized('Thread'),
submenu: [ submenu: [
{ label: localized('Reply'), command: 'core:reply' }, { label: localized('Reply'), command: 'core:reply' },
@ -138,7 +138,7 @@ module.exports = {
], ],
}, },
{ {
key: 'Developer', id: 'Developer',
label: localized('Developer'), label: localized('Developer'),
submenu: [ submenu: [
{ {
@ -148,7 +148,6 @@ module.exports = {
}, },
{ type: 'separator' }, { type: 'separator' },
{ label: localized('Calendar Preview'), command: 'application:show-calendar' }, { label: localized('Calendar Preview'), command: 'application:show-calendar' },
{ label: localized('Contacts Preview'), command: 'application:show-contacts' },
{ type: 'separator' }, { type: 'separator' },
{ label: localized('Create a Plugin') + '...', command: 'window:create-package' }, { label: localized('Create a Plugin') + '...', command: 'window:create-package' },
{ label: localized('Install a Plugin') + '...', command: 'window:install-package' }, { label: localized('Install a Plugin') + '...', command: 'window:install-package' },
@ -167,12 +166,23 @@ module.exports = {
], ],
}, },
{ {
key: 'Window', id: 'Window',
label: localized('Window'), label: localized('Window'),
submenu: [ submenu: [
{ label: localized('Minimize'), command: 'application:minimize' }, { label: localized('Minimize'), command: 'application:minimize' },
{ label: localized('Zoom'), command: 'application:zoom' }, { label: localized('Zoom'), command: 'application:zoom' },
{ type: 'separator' },
{
label: localized('Message Viewer'),
command: 'application:show-main-window',
accelerator: 'CmdOrCtrl+0',
},
{
label: localized('Contacts'),
command: 'application:show-contacts',
},
{ type: 'separator', id: 'window-list-separator' }, { type: 'separator', id: 'window-list-separator' },
{ type: 'separator', id: 'account-shortcuts-separator' },
], ],
}, },
{ type: 'separator' }, { type: 'separator' },

View file

@ -154,24 +154,17 @@ export default class ApplicationMenu {
} }
const idx = windowMenu.submenu.findIndex(({ id }) => id === 'window-list-separator'); const idx = windowMenu.submenu.findIndex(({ id }) => id === 'window-list-separator');
let workShortcut = 'CmdOrCtrl+alt+w';
if (process.platform === 'win32') {
workShortcut = 'ctrl+shift+w';
}
const accelerators = {
default: 'CmdOrCtrl+0',
work: workShortcut,
};
const windows = global.application.windowManager.getOpenWindows(); const windows = global.application.windowManager.getOpenWindows();
const windowsItems = windows.map(w => ({ const windowsItems = windows
label: w.loadSettings().title || 'Window', .filter(w => w.windowType !== 'default' && w.windowType !== 'contacts')
accelerator: accelerators[w.windowType], .map(w => ({
click() { label: w.loadSettings().title || 'Window',
w.show(); click() {
w.focus(); w.show();
}, w.focus();
})); },
}));
return windowMenu.submenu.splice(idx, 0, { type: 'separator' }, ...windowsItems); return windowMenu.submenu.splice(idx, 0, { type: 'separator' }, ...windowsItems);
} }

View file

@ -67,7 +67,7 @@ interface OutlineViewState {
* @param {props.onCollapseToggled} props.onCollapseToggled * @param {props.onCollapseToggled} props.onCollapseToggled
* @class OutlineView * @class OutlineView
*/ */
class OutlineView extends Component<OutlineViewProps, OutlineViewState> { export class OutlineView extends Component<OutlineViewProps, OutlineViewState> {
static displayName = 'OutlineView'; static displayName = 'OutlineView';
/* /*
@ -232,5 +232,3 @@ class OutlineView extends Component<OutlineViewProps, OutlineViewState> {
); );
} }
} }
export default OutlineView;

View file

@ -0,0 +1,32 @@
/* eslint global-require: 0 */
import { Model, AttributeValues } from './model';
import Attributes from '../attributes';
export class ContactBook extends Model {
static attributes = {
...Model.attributes,
readonly: Attributes.String({
modelKey: 'readonly',
}),
source: Attributes.String({
modelKey: 'source',
}),
};
readonly: boolean;
source: 'carddav' | 'gpeople';
static sortOrderAttribute = () => {
return ContactBook.attributes.id;
};
static naturalSortOrder = () => {
return ContactBook.sortOrderAttribute().ascending();
};
constructor(data: AttributeValues<typeof ContactBook.attributes>) {
super(data);
}
}

View file

@ -1,5 +1,4 @@
/* eslint global-require: 0 */ /* eslint global-require: 0 */
import _str from 'underscore.string';
import { Model, AttributeValues } from './model'; import { Model, AttributeValues } from './model';
import Attributes from '../attributes'; import Attributes from '../attributes';
@ -12,6 +11,8 @@ export class ContactGroup extends Model {
}), }),
}; };
public name: string;
static sortOrderAttribute = () => { static sortOrderAttribute = () => {
return ContactGroup.attributes.name; return ContactGroup.attributes.name;
}; };
@ -20,8 +21,6 @@ export class ContactGroup extends Model {
return ContactGroup.sortOrderAttribute().ascending(); return ContactGroup.sortOrderAttribute().ascending();
}; };
public name: string;
constructor(data: AttributeValues<typeof ContactGroup.attributes>) { constructor(data: AttributeValues<typeof ContactGroup.attributes>) {
super(data); super(data);
} }

View file

@ -40,7 +40,7 @@ export * from '../components/disclosure-triangle';
export const EditableList: typeof import('../components/editable-list').default; export const EditableList: typeof import('../components/editable-list').default;
export const DropdownMenu: typeof import('../components/dropdown-menu').default; export const DropdownMenu: typeof import('../components/dropdown-menu').default;
export const OutlineViewItem: typeof import('../components/outline-view-item').default; export const OutlineViewItem: typeof import('../components/outline-view-item').default;
export const OutlineView: typeof import('../components/outline-view').default; export * from '../components/outline-view';
export const DateInput: typeof import('../components/date-input').default; export const DateInput: typeof import('../components/date-input').default;
export const DatePicker: typeof import('../components/date-picker').default; export const DatePicker: typeof import('../components/date-picker').default;
export const TimePicker: typeof import('../components/time-picker').default; export const TimePicker: typeof import('../components/time-picker').default;

View file

@ -44,6 +44,7 @@ export * from '../flux/models/thread';
export * from '../flux/models/account'; export * from '../flux/models/account';
export * from '../flux/models/message'; export * from '../flux/models/message';
export * from '../flux/models/contact'; export * from '../flux/models/contact';
export * from '../flux/models/contact-book';
export * from '../flux/models/contact-group'; export * from '../flux/models/contact-group';
export * from '../flux/models/category'; export * from '../flux/models/category';
export * from '../flux/models/calendar'; export * from '../flux/models/calendar';
@ -80,6 +81,8 @@ export * from '../flux/tasks/change-role-mapping-task';
export * from '../flux/tasks/send-feature-usage-event-task'; export * from '../flux/tasks/send-feature-usage-event-task';
export * from '../flux/tasks/syncback-contact-task'; export * from '../flux/tasks/syncback-contact-task';
export * from '../flux/tasks/destroy-contact-task'; export * from '../flux/tasks/destroy-contact-task';
export * from '../flux/tasks/destroy-contactgroup-task';
export * from '../flux/tasks/syncback-contactgroup-task';
export * from '../flux/tasks/change-contactgroup-membership-task'; export * from '../flux/tasks/change-contactgroup-membership-task';
// Stores // Stores

View file

@ -81,6 +81,7 @@ lazyLoadAndRegisterModel(`Thread`, 'thread');
lazyLoadAndRegisterModel(`Account`, 'account'); lazyLoadAndRegisterModel(`Account`, 'account');
lazyLoadAndRegisterModel(`Message`, 'message'); lazyLoadAndRegisterModel(`Message`, 'message');
lazyLoadAndRegisterModel(`Contact`, 'contact'); lazyLoadAndRegisterModel(`Contact`, 'contact');
lazyLoadAndRegisterModel(`ContactBook`, 'contact-book');
lazyLoadAndRegisterModel(`ContactGroup`, 'contact-group'); lazyLoadAndRegisterModel(`ContactGroup`, 'contact-group');
lazyLoadAndRegisterModel(`Category`, 'category'); lazyLoadAndRegisterModel(`Category`, 'category');
lazyLoadAndRegisterModel(`Calendar`, 'calendar'); lazyLoadAndRegisterModel(`Calendar`, 'calendar');

View file

@ -8,24 +8,17 @@ import _ from 'underscore';
const ItemSpecificities = new WeakMap(); const ItemSpecificities = new WeakMap();
export type IMenuItem = export type IMenuItem = {
| { label?: string;
label: string; submenu?: IMenuItem[];
submenu?: IMenuItem[]; type?: 'separator';
type?: string;
key?: string; //unlocalized label id?: string; //unlocalized label
command?: string; command?: string;
enabled?: boolean; enabled?: boolean;
hideWhenDisabled?: boolean; hideWhenDisabled?: boolean;
visible?: boolean; visible?: boolean;
} };
| {
key?: string; //unlocalized label
label?: string;
submenu?: IMenuItem[];
type: 'separator';
};
export function merge(menu: IMenuItem[], item: IMenuItem, itemSpecificity?: number) { export function merge(menu: IMenuItem[], item: IMenuItem, itemSpecificity?: number) {
let matchingItem; let matchingItem;

@ -1 +1 @@
Subproject commit e78951655f7bc194029c709549c0f5c1a29e9c85 Subproject commit 8c3e2ad5ba07b19fd96fe18cbe589b8c05c72e8c