Compare commits
4 Commits
d8ee3d5f0f
...
1e9dae15f1
Author | SHA1 | Date |
---|---|---|
|
1e9dae15f1 | |
|
dfca32eb36 | |
|
5a36e46da5 | |
|
a412b6a0a4 |
|
@ -21,6 +21,7 @@
|
|||
"bootstrap": "^5.3.3",
|
||||
"react": "^18.3.1",
|
||||
"react-bootstrap": "^2.10.4",
|
||||
"react-bootstrap-icons": "^1.11.4",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"sass": "^1.77.8",
|
||||
|
@ -14746,6 +14747,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-bootstrap-icons": {
|
||||
"version": "1.11.4",
|
||||
"resolved": "https://registry.npmjs.org/react-bootstrap-icons/-/react-bootstrap-icons-1.11.4.tgz",
|
||||
"integrity": "sha512-lnkOpNEZ/Zr7mNxvjA9efuarCPSgtOuGA55XiRj7ASJnBjb1wEAdtJOd2Aiv9t07r7FLI1IgyZPg9P6jqWD/IA==",
|
||||
"dependencies": {
|
||||
"prop-types": "^15.7.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dev-utils": {
|
||||
"version": "12.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
"bootstrap": "^5.3.3",
|
||||
"react": "^18.3.1",
|
||||
"react-bootstrap": "^2.10.4",
|
||||
"react-bootstrap-icons": "^1.11.4",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"sass": "^1.77.8",
|
||||
|
|
|
@ -88,6 +88,10 @@ class EmailContent extends React.Component<
|
|||
Received
|
||||
</Form.Label>
|
||||
<Col sm={10}>{email.receivedAt}</Col>
|
||||
<Form.Label column sm={2}>
|
||||
Sent
|
||||
</Form.Label>
|
||||
<Col sm={10}>{email.sentAt}</Col>
|
||||
</Form.Group>
|
||||
</Form>
|
||||
</Row>
|
||||
|
|
|
@ -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}
|
||||
</span>
|
||||
</a>
|
||||
<ButtonToolbar>
|
||||
<ButtonGroup className="me-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.props.client.emailMoveTrash(
|
||||
this.props.account,
|
||||
this.props.emailId,
|
||||
);
|
||||
}}
|
||||
>
|
||||
<Trash />
|
||||
</Button>
|
||||
<Button>2</Button>
|
||||
</ButtonGroup>
|
||||
</ButtonToolbar>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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<string>;
|
||||
mailboxes: Set<string>;
|
||||
};
|
||||
mailboxes: {
|
||||
trash: IMailbox;
|
||||
} | null;
|
||||
session: ISession | null;
|
||||
}
|
||||
|
||||
class Account implements IAccount {
|
||||
id: string;
|
||||
|
||||
accountCapabilities: { [key: string]: jmaptypes.IMailCapabilities };
|
||||
isPersonal: boolean;
|
||||
isReadOnly: boolean;
|
||||
mailboxes: Array<IMailbox> | 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<Callback> = [];
|
||||
|
@ -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<keyof typeof email.mailboxIds, boolean>,
|
||||
);
|
||||
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,
|
||||
};
|
||||
|
@ -263,12 +344,16 @@ export default class Client {
|
|||
if (!this.jclient) return;
|
||||
|
||||
const session = this.jclient.getSession();
|
||||
// Subscribe to server-pushed events
|
||||
if (session.eventSourceUrl) {
|
||||
this._subscribeToEventSource(session.eventSourceUrl);
|
||||
}
|
||||
this.state.session = {
|
||||
...session,
|
||||
accounts: Object.fromEntries(
|
||||
Object.entries(session.accounts).map(([key, account]) => [
|
||||
key,
|
||||
{ ...account, id: key.toString(), mailboxes: null },
|
||||
new Account(key.toString(), account),
|
||||
]),
|
||||
),
|
||||
emails: {},
|
||||
|
@ -277,4 +362,16 @@ export default class Client {
|
|||
if (!this.state.session) return;
|
||||
this._triggerChange("Session");
|
||||
}
|
||||
|
||||
_subscribeToEventSource(url: string) {
|
||||
// For typechecker
|
||||
if (this.jclient === null) return;
|
||||
const eventSourceUrl = url
|
||||
.replace("{types}", "*")
|
||||
.replace("{closeafter}", "no")
|
||||
.replace("{ping}", "60");
|
||||
this.jclient.subscribeToEvents(eventSourceUrl, (e) => {
|
||||
console.log("Got an event!", e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit fdab37996a4709b13962b67d1aa49e85a8305755
|
||||
Subproject commit 2ef5f5b7fa0a22a499bd32831ac24622f17e10e6
|
|
@ -1,8 +1,10 @@
|
|||
import * as client from "./jmap-client-ts/src/types";
|
||||
|
||||
export type MailboxIdMap = { [mailboxId: string]: boolean };
|
||||
export interface IEmailStub {
|
||||
from: Array<client.IEmailAddress> | 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<IMailbox> | 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 };
|
||||
|
|
Loading…
Reference in New Issue