mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-02-22 15:15:12 +08:00
We can now ask Google for auth/contacts permission (not readonly!)
This commit is contained in:
parent
56f18a340b
commit
c4d7b229f4
11 changed files with 79 additions and 51 deletions
|
@ -17,17 +17,19 @@ class AddContactToolbarWithData extends React.Component<AddContactToolbarProps>
|
|||
}
|
||||
|
||||
onAdd = () => {
|
||||
if (showGPeopleReadonlyNotice(this.props.perspective.accountId)) {
|
||||
return;
|
||||
}
|
||||
const { perspective } = this.props;
|
||||
|
||||
if (!('accountId' in perspective)) return;
|
||||
if (showGPeopleReadonlyNotice(perspective.accountId)) return;
|
||||
|
||||
Actions.setFocus({ collection: 'contact', item: null });
|
||||
Store.setEditing('new');
|
||||
};
|
||||
|
||||
render() {
|
||||
const { editing, perspective } = this.props;
|
||||
const enabled = editing === false && perspective && perspective.accountId;
|
||||
const acct = perspective && AccountStore.accountForId(perspective.accountId);
|
||||
const enabled = 'accountId' in perspective && editing === false && perspective.accountId;
|
||||
const acct = 'accountId' in perspective && AccountStore.accountForId(perspective.accountId);
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', order: 1000 }}>
|
||||
|
|
|
@ -73,7 +73,7 @@ class ContactDetailWithFocus extends React.Component<ContactDetailProps, Contact
|
|||
const { editing, contacts, focusedId, perspective } = this.props;
|
||||
|
||||
const contact =
|
||||
editing === 'new'
|
||||
editing === 'new' && 'accountId' in perspective
|
||||
? emptyContactForAccountId(perspective.accountId)
|
||||
: contacts.find(c => c.id === focusedId);
|
||||
|
||||
|
@ -95,11 +95,14 @@ class ContactDetailWithFocus extends React.Component<ContactDetailProps, Contact
|
|||
};
|
||||
|
||||
onSaveChanges = () => {
|
||||
const { perspective } = this.props;
|
||||
const contact = apply(this.state.contact, this.state.data);
|
||||
|
||||
if (!('accountId' in perspective)) return;
|
||||
|
||||
const task = contact.id
|
||||
? SyncbackContactTask.forUpdating({ contact })
|
||||
: SyncbackContactTask.forCreating({ contact, accountId: this.props.perspective.accountId });
|
||||
: SyncbackContactTask.forCreating({ contact, accountId: perspective.accountId });
|
||||
Actions.queueTask(task);
|
||||
Store.setEditing(false);
|
||||
};
|
||||
|
|
|
@ -50,22 +50,19 @@ class ContactDetailToolbarWithData extends React.Component<ContactDetailToolbarP
|
|||
};
|
||||
|
||||
_onEdit = () => {
|
||||
if (showGPeopleReadonlyNotice(this.props.perspective.accountId)) {
|
||||
const contacts = this.actionSet();
|
||||
const contact = contacts[0];
|
||||
if (!contact || showGPeopleReadonlyNotice(contact.accountId)) {
|
||||
return;
|
||||
}
|
||||
const actionSet = this.actionSet();
|
||||
Store.setEditing(actionSet[0].id);
|
||||
Store.setEditing(contact.id);
|
||||
};
|
||||
|
||||
_onDelete = () => {
|
||||
const contacts = this.actionSet();
|
||||
if (
|
||||
contacts.some(c => c.source === 'gpeople') &&
|
||||
showGPeopleReadonlyNotice(this.props.perspective.accountId)
|
||||
) {
|
||||
if (contacts.some(c => c.source === 'gpeople' && showGPeopleReadonlyNotice(c.accountId))) {
|
||||
return;
|
||||
}
|
||||
|
||||
Actions.queueTask(
|
||||
DestroyContactTask.forRemoving({
|
||||
contacts: this.actionSet(),
|
||||
|
@ -90,7 +87,7 @@ class ContactDetailToolbarWithData extends React.Component<ContactDetailToolbarP
|
|||
}
|
||||
|
||||
const commands = {};
|
||||
if (perspective && perspective.type === 'group' && actionSet.length > 0) {
|
||||
if (perspective.type === 'group' && actionSet.length > 0) {
|
||||
commands['core:remove-from-view'] = this._onRemoveFromSource;
|
||||
}
|
||||
if (actionSet.length > 0) {
|
||||
|
@ -103,7 +100,7 @@ class ContactDetailToolbarWithData extends React.Component<ContactDetailToolbarP
|
|||
return (
|
||||
<BindGlobalCommands key={Object.keys(commands).join(',')} commands={commands}>
|
||||
<div style={{ display: 'flex', order: 1000, marginRight: 10 }}>
|
||||
{perspective && perspective.type === 'group' && (
|
||||
{perspective.type === 'group' && (
|
||||
<button
|
||||
tabIndex={-1}
|
||||
title={localized('Remove from Group')}
|
||||
|
|
|
@ -110,7 +110,7 @@ const ContactListSearchWithData = (props: ContactListSearchWithDataProps) => {
|
|||
ref={this._searchEl}
|
||||
value={props.search}
|
||||
placeholder={`${localized('Search')} ${
|
||||
props.perspective ? props.perspective.label : 'All Contacts'
|
||||
props.perspective.type === 'unified' ? 'All Contacts' : props.perspective.label
|
||||
}`}
|
||||
onChange={e => props.setSearch(e.currentTarget.value)}
|
||||
/>
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
ChangeContactGroupMembershipTask,
|
||||
localized,
|
||||
} from 'mailspring-exports';
|
||||
import { ContactsPerspective, Store } from './Store';
|
||||
import { ContactsPerspective, Store, ContactsPerspectiveForGroup } from './Store';
|
||||
import {
|
||||
ScrollRegion,
|
||||
OutlineView,
|
||||
|
@ -28,11 +28,11 @@ interface ContactsPerspectivesProps {
|
|||
groups: ContactGroup[];
|
||||
books: ContactBook[];
|
||||
findInMailDisabled: string[];
|
||||
selected: ContactsPerspective | null;
|
||||
onSelect: (item: ContactsPerspective | null) => void;
|
||||
selected: ContactsPerspective;
|
||||
onSelect: (item: ContactsPerspective) => void;
|
||||
}
|
||||
|
||||
function perspectiveForGroup(g: ContactGroup): ContactsPerspective {
|
||||
function perspectiveForGroup(g: ContactGroup): ContactsPerspectiveForGroup {
|
||||
return {
|
||||
accountId: g.accountId,
|
||||
type: 'group',
|
||||
|
@ -47,7 +47,7 @@ interface OutlineViewForAccountProps {
|
|||
books: ContactBook[];
|
||||
findInMailDisabled: boolean;
|
||||
selected: ContactsPerspective | null;
|
||||
onSelect: (item: ContactsPerspective | null) => void;
|
||||
onSelect: (item: ContactsPerspective) => void;
|
||||
}
|
||||
|
||||
const OutlineViewForAccount = ({
|
||||
|
@ -178,8 +178,8 @@ const ContactsPerspectivesWithData: React.FunctionComponent<ContactsPerspectives
|
|||
name: 'All Contacts',
|
||||
iconName: 'people.png',
|
||||
children: [],
|
||||
selected: selected === null,
|
||||
onSelect: () => onSelect(null),
|
||||
selected: selected.type === 'unified',
|
||||
onSelect: () => onSelect({ type: 'unified' }),
|
||||
}}
|
||||
/>
|
||||
</section>
|
||||
|
@ -190,7 +190,7 @@ const ContactsPerspectivesWithData: React.FunctionComponent<ContactsPerspectives
|
|||
findInMailDisabled={findInMailDisabled.includes(a.id)}
|
||||
books={books.filter(b => b.accountId === a.id)}
|
||||
groups={groups.filter(b => b.accountId === a.id)}
|
||||
selected={selected && selected.accountId === a.id ? selected : null}
|
||||
selected={'accountId' in selected && selected.accountId === a.id ? selected : null}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))}
|
||||
|
|
|
@ -1,13 +1,22 @@
|
|||
import { AccountStore } from 'mailspring-exports';
|
||||
import { AccountStore, localized } from 'mailspring-exports';
|
||||
import { remote } from 'electron';
|
||||
|
||||
const CONTACTS_OAUTH_SCOPE_ADDED = new Date(1581867440474);
|
||||
|
||||
export const showGPeopleReadonlyNotice = (accountId: string) => {
|
||||
const acct = AccountStore.accountForId(accountId);
|
||||
if (acct && acct.provider === 'gmail') {
|
||||
if (
|
||||
acct &&
|
||||
acct.provider === 'gmail' &&
|
||||
(!acct.authedAt || acct.authedAt < CONTACTS_OAUTH_SCOPE_ADDED)
|
||||
) {
|
||||
remote.dialog.showMessageBox({
|
||||
message: 'Coming Soon for Google Accounts',
|
||||
detail:
|
||||
"We've added support for creating, updating and deleting contacts in Google accounts, but Google is still reviewing Mailspring's use of the Google People API so we're unable to sync these changes back to their servers. Stay tuned!",
|
||||
message: localized(`Please re-authenticate with Google`),
|
||||
detail: localized(
|
||||
`To make changes to contacts in this account, you'll need to re-authorize Mailspring to access your data.\n\n` +
|
||||
`In Mailspring's main window, go to Preferences > Accounts, select this account, and click "Re-authenticate". ` +
|
||||
`You'll be prompted to give Mailspring additional permission to update and delete your contacts.`
|
||||
),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ import MailspringStore from 'mailspring-store';
|
|||
import { ListTabular } from 'mailspring-component-kit';
|
||||
|
||||
class ContactsWindowStore extends MailspringStore {
|
||||
_perspective: ContactsPerspective | null = null;
|
||||
_perspective: ContactsPerspective = { type: 'unified' };
|
||||
_listSource = new ListTabular.DataSource.DumbArrayDataSource<Contact>();
|
||||
|
||||
_contacts: Contact[] = [];
|
||||
|
@ -83,15 +83,15 @@ class ContactsWindowStore extends MailspringStore {
|
|||
this.trigger();
|
||||
}
|
||||
|
||||
setPerspective(perspective: ContactsPerspective | null) {
|
||||
setPerspective(perspective: ContactsPerspective) {
|
||||
let q = DatabaseStore.findAll<Contact>(Contact)
|
||||
.where(Contact.attributes.refs.greaterThan(0))
|
||||
.where(Contact.attributes.hidden.equal(false));
|
||||
|
||||
if (perspective && perspective.type === 'all') {
|
||||
if (perspective.type === 'all') {
|
||||
q.where(Contact.attributes.source.not('mail'));
|
||||
}
|
||||
if (perspective && perspective.type === 'group') {
|
||||
if (perspective.type === 'group') {
|
||||
q.where(Contact.attributes.contactGroups.contains(perspective.groupId));
|
||||
}
|
||||
|
||||
|
@ -116,13 +116,14 @@ class ContactsWindowStore extends MailspringStore {
|
|||
}
|
||||
|
||||
repopulate() {
|
||||
const perspective = this._perspective;
|
||||
let filtered = [...this._contacts];
|
||||
|
||||
if (this._perspective) {
|
||||
if (perspective.type !== 'unified') {
|
||||
filtered = filtered.filter(c => {
|
||||
if (c.accountId !== this._perspective.accountId) return false;
|
||||
if (c.source !== 'mail' && this._perspective.type === 'found-in-mail') return false;
|
||||
if (c.source === 'mail' && this._perspective.type !== 'found-in-mail') return false;
|
||||
if (c.accountId !== perspective.accountId) return false;
|
||||
if (c.source !== 'mail' && perspective.type === 'found-in-mail') return false;
|
||||
if (c.source === 'mail' && perspective.type !== 'found-in-mail') return false;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
@ -140,7 +141,15 @@ class ContactsWindowStore extends MailspringStore {
|
|||
}
|
||||
}
|
||||
|
||||
export type ContactsPerspectiveForGroup = {
|
||||
label: string;
|
||||
accountId: string;
|
||||
groupId: string;
|
||||
type: 'group';
|
||||
};
|
||||
|
||||
export type ContactsPerspective =
|
||||
| { type: 'unified' }
|
||||
| {
|
||||
label: string;
|
||||
accountId: string;
|
||||
|
@ -151,11 +160,6 @@ export type ContactsPerspective =
|
|||
accountId: string;
|
||||
type: 'found-in-mail';
|
||||
}
|
||||
| {
|
||||
label: string;
|
||||
accountId: string;
|
||||
groupId: string;
|
||||
type: 'group';
|
||||
};
|
||||
| ContactsPerspectiveForGroup;
|
||||
|
||||
export const Store = new ContactsWindowStore();
|
||||
|
|
|
@ -130,5 +130,5 @@ export const parseName = (name: { _data: string } | null) => {
|
|||
};
|
||||
|
||||
export const formatDisplayName = (name: ContactBase['name']) => {
|
||||
return `${name.givenName} ${name.familyName}`;
|
||||
return `${name.givenName || ''} ${name.familyName || ''}`.trim();
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@ const GMAIL_SCOPES = [
|
|||
'https://www.googleapis.com/auth/userinfo.email', // email address
|
||||
'https://www.googleapis.com/auth/userinfo.profile', // G+ profile
|
||||
'https://mail.google.com/', // email
|
||||
'https://www.googleapis.com/auth/contacts.readonly', // contacts
|
||||
'https://www.googleapis.com/auth/contacts', // contacts
|
||||
'https://www.googleapis.com/auth/calendar', // calendar
|
||||
];
|
||||
|
||||
|
@ -206,7 +206,7 @@ export function buildGmailAuthURL() {
|
|||
)}&access_type=offline&select_account%20consent`;
|
||||
}
|
||||
|
||||
export async function finalizeAndValidateAccount(account) {
|
||||
export async function finalizeAndValidateAccount(account: Account) {
|
||||
if (account.settings.imap_host) {
|
||||
account.settings.imap_host = account.settings.imap_host.trim();
|
||||
}
|
||||
|
@ -217,7 +217,9 @@ export async function finalizeAndValidateAccount(account) {
|
|||
account.id = idForAccount(account.emailAddress, account.settings);
|
||||
|
||||
// handle special case for exchange/outlook/hotmail username field
|
||||
account.settings.username = account.settings.username || account.settings.email;
|
||||
// TODO BG: I don't think this line is in use but not 100% sure
|
||||
(account.settings as any).username =
|
||||
(account.settings as any).username || (account.settings as any).email;
|
||||
|
||||
if (account.settings.imap_port) {
|
||||
account.settings.imap_port /= 1;
|
||||
|
@ -233,6 +235,8 @@ export async function finalizeAndValidateAccount(account) {
|
|||
const proc = new MailsyncProcess(AppEnv.getLoadSettings());
|
||||
proc.identity = IdentityStore.identity();
|
||||
proc.account = account;
|
||||
const { response } = await proc.test();
|
||||
return new Account(response.account);
|
||||
await proc.test();
|
||||
|
||||
// Record the date of successful auth
|
||||
account.authedAt = new Date();
|
||||
}
|
||||
|
|
|
@ -77,18 +77,25 @@ export class Account extends ModelWithMetadata {
|
|||
syncError: Attributes.Object({
|
||||
modelKey: 'syncError',
|
||||
}),
|
||||
|
||||
authedAt: Attributes.DateTime({
|
||||
modelKey: 'authedAt',
|
||||
}),
|
||||
};
|
||||
|
||||
public name: string;
|
||||
public provider: string;
|
||||
public emailAddress: string;
|
||||
public authedAt: Date;
|
||||
public settings: {
|
||||
imap_host: string;
|
||||
imap_port: number;
|
||||
imap_username: string;
|
||||
imap_password: string;
|
||||
imap_allow_insecure_ssl: boolean;
|
||||
imap_security: 'SSL / TLS' | 'STARTTLS' | 'none';
|
||||
smtp_host: string;
|
||||
smtp_port: number;
|
||||
smtp_username: string;
|
||||
smtp_password: string;
|
||||
smtp_allow_insecure_ssl: boolean;
|
||||
|
@ -107,6 +114,7 @@ export class Account extends ModelWithMetadata {
|
|||
this.aliases = this.aliases || [];
|
||||
this.label = this.label || this.emailAddress;
|
||||
this.syncState = this.syncState || Account.SYNC_STATE_OK;
|
||||
this.authedAt = this.authedAt || new Date(0);
|
||||
this.autoaddress = this.autoaddress || {
|
||||
type: 'bcc',
|
||||
value: '',
|
||||
|
|
|
@ -240,6 +240,7 @@ class _AccountStore extends MailspringStore {
|
|||
const existing = this._accounts[existingIdx];
|
||||
existing.syncState = Account.SYNC_STATE_OK;
|
||||
existing.name = cleanAccount.name;
|
||||
existing.authedAt = cleanAccount.authedAt;
|
||||
existing.emailAddress = cleanAccount.emailAddress;
|
||||
existing.settings = cleanAccount.settings;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue