snappymail/dev/Model/Message.js

568 lines
14 KiB
JavaScript
Raw Normal View History

2016-07-07 05:03:30 +08:00
import ko from 'ko';
2021-01-25 05:58:06 +08:00
import { MessagePriority } from 'Common/EnumsUser';
2019-07-05 03:19:24 +08:00
import { i18n } from 'Common/Translator';
2016-07-07 05:03:30 +08:00
import { encodeHtml } from 'Common/Html';
import { isArray, arrayLength, forEachObjectEntry } from 'Common/Utils';
2016-07-07 05:03:30 +08:00
2021-02-04 18:25:00 +08:00
import { serverRequestRaw } from 'Common/Links';
2016-07-07 05:03:30 +08:00
import { FolderUserStore } from 'Stores/User/Folder';
import { FileInfo } from 'Common/File';
import { AttachmentCollectionModel } from 'Model/AttachmentCollection';
import { EmailCollectionModel } from 'Model/EmailCollection';
2019-07-05 03:19:24 +08:00
import { AbstractModel } from 'Knoin/AbstractModel';
2016-07-07 05:03:30 +08:00
import PreviewHTML from 'Html/PreviewMessage.html';
const
2021-01-25 05:58:06 +08:00
SignedVerifyStatus = {
UnknownPublicKeys: -4,
UnknownPrivateKey: -3,
Unverified: -2,
Error: -1,
None: 0,
Success: 1
},
replyHelper = (emails, unic, localEmails) => {
emails.forEach(email => {
if (undefined === unic[email.email]) {
unic[email.email] = true;
localEmails.push(email);
}
});
};
2021-01-22 23:32:08 +08:00
export class MessageModel extends AbstractModel {
2016-07-16 05:29:42 +08:00
constructor() {
2020-10-19 01:19:45 +08:00
super();
2016-07-07 05:03:30 +08:00
this._reset();
2020-10-25 18:46:58 +08:00
this.addObservables({
subject: '',
size: 0,
spamScore: 0,
2021-04-09 15:01:48 +08:00
spamResult: '',
isSpam: false,
2021-09-02 18:09:16 +08:00
hasVirus: null, // or boolean when scanned
2020-10-25 18:46:58 +08:00
dateTimeStampInUTC: 0,
priority: MessagePriority.Normal,
senderEmailsString: '',
senderClearEmailsString: '',
deleted: false,
isDeleted: false,
isUnseen: false,
isFlagged: false,
isAnswered: false,
isForwarded: false,
isReadReceipt: false,
focused: false,
selected: false,
checked: false,
hasAttachments: false,
isHtml: false,
hasImages: false,
isPgpSigned: false,
isPgpEncrypted: false,
pgpSignedVerifyStatus: SignedVerifyStatus.None,
pgpSignedVerifyUser: '',
readReceipt: '',
hasUnseenSubMessage: false,
hasFlaggedSubMessage: false
});
2016-07-07 05:03:30 +08:00
2020-10-25 18:46:58 +08:00
this.attachments = ko.observableArray(new AttachmentCollectionModel);
this.attachmentsSpecData = ko.observableArray();
this.threads = ko.observableArray();
2021-09-01 22:10:44 +08:00
this.unsubsribeLinks = ko.observableArray();
2016-07-07 05:03:30 +08:00
2020-10-25 21:14:14 +08:00
this.addComputables({
attachmentIconClass: () => FileInfo.getCombinedIconClass(this.hasAttachments() ? this.attachmentsSpecData() : []),
threadsLen: () => this.threads().length,
2020-10-25 21:14:14 +08:00
isImportant: () => MessagePriority.High === this.priority(),
});
2016-07-07 05:03:30 +08:00
}
_reset() {
2020-10-23 21:15:54 +08:00
this.folder = '';
2021-09-10 22:28:29 +08:00
this.uid = 0;
2016-07-07 05:03:30 +08:00
this.hash = '';
this.requestHash = '';
2020-10-23 21:15:54 +08:00
this.externalProxy = false;
this.emails = [];
this.from = new EmailCollectionModel;
this.to = new EmailCollectionModel;
this.cc = new EmailCollectionModel;
this.bcc = new EmailCollectionModel;
this.replyTo = new EmailCollectionModel;
this.deliveredTo = new EmailCollectionModel;
this.body = null;
2020-10-23 21:15:54 +08:00
this.draftInfo = [];
this.messageId = '';
this.inReplyTo = '';
this.references = '';
}
clear() {
this._reset();
2016-07-07 05:03:30 +08:00
this.subject('');
this.size(0);
this.spamScore(0);
2021-04-09 15:01:48 +08:00
this.spamResult('');
this.isSpam(false);
2021-09-02 18:09:16 +08:00
this.hasVirus(null);
2016-07-07 05:03:30 +08:00
this.dateTimeStampInUTC(0);
this.priority(MessagePriority.Normal);
this.senderEmailsString('');
this.senderClearEmailsString('');
this.deleted(false);
2020-10-23 21:15:54 +08:00
this.isDeleted(false);
this.isUnseen(false);
this.isFlagged(false);
this.isAnswered(false);
this.isForwarded(false);
2016-07-07 05:03:30 +08:00
this.isReadReceipt(false);
this.selected(false);
this.checked(false);
this.hasAttachments(false);
this.attachmentsSpecData([]);
this.isHtml(false);
this.hasImages(false);
this.attachments(new AttachmentCollectionModel);
2016-07-07 05:03:30 +08:00
this.isPgpSigned(false);
this.isPgpEncrypted(false);
this.pgpSignedVerifyStatus(SignedVerifyStatus.None);
this.pgpSignedVerifyUser('');
this.priority(MessagePriority.Normal);
this.readReceipt('');
this.threads([]);
2021-09-01 22:10:44 +08:00
this.unsubsribeLinks([]);
2016-07-07 05:03:30 +08:00
this.hasUnseenSubMessage(false);
this.hasFlaggedSubMessage(false);
}
/**
* @param {Array} properties
* @returns {Array}
*/
getEmails(properties) {
return properties.reduce((carry, property) => carry.concat(this[property]), []).map(
oItem => oItem ? oItem.email : ''
).validUnique();
2016-07-07 05:03:30 +08:00
}
/**
* @returns {string}
*/
friendlySize() {
return FileInfo.friendlySize(this.size());
2016-07-07 05:03:30 +08:00
}
computeSenderEmail() {
2021-12-01 20:54:35 +08:00
const list = [FolderUserStore.sentFolder(), FolderUserStore.draftsFolder()].includes(this.folder) ? 'to' : 'from';
2020-10-23 21:15:54 +08:00
this.senderEmailsString(this[list].toString(true));
this.senderClearEmailsString(this[list].toStringClear());
2016-07-07 05:03:30 +08:00
}
/**
* @param {FetchJsonMessage} json
2016-07-07 05:03:30 +08:00
* @returns {boolean}
*/
2020-10-23 21:15:54 +08:00
revivePropertiesFromJson(json) {
2021-09-10 22:28:29 +08:00
if ('Priority' in json && ![MessagePriority.High, MessagePriority.Low].includes(json.Priority)) {
json.Priority = MessagePriority.Normal;
2020-10-23 21:15:54 +08:00
}
if (super.revivePropertiesFromJson(json)) {
// this.foundedCIDs = isArray(json.FoundedCIDs) ? json.FoundedCIDs : [];
// this.attachments(AttachmentCollectionModel.reviveFromJson(json.Attachments, this.foundedCIDs));
2016-07-07 05:03:30 +08:00
this.computeSenderEmail();
}
}
2016-07-17 23:03:38 +08:00
/**
* @returns {boolean}
*/
hasUnsubsribeLinks() {
2021-09-01 22:10:44 +08:00
return this.unsubsribeLinks().length;
2016-07-17 23:03:38 +08:00
}
/**
* @returns {string}
*/
getFirstUnsubsribeLink() {
2021-09-01 22:10:44 +08:00
return this.unsubsribeLinks()[0] || '';
2016-07-17 23:03:38 +08:00
}
2016-07-07 05:03:30 +08:00
/**
* @param {boolean} friendlyView
* @param {boolean=} wrapWithLink
2016-07-07 05:03:30 +08:00
* @returns {string}
*/
fromToLine(friendlyView, wrapWithLink) {
return this.from.toString(friendlyView, wrapWithLink);
2016-07-07 05:03:30 +08:00
}
/**
* @returns {string}
*/
fromDkimData() {
let result = ['none', ''];
2021-07-22 03:34:17 +08:00
if (1 === arrayLength(this.from) && this.from[0] && this.from[0].dkimStatus) {
2016-07-07 05:03:30 +08:00
result = [this.from[0].dkimStatus, this.from[0].dkimValue || ''];
}
return result;
}
/**
* @param {boolean} friendlyView
* @param {boolean=} wrapWithLink
2016-07-07 05:03:30 +08:00
* @returns {string}
*/
toToLine(friendlyView, wrapWithLink) {
return this.to.toString(friendlyView, wrapWithLink);
2016-07-07 05:03:30 +08:00
}
/**
* @param {boolean} friendlyView
* @param {boolean=} wrapWithLink
2016-07-07 05:03:30 +08:00
* @returns {string}
*/
ccToLine(friendlyView, wrapWithLink) {
return this.cc.toString(friendlyView, wrapWithLink);
2016-07-07 05:03:30 +08:00
}
/**
* @param {boolean} friendlyView
* @param {boolean=} wrapWithLink
2016-07-07 05:03:30 +08:00
* @returns {string}
*/
bccToLine(friendlyView, wrapWithLink) {
return this.bcc.toString(friendlyView, wrapWithLink);
2016-07-07 05:03:30 +08:00
}
/**
* @param {boolean} friendlyView
* @param {boolean=} wrapWithLink
2016-07-07 05:03:30 +08:00
* @returns {string}
*/
replyToToLine(friendlyView, wrapWithLink) {
return this.replyTo.toString(friendlyView, wrapWithLink);
2016-07-07 05:03:30 +08:00
}
/**
* @return string
*/
lineAsCss() {
2020-08-12 07:47:24 +08:00
let classes = [];
forEachObjectEntry({
deleted: this.deleted(),
2020-10-23 21:15:54 +08:00
'deleted-mark': this.isDeleted(),
selected: this.selected(),
checked: this.checked(),
flagged: this.isFlagged(),
unseen: this.isUnseen(),
answered: this.isAnswered(),
forwarded: this.isForwarded(),
focused: this.focused(),
important: this.isImportant(),
withAttachments: this.hasAttachments(),
emptySubject: !this.subject(),
// hasChildrenMessage: 1 < this.threadsLen(),
hasUnseenSubMessage: this.hasUnseenSubMessage(),
hasFlaggedSubMessage: this.hasFlaggedSubMessage()
}, (key, value) => value && classes.push(key));
2020-08-12 07:47:24 +08:00
return classes.join(' ');
2016-07-07 05:03:30 +08:00
}
/**
* @returns {string}
*/
fromAsSingleEmail() {
return isArray(this.from) && this.from[0] ? this.from[0].email : '';
2016-07-07 05:03:30 +08:00
}
/**
* @returns {string}
*/
viewLink() {
2021-02-04 18:25:00 +08:00
return serverRequestRaw('ViewAsPlain', this.requestHash);
2016-07-07 05:03:30 +08:00
}
/**
* @returns {string}
*/
downloadLink() {
2021-02-04 18:25:00 +08:00
return serverRequestRaw('Download', this.requestHash);
2016-07-07 05:03:30 +08:00
}
/**
* @param {Object} excludeEmails
* @param {boolean=} last = false
* @returns {Array}
*/
replyEmails(excludeEmails, last) {
2019-07-05 03:19:24 +08:00
const result = [],
unic = undefined === excludeEmails ? {} : excludeEmails;
2016-07-07 05:03:30 +08:00
replyHelper(this.replyTo, unic, result);
if (!result.length) {
2016-07-07 05:03:30 +08:00
replyHelper(this.from, unic, result);
}
if (!result.length && !last) {
2016-07-07 05:03:30 +08:00
return this.replyEmails({}, true);
}
return result;
}
/**
* @param {Object} excludeEmails
* @param {boolean=} last = false
* @returns {Array.<Array>}
*/
replyAllEmails(excludeEmails, last) {
2016-07-07 05:03:30 +08:00
let data = [];
2019-07-05 03:19:24 +08:00
const toResult = [],
2016-07-07 05:03:30 +08:00
ccResult = [],
unic = undefined === excludeEmails ? {} : excludeEmails;
2016-07-07 05:03:30 +08:00
replyHelper(this.replyTo, unic, toResult);
if (!toResult.length) {
2016-07-07 05:03:30 +08:00
replyHelper(this.from, unic, toResult);
}
replyHelper(this.to, unic, toResult);
replyHelper(this.cc, unic, ccResult);
if (!toResult.length && !last) {
2016-07-07 05:03:30 +08:00
data = this.replyAllEmails({}, true);
return [data[0], ccResult];
}
return [toResult, ccResult];
}
/**
* @param {boolean=} print = false
*/
viewPopupMessage(print) {
2019-07-05 03:19:24 +08:00
const timeStampInUTC = this.dateTimeStampInUTC() || 0,
ccLine = this.ccToLine(false),
2020-08-27 21:45:47 +08:00
m = 0 < timeStampInUTC ? new Date(timeStampInUTC * 1000) : null,
win = open(''),
doc = win.document;
doc.write(PreviewHTML
2020-10-03 19:04:23 +08:00
.replace(/{{subject}}/g, encodeHtml(this.subject()))
.replace('{{date}}', encodeHtml(m ? m.format('LLL') : ''))
.replace('{{fromCreds}}', encodeHtml(this.fromToLine(false)))
.replace('{{toCreds}}', encodeHtml(this.toToLine(false)))
2021-01-22 00:21:19 +08:00
.replace('{{toLabel}}', encodeHtml(i18n('GLOBAL/TO')))
2020-10-03 19:04:23 +08:00
.replace('{{ccHide}}', ccLine ? '' : 'hidden=""')
.replace('{{ccCreds}}', encodeHtml(ccLine))
2021-01-22 00:21:19 +08:00
.replace('{{ccLabel}}', encodeHtml(i18n('GLOBAL/CC')))
2020-10-03 19:04:23 +08:00
.replace('{{bodyClass}}', this.isHtml() ? 'html' : 'plain')
.replace('{{html}}', this.bodyAsHTML())
2019-07-05 03:19:24 +08:00
);
2020-08-27 21:45:47 +08:00
doc.close();
if (print) {
setTimeout(() => win.print(), 100);
}
2016-07-07 05:03:30 +08:00
}
printMessage() {
this.viewPopupMessage(true);
}
/**
* @returns {string}
*/
generateUid() {
2020-10-23 21:15:54 +08:00
return this.folder + '/' + this.uid;
2016-07-07 05:03:30 +08:00
}
/**
* @param {MessageModel} message
* @returns {MessageModel}
*/
populateByMessageListItem(message) {
2019-07-05 03:19:24 +08:00
if (message) {
2020-10-23 21:15:54 +08:00
this.folder = message.folder;
2016-07-07 05:03:30 +08:00
this.uid = message.uid;
this.hash = message.hash;
this.requestHash = message.requestHash;
this.subject(message.subject());
this.size(message.size());
this.spamScore(message.spamScore());
2021-04-09 15:01:48 +08:00
this.spamResult(message.spamResult());
this.isSpam(message.isSpam());
2021-09-02 18:09:16 +08:00
this.hasVirus(message.hasVirus());
2016-07-07 05:03:30 +08:00
this.dateTimeStampInUTC(message.dateTimeStampInUTC());
this.priority(message.priority());
2020-10-23 21:15:54 +08:00
this.externalProxy = message.externalProxy;
2016-07-07 05:03:30 +08:00
this.emails = message.emails;
this.from = message.from;
this.to = message.to;
this.cc = message.cc;
this.bcc = message.bcc;
this.replyTo = message.replyTo;
this.deliveredTo = message.deliveredTo;
2021-09-01 22:10:44 +08:00
this.unsubsribeLinks(message.unsubsribeLinks);
2016-07-07 05:03:30 +08:00
2020-10-23 21:15:54 +08:00
this.isUnseen(message.isUnseen());
this.isFlagged(message.isFlagged());
this.isAnswered(message.isAnswered());
this.isForwarded(message.isForwarded());
2016-07-07 05:03:30 +08:00
this.isReadReceipt(message.isReadReceipt());
2020-10-23 21:15:54 +08:00
this.isDeleted(message.isDeleted());
2016-07-07 05:03:30 +08:00
this.priority(message.priority());
this.selected(message.selected());
this.checked(message.checked());
this.hasAttachments(message.hasAttachments());
this.attachmentsSpecData(message.attachmentsSpecData());
}
this.body = null;
2020-10-23 21:15:54 +08:00
this.draftInfo = [];
this.messageId = '';
this.inReplyTo = '';
this.references = '';
2016-07-07 05:03:30 +08:00
2019-07-05 03:19:24 +08:00
if (message) {
2016-07-07 05:03:30 +08:00
this.threads(message.threads());
}
this.computeSenderEmail();
return this;
}
2020-08-27 21:45:47 +08:00
showExternalImages() {
if (this.body && this.body.rlHasImages) {
2016-07-07 05:03:30 +08:00
this.hasImages(false);
2020-08-27 21:45:47 +08:00
this.body.rlHasImages = false;
2016-07-07 05:03:30 +08:00
2020-10-23 21:15:54 +08:00
let body = this.body, attr = this.externalProxy ? 'data-x-additional-src' : 'data-x-src';
2020-08-27 21:45:47 +08:00
body.querySelectorAll('[' + attr + ']').forEach(node => {
if (node.matches('img')) {
node.loading = 'lazy';
2016-07-07 05:03:30 +08:00
}
2020-08-27 21:45:47 +08:00
node.src = node.getAttribute(attr);
2016-07-07 05:03:30 +08:00
});
2020-10-23 21:15:54 +08:00
attr = this.externalProxy ? 'data-x-additional-style-url' : 'data-x-style-url';
2020-08-27 21:45:47 +08:00
body.querySelectorAll('[' + attr + ']').forEach(node => {
node.setAttribute('style', ((node.getAttribute('style')||'')
+ ';' + node.getAttribute(attr))
.replace(/^[;\s]+/,''));
2016-07-07 05:03:30 +08:00
});
}
}
2020-08-27 21:45:47 +08:00
showInternalImages() {
const body = this.body;
if (body && !body.rlInitInternalImages) {
const findAttachmentByCid = cid => this.attachments().findByCid(cid);
body.rlInitInternalImages = true;
body.querySelectorAll('[data-x-src-cid],[data-x-src-location],[data-x-style-cid]').forEach(el => {
const data = el.dataset;
if (data.xSrcCid) {
const attachment = findAttachmentByCid(data.xSrcCid);
if (attachment && attachment.download) {
el.src = attachment.linkPreview();
}
} else if (data.xSrcLocation) {
const attachment = this.attachments.find(item => data.xSrcLocation === item.contentLocation)
|| findAttachmentByCid(data.xSrcLocation);
if (attachment && attachment.download) {
el.loading = 'lazy';
el.src = attachment.linkPreview();
}
} else if (data.xStyleCid) {
const name = data.xStyleCidName,
attachment = findAttachmentByCid(data.xStyleCid);
if (attachment && attachment.linkPreview && name) {
el.setAttribute('style', name + ": url('" + attachment.linkPreview() + "');"
+ (el.getAttribute('style') || ''));
2016-07-07 05:03:30 +08:00
}
}
});
}
}
fetchDataFromDom() {
2019-07-05 03:19:24 +08:00
if (this.body) {
2020-08-27 21:45:47 +08:00
this.isHtml(!!this.body.rlIsHtml);
this.hasImages(!!this.body.rlHasImages);
2016-07-07 05:03:30 +08:00
}
}
2020-08-27 21:45:47 +08:00
/**
* @returns {string}
*/
bodyAsHTML() {
2019-07-05 03:19:24 +08:00
if (this.body) {
2020-08-27 21:45:47 +08:00
let clone = this.body.cloneNode(true),
attr = 'data-html-editor-font-wrapper';
clone.querySelectorAll('blockquote.rl-bq-switcher').forEach(
node => node.classList.remove('rl-bq-switcher','hidden-bq')
);
clone.querySelectorAll('.rlBlockquoteSwitcher').forEach(
node => node.remove()
);
clone.querySelectorAll('['+attr+']').forEach(
node => node.removeAttribute(attr)
);
return clone.innerHTML;
2016-07-07 05:03:30 +08:00
}
2020-08-27 21:45:47 +08:00
return '';
2016-07-07 05:03:30 +08:00
}
/**
* @returns {string}
*/
flagHash() {
2019-07-05 03:19:24 +08:00
return [
this.deleted(),
2020-10-23 21:15:54 +08:00
this.isDeleted(),
this.isUnseen(),
this.isFlagged(),
this.isAnswered(),
this.isForwarded(),
2019-07-05 03:19:24 +08:00
this.isReadReceipt()
].join(',');
2016-07-07 05:03:30 +08:00
}
}