From 1e9dae15f13dc81288c438acd436b87c08610db1 Mon Sep 17 00:00:00 2001 From: Eli Ribble Date: Tue, 3 Sep 2024 12:03:04 -0700 Subject: [PATCH] Add button to move an email to trash. This is the first time I'm modifying data instead of just displaying it. And this commit is a mess, it's all over the place with duplicating types and breaking my class layers. But it works, technically, so, whatever, checkpoint! I need to totally start reworking the base client library as I'm not happy with it at all. Also, it turns out that we have very little type protection on the "set" methods. I had a totally improper signature for about an hour that led to useless debugging. Reading the standard, which is excellent, helped me get sorted out, but they type checker should be helping me. Additionally, I should be creating this Account class type within the client. --- src/EmailSummary.tsx | 19 +++++++++ src/client/Client.tsx | 89 +++++++++++++++++++++++++++++++++++++++++-- src/client/types.tsx | 16 +++++++- 3 files changed, 119 insertions(+), 5 deletions(-) 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 };