diff --git a/html-editor-lib.pl b/html-editor-lib.pl
index 94c85cf5d..09736bcec 100644
--- a/html-editor-lib.pl
+++ b/html-editor-lib.pl
@@ -242,6 +242,9 @@ my $iframe_styles =
$iframe_styles =~ s/\n/ /g;
my $navigation_type = $ENV{'HTTP_X_NAVIGATION_TYPE'};
$navigation_type ||= 'reload';
+
+# Key for preserving unsaved editor content across page reloads
+my $storage_key = $opts->{'storage'} || 'quill=last-message';
my $html_editor_init_script =
<
@@ -263,6 +266,60 @@ my $html_editor_init_script =
qf.whitelist = ["monospace"],
Quill.register(qf, true);
+ // Register signature class attributor, so signature blocks keep their
+ // marking class across editor round trips, e.g. when editing a draft
+ const qp = Quill.import('parchment');
+ Quill.register(new qp.Attributor.Class('signature', 'x-signature',
+ { scope: qp.Scope.BLOCK }), true);
+
+ // Convert the signature container into per-block marking classes,
+ // which the editor preserves via the registered signature attributor.
+ // Block children are marked directly, while runs of inline content,
+ // like plain text with line breaks, become marked paragraphs
+ const signature_marks = function(html) {
+ if (!html || html.indexOf('x-signature') < 0) {
+ return html;
+ }
+ const doc = document.createElement('div'),
+ blocks = ['P', 'DIV', 'UL', 'OL', 'TABLE',
+ 'BLOCKQUOTE', 'PRE', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6'];
+ doc.innerHTML = html;
+ doc.querySelectorAll('div.x-signature').forEach(function(sig) {
+ const frag = document.createDocumentFragment();
+ let run = [];
+ const run_empty = function() {
+ return !run.some(function(n) {
+ return n.nodeType === 1 || (n.textContent || '').trim();
+ });
+ };
+ const flush = function() {
+ if (run.length && !run_empty()) {
+ const p = document.createElement('p');
+ p.className = 'x-signature-mark';
+ run.forEach(function(n) { p.appendChild(n); });
+ frag.appendChild(p);
+ }
+ run = [];
+ };
+ Array.from(sig.childNodes).forEach(function(node) {
+ if (node.nodeType === 1 && blocks.includes(node.tagName)) {
+ // Mark block level parts of the signature directly
+ flush();
+ node.classList.add('x-signature-mark');
+ frag.appendChild(node);
+ } else if (node.nodeType === 1 && node.tagName === 'BR') {
+ // Line break ends the current inline line
+ flush();
+ } else {
+ run.push(node);
+ }
+ });
+ flush();
+ sig.replaceWith(frag);
+ });
+ return doc.innerHTML;
+ };
+
const editor = new Quill('.ql-container', {
modules: {
syntax: typeof hljs === 'object',
@@ -332,7 +389,7 @@ my $html_editor_init_script =
editor.on('text-change', function() {
// This should most probably go to onSubmit event
targ.value = editor.root.innerHTML + "
";
- sessionStorage.setItem('$module_name/quill=last-message', editor.root.innerHTML);
+ sessionStorage.setItem('$module_name/$storage_key', editor.root.innerHTML);
let extraValue = String(),
sync = JSON.parse('@{[&convert_to_json($opts->{'textarea'}->{'sync'}->{'data'})]}'),
position = '@{[$opts->{'textarea'}->{'sync'}->{'position'}]}',
@@ -386,7 +443,7 @@ my $html_editor_init_script =
restore_message = false;
}
if (restore_message) {
- const quill_last_message = sessionStorage.getItem('$module_name/quill=last-message');
+ const quill_last_message = sessionStorage.getItem('$module_name/$storage_key');
if (quill_last_message) {
editor.pasteHTML(quill_last_message);
return;
@@ -394,8 +451,8 @@ my $html_editor_init_script =
}
// Update editor on initial load
- editor.pasteHTML(targ.value);
- sessionStorage.setItem('$module_name/quill=last-message', editor.root.innerHTML);
+ editor.pasteHTML(signature_marks(targ.value));
+ sessionStorage.setItem('$module_name/$storage_key', editor.root.innerHTML);
}
@{[$opts->{'load'} ? "fn_${module_name}_html_editor_init()" : '']}
@@ -506,13 +563,13 @@ if ($document_styles_string) {
}
}
}
- # Fill tags with our inline styles
+ # Fill tags with our inline styles, preserving existing tag attributes
my (@document_styles_tag_names) = $styled_html_email =~ /<(?!style)(?!script)(?!s)(\w+)\s*.*?>/migx;
foreach my $tag_name (&unique(@document_styles_tag_names)) {
my (%document_styles_tag_names) = $document_styles_string =~ /(\Q$tag_name\E)\s*\{\s*([^}]*?)\s*\}/migx;
foreach my $tag (keys %document_styles_tag_names) {
my $tag_style = &$style_format($document_styles_tag_names{$tag});
- $styled_html_email =~ s/(<$tag)(?![^>]+style).*?>/$1 style="$tag_style">/mig;
+ $styled_html_email =~ s/(<$tag\b)(?![^>]+style)([^>]*)>/$1$2 style="$tag_style">/mig;
}
}
}
diff --git a/mailboxes/folders-lib.pl b/mailboxes/folders-lib.pl
index f897771e0..fa7f12c64 100755
--- a/mailboxes/folders-lib.pl
+++ b/mailboxes/folders-lib.pl
@@ -3285,6 +3285,14 @@ foreach my $a (@$attach) {
return @rv;
}
+# signature_is_html(sig)
+# Returns 1 if the given signature contains HTML markup
+sub signature_is_html
+{
+local ($sig) = @_;
+return 0 if (!$sig);
+return $sig =~ /<\/?(?:a|b|i|u|s|p|em|img|br|hr|div|span|font|code|pre|sub|sup|small|big|strong|strike|blockquote|table|tbody|thead|tr|td|th|ul|ol|li|h[1-6]|style)\b[^>]*>/i ? 1 : 0;
+}
# quoted_message(&mail, quote-mode, sig, 0=any,1=text,2=html, sig-at-top?)
# Returns the quoted text, html-flag and body attachment
sub quoted_message
@@ -3315,20 +3323,22 @@ local $qm = %userconfig ? $userconfig{'html_quote'} : $config{'html_quote'};
if (($cfg->{'html_edit'} == 2 ||
$cfg->{'html_edit'} == 1 && $htmlbody) &&
$bodymode != 1) {
- # Create quoted body HTML
+ # Create quoted body HTML, with the signature wrapped in a container
+ # with a known class so that it can be styled later if needed
+ $sig =~ s/\n/
\n/g if (!&signature_is_html($sig));
+ $sig = "$sig
" if ($sig);
if ($htmlbody) {
$body = $htmlbody;
- $sig =~ s/\n/
\n/g;
if ($qu && $qm == 0) {
# Quoted HTML as cite
$quote = &html_escape($writer)."\n".
"\n".
&safe_html($htmlbody->{'data'}).
"
";
- if ($sigtop) {
+ if ($sig && $sigtop) {
$quote = $sig."
\n".$quote;
}
- else {
+ elsif ($sig) {
$quote = $quote.$sig."
\n";
}
}
@@ -3341,10 +3351,10 @@ if (($cfg->{'html_edit'} == 2 ||
else {
# Un-quoted HTML
$quote = &safe_html($htmlbody->{'data'});
- if ($sigtop) {
+ if ($sig && $sigtop) {
$quote = $sig."
\n".$quote;
}
- else {
+ elsif ($sig) {
$quote = $quote.$sig."
\n";
}
}
@@ -3360,10 +3370,10 @@ if (($cfg->{'html_edit'} == 2 ||
"\n".
"$pd
".
"
";
- if ($sigtop) {
+ if ($sig && $sigtop) {
$quote = $sig."
\n".$quote;
}
- else {
+ elsif ($sig) {
$quote = $quote.$sig."
\n";
}
}
@@ -3376,10 +3386,10 @@ if (($cfg->{'html_edit'} == 2 ||
else {
# Un-quoted plain text as HTML
$quote = "$pd
";
- if ($sigtop) {
+ if ($sig && $sigtop) {
$quote = $sig."
\n".$quote;
}
- else {
+ elsif ($sig) {
$quote = $quote.$sig."
\n";
}
}
@@ -3388,6 +3398,7 @@ if (($cfg->{'html_edit'} == 2 ||
}
else {
# Create quoted body text
+ $sig = &html_to_text($sig) if (&signature_is_html($sig));
if ($plainbody) {
$body = $plainbody;
$quote = $plainbody->{'data'};
diff --git a/mailboxes/reply_mail.cgi b/mailboxes/reply_mail.cgi
index d4e02a6a3..465ab24ca 100755
--- a/mailboxes/reply_mail.cgi
+++ b/mailboxes/reply_mail.cgi
@@ -21,10 +21,12 @@ if ($in{'new'}) {
}
$sig = &get_signature($in{'user'});
if ($html_edit && $sig) {
- $sig =~ s/\n/
\n/g;
- $quote = "$sig";
+ $sig =~ s/\n/
\n/g if (!&signature_is_html($sig));
+ $quote = "$sig
".
+ "";
}
else {
+ $sig = &html_to_text($sig) if (&signature_is_html($sig));
$quote = "\n\n$sig" if ($sig);
}
$to = $in{'to'};
@@ -313,8 +315,9 @@ else {
$subject = "Fwd: ".$subject if ($subject !~ /^Fwd:/i &&
($in{'forward'} || @mailforward));
- # Construct the initial mail text
- $sig = &get_signature($in{'user'});
+ # Construct the initial mail text, without adding a signature when
+ # editing an existing draft which already contains one
+ $sig = &get_signature($in{'user'}) if (!$in{'enew'});
($quote, $html_edit, $body) = "ed_message($mail, $qu, $sig);
# Load images using server in replies
$quote = &disable_html_images($quote, 3);
@@ -476,10 +479,11 @@ if ($html_edit) {
after =>
{ editor => $iframe_quote }
});
- $sig =~ s/\n/
/g,
- $sig =~ s/^\s+//g,
- $sig = "
$sig
"
- if ($sig);
+ if ($sig) {
+ $sig =~ s/\n/
/g if (!&signature_is_html($sig));
+ $sig =~ s/^\s+//g;
+ $sig = "
$sig
";
+ }
print &ui_table_row(undef,
&ui_textarea("body", $sig, 12, 80, undef, 0,
"style='display: none' id=body data-html-mode='$config{'html_edit_mode'}'").
@@ -487,6 +491,7 @@ if ($html_edit) {
}
else {
# Show text editing area
+ $sig = &html_to_text($sig) if (&signature_is_html($sig));
$wm = $config{'wrap_mode'};
$wm =~ s/^wrap=//g;
$wcols = $config{'wrap_compose'};