From 9a48d1a16dbb222830379df11b9ff9051e51ac7b Mon Sep 17 00:00:00 2001
From: 64johnlee <64lamei@gmail.com>
Date: Sun, 3 May 2026 20:34:32 +0800
Subject: [PATCH 01/53] feat(mail): implement Reply All functionality
- Added Reply All button in MessageView
- Pass reply context (to+cc lists) via pendingReplyContext global
- Pre-fill CC field with original message's CC recipients
- Include CC info in the quoted original message header
- Handle both 'reply' and 'replyall' msgTypes in compose
---
webui-src/app/mail/mail_compose.js | 137 +++++++++++++++++++++++++---
webui-src/app/mail/mail_resolver.js | 6 +-
webui-src/app/mail/mail_util.js | 28 +++++-
3 files changed, 152 insertions(+), 19 deletions(-)
diff --git a/webui-src/app/mail/mail_compose.js b/webui-src/app/mail/mail_compose.js
index faff4f95..c0b420ff 100644
--- a/webui-src/app/mail/mail_compose.js
+++ b/webui-src/app/mail/mail_compose.js
@@ -2,6 +2,7 @@ const m = require('mithril');
const rs = require('rswebui');
const widget = require('widgets');
const peopleUtil = require('people/people_util');
+const { MSG_ADDRESS_MODE_CC } = require('mail/mail_util');
const Layout = () => {
const Data = {
@@ -29,11 +30,29 @@ const Layout = () => {
};
async function loadMailUserDetails(msgType, senderId, recipientList) {
Data.allUsers = await peopleUtil.sortUsers(rs.userList.users);
- if (msgType === 'reply') {
+ if (msgType === 'reply' || msgType === 'replyall') {
Data.allUsers.forEach(async (user) => {
if (user.mGroupId === (await senderId)) Data.recipients.to.sendList.push(user);
});
}
+ if (msgType === 'replyall') {
+ // Add all original recipients to appropriate fields
+ if (recipientList) {
+ Object.keys(recipientList).forEach((recipId) => {
+ const user = Data.allUsers.find((u) => u.mGroupId === recipId);
+ if (user) {
+ const dest = recipientList[recipId];
+ if (dest._mode === MSG_ADDRESS_MODE_CC) {
+ if (!Data.recipients.cc.sendList.find((u) => u.mGroupId === recipId)) {
+ Data.recipients.cc.sendList.push(user);
+ }
+ } else if (dest._mode === MSG_ADDRESS_MODE_TO) {
+ // Don't add to 'to' since sender is already there
+ }
+ }
+ });
+ }
+ }
await peopleUtil.ownIds(async (data) => {
Data.ownId = await data;
for (let i = 0; i < Data.ownId.length; i++) {
@@ -41,7 +60,7 @@ const Layout = () => {
Data.ownId.splice(i, 1); // workaround for id '0'
}
}
- if (msgType === 'reply') {
+ if (msgType === 'reply' || msgType === 'replyall') {
Data.identity = Data.ownId.filter((id) =>
Object.prototype.hasOwnProperty.call(recipientList, id)
)[0];
@@ -49,19 +68,34 @@ const Layout = () => {
});
}
async function loadDetails(attrs) {
- const { msgType, senderId, recipientList } = await attrs;
- await loadMailUserDetails(msgType, senderId, recipientList);
+ const { msgType, senderId, recipientList, pendingCtx } = await attrs;
- Object.keys(Data.recipients).forEach((item) => {
- Data.recipients[item].inputList = Data.allUsers;
- });
+ // Handle pending reply context from MessageView (reply/replyall)
+ if (pendingCtx) {
+ const ctx = pendingCtx;
+ if (ctx.msgType === 'reply') {
+ await loadMailUserDetails('reply', ctx.from._addr_string, ctx.toList);
+ } else if (ctx.msgType === 'replyall') {
+ await loadMailUserDetails('replyall', ctx.from._addr_string, ctx.toList);
+ // For reply all, pre-fill CC with the original CC list
+ if (ctx.ccList) {
+ Object.keys(ctx.ccList).forEach((ccId) => {
+ const ccUser = Data.allUsers.find((u) => u.mGroupId === ccId);
+ if (ccUser && !Data.recipients.cc.sendList.find((u) => u.mGroupId === ccId)) {
+ Data.recipients.cc.sendList.push(ccUser);
+ }
+ });
+ }
+ }
+
+ Object.keys(Data.recipients).forEach((item) => {
+ Data.recipients[item].inputList = Data.allUsers;
+ });
- if (msgType === 'compose') {
Data.identity = Data.ownId[0];
- }
- if (msgType === 'reply') {
- const { subject, replyMessage, timeStamp } = await attrs;
+ // Set subject and original message quote
+ const { subject, replyMessage, timeStamp } = ctx;
const tmb = document.querySelector('#composerMailBody');
const time = timeStamp.toLocaleTimeString('UTC', { hour: '2-digit', minute: '2-digit' });
const dateLong = timeStamp.toLocaleDateString('UTC', {
@@ -73,16 +107,27 @@ const Layout = () => {
-----Original Message-----
From:
- ${rs.userList.userMap[senderId]}
+ ${rs.userList.userMap[ctx.from._addr_string]}
To:
- ${Object.keys(recipientList).map(
+ ${Object.keys(ctx.toList).map(
(recip) => `
- ${rs.userList.userMap[recipientList[recip]._addr_string] || 'Unknown'},
+ ${rs.userList.userMap[ctx.toList[recip]._addr_string] || 'Unknown'},
+
+ `
+ )}
+ ${ctx.ccList && Object.keys(ctx.ccList).length > 0 ? `
+
+ CC:
+ ${Object.keys(ctx.ccList).map(
+ (ccId) => `
+
+ ${rs.userList.userMap[ctx.ccList[ccId]._addr_string] || 'Unknown'},
`
)}
+ ` : ''}
Sent:
@@ -94,7 +139,7 @@ const Layout = () => {
On ${timeStamp.toLocaleDateString()} ${time},
- ${rs.userList.userMap[senderId]}
+ ${rs.userList.userMap[ctx.from._addr_string]}
wrote:
`;
@@ -109,6 +154,68 @@ const Layout = () => {
`;
Data.subject = subject.substring(0, 4) === 'Re: ' ? subject : `Re: ${subject}`;
+ } else {
+ // Original compose logic
+ await loadMailUserDetails(msgType, senderId, recipientList);
+
+ Object.keys(Data.recipients).forEach((item) => {
+ Data.recipients[item].inputList = Data.allUsers;
+ });
+
+ if (msgType === 'compose') {
+ Data.identity = Data.ownId[0];
+ }
+
+ if (msgType === 'reply') {
+ const { subject, replyMessage, timeStamp } = await attrs;
+ const tmb = document.querySelector('#composerMailBody');
+ const time = timeStamp.toLocaleTimeString('UTC', { hour: '2-digit', minute: '2-digit' });
+ const dateLong = timeStamp.toLocaleDateString('UTC', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ });
+ const replyMessageHeader = `
+ -----Original Message-----
+
+ From:
+ ${rs.userList.userMap[senderId]}
+
+ To:
+ ${Object.keys(recipientList).map(
+ (recip) => `
+
+ ${rs.userList.userMap[recipientList[recip]._addr_string] || 'Unknown'},
+
+ `
+ )}
+
+
+ Sent:
+ ${dateLong} ${time}
+
+ Subject:
+ ${subject}
+
+
+
+ On ${timeStamp.toLocaleDateString()} ${time},
+ ${rs.userList.userMap[senderId]}
+ wrote:
+
+ `;
+ tmb.innerHTML = `
+
+
+