mirror of
https://github.com/mjl-/mox.git
synced 2024-12-26 16:33:47 +03:00
webmail: better save/close/cancel buttons in compose window
- keep them on the right side of the window (more important now that we can resize) - merge the close & cancel buttons into a close button, with a popup asking what to do for changes not saved as draft.
This commit is contained in:
parent
76aa96ab6f
commit
4d28a02621
2 changed files with 23 additions and 36 deletions
|
@ -2443,7 +2443,7 @@ const cmdHelp = async () => {
|
||||||
].map(t => dom.tr(dom.td(t[0]), dom.td(t[1]))))), dom.div(style({ width: '40em' }), dom.table(dom.tr(dom.td(attr.colspan('2'), dom.h2('Compose', style({ margin: '0' })))), [
|
].map(t => dom.tr(dom.td(t[0]), dom.td(t[1]))))), dom.div(style({ width: '40em' }), dom.table(dom.tr(dom.td(attr.colspan('2'), dom.h2('Compose', style({ margin: '0' })))), [
|
||||||
['ctrl Enter', 'send message'],
|
['ctrl Enter', 'send message'],
|
||||||
['ctrl shift Enter', 'send message and archive thread'],
|
['ctrl shift Enter', 'send message and archive thread'],
|
||||||
['ctrl w', 'cancel message'],
|
['ctrl w', 'close message'],
|
||||||
['ctrl O', 'add To'],
|
['ctrl O', 'add To'],
|
||||||
['ctrl C', 'add Cc'],
|
['ctrl C', 'add Cc'],
|
||||||
['ctrl B', 'add Bcc'],
|
['ctrl B', 'add Bcc'],
|
||||||
|
@ -2624,17 +2624,20 @@ const compose = (opts, listMailboxes) => {
|
||||||
// when focus is on a textarea or not any specific UI element. So this isn't always
|
// when focus is on a textarea or not any specific UI element. So this isn't always
|
||||||
// triggered. But we still have the beforeunload handler that checks for
|
// triggered. But we still have the beforeunload handler that checks for
|
||||||
// unsavedChanges to protect the user in such cases.
|
// unsavedChanges to protect the user in such cases.
|
||||||
const cmdCancel = async () => {
|
const cmdClose = async () => {
|
||||||
draftCancelSave();
|
draftCancelSave();
|
||||||
await draftSavePromise;
|
await draftSavePromise;
|
||||||
if (unsavedChanges()) {
|
if (unsavedChanges()) {
|
||||||
const action = await new Promise((resolve) => {
|
const action = await new Promise((resolve) => {
|
||||||
const remove = popup(dom.p('Message has unsaved changes.'), dom.br(), dom.div(dom.clickbutton('Save draft', function click() {
|
const remove = popup(dom.p(dom.b('Message has unsaved changes')), dom.br(), dom.div(dom.clickbutton('Save draft', function click() {
|
||||||
resolve('save');
|
resolve('save');
|
||||||
remove();
|
remove();
|
||||||
}), ' ', dom.clickbutton('Remove draft', function click() {
|
}), ' ', draftMessageID ? dom.clickbutton('Remove draft', function click() {
|
||||||
resolve('remove');
|
resolve('remove');
|
||||||
remove();
|
remove();
|
||||||
|
}) : [], ' ', dom.clickbutton('Discard changes', function click() {
|
||||||
|
resolve('discard');
|
||||||
|
remove();
|
||||||
}), ' ', dom.clickbutton('Cancel', function click() {
|
}), ' ', dom.clickbutton('Cancel', function click() {
|
||||||
resolve('cancel');
|
resolve('cancel');
|
||||||
remove();
|
remove();
|
||||||
|
@ -2648,22 +2651,13 @@ const compose = (opts, listMailboxes) => {
|
||||||
await withStatus('Removing draft', client.MessageDelete([draftMessageID]));
|
await withStatus('Removing draft', client.MessageDelete([draftMessageID]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else if (action === 'cancel') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
composeElem.remove();
|
composeElem.remove();
|
||||||
composeView = null;
|
composeView = null;
|
||||||
};
|
};
|
||||||
const cmdClose = async () => {
|
|
||||||
draftCancelSave();
|
|
||||||
await draftSavePromise;
|
|
||||||
if (unsavedChanges()) {
|
|
||||||
await withStatus('Saving draft', draftSave());
|
|
||||||
}
|
|
||||||
composeElem.remove();
|
|
||||||
composeView = null;
|
|
||||||
};
|
|
||||||
const cmdSave = async () => {
|
const cmdSave = async () => {
|
||||||
draftCancelSave();
|
draftCancelSave();
|
||||||
await draftSavePromise;
|
await draftSavePromise;
|
||||||
|
@ -2740,7 +2734,7 @@ const compose = (opts, listMailboxes) => {
|
||||||
const shortcuts = {
|
const shortcuts = {
|
||||||
'ctrl Enter': cmdSend,
|
'ctrl Enter': cmdSend,
|
||||||
'ctrl shift Enter': cmdSendArchive,
|
'ctrl shift Enter': cmdSendArchive,
|
||||||
'ctrl w': cmdCancel,
|
'ctrl w': cmdClose,
|
||||||
'ctrl O': cmdAddTo,
|
'ctrl O': cmdAddTo,
|
||||||
'ctrl C': cmdAddCc,
|
'ctrl C': cmdAddCc,
|
||||||
'ctrl B': cmdAddBcc,
|
'ctrl B': cmdAddBcc,
|
||||||
|
@ -3010,10 +3004,9 @@ const compose = (opts, listMailboxes) => {
|
||||||
flexGrow: '1',
|
flexGrow: '1',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
}), dom.table(style({ width: '100%' }), dom.tr(dom.td(composeTextMildStyle, dom.span('From:')), dom.td(dom.div(style({ display: 'flex', gap: '1em' }), dom.div(from = dom.select(attr.required(''), style({ width: 'auto' }), fromOptions), ' ', toBtn = dom.clickbutton('To', clickCmd(cmdAddTo, shortcuts)), ' ', ccBtn = dom.clickbutton('Cc', clickCmd(cmdAddCc, shortcuts)), ' ', bccBtn = dom.clickbutton('Bcc', clickCmd(cmdAddBcc, shortcuts)), ' ', replyToBtn = dom.clickbutton('ReplyTo', clickCmd(cmdReplyTo, shortcuts)), ' ', customFromBtn = dom.clickbutton('From', attr.title('Set custom From address/name.'), clickCmd(cmdCustomFrom, shortcuts))), dom.div(listMailboxes().find(mb => mb.Draft) ? [
|
}), dom.table(style({ width: '100%' }), dom.tr(dom.td(composeTextMildStyle, dom.span('From:')), dom.td(dom.div(css('composeButtonsSpread', { display: 'flex', gap: '1em', justifyContent: 'space-between' }), dom.div(from = dom.select(attr.required(''), style({ width: 'auto' }), fromOptions), ' ', toBtn = dom.clickbutton('To', clickCmd(cmdAddTo, shortcuts)), ' ', ccBtn = dom.clickbutton('Cc', clickCmd(cmdAddCc, shortcuts)), ' ', bccBtn = dom.clickbutton('Bcc', clickCmd(cmdAddBcc, shortcuts)), ' ', replyToBtn = dom.clickbutton('ReplyTo', clickCmd(cmdReplyTo, shortcuts)), ' ', customFromBtn = dom.clickbutton('From', attr.title('Set custom From address/name.'), clickCmd(cmdCustomFrom, shortcuts))), dom.div(listMailboxes().find(mb => mb.Draft) ? [
|
||||||
dom.clickbutton('Save', attr.title('Save draft message.'), clickCmd(cmdSave, shortcuts)), ' ',
|
dom.clickbutton('Save', attr.title('Save draft message.'), clickCmd(cmdSave, shortcuts)), ' ',
|
||||||
dom.clickbutton('Close', attr.title('Close window, saving draft message if body has changed or a draft was saved earlier.'), clickCmd(cmdClose, shortcuts)), ' ',
|
] : [], dom.clickbutton('Close', attr.title('Close window, saving draft message if body has changed or a draft was saved earlier.'), clickCmd(cmdClose, shortcuts)))))), toRow = dom.tr(dom.td('To:', composeTextMildStyle), toCell = dom.td(composeCellStyle)), replyToRow = dom.tr(dom.td('Reply-To:', composeTextMildStyle), replyToCell = dom.td(composeCellStyle)), ccRow = dom.tr(dom.td('Cc:', composeTextMildStyle), ccCell = dom.td(composeCellStyle)), bccRow = dom.tr(dom.td('Bcc:', composeTextMildStyle), bccCell = dom.td(composeCellStyle)), dom.tr(dom.td('Subject:', composeTextMildStyle), dom.td(subjectAutosize = dom.span(dom._class('autosize'), style({ width: '100%' }), // Without 100% width, the span takes minimal width for input, we want the full table cell.
|
||||||
] : [], dom.clickbutton('Cancel', attr.title('Close window, discarding (draft) message.'), clickCmd(cmdCancel, shortcuts)))))), toRow = dom.tr(dom.td('To:', composeTextMildStyle), toCell = dom.td(composeCellStyle)), replyToRow = dom.tr(dom.td('Reply-To:', composeTextMildStyle), replyToCell = dom.td(composeCellStyle)), ccRow = dom.tr(dom.td('Cc:', composeTextMildStyle), ccCell = dom.td(composeCellStyle)), bccRow = dom.tr(dom.td('Bcc:', composeTextMildStyle), bccCell = dom.td(composeCellStyle)), dom.tr(dom.td('Subject:', composeTextMildStyle), dom.td(subjectAutosize = dom.span(dom._class('autosize'), style({ width: '100%' }), // Without 100% width, the span takes minimal width for input, we want the full table cell.
|
|
||||||
subject = dom.input(style({ width: '100%' }), attr.value(opts.subject || ''), attr.required(''), focusPlaceholder('subject...'), function input() {
|
subject = dom.input(style({ width: '100%' }), attr.value(opts.subject || ''), attr.required(''), focusPlaceholder('subject...'), function input() {
|
||||||
subjectAutosize.dataset.value = subject.value;
|
subjectAutosize.dataset.value = subject.value;
|
||||||
}))))), body = dom.textarea(dom._class('mono'), style({
|
}))))), body = dom.textarea(dom._class('mono'), style({
|
||||||
|
|
|
@ -1236,7 +1236,7 @@ const cmdHelp = async () => {
|
||||||
[
|
[
|
||||||
['ctrl Enter', 'send message'],
|
['ctrl Enter', 'send message'],
|
||||||
['ctrl shift Enter', 'send message and archive thread'],
|
['ctrl shift Enter', 'send message and archive thread'],
|
||||||
['ctrl w', 'cancel message'],
|
['ctrl w', 'close message'],
|
||||||
['ctrl O', 'add To'],
|
['ctrl O', 'add To'],
|
||||||
['ctrl C', 'add Cc'],
|
['ctrl C', 'add Cc'],
|
||||||
['ctrl B', 'add Bcc'],
|
['ctrl B', 'add Bcc'],
|
||||||
|
@ -1506,22 +1506,26 @@ const compose = (opts: ComposeOptions, listMailboxes: listMailboxes) => {
|
||||||
// when focus is on a textarea or not any specific UI element. So this isn't always
|
// when focus is on a textarea or not any specific UI element. So this isn't always
|
||||||
// triggered. But we still have the beforeunload handler that checks for
|
// triggered. But we still have the beforeunload handler that checks for
|
||||||
// unsavedChanges to protect the user in such cases.
|
// unsavedChanges to protect the user in such cases.
|
||||||
const cmdCancel = async () => {
|
const cmdClose = async () => {
|
||||||
draftCancelSave()
|
draftCancelSave()
|
||||||
await draftSavePromise
|
await draftSavePromise
|
||||||
if (unsavedChanges()) {
|
if (unsavedChanges()) {
|
||||||
const action = await new Promise<string>((resolve) => {
|
const action = await new Promise<string>((resolve) => {
|
||||||
const remove = popup(
|
const remove = popup(
|
||||||
dom.p('Message has unsaved changes.'),
|
dom.p(dom.b('Message has unsaved changes')),
|
||||||
dom.br(),
|
dom.br(),
|
||||||
dom.div(
|
dom.div(
|
||||||
dom.clickbutton('Save draft', function click() {
|
dom.clickbutton('Save draft', function click() {
|
||||||
resolve('save')
|
resolve('save')
|
||||||
remove()
|
remove()
|
||||||
}), ' ',
|
}), ' ',
|
||||||
dom.clickbutton('Remove draft', function click() {
|
draftMessageID ? dom.clickbutton('Remove draft', function click() {
|
||||||
resolve('remove')
|
resolve('remove')
|
||||||
remove()
|
remove()
|
||||||
|
}) : [], ' ',
|
||||||
|
dom.clickbutton('Discard changes', function click() {
|
||||||
|
resolve('discard')
|
||||||
|
remove()
|
||||||
}), ' ',
|
}), ' ',
|
||||||
dom.clickbutton('Cancel', function click() {
|
dom.clickbutton('Cancel', function click() {
|
||||||
resolve('cancel')
|
resolve('cancel')
|
||||||
|
@ -1536,7 +1540,7 @@ const compose = (opts: ComposeOptions, listMailboxes: listMailboxes) => {
|
||||||
if (draftMessageID) {
|
if (draftMessageID) {
|
||||||
await withStatus('Removing draft', client.MessageDelete([draftMessageID]))
|
await withStatus('Removing draft', client.MessageDelete([draftMessageID]))
|
||||||
}
|
}
|
||||||
} else {
|
} else if (action === 'cancel') {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1544,15 +1548,6 @@ const compose = (opts: ComposeOptions, listMailboxes: listMailboxes) => {
|
||||||
composeView = null
|
composeView = null
|
||||||
}
|
}
|
||||||
|
|
||||||
const cmdClose = async () => {
|
|
||||||
draftCancelSave()
|
|
||||||
await draftSavePromise
|
|
||||||
if (unsavedChanges()) {
|
|
||||||
await withStatus('Saving draft', draftSave())
|
|
||||||
}
|
|
||||||
composeElem.remove()
|
|
||||||
composeView = null
|
|
||||||
}
|
|
||||||
const cmdSave = async () => {
|
const cmdSave = async () => {
|
||||||
draftCancelSave()
|
draftCancelSave()
|
||||||
await draftSavePromise
|
await draftSavePromise
|
||||||
|
@ -1637,7 +1632,7 @@ const compose = (opts: ComposeOptions, listMailboxes: listMailboxes) => {
|
||||||
const shortcuts: {[key: string]: command} = {
|
const shortcuts: {[key: string]: command} = {
|
||||||
'ctrl Enter': cmdSend,
|
'ctrl Enter': cmdSend,
|
||||||
'ctrl shift Enter': cmdSendArchive,
|
'ctrl shift Enter': cmdSendArchive,
|
||||||
'ctrl w': cmdCancel,
|
'ctrl w': cmdClose,
|
||||||
'ctrl O': cmdAddTo,
|
'ctrl O': cmdAddTo,
|
||||||
'ctrl C': cmdAddCc,
|
'ctrl C': cmdAddCc,
|
||||||
'ctrl B': cmdAddBcc,
|
'ctrl B': cmdAddBcc,
|
||||||
|
@ -1966,7 +1961,7 @@ const compose = (opts: ComposeOptions, listMailboxes: listMailboxes) => {
|
||||||
),
|
),
|
||||||
dom.td(
|
dom.td(
|
||||||
dom.div(
|
dom.div(
|
||||||
style({display: 'flex', gap: '1em'}),
|
css('composeButtonsSpread', {display: 'flex', gap: '1em', justifyContent: 'space-between'}),
|
||||||
dom.div(
|
dom.div(
|
||||||
from=dom.select(
|
from=dom.select(
|
||||||
attr.required(''),
|
attr.required(''),
|
||||||
|
@ -1983,9 +1978,8 @@ const compose = (opts: ComposeOptions, listMailboxes: listMailboxes) => {
|
||||||
dom.div(
|
dom.div(
|
||||||
listMailboxes().find(mb => mb.Draft) ? [
|
listMailboxes().find(mb => mb.Draft) ? [
|
||||||
dom.clickbutton('Save', attr.title('Save draft message.'), clickCmd(cmdSave, shortcuts)), ' ',
|
dom.clickbutton('Save', attr.title('Save draft message.'), clickCmd(cmdSave, shortcuts)), ' ',
|
||||||
dom.clickbutton('Close', attr.title('Close window, saving draft message if body has changed or a draft was saved earlier.'), clickCmd(cmdClose, shortcuts)), ' ',
|
|
||||||
] : [],
|
] : [],
|
||||||
dom.clickbutton('Cancel', attr.title('Close window, discarding (draft) message.'), clickCmd(cmdCancel, shortcuts)),
|
dom.clickbutton('Close', attr.title('Close window, saving draft message if body has changed or a draft was saved earlier.'), clickCmd(cmdClose, shortcuts)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
Loading…
Reference in a new issue