Unsnooze to top of inbox by default, fallback to mark as unread #267

This commit is contained in:
Ben Gotow 2017-10-30 17:02:05 -07:00
parent ef0fe94d71
commit 5a145c415c
11 changed files with 92 additions and 57 deletions

View file

@ -1,4 +1,4 @@
import { React, PropTypes, Actions, SendActionsStore } from 'mailspring-exports';
import { React, PropTypes, Actions, SendActionsStore, SoundRegistry } from 'mailspring-exports';
import { Menu, RetinaImg, ButtonDropdown, ListensToFluxStore } from 'mailspring-component-kit';
class SendActionButton extends React.Component {
@ -26,6 +26,9 @@ class SendActionButton extends React.Component {
_onSendWithAction = sendAction => {
const { isValidDraft, draft } = this.props;
if (isValidDraft()) {
if (AppEnv.config.get('core.sending.sounds')) {
SoundRegistry.playSound('hit-send');
}
Actions.sendDraft(draft.headerMessageId, sendAction.configKey);
}
};

View file

@ -1,12 +1,9 @@
import {
Actions,
FocusedContentStore,
SyncbackDraftTask,
SendDraftTask,
DatabaseStore,
AccountStore,
TaskQueue,
Thread,
Contact,
DraftFactory,
} from 'mailspring-exports';
import MailspringStore from 'mailspring-store';
@ -36,26 +33,13 @@ class SendRemindersStore extends MailspringStore {
}
_sendReminderEmail = async (thread, sentHeaderMessageId) => {
const account = AccountStore.accountForId(thread.accountId);
const draft = await DraftFactory.createDraft({
from: [new Contact({ email: account.emailAddress, name: `${account.name} via Mailspring` })],
to: [account.defaultMe()],
cc: [],
pristine: false,
subject: thread.subject,
threadId: thread.id,
accountId: thread.accountId,
replyToHeaderMessageId: sentHeaderMessageId,
body: `
<strong>Mailspring Reminder:</strong> This thread has been moved to the top of
your inbox by Mailspring because no one has replied to your message.</p>
<p>--The Mailspring Team</p>`,
});
const body = `
<strong>Mailspring Reminder:</strong> This thread has been moved to the top of
your inbox by Mailspring because no one has replied to your message.</p>
<p>--The Mailspring Team</p>`;
const saveTask = new SyncbackDraftTask({ draft });
Actions.queueTask(saveTask);
await TaskQueue.waitForPerformLocal(saveTask);
Actions.sendDraft(draft.headerMessageId);
const draft = await DraftFactory.createDraftForResurfacing(thread, sentHeaderMessageId, body);
Actions.queueTask(new SendDraftTask({ draft, silent: true }));
};
_onDraftDeliverySucceeded = ({ headerMessageId, accountId }) => {

View file

@ -29,7 +29,7 @@ class SnoozeMailLabel extends Component {
}
const metadata = thread.metadataForPluginId(PLUGIN_ID);
if (!metadata) {
if (!metadata || !metadata.expiration) {
return false;
}
const content = (

View file

@ -8,7 +8,7 @@ import {
Thread,
} from 'mailspring-exports';
import { markUnreadIfSet, moveThreads, snoozedUntilMessage } from './snooze-utils';
import { markUnreadOrResurfaceThreads, moveThreads, snoozedUntilMessage } from './snooze-utils';
import { PLUGIN_ID } from './snooze-constants';
import SnoozeActions from './snooze-actions';
@ -92,23 +92,7 @@ class SnoozeStore extends MailspringStore {
moveThreads(threads, { snooze: false, description: 'Unsnoozed' });
// mark the threads unread if setting is enabled
markUnreadIfSet(threads, 'Unsnoozed message');
// remove the expiration on the metadata. note this is super important,
// otherwise we'll receive a notification from the sync worker over and
// over again.
Actions.queueTasks(
threads.map(
model =>
new SyncbackMetadataTask({
model,
pluginId: PLUGIN_ID,
value: {
expiration: null,
},
})
)
);
markUnreadOrResurfaceThreads(threads, 'Unsnoozed message');
};
_onMetadataExpired = threads => {
@ -117,7 +101,25 @@ class SnoozeStore extends MailspringStore {
return metadata && metadata.expiration && metadata.expiration < new Date();
});
if (unsnooze.length > 0) {
this._onUnsnoozeThreads(unsnooze);
// remove the expiration on the metadata. note this is super important,
// otherwise we'll receive a notification from the sync worker over and
// over again.
Actions.queueTasks(
threads.map(
model =>
new SyncbackMetadataTask({
model,
pluginId: PLUGIN_ID,
value: {
expiration: null,
},
})
)
);
// unsnooze messages that are still in the snoozed folder. (The user may have
// moved the thread out of the snoozed folder using another client )
this._onUnsnoozeThreads(unsnooze.filter(t => t.categories.find(c => c.role === 'snoozed')));
}
};
}

View file

@ -7,6 +7,8 @@ import {
CategoryStore,
ChangeLabelsTask,
ChangeFolderTask,
DraftFactory,
SendDraftTask,
} from 'mailspring-exports';
export function snoozedUntilMessage(snoozeDate, now = moment()) {
@ -40,6 +42,7 @@ export function moveThreads(threads, { snooze, description } = {}) {
taskDescription: description,
labelsToAdd: snooze ? [snoozeCat] : [inboxCat],
labelsToRemove: snooze ? [inboxCat] : [snoozeCat],
canBeUndone: snooze ? true : false,
});
}
return new ChangeFolderTask({
@ -47,20 +50,37 @@ export function moveThreads(threads, { snooze, description } = {}) {
threads: accountThreads,
taskDescription: description,
folder: snooze ? snoozeCat : inboxCat,
canBeUndone: snooze ? true : false,
});
});
Actions.queueTasks(tasks);
}
export function markUnreadIfSet(threads, source) {
if (AppEnv.config.get('core.notifications.unreadOnSnooze')) {
export async function markUnreadOrResurfaceThreads(threads, source) {
if (AppEnv.config.get('core.notifications.unsnoozeToTop')) {
// send a hidden email that will mark the thread as unread and bring it
// to the top of your inbox in any mail client
const body = `
<strong>Mailspring Reminder:</strong> This thread has been moved to the top of
your inbox by Mailspring.</p>
<p>--The Mailspring Team</p>`;
for (const thread of threads) {
const draft = await DraftFactory.createDraftForResurfacing(thread, null, body);
Actions.queueTask(new SendDraftTask({ draft, silent: true }));
}
} else {
// just mark the threads as unread (unless they're all already unread)
if (!threads.some(t => !t.unread)) {
return;
}
Actions.queueTask(
TaskFactory.taskForSettingUnread({
unread: true,
threads: threads,
source: source,
canBeUndone: true,
canBeUndone: false,
})
);
}

View file

@ -158,10 +158,10 @@ export default {
default: true,
title: 'Play sound when receiving new mail',
},
unreadOnSnooze: {
unsnoozeToTop: {
type: 'boolean',
default: false,
title: 'Mark a message unread when returning from snooze',
default: true,
title: 'Resurface messages to the top of the inbox when unsnoozing',
},
countBadge: {
type: 'string',

View file

@ -351,7 +351,7 @@ export default class Message extends ModelWithMetadata {
this.to.length === 1 &&
this.from.length === 1 &&
this.to[0].email === this.from[0].email &&
(this.snippet || '').startsWith('Mailspring Reminder:');
(this.from[0].name || '').endsWith('via Mailspring');
const isDraftBeingDeleted = this.id.startsWith('deleted-');
return isReminder || isDraftBeingDeleted;

View file

@ -103,7 +103,7 @@ module.exports =
setupEmitter: ->
return if @_emitter
@_emitter ?= new EventEmitter()
@_emitter.setMaxListeners(50)
@_emitter.setMaxListeners(100)
listen: (callback, bindContext) ->
if not callback

View file

@ -158,6 +158,33 @@ class DraftFactory
</div>"""
)
createDraftForResurfacing: (thread, threadMessageId, body) =>
account = AccountStore.accountForId(thread.accountId)
if threadMessageId
rthmsid = Promise.resolve(threadMessageId)
else
rthmsid = DatabaseStore
.findBy(Message, {accountId: thread.accountId, threadId: thread.id})
.order(Message.attributes.date.descending())
.limit(1)
.then((msg) =>
return (msg && msg.headerMessageId || "")
)
return rthmsid.then((replyToHeaderMessageId) =>
@createDraft({
from: [new Contact({ email: account.emailAddress, name: "#{account.name} via Mailspring" })],
to: [account.defaultMe()],
cc: [],
pristine: false,
subject: thread.subject,
threadId: thread.id,
accountId: thread.accountId,
replyToHeaderMessageId: replyToHeaderMessageId,
body: body
})
)
candidateDraftForUpdating: (message, behavior) =>
if behavior not in ['prefer-existing-if-pristine', 'prefer-existing']
return Promise.resolve(null)

View file

@ -146,6 +146,9 @@ class DraftStore extends MailspringStore {
};
_onSendQuickReply = ({ thread, threadId, message, messageId }, body) => {
if (AppEnv.config.get('core.sending.sounds')) {
SoundRegistry.playSound('hit-send');
}
return Promise.props(this._modelifyContext({ thread, threadId, message, messageId }))
.then(({ message: m, thread: t }) => {
return DraftFactory.createDraftForReply({ message: m, thread: t, type: 'reply' });
@ -346,10 +349,6 @@ class DraftStore extends MailspringStore {
throw new Error(`Cant find send action ${sendActionKey} `);
}
if (AppEnv.config.get('core.sending.sounds')) {
SoundRegistry.playSound('hit-send');
}
// get the draft session, apply any last-minute edits and get the final draft.
// We need to call `changes.commit` here to ensure the body of the draft is
// completely saved and the user won't see old content briefly.

View file

@ -67,7 +67,7 @@ export default class SendDraftTask extends Task {
}
label() {
return 'Sending message';
return this.silent ? null : 'Sending message';
}
validate() {