Use alternate text part to generate reply/forward text when switching compose modes...
authorMichael M Slusarz <slusarz@curecanti.org>
Thu, 2 Sep 2010 20:35:27 +0000 (14:35 -0600)
committerMichael M Slusarz <slusarz@curecanti.org>
Thu, 2 Sep 2010 21:11:38 +0000 (15:11 -0600)
This does a much better job of trying to conserve format (especially
from HTML -> Text) since the alternative display part should
(theoretically) be a better representation of the original data than
message text created via our Text -> HTML or HTML -> Text conversion.

imp/docs/CHANGES
imp/js/compose-dimp.js
imp/lib/Ajax/Application.php
imp/lib/Compose.php

index e6a7984..8bb6244 100644 (file)
@@ -2,6 +2,8 @@
 v5.0-git
 --------
 
+[mms] Use alternate text part to generate reply/forward text when switching
+      compose modes if user has not altered message text (DIMP).
 [mms] Improved login error reporting/logging in IMP (Request #9211).
 [mms] Add hook to skip MDN prompt based on content of message headers.
 [mms] Allow expand/collapse of folders in MIMP.
index e8f05b2..515b068 100644 (file)
@@ -10,8 +10,9 @@
 var DimpCompose = {
     // Variables defaulting to empty/false:
     //   auto_save_interval, compose_cursor, disabled, drafts_mbox,
-    //   editor_wait, is_popup, knl, last_msg, old_action, old_identity,
-    //   resizing, rte, skip_spellcheck, spellcheck, sc_submit, uploading
+    //   editor_wait, is_popup, knl, md5_hdrs, md5_msg, md5_msgOrig,
+    //   old_action, old_identity, resizing, rte, rte_loaded, skip_spellcheck,
+    //   spellcheck, sc_submit, uploading
 
     knl: {},
 
@@ -52,7 +53,7 @@ var DimpCompose = {
     closeQReply: function()
     {
         var al = $('attach_list').childElements();
-        this.last_msg = '';
+        this.md5_hdrs = this.md5_msg = this.md5_msgOrig = '';
 
         if (al.size()) {
             this.removeAttach(al);
@@ -364,18 +365,36 @@ var DimpCompose = {
             DIMP.SpellChecker.resume();
         }
 
-        var config, text;
+        var changed, config, text;
 
         if (IMP_Compose_Base.editor_on) {
+            changed = (this.msgHash() != this.md5_msgOrig);
             text = this.rte.getData();
             this.rte.destroy();
+            this.rte_loaded = false;
 
             this.RTELoading('show');
-            DimpCore.doAction('html2Text', { identity: $F('identity'), text: text }, { callback: this.setMessageText.bind(this), ajaxopts: { asynchronous: false } });
+            DimpCore.doAction('html2Text', {
+                changed: Number(changed),
+                identity: $F('identity'),
+                imp_compose: $F('composeCache'),
+                text: text
+            }, {
+                ajaxopts: { asynchronous: false },
+                callback: this.setMessageText.bind(this)
+            });
             this.RTELoading('hide');
         } else {
             if (!noupdate) {
-                DimpCore.doAction('text2Html', { identity: $F('identity'), text: $F('composeMessage') }, { callback: this.setMessageText.bind(this), ajaxopts: { asynchronous: false } });
+                DimpCore.doAction('text2Html', {
+                    changed: Number(this.msgHash() != this.md5_msgOrig),
+                    identity: $F('identity'),
+                    imp_compose: $F('composeCache'),
+                    text: $F('composeMessage')
+                }, {
+                    ajaxopts: { asynchronous: false },
+                    callback: this.setMessageText.bind(this)
+                });
             }
 
             config = Object.clone(IMP.ckeditor_config);
@@ -386,6 +405,7 @@ var DimpCompose = {
                 this.resizeMsgArea();
                 this.RTELoading('hide');
                 this.rte.focus();
+                this.rte_loaded = true;
             }.bind(this);
             this.RTELoading('show');
             this.rte = CKEDITOR.replace('composeMessage', config);
@@ -477,22 +497,6 @@ var DimpCompose = {
             identity = IMP_Compose_Base.getIdentity($F('last_identity'));
         opts = opts || {};
 
-        // Set auto-save-drafts now if not already active.
-        if (DIMP.conf_compose.auto_save_interval_val &&
-            !this.auto_save_interval) {
-            this.auto_save_interval = new PeriodicalExecuter(function() {
-                if ($('compose').visible()) {
-                    var curr_hash = MD5.hash($('to', 'cc', 'bcc', 'subject').invoke('getValue').join('\0') + (IMP_Compose_Base.editor_on ? this.rte.getData() : $F('composeMessage')));
-                    if (this.last_msg && curr_hash != this.last_msg) {
-                        this.uniqueSubmit('autoSaveDraft');
-                    }
-                    this.last_msg = curr_hash;
-                }
-            }.bind(this), DIMP.conf_compose.auto_save_interval_val * 60);
-            /* Immediately execute to get MD5 hash of empty message. */
-            this.auto_save_interval.execute();
-        }
-
         $('to').setValue(header.to);
         if (header.cc) {
             $('cc').setValue(header.cc);
@@ -551,6 +555,48 @@ var DimpCompose = {
                 this.focusEditor();
             }
         }
+
+        this.fillFormHash();
+    },
+
+    fillFormHash: function()
+    {
+        if (IMP_Compose_Base.editor_on && !this.rte_loaded) {
+            this.fillFormHash.bind(this).defer();
+            return;
+        }
+
+        // This value is used to determine if the text has changed when
+        // swapping compose modes.
+        this.md5_msgOrig = this.msgHash();
+
+        // Set auto-save-drafts now if not already active.
+        if (DIMP.conf_compose.auto_save_interval_val &&
+            !this.auto_save_interval) {
+            this.auto_save_interval = new PeriodicalExecuter(function() {
+                if ($('compose').visible()) {
+                    var hdrs = MD5.hash($('to', 'cc', 'bcc', 'subject').invoke('getValue').join('\0')), msg;
+                    if (this.md5_hdrs) {
+                        msg = this.msgHash();
+                        if (this.md5_hdrs != hdrs || this.md5_msg != msg) {
+                            this.uniqueSubmit('autoSaveDraft');
+                        }
+                    } else {
+                        msg = this.md5_msgOrig;
+                    }
+                    this.md5_hdrs = hdrs;
+                    this.md5_msg = msg;
+                }
+            }.bind(this), DIMP.conf_compose.auto_save_interval_val * 60);
+
+            /* Immediately execute to get MD5 hash of headers. */
+            this.auto_save_interval.execute();
+        }
+    },
+
+    msgHash: function()
+    {
+        return MD5.hash(IMP_Compose_Base.editor_on ? this.rte.getData() : $F('composeMessage'));
     },
 
     fadeNotice: function(elt)
index a5d956d..ef8576a 100644 (file)
@@ -941,7 +941,9 @@ class IMP_Ajax_Application extends Horde_Core_Ajax_Application
      *
      * Variables used:
      * <pre>
+     * 'changed' - (integer) Has the text changed from the original?
      * 'identity' - (integer) The current identity.
+     * 'imp_compose' - (string) The IMP_Compose cache identifier.
      * 'text' - (string) The text to convert.
      * </pre>
      *
@@ -953,6 +955,34 @@ class IMP_Ajax_Application extends Horde_Core_Ajax_Application
     public function html2Text()
     {
         $result = new stdClass;
+
+        if (!$this->_vars->changed) {
+            list($imp_compose, $imp_contents) = $this->_initCompose();
+
+            switch ($imp_compose->getMetadata('reply_type')) {
+            case 'forward':
+                switch ($imp_compose->getMetadata('forward_type')) {
+                case 'forward_body':
+                case 'forward_both':
+                    $data = $imp_compose->forwardMessageText($imp_contents, array(
+                        'format' => 'text'
+                    ));
+                    $result->text = $data['body'];
+                    return $result;
+                }
+                break;
+
+            case 'reply':
+            case 'reply_all':
+            case 'reply_list':
+                $data = $imp_compose->replyMessageText($imp_contents, array(
+                    'format' => 'text'
+                ));
+                $result->text = $data['body'];
+                return $result;
+            }
+        }
+
         $result->text = $GLOBALS['injector']->getInstance('IMP_Ui_Compose')->convertComposeText($this->_vars->text, 'text', intval($this->_vars->identity));
 
         return $result;
@@ -963,7 +993,9 @@ class IMP_Ajax_Application extends Horde_Core_Ajax_Application
      *
      * Variables used:
      * <pre>
+     * 'changed' - (integer) Has the text changed from the original?
      * 'identity' - (integer) The current identity.
+     * 'imp_compose' - (string) The IMP_Compose cache identifier.
      * 'text' - (string) The text to convert.
      * </pre>
      *
@@ -975,6 +1007,34 @@ class IMP_Ajax_Application extends Horde_Core_Ajax_Application
     public function text2Html()
     {
         $result = new stdClass;
+
+        if (!$this->_vars->changed) {
+            list($imp_compose, $imp_contents) = $this->_initCompose();
+
+            switch ($imp_compose->getMetadata('reply_type')) {
+            case 'forward':
+                switch ($imp_compose->getMetadata('forward_type')) {
+                case 'forward_body':
+                case 'forward_both':
+                    $data = $imp_compose->forwardMessageText($imp_contents, array(
+                        'format' => 'html'
+                    ));
+                    $result->text = $data['body'];
+                    return $result;
+                }
+                break;
+
+            case 'reply':
+            case 'reply_all':
+            case 'reply_list':
+                $data = $imp_compose->replyMessageText($imp_contents, array(
+                    'format' => 'html'
+                ));
+                $result->text = $data['body'];
+                return $result;
+            }
+        }
+
         $result->text = $GLOBALS['injector']->getInstance('IMP_Ui_Compose')->convertComposeText($this->_vars->text, 'html', intval($this->_vars->identity));
 
         return $result;
index 62e7727..cdc9b63 100644 (file)
@@ -1460,16 +1460,45 @@ class IMP_Compose
             $this->_modified = true;
         }
 
+        return array_merge(array(
+            'headers' => $header,
+            'identity' => $match_identity,
+            'type' => $reply_type
+        ), $this->replyMessageText($contents));
+    }
+
+    /**
+     * Returns the reply text for a message.
+     *
+     * @param IMP_Contents $contents  An IMP_Contents object.
+     * @param array $opts             Additional options:
+     * <pre>
+     * 'format' - (string) Force to this format.
+     *            DEFAULT: Auto-determine.
+     * </pre>
+     *
+     * @return array  An array with the following keys:
+     * <pre>
+     * 'body'     - The text of the body part
+     * 'encoding' - The guessed charset to use for the reply
+     * 'format'   - The format of the body message
+     * </pre>
+     */
+    public function replyMessageText($contents, array $opts = array())
+    {
+        global $prefs;
+
         if (!$prefs->getValue('reply_quote')) {
             return array(
                 'body' => '',
-                'format' => 'text',
-                'headers' => $header,
-                'identity' => $match_identity,
-                'type' => $reply_type
+                'encoding' => '',
+                'format' => 'text'
             );
         }
 
+        $charset = $GLOBALS['registry']->getCharset();
+        $h = $contents->getHeaderOb();
+
         $from = Horde_Mime_Address::addrArray2String($h->getOb('from'), array('charset' => $charset));
 
         if ($prefs->getValue('reply_headers') && !empty($h)) {
@@ -1487,17 +1516,24 @@ class IMP_Compose
             $msg_post = '';
         }
 
-        $compose_html = (($_SESSION['imp']['view'] != 'mimp') && $GLOBALS['prefs']->getValue('compose_html'));
+        if ($_SESSION['imp']['view'] == 'mimp') {
+            $compose_html = false;
+        } elseif (!empty($opts['format'])) {
+            $compose_html = ($opts['format'] == 'html');
+        } else {
+            $compose_html = ($prefs->getValue('compose_html') || $prefs->getValue('reply_format'));
+        }
 
         $msg_text = $this->_getMessageText($contents, array(
-            'html' => ($GLOBALS['prefs']->getValue('reply_format') || $compose_html),
+            'html' => $compose_html,
             'replylimit' => true,
             'toflowed' => true,
             'type' => 'reply'
         ));
 
         if (!empty($msg_text) &&
-            ($compose_html || ($msg_text['mode'] == 'html'))) {
+            ($prefs->getValue('compose_html') ||
+             ($msg_text['mode'] == 'html'))) {
             $msg = '<p>' . $this->text2html(trim($msg_pre)) . '</p>' .
                    '<blockquote type="cite" style="background-color:#f0f0f0;border-left:1px solid blue;padding-left:1em;">' .
                    (($msg_text['mode'] == 'text') ? $this->text2html($msg_text['text']) : $msg_text['text']) .
@@ -1508,15 +1544,13 @@ class IMP_Compose
             $msg = empty($msg_text['text'])
                 ? '[' . _("No message body text") . ']'
                 : $msg_pre . $msg_text['text'] . $msg_post;
+            $msg_text['mode'] = 'text';
         }
 
         return array(
             'body' => $msg . "\n",
             'encoding' => $msg_text['encoding'],
-            'format' => $msg_text['mode'],
-            'headers' => $header,
-            'identity' => $match_identity,
-            'type' => $reply_type
+            'format' => $msg_text['mode']
         );
     }
 
@@ -1569,6 +1603,7 @@ class IMP_Compose
          * added to the outgoing messages. */
         $this->_metadata['in_reply_to'] = trim($h->getValue('message-id'));
         $this->_metadata['reply_type'] = 'forward';
+        $this->_metadata['forward_type'] = $type;
         $this->_modified = true;
 
         $header['subject'] = $h->getValue('subject');
@@ -1581,44 +1616,89 @@ class IMP_Compose
             $header['subject'] = 'Fwd:';
         }
 
+        if ($attach &&
+            in_array($type, array('forward_attach', 'forward_both'))) {
+            $this->attachIMAPMessage(new IMP_Indices($contents));
+        }
+
         if (in_array($type, array('forward_body', 'forward_both'))) {
-            $from = Horde_Mime_Address::addrArray2String($h->getOb('from'), array('charset' => $GLOBALS['registry']->getCharset()));
+            $ret = $this->forwardMessageText($contents);
+        } else {
+            $ret = array(
+                'body' => '',
+                'encoding' => '',
+                'format' => 'text'
+            );
+        }
 
-            $msg_pre = "\n----- " .
-                ($from ? sprintf(_("Forwarded message from %s"), $from) : _("Forwarded message")) .
-                " -----\n" . $this->_getMsgHeaders($h) . "\n";
-            $msg_post = "\n\n----- " . _("End forwarded message") . " -----\n";
+        return array_merge(array(
+            'headers' => $header,
+            'identity' => $this->_getMatchingIdentity($h),
+            'type' => $type
+        ), $ret);
+    }
 
-            $compose_html = (($_SESSION['imp']['view'] != 'mimp') && $GLOBALS['prefs']->getValue('compose_html'));
+    /**
+     * Returns the forward text for a message.
+     *
+     * @param IMP_Contents $contents  An IMP_Contents object.
+     * @param array $opts             Additional options:
+     * <pre>
+     * 'format' - (string) Force to this format.
+     *            DEFAULT: Auto-determine.
+     * </pre>
+     *
+     * @return array  An array with the following keys:
+     * <pre>
+     * 'body'     - The text of the body part
+     * 'encoding' - The guessed charset to use for the reply
+     * 'format'   - The format of the body message
+     * </pre>
+     */
+    public function forwardMessageText($contents, array $opts = array())
+    {
+        global $prefs;
 
-            $msg_text = $this->_getMessageText($contents, array(
-                'html' => ($GLOBALS['prefs']->getValue('forward_format') || $compose_html),
-                'type' => 'forward'
-            ));
+        $h = $contents->getHeaderOb();
 
-            if (!empty($msg_text) &&
-                ($compose_html || ($msg_text['mode'] == 'html'))) {
-                $msg = $this->text2html($msg_pre) .
-                    (($msg_text['mode'] == 'text') ? $this->text2html($msg_text['text']) : $msg_text['text']) .
-                    $this->text2html($msg_post);
-                $format = 'html';
-            } else {
-                $msg = $msg_pre . $msg_text['text'] . $msg_post;
-            }
+        $from = Horde_Mime_Address::addrArray2String($h->getOb('from'), array(
+            'charset' => $GLOBALS['registry']->getCharset()
+        ));
+
+        $msg_pre = "\n----- " .
+            ($from ? sprintf(_("Forwarded message from %s"), $from) : _("Forwarded message")) .
+            " -----\n" . $this->_getMsgHeaders($h) . "\n";
+        $msg_post = "\n\n----- " . _("End forwarded message") . " -----\n";
+
+        if ($_SESSION['imp']['view'] == 'mimp') {
+            $compose_html = false;
+        } elseif (!empty($opts['format'])) {
+            $compose_html = ($opts['format'] == 'html');
+        } else {
+            $compose_html = ($prefs->getValue('compose_html') || $prefs->getValue('forward_format'));
         }
 
-        if ($attach &&
-            in_array($type, array('forward_attach', 'forward_both'))) {
-            $this->attachIMAPMessage(new IMP_Indices($contents));
+        $msg_text = $this->_getMessageText($contents, array(
+            'html' => $compose_html,
+            'type' => 'forward'
+        ));
+
+        if (!empty($msg_text) &&
+            ($prefs->getValue('compose_html') ||
+             ($msg_text['mode'] == 'html'))) {
+            $msg = $this->text2html($msg_pre) .
+                (($msg_text['mode'] == 'text') ? $this->text2html($msg_text['text']) : $msg_text['text']) .
+                $this->text2html($msg_post);
+            $format = 'html';
+        } else {
+            $msg = $msg_pre . $msg_text['text'] . $msg_post;
+            $format = 'text';
         }
 
         return array(
             'body' => $msg,
-            'encoding' => isset($msg_text) ? $msg_text['encoding'] : $GLOBALS['registry']->getCharset(),
-            'format' => $format,
-            'headers' => $header,
-            'identity' => $this->_getMatchingIdentity($h),
-            'type' => $type
+            'encoding' => $msg_text['encoding'],
+            'format' => $format
         );
     }