diff --git a/src/EmailSummary.tsx b/src/EmailSummary.tsx index a9514ea..66d3517 100644 --- a/src/EmailSummary.tsx +++ b/src/EmailSummary.tsx @@ -1,3 +1,7 @@ +import { Trash } from "react-bootstrap-icons"; +import Button from "react-bootstrap/Button"; +import ButtonGroup from "react-bootstrap/ButtonGroup"; +import ButtonToolbar from "react-bootstrap/ButtonToolbar"; import Placeholder from "react-bootstrap/Placeholder"; import React from "react"; @@ -55,6 +59,21 @@ class EmailSummary extends React.Component< stub.subject} + + + + + + ); } diff --git a/src/client/Client.tsx b/src/client/Client.tsx index b85c74d..25eb9ab 100644 --- a/src/client/Client.tsx +++ b/src/client/Client.tsx @@ -3,10 +3,19 @@ * None of the dependencies should leak, in types or otherwise */ import * as base64 from "base-64"; +import * as jmaptypes from "./jmap-client-ts/src/types"; import * as jmapclient from "./jmap-client-ts/src"; import { FetchTransport } from "./jmap-client-ts/src/utils/fetch-transport"; -import { IAccount, IEmail, IEmailStub, IMailbox, ISession } from "./types"; +import { + IAccount, + IEmail, + IEmailStub, + IMailbox, + ISession, + MailboxRole, + PushMessage, +} from "./types"; type Callback = () => void; @@ -21,9 +30,40 @@ export interface ClientState { emailStubs: Set; mailboxes: Set; }; + mailboxes: { + trash: IMailbox; + } | null; session: ISession | null; } +class Account implements IAccount { + id: string; + + accountCapabilities: { [key: string]: jmaptypes.IMailCapabilities }; + isPersonal: boolean; + isReadOnly: boolean; + mailboxes: Array | null; + name: string; + + constructor(id: string, props: jmaptypes.IAccount) { + this.id = id; + this.accountCapabilities = props.accountCapabilities; + this.isPersonal = props.isPersonal; + this.isReadOnly = props.isReadOnly; + this.mailboxes = null; + this.name = props.name; + } + mailboxByRole(role: MailboxRole): IMailbox | null { + if (this.mailboxes === null) return null; + for (let i = 0; i < this.mailboxes.length; i++) { + const m = this.mailboxes[i]; + if (m.role === role) { + return m; + } + } + return null; + } +} export default class Client { // All objects which currently are listening for changes callbacks: Array = []; @@ -34,6 +74,7 @@ export default class Client { emailStubs: new Set(), mailboxes: new Set(), }, + mailboxes: null, session: null, }; @@ -67,6 +108,43 @@ export default class Client { return; } + emailMoveTrash(account: IAccount, emailId: string) { + if (this.jclient === null) return; + console.log("Trashing", emailId); + const email = this.emailStub(emailId); + if (email === null) return; + const trashMailbox = account.mailboxByRole("trash"); + if (trashMailbox === null) { + console.error( + "Cannot trash ", + emailId, + " because ", + account.id, + " does not have a 'trash' mailbox", + ); + return; + } + let mailboxIds = Object.keys(email.mailboxIds).reduce( + (acc, key) => { + acc[key as keyof typeof email.mailboxIds] = false; + return acc; + }, + {} as Record, + ); + mailboxIds[trashMailbox.id] = true; + const props = { + accountId: account.id, + update: { + [email.id]: { + mailboxIds: mailboxIds, + }, + }, + }; + this.jclient.email_set(props).then((response) => { + console.log("Trashed", emailId); + }); + } + // Ensure we have the full email content ensureEmailContent(accountId: string, emailId: string) { if (this.state.session == null) return; @@ -98,7 +176,9 @@ export default class Client { email(emailId: string): IEmail | null { if (this.state.session == null) return null; - return this.state.session.emails[emailId]; + const result = this.state.session.emails[emailId]; + if (result === undefined) return null; + return result; } emailGetContent(accountId: string, emailId: string) { @@ -148,7 +228,7 @@ export default class Client { .email_get({ accountId: accountId, ids: [emailId], - properties: ["from", "receivedAt", "subject"], + properties: ["from", "mailboxIds", "receivedAt", "subject"], }) .then((response) => { console.log(msg, "response", response); @@ -163,6 +243,7 @@ export default class Client { this.state.session.emailStubs[e.id] = { from: e.from, id: e.id, + mailboxIds: e.mailboxIds, receivedAt: e.receivedAt, subject: e.subject, }; @@ -272,7 +353,7 @@ export default class Client { accounts: Object.fromEntries( Object.entries(session.accounts).map(([key, account]) => [ key, - { ...account, id: key.toString(), mailboxes: null }, + new Account(key.toString(), account), ]), ), emails: {}, diff --git a/src/client/types.tsx b/src/client/types.tsx index a479d70..e8fa48c 100644 --- a/src/client/types.tsx +++ b/src/client/types.tsx @@ -1,8 +1,10 @@ import * as client from "./jmap-client-ts/src/types"; +export type MailboxIdMap = { [mailboxId: string]: boolean }; export interface IEmailStub { from: Array | null; id: string; + mailboxIds: MailboxIdMap; receivedAt: string; subject: string; } @@ -18,8 +20,20 @@ export interface IEmail extends client.IEmailProperties { export interface IAccount extends client.IAccount { id: string; mailboxes: Array | null; -} + mailboxByRole(role: MailboxRole): IMailbox | null; +} +export type MailboxRole = + | "all" + | "archive" + | "drafts" + | "flagged" + | "important" + | "junk" + | "sent" + | "subscribed" + | "trash"; +export type PushMessage = client.PushMessage; export type AccountIdMap = { [accountId: string]: IAccount }; export type EmailStubIdMap = { [emailId: string]: IEmailStub }; export type EmailIdMap = { [emailId: string]: IEmail };