VulpesOS/gaia/apps/email/js/ext/mailchew.js
OverSu b38dea0597 Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
2026-09-30 23:13:03 +02:00

382 lines
12 KiB
JavaScript
Executable file

/**
* Message processing logic that deals with message representations at a higher
* level than just text/plain processing (`quotechew.js`) or text/html
* (`htmlchew.js`) parsing. We are particularly concerned with replying to
* messages and forwarding messages, and use the aforementioned libs to do the
* gruntwork.
*
* For replying and forwarding, we synthesize messages so that there is always
* a text part that is the area where the user can enter text which may be
* followed by a read-only editable HTML block. If replying to a text/plain
* message, the quoted text is placed in the text area. If replying to a
* message with any text/html parts, we generate an HTML block for all parts.
**/
define(
[
'exports',
'logic',
'./util',
'./mailchew-strings',
'./quotechew',
'./htmlchew'
],
function(
exports,
logic,
$util,
$mailchewStrings,
$quotechew,
$htmlchew
) {
var DESIRED_SNIPPET_LENGTH = 100;
var scope = logic.scope('MailChew');
/**
* Generate the default compose body for a new e-mail
* @param {MailSenderIdentity} identity The current composer identity
* @return {String} The text to be inserted into the body
*/
exports.generateBaseComposeBody = function generateBaseComposeBody(identity) {
if (identity.signatureEnabled &&
identity.signature &&
identity.signature.length > 0) {
var body = '\n\n--\n' + identity.signature;
return body;
} else {
return '';
}
};
var RE_RE = /^[Rr][Ee]:/;
/**
* Generate the reply subject for a message given the prior subject. This is
* simply prepending "Re: " to the message if it does not already have an
* "Re:" equivalent.
*
* Note, some clients/gateways (ex: I think the google groups web client? at
* least whatever has a user-agent of G2/1.0) will structure mailing list
* replies so they look like "[list] Re: blah" rather than the "Re: [list] blah"
* that Thunderbird would produce. Thunderbird (and other clients) pretend like
* that inner "Re:" does not exist, and so do we.
*
* We _always_ use the exact string "Re: " when prepending and do not localize.
* This is done primarily for consistency with Thunderbird, but it also is
* friendly to other e-mail applications out there.
*
* Thunderbird does support recognizing a
* mail/chrome/messenger-region/region.properties property,
* "mailnews.localizedRe" for letting locales specify other strings used by
* clients that do attempt to localize "Re:". Thunderbird also supports a
* weird "Re(###):" or "Re[###]:" idiom; see
* http://mxr.mozilla.org/comm-central/ident?i=NS_MsgStripRE for more details.
*/
exports.generateReplySubject = function generateReplySubject(origSubject) {
var re = 'Re: ';
if (origSubject) {
if (RE_RE.test(origSubject))
return origSubject;
return re + origSubject;
}
return re;
};
var FWD_FWD = /^[Ff][Ww][Dd]:/;
/**
* Generate the forward subject for a message given the prior subject. This is
* simply prepending "Fwd: " to the message if it does not already have an
* "Fwd:" equivalent.
*/
exports.generateForwardSubject = function generateForwardSubject(origSubject) {
var fwd = 'Fwd: ';
if (origSubject) {
if (FWD_FWD.test(origSubject))
return origSubject;
return fwd + origSubject;
}
return fwd;
};
var l10n_wroteString = '{name} wrote',
l10n_originalMessageString = 'Original Message';
/*
* L10n strings for forward headers. In Thunderbird, these come from
* mime.properties:
* http://mxr.mozilla.org/comm-central/source/mail/locales/en-US/chrome/messenger/mime.properties
*
* The libmime logic that injects them is mime_insert_normal_headers:
* http://mxr.mozilla.org/comm-central/source/mailnews/mime/src/mimedrft.cpp#791
*
* Our dictionary maps from the lowercased header name to the human-readable
* string.
*
* XXX actually do the l10n hookup for this
*/
var l10n_forward_header_labels = {
subject: 'Subject',
date: 'Date',
from: 'From',
replyTo: 'Reply-To',
to: 'To',
cc: 'CC'
};
exports.setLocalizedStrings = function(strings) {
l10n_wroteString = strings.wrote;
l10n_originalMessageString = strings.originalMessage;
l10n_forward_header_labels = strings.forwardHeaderLabels;
};
// Grab the localized strings, if not available, listen for the event that
// sets them.
if ($mailchewStrings.strings) {
exports.setLocalizedStrings($mailchewStrings.strings);
}
$mailchewStrings.events.on('strings', function(strings) {
exports.setLocalizedStrings(strings);
});
/**
* Generate the reply body representation given info about the message we are
* replying to.
*
* This does not include potentially required work such as propagating embedded
* attachments or de-sanitizing links/embedded images/external images.
*/
exports.generateReplyBody = function generateReplyMessage(reps, authorPair,
msgDate,
identity, refGuid) {
var useName = authorPair.name ? authorPair.name.trim() : authorPair.address;
var textMsg = '\n\n' +
l10n_wroteString.replace('{name}', useName) + ':\n',
htmlMsg = null;
for (var i = 0; i < reps.length; i++) {
var repType = reps[i].type, rep = reps[i].content;
if (repType === 'plain') {
var replyText = $quotechew.generateReplyText(rep);
// If we've gone HTML, this needs to get concatenated onto the HTML.
if (htmlMsg) {
htmlMsg += $htmlchew.wrapTextIntoSafeHTMLString(replyText) + '\n';
}
// We haven't gone HTML yet, so this can all still be text.
else {
textMsg += replyText;
}
}
else if (repType === 'html') {
if (!htmlMsg) {
htmlMsg = '';
// slice off the trailing newline of textMsg
if (textMsg.slice(-1) === '\n') {
textMsg = textMsg.slice(0, -1);
}
}
// rep has already been sanitized and therefore all HTML tags are balanced
// and so there should be no rude surprises from this simplistic looking
// HTML creation. The message-id of the message never got sanitized,
// however, so it needs to be escaped. Also, in some cases (Activesync),
// we won't have the message-id so we can't cite it.
htmlMsg += '<blockquote ';
if (refGuid) {
htmlMsg += 'cite="mid:' + $htmlchew.escapeAttrValue(refGuid) + '" ';
}
htmlMsg += 'type="cite">' + rep + '</blockquote>';
}
}
// Thunderbird's default is to put the signature after the quote, so us too.
// (It also has complete control over all of this, but not us too.)
if (identity.signature && identity.signatureEnabled) {
// Thunderbird wraps its signature in a:
// <pre class="moz-signature" cols="72"> construct and so we do too.
if (htmlMsg)
htmlMsg += $htmlchew.wrapTextIntoSafeHTMLString(
identity.signature, 'pre', false,
['class', 'moz-signature', 'cols', '72']);
else
textMsg += '\n\n-- \n' + identity.signature;
}
return {
text: textMsg,
html: htmlMsg
};
};
/**
* Generate the body of an inline forward message. XXX we need to generate
* the header summary which needs some localized strings.
*/
exports.generateForwardMessage =
function(author, date, subject, headerInfo, bodyInfo, identity) {
var textMsg = '\n\n', htmlMsg = null;
if (identity.signature && identity.signatureEnabled)
textMsg += '-- \n' + identity.signature + '\n\n';
textMsg += '-------- ' + l10n_originalMessageString + ' --------\n';
// XXX l10n! l10n! l10n!
// Add the headers in the same order libmime adds them in
// mime_insert_normal_headers so that any automated attempt to re-derive
// the headers has a little bit of a chance (since the strings are
// localized.)
// : subject
textMsg += l10n_forward_header_labels['subject'] + ': ' + subject + '\n';
// We do not track or remotely care about the 'resent' headers
// : resent-comments
// : resent-date
// : resent-from
// : resent-to
// : resent-cc
// : date
textMsg += l10n_forward_header_labels['date'] + ': ' + new Date(date) + '\n';
// : from
textMsg += l10n_forward_header_labels['from'] + ': ' +
$util.formatAddresses([author]) + '\n';
// : reply-to
if (headerInfo.replyTo)
textMsg += l10n_forward_header_labels['replyTo'] + ': ' +
$util.formatAddresses([headerInfo.replyTo]) + '\n';
// : organization
// : to
if (headerInfo.to)
textMsg += l10n_forward_header_labels['to'] + ': ' +
$util.formatAddresses(headerInfo.to) + '\n';
// : cc
if (headerInfo.cc)
textMsg += l10n_forward_header_labels['cc'] + ': ' +
$util.formatAddresses(headerInfo.cc) + '\n';
// (bcc should never be forwarded)
// : newsgroups
// : followup-to
// : references (only for newsgroups)
textMsg += '\n';
var reps = bodyInfo.bodyReps;
for (var i = 0; i < reps.length; i++) {
var repType = reps[i].type, rep = reps[i].content;
if (repType === 'plain') {
var forwardText = $quotechew.generateForwardBodyText(rep);
// If we've gone HTML, this needs to get concatenated onto the HTML.
if (htmlMsg) {
htmlMsg += $htmlchew.wrapTextIntoSafeHTMLString(forwardText) + '\n';
}
// We haven't gone HTML yet, so this can all still be text.
else {
textMsg += forwardText;
}
}
else if (repType === 'html') {
if (!htmlMsg) {
htmlMsg = '';
// slice off the trailing newline of textMsg
if (textMsg.slice(-1) === '\n') {
textMsg = textMsg.slice(0, -1);
}
}
htmlMsg += rep;
}
}
return {
text: textMsg,
html: htmlMsg
};
};
var HTML_WRAP_TOP =
'<html><body><body bgcolor="#FFFFFF" text="#000000">';
var HTML_WRAP_BOTTOM =
'</body></html>';
/**
* Combine the user's plaintext composition with the read-only HTML we provided
* them into a final HTML representation.
*/
exports.mergeUserTextWithHTML = function mergeReplyTextWithHTML(text, html) {
return HTML_WRAP_TOP +
$htmlchew.wrapTextIntoSafeHTMLString(text, 'div') +
html +
HTML_WRAP_BOTTOM;
};
/**
* Generate the snippet and parsed body from the message body's content.
*/
exports.processMessageContent = function processMessageContent(
content, type, isDownloaded, generateSnippet) {
// Strip any trailing newline.
if (content.slice(-1) === '\n') {
content = content.slice(0, -1);
}
var parsedContent, snippet;
switch (type) {
case 'plain':
try {
parsedContent = $quotechew.quoteProcessTextBody(content);
}
catch (ex) {
logic(scope, 'textChewError', { ex: ex });
// An empty content rep is better than nothing.
parsedContent = [];
}
if (generateSnippet) {
try {
snippet = $quotechew.generateSnippet(
parsedContent, DESIRED_SNIPPET_LENGTH
);
}
catch (ex) {
logic(scope, 'textSnippetError', { ex: ex });
snippet = '';
}
}
break;
case 'html':
if (generateSnippet) {
try {
snippet = $htmlchew.generateSnippet(content);
}
catch (ex) {
logic(scope, 'htmlSnippetError', { ex: ex });
snippet = '';
}
}
if (isDownloaded) {
try {
parsedContent = $htmlchew.sanitizeAndNormalizeHtml(content);
}
catch (ex) {
logic(scope, 'htmlParseError', { ex: ex });
parsedContent = '';
}
}
break;
}
return { content: parsedContent, snippet: snippet };
};
}); // end define