mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-20 07:16:08 +08:00
Making composer recipient name warnings optional (#2420)
* recipient warnings and errors split into a separate step for drafts * checking and updating recipient warning blacklist through localstorage * add option to reset emails that ignore warning in preferences
This commit is contained in:
parent
c02898ac66
commit
c763b612ec
|
@ -342,7 +342,7 @@ export default class ComposerView extends React.Component<ComposerViewProps, Com
|
|||
});
|
||||
};
|
||||
|
||||
_isValidDraft = (options: { force?: boolean } = {}) => {
|
||||
_isValidDraft = (options: { forceRecipientWarnings?: boolean, forceMiscWarnings?: boolean } = {}) => {
|
||||
// We need to check the `DraftStore` because the `DraftStore` is
|
||||
// immediately and synchronously updated as soon as this function
|
||||
// fires. Since `setState` is asynchronous, if we used that as our only
|
||||
|
@ -353,31 +353,61 @@ export default class ComposerView extends React.Component<ComposerViewProps, Com
|
|||
|
||||
const dialog = require('@electron/remote').dialog;
|
||||
const { session } = this.props;
|
||||
const { errors, warnings } = session.validateDraftForSending();
|
||||
const { recipientErrors, recipientWarnings } = session.validateDraftRecipients();
|
||||
const { miscErrors, miscWarnings } = session.validateDraftForSending();
|
||||
|
||||
if (errors.length > 0) {
|
||||
// Display errors
|
||||
if (recipientErrors.length > 0) {
|
||||
dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
buttons: [localized('Edit Message'), localized('Cancel')],
|
||||
message: localized('Cannot send message'),
|
||||
detail: errors[0],
|
||||
detail: recipientErrors[0],
|
||||
});
|
||||
return false;
|
||||
}
|
||||
if (miscErrors.length > 0) {
|
||||
dialog.showMessageBox({
|
||||
type: 'warning',
|
||||
buttons: [localized('Edit Message'), localized('Cancel')],
|
||||
message: localized('Cannot send message'),
|
||||
detail: miscErrors[0],
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
if (warnings.length > 0 && !options.force) {
|
||||
// Display warnings
|
||||
if (recipientWarnings.length > 0 && !options.forceRecipientWarnings) {
|
||||
const response = dialog.showMessageBoxSync({
|
||||
type: 'warning',
|
||||
buttons: [localized('Send Anyway'), localized('Send & Ignore Warnings For This Email'), localized('Cancel')],
|
||||
message: localized('Are you sure?'),
|
||||
detail: recipientWarnings.join('. '),
|
||||
});
|
||||
if (response === 0) {
|
||||
// response is button array index
|
||||
return this._isValidDraft({ forceRecipientWarnings: true, forceMiscWarnings: options.forceMiscWarnings });
|
||||
} else if (response === 1) {
|
||||
// Send & Ignore Future Warnings for Recipient Email
|
||||
session.addRecipientsToWarningBlacklist()
|
||||
return this._isValidDraft({ forceRecipientWarnings: true, forceMiscWarnings: options.forceMiscWarnings });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (miscWarnings.length > 0 && !options.forceMiscWarnings) {
|
||||
const response = dialog.showMessageBoxSync({
|
||||
type: 'warning',
|
||||
buttons: [localized('Send Anyway'), localized('Cancel')],
|
||||
message: localized('Are you sure?'),
|
||||
detail: warnings.join('. '),
|
||||
detail: miscWarnings.join('. '),
|
||||
});
|
||||
if (response === 0) {
|
||||
// response is button array index
|
||||
return this._isValidDraft({ force: true });
|
||||
return this._isValidDraft({ forceRecipientWarnings: options.forceRecipientWarnings, forceMiscWarnings: true });
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
|
|
|
@ -27,6 +27,10 @@ class PreferencesGeneral extends React.Component<{
|
|||
app.quit();
|
||||
};
|
||||
|
||||
_onResetEmailsThatIgnoreWarnings = () => {
|
||||
localStorage.removeItem("recipientWarningBlacklist");
|
||||
}
|
||||
|
||||
_onResetAccountsAndSettings = () => {
|
||||
const chosen = require('@electron/remote').dialog.showMessageBoxSync({
|
||||
type: 'info',
|
||||
|
@ -55,6 +59,7 @@ class PreferencesGeneral extends React.Component<{
|
|||
ipc.send('command', 'application:reset-database', {});
|
||||
};
|
||||
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="container-general">
|
||||
|
@ -76,6 +81,9 @@ class PreferencesGeneral extends React.Component<{
|
|||
<div className="two-columns-flexbox" style={{ paddingTop: 30 }}>
|
||||
<div style={{ flex: 1 }}>
|
||||
<SendingSection config={this.props.config} configSchema={this.props.configSchema} />
|
||||
<div className="btn" onClick={this._onResetEmailsThatIgnoreWarnings} style={{ marginLeft: 0, marginTop:5 }}>
|
||||
{localized('Reset Emails that Ignore Warnings')}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ width: 30 }} />
|
||||
<div style={{ flex: 1 }}>
|
||||
|
|
64
app/package-lock.json
generated
64
app/package-lock.json
generated
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "mailspring",
|
||||
"version": "1.10.4",
|
||||
"version": "1.10.5",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "mailspring",
|
||||
"version": "1.10.4",
|
||||
"version": "1.10.5",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@bengotow/slate-edit-list": "github:bengotow/slate-edit-list#b868e108",
|
||||
|
@ -460,9 +460,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/array.prototype.flat/node_modules/object.assign": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.3.tgz",
|
||||
"integrity": "sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
|
||||
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.4",
|
||||
|
@ -1667,9 +1667,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/enzyme/node_modules/object.assign": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.3.tgz",
|
||||
"integrity": "sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
|
||||
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.4",
|
||||
|
@ -4821,9 +4821,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/string.prototype.trim/node_modules/object.assign": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.3.tgz",
|
||||
"integrity": "sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
|
||||
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.4",
|
||||
|
@ -4972,9 +4972,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/string.prototype.trimend/node_modules/object.assign": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.3.tgz",
|
||||
"integrity": "sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
|
||||
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.4",
|
||||
|
@ -5123,9 +5123,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/string.prototype.trimstart/node_modules/object.assign": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.3.tgz",
|
||||
"integrity": "sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
|
||||
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.4",
|
||||
|
@ -6001,9 +6001,9 @@
|
|||
}
|
||||
},
|
||||
"object.assign": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.3.tgz",
|
||||
"integrity": "sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
|
||||
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.4",
|
||||
|
@ -6918,9 +6918,9 @@
|
|||
}
|
||||
},
|
||||
"object.assign": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.3.tgz",
|
||||
"integrity": "sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
|
||||
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.4",
|
||||
|
@ -9554,9 +9554,9 @@
|
|||
}
|
||||
},
|
||||
"object.assign": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.3.tgz",
|
||||
"integrity": "sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
|
||||
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.4",
|
||||
|
@ -9656,9 +9656,9 @@
|
|||
}
|
||||
},
|
||||
"object.assign": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.3.tgz",
|
||||
"integrity": "sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
|
||||
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.4",
|
||||
|
@ -9758,9 +9758,9 @@
|
|||
}
|
||||
},
|
||||
"object.assign": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.3.tgz",
|
||||
"integrity": "sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
|
||||
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
"define-properties": "^1.1.4",
|
||||
|
|
|
@ -246,22 +246,55 @@ export class DraftEditingSession extends MailspringStore {
|
|||
}
|
||||
|
||||
validateDraftForSending() {
|
||||
const warnings = [];
|
||||
const errors = [];
|
||||
const allRecipients = [...this._draft.to, ...this._draft.cc, ...this._draft.bcc];
|
||||
const miscWarnings = [];
|
||||
const miscErrors = [];
|
||||
const hasAttachment = this._draft.files && this._draft.files.length > 0;
|
||||
|
||||
if (this._draft.subject.length === 0) {
|
||||
miscWarnings.push(localized('The subject field is blank.'));
|
||||
}
|
||||
|
||||
let cleaned = QuotedHTMLTransformer.removeQuotedHTML(this._draft.body.trim());
|
||||
const sigIndex = cleaned.search(RegExpUtils.mailspringSignatureRegex());
|
||||
cleaned = sigIndex > -1 ? cleaned.substr(0, sigIndex) : cleaned;
|
||||
|
||||
const signatureIndex = cleaned.indexOf('<signature>');
|
||||
if (signatureIndex !== -1) {
|
||||
cleaned = cleaned.substr(0, signatureIndex - 1);
|
||||
}
|
||||
|
||||
if (cleaned.toLowerCase().includes('attach') && !hasAttachment) {
|
||||
miscWarnings.push(localized('The message mentions an attachment but none are attached.'));
|
||||
}
|
||||
|
||||
// Check third party warnings added via Composer extensions
|
||||
for (const extension of ComposerExtensionRegistry.extensions()) {
|
||||
if (!extension.warningsForSending) {
|
||||
continue;
|
||||
}
|
||||
miscWarnings.push(...extension.warningsForSending({ draft: this._draft }));
|
||||
}
|
||||
|
||||
return { miscErrors, miscWarnings };
|
||||
}
|
||||
|
||||
|
||||
validateDraftRecipients() {
|
||||
const recipientWarnings = [];
|
||||
const recipientErrors = [];
|
||||
const allRecipients = [...this._draft.to, ...this._draft.cc, ...this._draft.bcc];
|
||||
|
||||
const allNames = [...Utils.commonlyCapitalizedSalutations];
|
||||
let unnamedRecipientPresent = false;
|
||||
|
||||
for (const contact of allRecipients) {
|
||||
if (!ContactStore.isValidContact(contact)) {
|
||||
errors.push(
|
||||
recipientErrors.push(
|
||||
`${contact.email} is not a valid email address - please remove or edit it before sending.`
|
||||
);
|
||||
}
|
||||
const name = contact.fullName();
|
||||
if (name && name.length && name !== contact.email) {
|
||||
if (name && name.length && name !== contact.email && !this.checkRecipientInWarningBlacklist(contact.email)) {
|
||||
allNames.push(name.toLowerCase()); // ben gotow
|
||||
allNames.push(...name.toLowerCase().split(' ')); // ben, gotow
|
||||
allNames.push(...name.toLowerCase().split('-')); // anne-marie => anne, marie
|
||||
|
@ -276,17 +309,13 @@ export class DraftEditingSession extends MailspringStore {
|
|||
}
|
||||
|
||||
if (allRecipients.length === 0) {
|
||||
errors.push(
|
||||
recipientErrors.push(
|
||||
localized('You need to provide one or more recipients before sending the message.')
|
||||
);
|
||||
}
|
||||
|
||||
if (errors.length > 0) {
|
||||
return { errors, warnings };
|
||||
}
|
||||
|
||||
if (this._draft.subject.length === 0) {
|
||||
warnings.push(localized('The subject field is blank.'));
|
||||
if (recipientErrors.length > 0) {
|
||||
return { errors: recipientErrors, warnings: recipientWarnings };
|
||||
}
|
||||
|
||||
let cleaned = QuotedHTMLTransformer.removeQuotedHTML(this._draft.body.trim());
|
||||
|
@ -298,10 +327,6 @@ export class DraftEditingSession extends MailspringStore {
|
|||
cleaned = cleaned.substr(0, signatureIndex - 1);
|
||||
}
|
||||
|
||||
if (cleaned.toLowerCase().includes('attach') && !hasAttachment) {
|
||||
warnings.push(localized('The message mentions an attachment but none are attached.'));
|
||||
}
|
||||
|
||||
if (!unnamedRecipientPresent) {
|
||||
// https://www.regexpal.com/?fam=99334
|
||||
// note: requires that the name is capitalized, to avoid catching "Hey guys"
|
||||
|
@ -312,7 +337,7 @@ export class DraftEditingSession extends MailspringStore {
|
|||
if (salutation.endsWith('-')) salutation = salutation.substr(0, salutation.length - 1);
|
||||
|
||||
if (!allNames.find(n => n === salutation || (n.length > 1 && salutation.includes(n)))) {
|
||||
warnings.push(
|
||||
recipientWarnings.push(
|
||||
localized(
|
||||
`The message is addressed to a name that doesn't appear to be a recipient ("%@")`,
|
||||
match[1]
|
||||
|
@ -322,17 +347,25 @@ export class DraftEditingSession extends MailspringStore {
|
|||
}
|
||||
}
|
||||
|
||||
// Check third party warnings added via Composer extensions
|
||||
for (const extension of ComposerExtensionRegistry.extensions()) {
|
||||
if (!extension.warningsForSending) {
|
||||
continue;
|
||||
}
|
||||
warnings.push(...extension.warningsForSending({ draft: this._draft }));
|
||||
}
|
||||
|
||||
return { errors, warnings };
|
||||
return { recipientErrors, recipientWarnings };
|
||||
}
|
||||
|
||||
addRecipientsToWarningBlacklist() {
|
||||
const allRecipients = [...this._draft.to, ...this._draft.cc, ...this._draft.bcc];
|
||||
const allRecipientEmails = allRecipients.map(contact => contact.email);
|
||||
let blacklist = JSON.parse(localStorage.getItem("recipientWarningBlacklist"));
|
||||
if (blacklist === null) blacklist = [];
|
||||
blacklist.push(...allRecipientEmails);
|
||||
localStorage.setItem("recipientWarningBlacklist", JSON.stringify(blacklist));
|
||||
}
|
||||
|
||||
checkRecipientInWarningBlacklist(email) {
|
||||
const blacklist = JSON.parse(localStorage.getItem("recipientWarningBlacklist"));
|
||||
if (blacklist && blacklist.includes(email)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// This function makes sure the draft is attached to a valid account, and changes
|
||||
// it's accountId if the from address does not match the account for the from
|
||||
// address.
|
||||
|
|
Loading…
Reference in a new issue