mirror of
https://github.com/root-fr/jmap-webmail.git
synced 2026-09-22 16:01:12 +00:00
fix: v1.5.2 - patch Next.js WebSocket SSRF (CVE-2026-44578)
Security: upgrade Next.js 16.2.4 -> 16.2.6 to patch CVE-2026-44578 (GHSA-c4j6-fc7j-m34r, CVSS 8.6, unauthenticated WebSocket-upgrade SSRF on the built-in Node server) and eleven other May 2026 advisories bundled in the same release. next-intl patched for GHSA-4c35-wcg5-mm9h prototype pollution in the experimental precompile path. React/react-dom rolled forward to 19.2.6. Fixes: - bulk delete now honours the "delete to trash" setting instead of always hard-deleting - favicon repaints the base icon on zero unread so the badge clears
This commit is contained in:
@@ -1,5 +1,41 @@
|
||||
# Changelog
|
||||
|
||||
## 1.5.2 (2026-05-14)
|
||||
|
||||
### Security
|
||||
|
||||
- **Next.js upgraded to 16.2.6 to patch CVE-2026-44578**
|
||||
(GHSA-c4j6-fc7j-m34r, CVSS 8.6). The framework's WebSocket upgrade
|
||||
handler did not apply the safe-rewrite checks used for normal HTTP
|
||||
requests, so a single unauthenticated HTTP upgrade could cause the
|
||||
Node server to issue an internal request to any host reachable on
|
||||
port 80 and return the response to the attacker. Self-hosted
|
||||
deployments (the only mode this project supports) were exposed to
|
||||
cloud metadata endpoints, internal APIs, and admin panels. The bump
|
||||
to 16.2.6 also pulls in eleven other May 2026 advisories covering
|
||||
middleware/proxy bypass, denial of service, and a React patch.
|
||||
- **`next-intl` upgraded to patch GHSA-4c35-wcg5-mm9h**, a prototype
|
||||
pollution issue in the experimental precompile path. This project
|
||||
doesn't enable that path, but the dependency is patched anyway.
|
||||
- **Hardening note for self-hosters**: a public PoC for CVE-2026-44578
|
||||
exists. Redeploy on `rootfr/jmap-webmail:1.5.2` (or `latest`) and,
|
||||
where you can, keep the container off untrusted networks and block
|
||||
egress to cloud metadata endpoints (AWS IMDS, GCP metadata).
|
||||
|
||||
### Fixes
|
||||
|
||||
- **Bulk delete now honours the "delete to trash" setting**: emptying
|
||||
a selection from the toolbar always performed a hard delete, even
|
||||
when the user had configured deletes to move to Trash first. The
|
||||
store path used by the toolbar now routes through the same trash
|
||||
helper as the single-message delete action, so the behaviour matches
|
||||
Settings.
|
||||
- **Favicon clears the unread badge as soon as the inbox empties**:
|
||||
on `unreadCount === 0` the favicon kept the last-painted badge until
|
||||
the next refresh because the redraw skipped the zero case. The hook
|
||||
now repaints the base icon on the zero transition so the badge
|
||||
disappears immediately.
|
||||
|
||||
## 1.5.1 (2026-04-17)
|
||||
|
||||
### Fixes
|
||||
|
||||
@@ -276,6 +276,11 @@ This document tracks the development status and planned features for JMAP Webmai
|
||||
- [x] Attachments render for mail originating from providers that stamp a Content-ID on every part (#58)
|
||||
- [x] Email-to-self delivered instead of being dropped by the MTA's duplicate-message check (#60)
|
||||
|
||||
### Release 1.5.2 (2026-05-14)
|
||||
- [x] Patch CVE-2026-44578 (Next.js WebSocket SSRF, CVSS 8.6) by upgrading to 16.2.6, plus eleven other May 2026 advisories
|
||||
- [x] Bulk delete honours the "delete to trash" setting instead of always hard-deleting
|
||||
- [x] Favicon unread badge clears on the zero transition instead of sticking until the next refresh
|
||||
|
||||
## Planned Features
|
||||
|
||||
### Real-time and PWA (1.6.0)
|
||||
|
||||
+27
-10
@@ -34,9 +34,7 @@ export function useFaviconBadge(count: number) {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
imageRef.current = img;
|
||||
if (countRef.current > 0) {
|
||||
applyBadge(canvas, img, countRef.current);
|
||||
}
|
||||
renderFavicon(canvas, img, countRef.current);
|
||||
};
|
||||
img.src = reader.result as string;
|
||||
};
|
||||
@@ -52,17 +50,36 @@ export function useFaviconBadge(count: number) {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (count <= 0) {
|
||||
clearDynamicFavicon();
|
||||
return;
|
||||
}
|
||||
|
||||
if (canvasRef.current) {
|
||||
applyBadge(canvasRef.current, imageRef.current, count);
|
||||
if (canvasRef.current && imageRef.current) {
|
||||
renderFavicon(canvasRef.current, imageRef.current, count);
|
||||
}
|
||||
}, [count]);
|
||||
}
|
||||
|
||||
function renderFavicon(
|
||||
canvas: HTMLCanvasElement,
|
||||
image: HTMLImageElement,
|
||||
count: number,
|
||||
) {
|
||||
if (count > 0) {
|
||||
applyBadge(canvas, image, count);
|
||||
} else {
|
||||
// Paint the plain base icon into the dynamic link instead of removing
|
||||
// it. Chromium caches the previously-painted badged favicon until a
|
||||
// new href replaces it; simply removing the link leaves the stale
|
||||
// badge visible until the tab is reloaded.
|
||||
drawBase(canvas, image);
|
||||
}
|
||||
}
|
||||
|
||||
function drawBase(canvas: HTMLCanvasElement, image: HTMLImageElement) {
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
ctx.clearRect(0, 0, SIZE, SIZE);
|
||||
ctx.drawImage(image, 0, 0, SIZE, SIZE);
|
||||
setDynamicFavicon(canvas.toDataURL("image/png"));
|
||||
}
|
||||
|
||||
function applyBadge(
|
||||
canvas: HTMLCanvasElement,
|
||||
image: HTMLImageElement | null,
|
||||
|
||||
+3
-2
@@ -764,12 +764,13 @@ export class JMAPClient {
|
||||
};
|
||||
}
|
||||
|
||||
async batchMoveEmails(emailIds: string[], toMailboxId: string): Promise<void> {
|
||||
async batchMoveEmails(emailIds: string[], toMailboxId: string, accountId?: string): Promise<void> {
|
||||
if (emailIds.length === 0) return;
|
||||
|
||||
const targetAccountId = accountId || this.accountId;
|
||||
const updates = Object.fromEntries(emailIds.map(id => [id, { mailboxIds: { [toMailboxId]: true } }]));
|
||||
await this.request([
|
||||
["Email/set", { accountId: this.accountId, update: updates }, "0"],
|
||||
["Email/set", { accountId: targetAccountId, update: updates }, "0"],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Generated
+86
-80
@@ -14,10 +14,10 @@
|
||||
"date-fns": "^4.1.0",
|
||||
"dompurify": "^3.4.0",
|
||||
"lucide-react": "^0.575.0",
|
||||
"next": "^16.2.4",
|
||||
"next": "^16.2.6",
|
||||
"next-intl": "^4.9.1",
|
||||
"react": "^19.2.1",
|
||||
"react-dom": "^19.2.1",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"zustand": "^5.0.9"
|
||||
@@ -1317,18 +1317,18 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@formatjs/icu-messageformat-parser": {
|
||||
"version": "3.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.4.tgz",
|
||||
"integrity": "sha512-JVY39ROgLt+pIYngo6piyj4OVfZmXs/2FkC4wLS+ql1Eig/sGJKB7YwDO/5bkJFkfwaFAeIpgEiJc8hiYxNalw==",
|
||||
"version": "3.5.8",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.8.tgz",
|
||||
"integrity": "sha512-uZLvzLFN7iV2l8cbDdROwgKGtdELeLI4bpnsuz1DnyscHDxn8TdDE0anHzcfjtWK66XYCllGLV3Mi3CYcEPg/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@formatjs/icu-skeleton-parser": "2.1.4"
|
||||
"@formatjs/icu-skeleton-parser": "2.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@formatjs/icu-skeleton-parser": {
|
||||
"version": "2.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.4.tgz",
|
||||
"integrity": "sha512-8bSFZbrlvGX11ywMZxtgkPBt5Q8/etyts7j7j+GWpOVK1g43zwMIH3LZxk43HAtEP7L/jtZ+OZaMiFTOiBj9CA==",
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.8.tgz",
|
||||
"integrity": "sha512-iX5i0O15gPf69l1WqmLFYwn7wq53lauvytvWFnHamIfX/5Ta56gpFj6fdeHRcKTV58IhrKv8QOvWfTYZYm7f+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@formatjs/intl-localematcher": {
|
||||
@@ -1926,15 +1926,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "16.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.4.tgz",
|
||||
"integrity": "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw==",
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.6.tgz",
|
||||
"integrity": "sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "16.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.4.tgz",
|
||||
"integrity": "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A==",
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.6.tgz",
|
||||
"integrity": "sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1948,9 +1948,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"version": "16.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.4.tgz",
|
||||
"integrity": "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ==",
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.6.tgz",
|
||||
"integrity": "sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -1964,9 +1964,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "16.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.4.tgz",
|
||||
"integrity": "sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ==",
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.6.tgz",
|
||||
"integrity": "sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1980,9 +1980,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "16.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.4.tgz",
|
||||
"integrity": "sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==",
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.6.tgz",
|
||||
"integrity": "sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -1996,9 +1996,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-gnu": {
|
||||
"version": "16.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.4.tgz",
|
||||
"integrity": "sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==",
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.6.tgz",
|
||||
"integrity": "sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2012,9 +2012,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "16.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.4.tgz",
|
||||
"integrity": "sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==",
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.6.tgz",
|
||||
"integrity": "sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -2028,9 +2028,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "16.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.4.tgz",
|
||||
"integrity": "sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==",
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.6.tgz",
|
||||
"integrity": "sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@@ -2044,9 +2044,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "16.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.4.tgz",
|
||||
"integrity": "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw==",
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.6.tgz",
|
||||
"integrity": "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@@ -5742,9 +5742,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/icu-minify": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.9.1.tgz",
|
||||
"integrity": "sha512-6NkfF9GHHFouqnz+wuiLjCWQiyxoEyJ5liUv4Jxxo/8wyhV7MY0L0iTEGDAVEa4aAD58WqTxFMa20S5nyMjwNw==",
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.12.0.tgz",
|
||||
"integrity": "sha512-zDmM05uav3t3+kxSfRrNlmyXOdj2b+uHA+p04CG32eJabtaHbugXujuL+YfRkwP9joAnf0Uh+RMGCKD5NLa5rQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -5819,15 +5819,21 @@
|
||||
}
|
||||
},
|
||||
"node_modules/intl-messageformat": {
|
||||
"version": "11.2.1",
|
||||
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.1.tgz",
|
||||
"integrity": "sha512-1gAVEUt3wEPvTqML4Fsw9klZV5j0vszQxayP/fi6gUroAc8AUHiNaisBKLWxybL1AdWq1mP07YV1q8v4N92ilQ==",
|
||||
"version": "11.2.5",
|
||||
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.5.tgz",
|
||||
"integrity": "sha512-zaROHiUsnlSFXVypU54AsQuAm3DLmmSH8KfDhiUuG1XZ9NTQ4o3xlxIJYVNmeWAklyp3CWg0lhexNUnee8PsYQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@formatjs/fast-memoize": "3.1.2",
|
||||
"@formatjs/icu-messageformat-parser": "3.5.4"
|
||||
"@formatjs/fast-memoize": "3.1.5",
|
||||
"@formatjs/icu-messageformat-parser": "3.5.8"
|
||||
}
|
||||
},
|
||||
"node_modules/intl-messageformat/node_modules/@formatjs/fast-memoize": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.5.tgz",
|
||||
"integrity": "sha512-KLi3fan6WnCHmigd9pmEEN8Hid0v4wiFBW576M/d07KMWYecf1CvyMI3n34vCmHT4AoVqG2n702kiHbXjzZX2A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/is-array-buffer": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
|
||||
@@ -6837,12 +6843,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "16.2.4",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-16.2.4.tgz",
|
||||
"integrity": "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q==",
|
||||
"version": "16.2.6",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-16.2.6.tgz",
|
||||
"integrity": "sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/env": "16.2.4",
|
||||
"@next/env": "16.2.6",
|
||||
"@swc/helpers": "0.5.15",
|
||||
"baseline-browser-mapping": "^2.9.19",
|
||||
"caniuse-lite": "^1.0.30001579",
|
||||
@@ -6856,14 +6862,14 @@
|
||||
"node": ">=20.9.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@next/swc-darwin-arm64": "16.2.4",
|
||||
"@next/swc-darwin-x64": "16.2.4",
|
||||
"@next/swc-linux-arm64-gnu": "16.2.4",
|
||||
"@next/swc-linux-arm64-musl": "16.2.4",
|
||||
"@next/swc-linux-x64-gnu": "16.2.4",
|
||||
"@next/swc-linux-x64-musl": "16.2.4",
|
||||
"@next/swc-win32-arm64-msvc": "16.2.4",
|
||||
"@next/swc-win32-x64-msvc": "16.2.4",
|
||||
"@next/swc-darwin-arm64": "16.2.6",
|
||||
"@next/swc-darwin-x64": "16.2.6",
|
||||
"@next/swc-linux-arm64-gnu": "16.2.6",
|
||||
"@next/swc-linux-arm64-musl": "16.2.6",
|
||||
"@next/swc-linux-x64-gnu": "16.2.6",
|
||||
"@next/swc-linux-x64-musl": "16.2.6",
|
||||
"@next/swc-win32-arm64-msvc": "16.2.6",
|
||||
"@next/swc-win32-x64-msvc": "16.2.6",
|
||||
"sharp": "^0.34.5"
|
||||
},
|
||||
"peerDependencies": {
|
||||
@@ -6890,9 +6896,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/next-intl": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.9.1.tgz",
|
||||
"integrity": "sha512-N7ga0CjtYcdxNvaKNIi6eJ2mmatlHK5hp8rt0YO2Omoc1m0gean242/Ukdj6+gJNiReBVcYIjK0HZeNx7CV1ug==",
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.12.0.tgz",
|
||||
"integrity": "sha512-v8KpppWG0yLLlChJ3Of6uoPew9LeRDBAtY6vpJmF7YJmBZlHEzzoEL4w1g1dAU+VleEPNoXNm9hg1eEsKWV5hw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -6904,11 +6910,11 @@
|
||||
"@formatjs/intl-localematcher": "^0.8.1",
|
||||
"@parcel/watcher": "^2.4.1",
|
||||
"@swc/core": "^1.15.2",
|
||||
"icu-minify": "^4.9.1",
|
||||
"icu-minify": "^4.12.0",
|
||||
"negotiator": "^1.0.0",
|
||||
"next-intl-swc-plugin-extractor": "^4.9.1",
|
||||
"next-intl-swc-plugin-extractor": "^4.12.0",
|
||||
"po-parser": "^2.1.1",
|
||||
"use-intl": "^4.9.1"
|
||||
"use-intl": "^4.12.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
|
||||
@@ -6921,9 +6927,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/next-intl-swc-plugin-extractor": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.9.1.tgz",
|
||||
"integrity": "sha512-8whJJ6oxJz8JqkHarggmmuEDyXgC7nEnaPhZD91CJwEWW4xp0AST3Mw17YxvHyP2vAF3taWfFbs1maD+WWtz3w==",
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.12.0.tgz",
|
||||
"integrity": "sha512-jUxVEu1Nryjt4YgaDktSys7ioOgQfcNPF/SF2dbPNxbVb6U+P1INRgHeCVN+EC59H2rnTFIQwbddmOCrUWFr3g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/next-intl/node_modules/@swc/core": {
|
||||
@@ -7350,9 +7356,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.5.8",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
|
||||
"integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
|
||||
"version": "8.5.14",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
|
||||
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -7446,9 +7452,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
|
||||
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
|
||||
"version": "19.2.6",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
|
||||
"integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
@@ -7456,16 +7462,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.2.4",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
|
||||
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
|
||||
"version": "19.2.6",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz",
|
||||
"integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.4"
|
||||
"react": "^19.2.6"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
@@ -8495,9 +8501,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/use-intl": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.9.1.tgz",
|
||||
"integrity": "sha512-iGVV/xFYlhe3btafRlL8RPLD2Jsuet4yqn9DR6LWWbMhULsJnXgLonDkzDmsAIBIwFtk02oJuX/Ox2vwHKF+UQ==",
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.12.0.tgz",
|
||||
"integrity": "sha512-r+qVb7UI1+kiOhjYsmsNUCY+jrnjVopwGeFlmMyQj4YInlwZzgMeMSv9n8MqnWWy77HL5BVM8K2WgX50SbtcpA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -8508,7 +8514,7 @@
|
||||
"dependencies": {
|
||||
"@formatjs/fast-memoize": "^3.1.0",
|
||||
"@schummar/icu-type-parser": "1.21.5",
|
||||
"icu-minify": "^4.9.1",
|
||||
"icu-minify": "^4.12.0",
|
||||
"intl-messageformat": "^11.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jmap-webmail",
|
||||
"version": "1.5.1",
|
||||
"version": "1.5.2",
|
||||
"description": "A modern JMAP webmail client built for Stalwart Mail Server",
|
||||
"author": "Matthieu MALVACHE <matthieu@root.cloud>",
|
||||
"license": "MIT",
|
||||
@@ -36,10 +36,10 @@
|
||||
"date-fns": "^4.1.0",
|
||||
"dompurify": "^3.4.0",
|
||||
"lucide-react": "^0.575.0",
|
||||
"next": "^16.2.4",
|
||||
"next": "^16.2.6",
|
||||
"next-intl": "^4.9.1",
|
||||
"react": "^19.2.1",
|
||||
"react-dom": "^19.2.1",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"zustand": "^5.0.9"
|
||||
|
||||
+27
-5
@@ -897,19 +897,35 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
},
|
||||
|
||||
batchDelete: async (client) => {
|
||||
const { selectedEmailIds, emails, mailboxes } = get();
|
||||
const { selectedEmailIds, emails, mailboxes, selectedMailbox } = get();
|
||||
if (selectedEmailIds.size === 0) return;
|
||||
|
||||
set({ error: null });
|
||||
try {
|
||||
const emailIdsArray = Array.from(selectedEmailIds);
|
||||
await client.batchDeleteEmails(emailIdsArray);
|
||||
const deletedEmails = emails.filter(e => selectedEmailIds.has(e.id));
|
||||
|
||||
const deleteAction = useSettingsStore.getState().deleteAction;
|
||||
const currentMailbox = mailboxes.find(mb => mb.id === selectedMailbox);
|
||||
const accountId = currentMailbox?.isShared ? currentMailbox.accountId : undefined;
|
||||
|
||||
let trashMailbox: typeof mailboxes[number] | undefined;
|
||||
if (deleteAction === 'trash') {
|
||||
trashMailbox = mailboxes.find(mb => {
|
||||
if (accountId) return mb.role === 'trash' && mb.accountId === accountId;
|
||||
return mb.role === 'trash' && !mb.isShared;
|
||||
});
|
||||
}
|
||||
|
||||
if (trashMailbox) {
|
||||
const trashId = trashMailbox.originalId || trashMailbox.id;
|
||||
await client.batchMoveEmails(emailIdsArray, trashId, accountId);
|
||||
} else {
|
||||
await client.batchDeleteEmails(emailIdsArray);
|
||||
}
|
||||
|
||||
// Remove deleted emails from local state
|
||||
const remainingEmails = emails.filter(e => !selectedEmailIds.has(e.id));
|
||||
|
||||
// Update mailbox counters
|
||||
const deletedEmails = emails.filter(e => selectedEmailIds.has(e.id));
|
||||
const updatedMailboxes = mailboxes.map(mailbox => {
|
||||
let deltaTotalEmails = 0;
|
||||
let deltaUnreadEmails = 0;
|
||||
@@ -921,6 +937,12 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
|
||||
deltaUnreadEmails--;
|
||||
}
|
||||
}
|
||||
if (trashMailbox && mailbox.id === trashMailbox.id && !email.mailboxIds?.[mailbox.id]) {
|
||||
deltaTotalEmails++;
|
||||
if (!email.keywords?.$seen) {
|
||||
deltaUnreadEmails++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user