mirror of
https://github.com/mjl-/mox.git
synced 2025-06-28 08:18:16 +03:00
webmail: add button to mark a mailbox and its children as read
this sets the seen flag on all messages in the mailbox and its children.
This commit is contained in:
parent
c8fd9ca664
commit
ad26fd265d
@ -1195,6 +1195,16 @@ func (Webmail) FlagsClear(ctx context.Context, messageIDs []int64, flaglist []st
|
|||||||
xops.MessageFlagsClear(ctx, log, acc, messageIDs, flaglist)
|
xops.MessageFlagsClear(ctx, log, acc, messageIDs, flaglist)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MailboxesMarkRead marks all messages in mailboxes as read. Child mailboxes are
|
||||||
|
// not automatically included, they must explicitly be included in the list of IDs.
|
||||||
|
func (Webmail) MailboxesMarkRead(ctx context.Context, mailboxIDs []int64) {
|
||||||
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
|
acc := reqInfo.Account
|
||||||
|
log := reqInfo.Log
|
||||||
|
|
||||||
|
xops.MailboxesMarkRead(ctx, log, acc, mailboxIDs)
|
||||||
|
}
|
||||||
|
|
||||||
// MailboxCreate creates a new mailbox.
|
// MailboxCreate creates a new mailbox.
|
||||||
func (Webmail) MailboxCreate(ctx context.Context, name string) {
|
func (Webmail) MailboxCreate(ctx context.Context, name string) {
|
||||||
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
reqInfo := ctx.Value(requestInfoCtxKey).(requestInfo)
|
||||||
|
@ -247,6 +247,20 @@
|
|||||||
],
|
],
|
||||||
"Returns": []
|
"Returns": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Name": "MailboxesMarkRead",
|
||||||
|
"Docs": "MailboxesMarkRead marks all messages in mailboxes as read. Child mailboxes are\nnot automatically included, they must explicitly be included in the list of IDs.",
|
||||||
|
"Params": [
|
||||||
|
{
|
||||||
|
"Name": "mailboxIDs",
|
||||||
|
"Typewords": [
|
||||||
|
"[]",
|
||||||
|
"int64"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Returns": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Name": "MailboxCreate",
|
"Name": "MailboxCreate",
|
||||||
"Docs": "MailboxCreate creates a new mailbox.",
|
"Docs": "MailboxCreate creates a new mailbox.",
|
||||||
|
@ -875,6 +875,16 @@ export class Client {
|
|||||||
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
|
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MailboxesMarkRead marks all messages in mailboxes as read. Child mailboxes are
|
||||||
|
// not automatically included, they must explicitly be included in the list of IDs.
|
||||||
|
async MailboxesMarkRead(mailboxIDs: number[] | null): Promise<void> {
|
||||||
|
const fn: string = "MailboxesMarkRead"
|
||||||
|
const paramTypes: string[][] = [["[]","int64"]]
|
||||||
|
const returnTypes: string[][] = []
|
||||||
|
const params: any[] = [mailboxIDs]
|
||||||
|
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params) as void
|
||||||
|
}
|
||||||
|
|
||||||
// MailboxCreate creates a new mailbox.
|
// MailboxCreate creates a new mailbox.
|
||||||
async MailboxCreate(name: string): Promise<void> {
|
async MailboxCreate(name: string): Promise<void> {
|
||||||
const fn: string = "MailboxCreate"
|
const fn: string = "MailboxCreate"
|
||||||
|
@ -227,6 +227,11 @@ func TestAPI(t *testing.T) {
|
|||||||
api.MailboxSetSpecialUse(ctx, store.Mailbox{ID: sent.ID, SpecialUse: store.SpecialUse{Sent: true}}) // Sent, for sending mail later.
|
api.MailboxSetSpecialUse(ctx, store.Mailbox{ID: sent.ID, SpecialUse: store.SpecialUse{Sent: true}}) // Sent, for sending mail later.
|
||||||
tneedError(t, func() { api.MailboxSetSpecialUse(ctx, store.Mailbox{ID: 0}) })
|
tneedError(t, func() { api.MailboxSetSpecialUse(ctx, store.Mailbox{ID: 0}) })
|
||||||
|
|
||||||
|
// MailboxesMarkRead
|
||||||
|
api.FlagsClear(ctx, []int64{inboxText.ID, inboxMinimal.ID}, []string{`\seen`})
|
||||||
|
api.MailboxesMarkRead(ctx, []int64{inbox.ID, archive.ID, sent.ID})
|
||||||
|
tneedError(t, func() { api.MailboxesMarkRead(ctx, []int64{inbox.ID + 999}) }) // Does not exist.
|
||||||
|
|
||||||
// MailboxRename
|
// MailboxRename
|
||||||
api.MailboxRename(ctx, testbox1.ID, "Testbox2")
|
api.MailboxRename(ctx, testbox1.ID, "Testbox2")
|
||||||
api.MailboxRename(ctx, testbox1.ID, "Test/A/B/Box1")
|
api.MailboxRename(ctx, testbox1.ID, "Test/A/B/Box1")
|
||||||
|
@ -556,6 +556,15 @@ var api;
|
|||||||
const params = [messageIDs, flaglist];
|
const params = [messageIDs, flaglist];
|
||||||
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
|
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
|
||||||
}
|
}
|
||||||
|
// MailboxesMarkRead marks all messages in mailboxes as read. Child mailboxes are
|
||||||
|
// not automatically included, they must explicitly be included in the list of IDs.
|
||||||
|
async MailboxesMarkRead(mailboxIDs) {
|
||||||
|
const fn = "MailboxesMarkRead";
|
||||||
|
const paramTypes = [["[]", "int64"]];
|
||||||
|
const returnTypes = [];
|
||||||
|
const params = [mailboxIDs];
|
||||||
|
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
|
||||||
|
}
|
||||||
// MailboxCreate creates a new mailbox.
|
// MailboxCreate creates a new mailbox.
|
||||||
async MailboxCreate(name) {
|
async MailboxCreate(name) {
|
||||||
const fn = "MailboxCreate";
|
const fn = "MailboxCreate";
|
||||||
|
@ -556,6 +556,15 @@ var api;
|
|||||||
const params = [messageIDs, flaglist];
|
const params = [messageIDs, flaglist];
|
||||||
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
|
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
|
||||||
}
|
}
|
||||||
|
// MailboxesMarkRead marks all messages in mailboxes as read. Child mailboxes are
|
||||||
|
// not automatically included, they must explicitly be included in the list of IDs.
|
||||||
|
async MailboxesMarkRead(mailboxIDs) {
|
||||||
|
const fn = "MailboxesMarkRead";
|
||||||
|
const paramTypes = [["[]", "int64"]];
|
||||||
|
const returnTypes = [];
|
||||||
|
const params = [mailboxIDs];
|
||||||
|
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
|
||||||
|
}
|
||||||
// MailboxCreate creates a new mailbox.
|
// MailboxCreate creates a new mailbox.
|
||||||
async MailboxCreate(name) {
|
async MailboxCreate(name) {
|
||||||
const fn = "MailboxCreate";
|
const fn = "MailboxCreate";
|
||||||
|
@ -556,6 +556,15 @@ var api;
|
|||||||
const params = [messageIDs, flaglist];
|
const params = [messageIDs, flaglist];
|
||||||
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
|
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
|
||||||
}
|
}
|
||||||
|
// MailboxesMarkRead marks all messages in mailboxes as read. Child mailboxes are
|
||||||
|
// not automatically included, they must explicitly be included in the list of IDs.
|
||||||
|
async MailboxesMarkRead(mailboxIDs) {
|
||||||
|
const fn = "MailboxesMarkRead";
|
||||||
|
const paramTypes = [["[]", "int64"]];
|
||||||
|
const returnTypes = [];
|
||||||
|
const params = [mailboxIDs];
|
||||||
|
return await _sherpaCall(this.baseURL, this.authState, { ...this.options }, paramTypes, returnTypes, fn, params);
|
||||||
|
}
|
||||||
// MailboxCreate creates a new mailbox.
|
// MailboxCreate creates a new mailbox.
|
||||||
async MailboxCreate(name) {
|
async MailboxCreate(name) {
|
||||||
const fn = "MailboxCreate";
|
const fn = "MailboxCreate";
|
||||||
@ -5486,7 +5495,11 @@ const newMailboxView = (xmb, mailboxlistView, otherMailbox) => {
|
|||||||
let actionBtn;
|
let actionBtn;
|
||||||
const cmdOpenActions = async () => {
|
const cmdOpenActions = async () => {
|
||||||
const trashmb = mailboxlistView.mailboxes().find(mb => mb.Trash);
|
const trashmb = mailboxlistView.mailboxes().find(mb => mb.Trash);
|
||||||
const remove = popover(actionBtn, { transparent: true }, dom.div(style({ display: 'flex', flexDirection: 'column', gap: '.5ex' }), dom.div(dom.clickbutton('Move to trash', attr.title('Move mailbox, its messages and its mailboxes to the trash.'), async function click() {
|
const remove = popover(actionBtn, { transparent: true }, dom.div(style({ display: 'flex', flexDirection: 'column', gap: '.5ex' }), dom.div(dom.clickbutton('Mark as read', attr.title('Mark all messages in the mailbox and its sub mailboxes as read.'), async function click() {
|
||||||
|
remove();
|
||||||
|
const mailboxIDs = [mbv.mailbox.ID, ...mailboxlistView.mailboxes().filter(mb => mb.Name.startsWith(mbv.mailbox.Name + '/')).map(mb => mb.ID)];
|
||||||
|
await withStatus('Marking mailboxes as read', client.MailboxesMarkRead(mailboxIDs));
|
||||||
|
})), dom.div(dom.clickbutton('Move to trash', attr.title('Move mailbox, its messages and its mailboxes to the trash.'), async function click() {
|
||||||
if (!trashmb) {
|
if (!trashmb) {
|
||||||
window.alert('No mailbox configured for trash yet.');
|
window.alert('No mailbox configured for trash yet.');
|
||||||
return;
|
return;
|
||||||
|
@ -5149,6 +5149,13 @@ const newMailboxView = (xmb: api.Mailbox, mailboxlistView: MailboxlistView, othe
|
|||||||
|
|
||||||
const remove = popover(actionBtn, {transparent: true},
|
const remove = popover(actionBtn, {transparent: true},
|
||||||
dom.div(style({display: 'flex', flexDirection: 'column', gap: '.5ex'}),
|
dom.div(style({display: 'flex', flexDirection: 'column', gap: '.5ex'}),
|
||||||
|
dom.div(
|
||||||
|
dom.clickbutton('Mark as read', attr.title('Mark all messages in the mailbox and its sub mailboxes as read.'), async function click() {
|
||||||
|
remove()
|
||||||
|
const mailboxIDs = [mbv.mailbox.ID, ...mailboxlistView.mailboxes().filter(mb => mb.Name.startsWith(mbv.mailbox.Name+'/')).map(mb => mb.ID)]
|
||||||
|
await withStatus('Marking mailboxes as read', client.MailboxesMarkRead(mailboxIDs))
|
||||||
|
}),
|
||||||
|
),
|
||||||
dom.div(
|
dom.div(
|
||||||
dom.clickbutton('Move to trash', attr.title('Move mailbox, its messages and its mailboxes to the trash.'), async function click() {
|
dom.clickbutton('Move to trash', attr.title('Move mailbox, its messages and its mailboxes to the trash.'), async function click() {
|
||||||
if (!trashmb) {
|
if (!trashmb) {
|
||||||
|
@ -287,6 +287,62 @@ func (x XOps) MessageFlagsClear(ctx context.Context, log mlog.Log, acc *store.Ac
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MailboxesMarkRead updates all messages in the referenced mailboxes as seen when
|
||||||
|
// they aren't yet. The mailboxes are updated with their unread messages counts,
|
||||||
|
// and the changes are propagated.
|
||||||
|
func (x XOps) MailboxesMarkRead(ctx context.Context, log mlog.Log, acc *store.Account, mailboxIDs []int64) {
|
||||||
|
acc.WithRLock(func() {
|
||||||
|
var changes []store.Change
|
||||||
|
|
||||||
|
x.DBWrite(ctx, acc, func(tx *bstore.Tx) {
|
||||||
|
var modseq store.ModSeq
|
||||||
|
|
||||||
|
// Note: we don't need to retrain, changing the "seen" flag is not relevant.
|
||||||
|
|
||||||
|
for _, mbID := range mailboxIDs {
|
||||||
|
mb := x.mailboxID(ctx, tx, mbID)
|
||||||
|
|
||||||
|
// Find messages to update.
|
||||||
|
q := bstore.QueryTx[store.Message](tx)
|
||||||
|
q.FilterNonzero(store.Message{MailboxID: mb.ID})
|
||||||
|
q.FilterEqual("Seen", false)
|
||||||
|
q.FilterEqual("Expunged", false)
|
||||||
|
q.SortAsc("UID")
|
||||||
|
var have bool
|
||||||
|
err := q.ForEach(func(m store.Message) error {
|
||||||
|
have = true // We need to update mailbox.
|
||||||
|
|
||||||
|
oflags := m.Flags
|
||||||
|
mb.Sub(m.MailboxCounts())
|
||||||
|
m.Seen = true
|
||||||
|
mb.Add(m.MailboxCounts())
|
||||||
|
|
||||||
|
if modseq == 0 {
|
||||||
|
var err error
|
||||||
|
modseq, err = acc.NextModSeq(tx)
|
||||||
|
x.Checkf(ctx, err, "assigning next modseq")
|
||||||
|
}
|
||||||
|
m.ModSeq = modseq
|
||||||
|
err := tx.Update(&m)
|
||||||
|
x.Checkf(ctx, err, "updating message")
|
||||||
|
|
||||||
|
changes = append(changes, m.ChangeFlags(oflags))
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
x.Checkf(ctx, err, "listing messages to mark as read")
|
||||||
|
|
||||||
|
if have {
|
||||||
|
err := tx.Update(&mb)
|
||||||
|
x.Checkf(ctx, err, "updating mailbox")
|
||||||
|
changes = append(changes, mb.ChangeCounts())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
store.BroadcastChanges(acc, changes)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// MessageMove moves messages to the mailbox represented by mailboxName, or to mailboxID if mailboxName is empty.
|
// MessageMove moves messages to the mailbox represented by mailboxName, or to mailboxID if mailboxName is empty.
|
||||||
func (x XOps) MessageMove(ctx context.Context, log mlog.Log, acc *store.Account, messageIDs []int64, mailboxName string, mailboxID int64) {
|
func (x XOps) MessageMove(ctx context.Context, log mlog.Log, acc *store.Account, messageIDs []int64, mailboxName string, mailboxID int64) {
|
||||||
acc.WithRLock(func() {
|
acc.WithRLock(func() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user