mirror of
https://github.com/the-djmaze/snappymail.git
synced 2025-09-18 02:54:24 +08:00
Improved Composer handling with PGP messages
This commit is contained in:
parent
96de0be977
commit
3fc5216841
5 changed files with 103 additions and 125 deletions
|
@ -376,60 +376,57 @@ export const
|
|||
);
|
||||
},
|
||||
|
||||
convertDivs = (...args) => {
|
||||
let divText = 1 < args.length ? args[1].trim() : '';
|
||||
if (divText.length) {
|
||||
divText = '\n' + divText.replace(/<div[^>]*>([\s\S\r\n]*)<\/div>/gim, convertDivs).trim() + '\n';
|
||||
}
|
||||
|
||||
return divText;
|
||||
},
|
||||
|
||||
convertPre = (...args) =>
|
||||
1 < args.length
|
||||
? args[1]
|
||||
.toString()
|
||||
.replace(/[\n]/gm, '<br/>')
|
||||
.replace(/[\r]/gm, '')
|
||||
? args[1].toString().replace(/\n/g, '<br>')
|
||||
: '',
|
||||
|
||||
fixAttibuteValue = (...args) => (1 < args.length ? args[1] + encodeHtml(args[2]) : ''),
|
||||
|
||||
convertLinks = (...args) => (1 < args.length ? args[1].trim() : '');
|
||||
|
||||
html = html
|
||||
.replace(/\r?\n/, '')
|
||||
.replace(/<pre[^>]*>([\s\S]*?)<\/pre>/gim, convertPre)
|
||||
.replace(/\s+/gm, ' ');
|
||||
|
||||
while (/<(div|tr)[\s>]/i.test(html)) {
|
||||
html = html.replace(/\n*<(div|tr)(\s[\s\S]*?)?>\n*/gi, '\n');
|
||||
}
|
||||
while (/<\/(div|tr)[\s>]/i.test(html)) {
|
||||
html = html.replace(/\n*<\/(div|tr)(\s[\s\S]*?)?>\n*/gi, '\n');
|
||||
}
|
||||
|
||||
while (/<(ul|ol|p|h\d)[\s>]/i.test(html)) {
|
||||
html = html.replace(/\n*<(ul|ol|p|h\d)(\s[\s\S]*?)?>\n*/gi, '\n\n');
|
||||
}
|
||||
while (/<\/(ul|ol|p|h\d)[\s>]/i.test(html)) {
|
||||
html = html.replace(/\n*<\/(ul|ol|p|h\d)(\s[\s\S]*?)?>\n*/gi, '\n\n');
|
||||
}
|
||||
|
||||
tpl.innerHTML = html
|
||||
.replace(/<p[^>]*><\/p>/gi, '')
|
||||
.replace(/<pre[^>]*>([\s\S\r\n\t]*)<\/pre>/gim, convertPre)
|
||||
.replace(/[\s]+/gm, ' ')
|
||||
.replace(/((?:href|data)\s?=\s?)("[^"]+?"|'[^']+?')/gim, fixAttibuteValue)
|
||||
.replace(/<br[^>]*>/gim, '\n')
|
||||
.replace(/<\/h[\d]>/gi, '\n')
|
||||
.replace(/<\/p>/gi, '\n\n')
|
||||
.replace(/<ul[^>]*>/gim, '\n')
|
||||
.replace(/<\/ul>/gi, '\n')
|
||||
.replace(/<li[^>]*>/gim, ' * ')
|
||||
.replace(/<\/li>/gi, '\n')
|
||||
.replace(/<\/td>/gi, '\n')
|
||||
.replace(/<\/tr>/gi, '\n')
|
||||
.replace(/<hr[^>]*>/gim, '\n_______________________________\n\n')
|
||||
.replace(/<div[^>]*>([\s\S\r\n]*)<\/div>/gim, convertDivs)
|
||||
.replace(/\n*<t[dh](\s[\s\S]*?)?>/gi, '\t')
|
||||
.replace(/<\/t[dh](\s[\s\S]*?)?>/gi, '\n')
|
||||
.replace(/\n*<hr[\s\S]*?>\n*/gi, '\n\n' + '⎯'.repeat(64) + '\n\n')
|
||||
.replace(/<blockquote[^>]*>/gim, '\n__bq__start__\n')
|
||||
.replace(/<\/blockquote>/gim, '\n__bq__end__\n')
|
||||
.replace(/<a [^>]*>([\s\S\r\n]*?)<\/a>/gim, convertLinks)
|
||||
.replace(/<\/div>/gi, '\n')
|
||||
.replace(/<a [^>]*>([\s\S]*?)<\/a>/gim, convertLinks)
|
||||
.replace(/ /gi, ' ')
|
||||
.replace(/"/gi, '"')
|
||||
.replace(/<[^>]*>/gm, '');
|
||||
.replace(/<br(\s[\s\S]*?)?>/gi, '\n')
|
||||
.replace(/<[\s\S]+?>/g, '');
|
||||
|
||||
text = tpl.content.textContent;
|
||||
if (text) {
|
||||
text = text
|
||||
.replace(/\n[ \t]+/gm, '\n')
|
||||
.replace(/[\n]{3,}/gm, '\n\n')
|
||||
.replace(/\n{3,}/gm, '\n\n')
|
||||
.replace(/>/gi, '>')
|
||||
.replace(/</gi, '<')
|
||||
.replace(/&/gi, '&')
|
||||
// wordwrap max line length 100
|
||||
.match(/.{1,100}(\s|$)|\S+?(\s|$)/g).join('\n');
|
||||
.replace(/&/gi, '&');
|
||||
}
|
||||
|
||||
while (0 < --limit) {
|
||||
|
@ -506,9 +503,9 @@ export const
|
|||
.replace(/&/g, '&')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/</g, '<')
|
||||
.replace(/~~~blockquote~~~[\s]*/g, '<blockquote>')
|
||||
.replace(/[\s]*~~~\/blockquote~~~/g, '</blockquote>')
|
||||
.replace(/\n/g, '<br/>');
|
||||
.replace(/~~~blockquote~~~\s*/g, '<blockquote>')
|
||||
.replace(/\s*~~~\/blockquote~~~/g, '</blockquote>')
|
||||
.replace(/\n/g, '<br>');
|
||||
};
|
||||
|
||||
export class HtmlEditor {
|
||||
|
@ -600,31 +597,25 @@ export class HtmlEditor {
|
|||
* @param {boolean=} wrapIsHtml = false
|
||||
* @returns {string}
|
||||
*/
|
||||
getData(wrapIsHtml = false) {
|
||||
getData() {
|
||||
let result = '';
|
||||
if (this.editor) {
|
||||
try {
|
||||
if (this.isPlain() && this.editor.plugins.plain && this.editor.__plain) {
|
||||
result = this.editor.__plain.getRawData();
|
||||
} else {
|
||||
result = wrapIsHtml
|
||||
? '<div data-html-editor-font-wrapper="true" style="font-family: arial, sans-serif; font-size: 13px;">' +
|
||||
this.editor.getData() +
|
||||
'</div>'
|
||||
: this.editor.getData();
|
||||
result = this.editor.getData();
|
||||
}
|
||||
} catch (e) {} // eslint-disable-line no-empty
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean=} wrapIsHtml = false
|
||||
* @returns {string}
|
||||
*/
|
||||
getDataWithHtmlMark(wrapIsHtml = false) {
|
||||
return (this.isHtml() ? ':HTML:' : '') + this.getData(wrapIsHtml);
|
||||
getDataWithHtmlMark() {
|
||||
return (this.isHtml() ? ':HTML:' : '') + this.getData();
|
||||
}
|
||||
|
||||
modeWysiwyg() {
|
||||
|
|
4
dev/External/SquireUI.js
vendored
4
dev/External/SquireUI.js
vendored
|
@ -35,7 +35,7 @@ const
|
|||
addLinks: true // allow_smart_html_links
|
||||
*/
|
||||
sanitizeToDOMFragment: (html, isPaste/*, squire*/) => {
|
||||
tpl.innerHTML = html
|
||||
tpl.innerHTML = (html||'')
|
||||
.replace(/<\/?(BODY|HTML)[^>]*>/gi,'')
|
||||
.replace(/<!--[^>]+-->/g,'')
|
||||
.replace(/<span[^>]*>\s*<\/span>/gi,'')
|
||||
|
@ -104,7 +104,7 @@ const
|
|||
}
|
||||
|
||||
if (!skipInsert) {
|
||||
signature = (isHtml ? '<br/><br/><signature>' : "\n\n") + signature + (isHtml ? '</signature>' : '');
|
||||
signature = isHtml ? `<p><signature>${signature}</signature></p>` : `\n\n${signature}\n\n`;
|
||||
|
||||
text = insertBefore ? signature + text : text + signature;
|
||||
|
||||
|
|
|
@ -616,23 +616,16 @@ export class MessageModel extends AbstractModel {
|
|||
bodyAsHTML() {
|
||||
// if (this.body && !this.body.querySelector('iframe[src*=decrypt]')) {
|
||||
if (this.body && !this.body.querySelector('iframe')) {
|
||||
let clone = this.body.cloneNode(true),
|
||||
attr = 'data-html-editor-font-wrapper';
|
||||
let clone = this.body.cloneNode(true);
|
||||
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;
|
||||
}
|
||||
if (this.isPgpEncrypted()) {
|
||||
return this.html() || plainToHtml(this.plain());
|
||||
}
|
||||
return '';
|
||||
return this.html() || plainToHtml(this.plain());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -67,6 +67,13 @@ export const PgpUserStore = new class {
|
|||
return !!(OpenPGPUserStore.isSupported() || GnuPGUserStore.isSupported() || window.mailvelope);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isEncrypted(text) {
|
||||
return 0 === text.trim().indexOf('-----BEGIN PGP MESSAGE-----');
|
||||
}
|
||||
|
||||
async mailvelopeHasPublicKeyForEmails(recipients, all) {
|
||||
const
|
||||
keyring = this.mailvelopeKeyring,
|
||||
|
@ -129,7 +136,7 @@ export const PgpUserStore = new class {
|
|||
const sender = message.from[0].email,
|
||||
armoredText = message.plain();
|
||||
|
||||
if (!armoredText.includes('-----BEGIN PGP MESSAGE-----')) {
|
||||
if (!this.isEncrypted(armoredText)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -364,7 +364,7 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
sign = !draft && this.pgpSign() && this.canPgpSign(),
|
||||
encrypt = this.pgpEncrypt() && this.canPgpEncrypt(),
|
||||
TextIsHtml = this.oEditor.isHtml(),
|
||||
Text = this.oEditor.getData(true);
|
||||
Text = this.oEditor.getData();
|
||||
if (TextIsHtml) {
|
||||
let l;
|
||||
do {
|
||||
|
@ -708,36 +708,27 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
}
|
||||
}
|
||||
|
||||
convertSignature(signature) {
|
||||
let fromLine = this.oLastMessage ? this.emailArrayToStringLineHelper(this.oLastMessage.from, true) : '';
|
||||
if (fromLine) {
|
||||
signature = signature.replace(/{{FROM-FULL}}/g, fromLine);
|
||||
|
||||
if (!fromLine.includes(' ') && 0 < fromLine.indexOf('@')) {
|
||||
fromLine = fromLine.replace(/@\S+/, '');
|
||||
}
|
||||
|
||||
signature = signature.replace(/{{FROM}}/g, fromLine);
|
||||
}
|
||||
|
||||
return signature
|
||||
.replace(/\r/g, '')
|
||||
.replace(/\s{1,2}?{{FROM}}/g, '')
|
||||
.replace(/\s{1,2}?{{FROM-FULL}}/g, '')
|
||||
.replace(/{{DATE}}/g, new Date().format('LLLL'))
|
||||
.replace(/{{TIME}}/g, new Date().format('LT'))
|
||||
.replace(/{{MOMENT:[^}]+}}/g, '');
|
||||
}
|
||||
|
||||
setSignatureFromIdentity(identity) {
|
||||
if (identity) {
|
||||
this.editor(editor => {
|
||||
let signature = identity.signature(),
|
||||
isHtml = signature && ':HTML:' === signature.slice(0, 6);
|
||||
|
||||
editor.setSignature(
|
||||
this.convertSignature(isHtml ? signature.slice(6) : signature),
|
||||
isHtml, !!identity.signatureInsertBefore());
|
||||
let signature = identity.signature() || '',
|
||||
isHtml = ':HTML:' === signature.slice(0, 6),
|
||||
fromLine = this.oLastMessage ? this.emailArrayToStringLineHelper(this.oLastMessage.from, true) : '';
|
||||
if (fromLine) {
|
||||
signature = signature.replace(/{{FROM-FULL}}/g, fromLine);
|
||||
if (!fromLine.includes(' ') && 0 < fromLine.indexOf('@')) {
|
||||
fromLine = fromLine.replace(/@\S+/, '');
|
||||
}
|
||||
signature = signature.replace(/{{FROM}}/g, fromLine);
|
||||
}
|
||||
signature = (isHtml ? signature.slice(6) : signature)
|
||||
.replace(/\r/g, '')
|
||||
.replace(/\s{1,2}?{{FROM}}/g, '')
|
||||
.replace(/\s{1,2}?{{FROM-FULL}}/g, '')
|
||||
.replace(/{{DATE}}/g, new Date().format('LLLL'))
|
||||
.replace(/{{TIME}}/g, new Date().format('LT'))
|
||||
.replace(/{{MOMENT:[^}]+}}/g, '');
|
||||
editor.setSignature(signature, isHtml, !!identity.signatureInsertBefore());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -834,7 +825,6 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
sDate = '',
|
||||
sSubject = '',
|
||||
sText = '',
|
||||
sReplyTitle = '',
|
||||
identity = null,
|
||||
aDraftInfo = null,
|
||||
message = null;
|
||||
|
@ -882,7 +872,6 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
sDate = timestampToString(message.dateTimeStampInUTC(), 'FULL');
|
||||
sSubject = message.subject();
|
||||
aDraftInfo = message.aDraftInfo;
|
||||
sText = message.bodyAsHTML();
|
||||
|
||||
let resplyAllParts = null;
|
||||
switch (lineComposeType) {
|
||||
|
@ -960,65 +949,63 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
// no default
|
||||
}
|
||||
|
||||
sText = message.bodyAsHTML();
|
||||
let encrypted;
|
||||
|
||||
switch (lineComposeType) {
|
||||
case ComposeType.Reply:
|
||||
case ComposeType.ReplyAll:
|
||||
sFrom = message.fromToLine(false, true);
|
||||
sReplyTitle = i18n('COMPOSE/REPLY_MESSAGE_TITLE', {
|
||||
DATETIME: sDate,
|
||||
EMAIL: sFrom
|
||||
});
|
||||
|
||||
sText = sText.replace(/<img[^>]+>/g, '').replace(/<a\s[^>]+><\/a>/g, '').trim();
|
||||
sText = '<br/><br/>' + sReplyTitle + ':<br/><br/><blockquote>' + sText + '</blockquote>';
|
||||
|
||||
sText = '<div><p>' + i18n('COMPOSE/REPLY_MESSAGE_TITLE', { DATETIME: sDate, EMAIL: sFrom })
|
||||
+ ':</p><blockquote>'
|
||||
+ sText.replace(/<img[^>]+>/g, '').replace(/<a\s[^>]+><\/a>/g, '').trim()
|
||||
+ '</blockquote></div>';
|
||||
break;
|
||||
|
||||
case ComposeType.Forward:
|
||||
sFrom = message.fromToLine(false, true);
|
||||
sTo = message.toToLine(false, true);
|
||||
sCc = message.ccToLine(false, true);
|
||||
sText =
|
||||
'<br/><br/>' +
|
||||
i18n('COMPOSE/FORWARD_MESSAGE_TOP_TITLE') +
|
||||
'<br/>' +
|
||||
i18n('GLOBAL/FROM') +
|
||||
': ' +
|
||||
sFrom +
|
||||
'<br/>' +
|
||||
i18n('GLOBAL/TO') +
|
||||
': ' +
|
||||
sTo +
|
||||
(sCc.length ? '<br/>' + i18n('GLOBAL/CC') + ': ' + sCc : '') +
|
||||
'<br/>' +
|
||||
i18n('COMPOSE/FORWARD_MESSAGE_TOP_SENT') +
|
||||
': ' +
|
||||
encodeHtml(sDate) +
|
||||
'<br/>' +
|
||||
i18n('GLOBAL/SUBJECT') +
|
||||
': ' +
|
||||
encodeHtml(sSubject) +
|
||||
'<br/><br/>' +
|
||||
sText.trim() +
|
||||
'<br/><br/>';
|
||||
sText = '<div><p>' + i18n('COMPOSE/FORWARD_MESSAGE_TOP_TITLE') + '</p>'
|
||||
+ i18n('GLOBAL/FROM') + ': ' + sFrom
|
||||
+ '<br>'
|
||||
+ i18n('GLOBAL/TO') + ': ' + sTo
|
||||
+ (sCc.length ? '<br>' + i18n('GLOBAL/CC') + ': ' + sCc : '')
|
||||
+ '<br>'
|
||||
+ i18n('COMPOSE/FORWARD_MESSAGE_TOP_SENT')
|
||||
+ ': '
|
||||
+ encodeHtml(sDate)
|
||||
+ '<br>'
|
||||
+ i18n('GLOBAL/SUBJECT')
|
||||
+ ': '
|
||||
+ encodeHtml(sSubject)
|
||||
+ '<br><br>'
|
||||
+ sText.trim()
|
||||
+ '</div>';
|
||||
break;
|
||||
|
||||
case ComposeType.ForwardAsAttachment:
|
||||
sText = '';
|
||||
break;
|
||||
// no default
|
||||
default:
|
||||
encrypted = PgpUserStore.isEncrypted(sText);
|
||||
if (encrypted) {
|
||||
sText = message.plain();
|
||||
}
|
||||
}
|
||||
|
||||
this.editor(editor => {
|
||||
editor.setHtml(sText);
|
||||
encrypted || editor.setHtml(sText);
|
||||
|
||||
if (
|
||||
EditorDefaultType.PlainForced === SettingsUserStore.editorDefaultType() ||
|
||||
(!message.isHtml() && EditorDefaultType.HtmlForced !== SettingsUserStore.editorDefaultType())
|
||||
if (encrypted
|
||||
|| EditorDefaultType.PlainForced === SettingsUserStore.editorDefaultType()
|
||||
|| (!message.isHtml() && EditorDefaultType.HtmlForced !== SettingsUserStore.editorDefaultType())
|
||||
) {
|
||||
editor.modePlain();
|
||||
}
|
||||
|
||||
!encrypted || editor.setPlain(sText);
|
||||
|
||||
if (identity && ComposeType.Draft !== lineComposeType && ComposeType.EditAsNew !== lineComposeType) {
|
||||
this.setSignatureFromIdentity(identity);
|
||||
}
|
||||
|
@ -1507,8 +1494,8 @@ class ComposePopupView extends AbstractViewPopup {
|
|||
* The iframe will be injected into the container identified by selector.
|
||||
* https://mailvelope.github.io/mailvelope/Editor.html
|
||||
*/
|
||||
let text = this.oEditor.getData(true),
|
||||
encrypted = text.includes('-----BEGIN PGP MESSAGE-----'),
|
||||
let text = this.oEditor.getData(),
|
||||
encrypted = PgpUserStore.isEncrypted(text),
|
||||
size = SettingsGet('PhpUploadSizes')['post_max_size'],
|
||||
quota = pInt(size);
|
||||
switch (size.slice(-1)) {
|
||||
|
|
Loading…
Add table
Reference in a new issue