Keep Cc recipients in reply-all

* fix: populate window._myEmailAddress from the active email account

* fix: keep Cc recipients in reply-all when own address is empty or unknown

* test: cover reply-all recipient building (issue #360)
This commit is contained in:
Afonso Coutinho
2026-06-01 10:29:22 +01:00
committed by GitHub
parent 3884f2b8b7
commit 16d6484492
4 changed files with 89 additions and 11 deletions

View File

@@ -8,6 +8,7 @@ import sessionModule from './sessions.js';
import { initEmailLibrary, openEmailLibrary, closeEmailLibrary, isOpen as isLibOpen, prewarmEmailLibrary } from './emailLibrary.js';
import * as Modals from './modalManager.js';
import { applyEdgeDock } from './modalSnap.js';
import { buildReplyAllCc } from './emailLibrary/replyRecipients.js';
const API_BASE = window.location.origin;
const _acct = () => window.__odysseusActiveEmailAccount
@@ -696,17 +697,7 @@ async function _openEmail(em, itemEl, preloadedData = null, mode = 'reply') {
if (mode === 'reply-all') {
// Build reply-all: TO = original sender, CC = everyone else (To + Cc minus me)
const origTo = (data.to || '').split(',').map(s => s.trim()).filter(Boolean);
const origCc = (data.cc || '').split(',').map(s => s.trim()).filter(Boolean);
const allOthers = [...origTo, ...origCc]
.filter(addr => {
// Extract email from "Name <email@x>" or "email@x"
const match = addr.match(/<([^>]+)>/) || [null, addr];
return !match[1].toLowerCase().includes(myAddress);
});
if (allOthers.length > 0) {
ccAddresses = allOthers.join(', ');
}
ccAddresses = buildReplyAllCc(data, myAddress);
} else if (mode === 'forward') {
toAddress = '';
subjectPrefix = 'Fwd: ';

View File

@@ -492,6 +492,15 @@ function _libCacheWriteBack() {
// Simple global rather than cross-module import to keep coupling minimal.
function _publishActiveAccount() {
try { window.__odysseusActiveEmailAccount = state._libAccountId || null; } catch (_) {}
// Publish the active account's own address so reply-all can exclude us from
// the recipient list. This global was read in emailInbox.js but never set.
try {
const accts = state._libAccounts || [];
const active = accts.find(a => a && a.id === state._libAccountId)
|| accts.find(a => a && a.is_default)
|| accts[0];
window._myEmailAddress = (active && (active.from_address || active.imap_user)) || '';
} catch (_) {}
}
export function initEmailLibrary(config) {

View File

@@ -0,0 +1,25 @@
// static/js/emailLibrary/replyRecipients.js
//
// Pure helpers for building reply-all recipient lists. No DOM, no fetch,
// no shared state — safe to import anywhere and to unit-test under node.
// Extract the bare email from "Name <email@x>" or a plain "email@x".
export function extractEmail(addr) {
const m = (addr || '').match(/<([^>]+)>/);
return (m ? m[1] : (addr || '')).trim().toLowerCase();
}
// Reply-all CC = everyone on the original To + Cc, minus ourselves, with the
// original "Name <email>" form preserved.
//
// `myAddress` empty/unknown ⇒ no exclusion. Comparing by exact extracted email
// (not a substring `includes`) is what fixes issue #360: an empty self address
// made `"...".includes("")` true for every recipient, so reply-all dropped the
// entire Cc list and kept only the original sender.
export function buildReplyAllCc(data, myAddress) {
const me = (myAddress || '').toLowerCase();
const split = (s) => (s || '').split(',').map((x) => x.trim()).filter(Boolean);
return [...split(data && data.to), ...split(data && data.cc)]
.filter((addr) => !me || extractEmail(addr) !== me)
.join(', ');
}