[REF] Chatter: refactored system for read more / read less, now based on html_email_c...
authorThibault Delavallée <tde@openerp.com>
Tue, 23 Apr 2013 15:36:45 +0000 (17:36 +0200)
committerThibault Delavallée <tde@openerp.com>
Tue, 23 Apr 2013 15:36:45 +0000 (17:36 +0200)
bzr revid: tde@openerp.com-20130423153645-k8vcb9hiuapzf7sg

addons/mail/__openerp__.py
addons/mail/mail_message.py
addons/mail/static/lib/jquery.expander/jquery.expander.js [deleted file]
addons/mail/static/src/js/mail.js
addons/mail/static/src/xml/mail.xml

index 63cd088..db0d8c8 100644 (file)
@@ -83,7 +83,6 @@ Main Features
         'static/src/css/mail_group.css',
     ],
     'js': [
-        'static/lib/jquery.expander/jquery.expander.js',
         'static/src/js/mail.js',
         'static/src/js/mail_followers.js',
         'static/src/js/many2many_tags_email.js',
index defc12a..ec6a8db 100644 (file)
@@ -362,15 +362,16 @@ class mail_message(osv.Model):
         has_voted = uid in [user.id for user in message.vote_user_ids]
 
         try:
-            body_html = html_email_clean(message.body)
+            body_short = html_email_clean(message.body, remove_unwanted=True, use_max_length=True, max_length=50)
         except Exception:
-            body_html = '<p><b>Encoding Error : </b><br/>Unable to convert this message (id: %s).</p>' % message.id
+            body_short = '<p><b>Encoding Error : </b><br/>Unable to convert this message (id: %s).</p>' % message.id
             _logger.exception(Exception)
 
         return {'id': message.id,
                 'type': message.type,
                 'subtype': message.subtype_id.name if message.subtype_id else False,
-                'body': body_html,
+                'body': message.body,
+                'body_short': body_short,
                 'model': message.model,
                 'res_id': message.res_id,
                 'record_name': message.record_name,
diff --git a/addons/mail/static/lib/jquery.expander/jquery.expander.js b/addons/mail/static/lib/jquery.expander/jquery.expander.js
deleted file mode 100644 (file)
index 91cc03d..0000000
+++ /dev/null
@@ -1,385 +0,0 @@
-/*!
- * jQuery Expander Plugin v1.4.2
- *
- * Date: Fri Mar 16 14:29:56 2012 EDT
- * Requires: jQuery v1.3+
- *
- * Copyright 2011, Karl Swedberg
- * Dual licensed under the MIT and GPL licenses (just like jQuery):
- * http://www.opensource.org/licenses/mit-license.php
- * http://www.gnu.org/licenses/gpl.html
- *
- *
- *
- *
-*/
-
-(function($) {
-  $.expander = {
-    version: '1.4.2',
-    defaults: {
-      // the number of characters at which the contents will be sliced into two parts.
-      slicePoint: 100,
-
-      // whether to keep the last word of the summary whole (true) or let it slice in the middle of a word (false)
-      preserveWords: true,
-
-      // a threshold of sorts for whether to initially hide/collapse part of the element's contents.
-      // If after slicing the contents in two there are fewer words in the second part than
-      // the value set by widow, we won't bother hiding/collapsing anything.
-      widow: 4,
-
-      // text displayed in a link instead of the hidden part of the element.
-      // clicking this will expand/show the hidden/collapsed text
-      expandText: 'read more',
-      expandPrefix: '&hellip; ',
-
-      expandAfterSummary: false,
-
-      // class names for summary element and detail element
-      summaryClass: 'summary',
-      detailClass: 'details',
-
-      // class names for <span> around "read-more" link and "read-less" link
-      moreClass: 'read-more',
-      lessClass: 'read-less',
-
-      // number of milliseconds after text has been expanded at which to collapse the text again.
-      // when 0, no auto-collapsing
-      collapseTimer: 0,
-
-      // effects for expanding and collapsing
-      expandEffect: 'fadeIn',
-      expandSpeed: 250,
-      collapseEffect: 'fadeOut',
-      collapseSpeed: 200,
-
-      // allow the user to re-collapse the expanded text.
-      userCollapse: true,
-
-      // text to use for the link to re-collapse the text
-      userCollapseText: 'read less',
-      userCollapsePrefix: ' ',
-
-
-      // all callback functions have the this keyword mapped to the element in the jQuery set when .expander() is called
-
-      onSlice: null, // function() {}
-      beforeExpand: null, // function() {},
-      afterExpand: null, // function() {},
-      onCollapse: null // function(byUser) {}
-    }
-  };
-
-  $.fn.expander = function(options) {
-    var meth = 'init';
-
-    if (typeof options == 'string') {
-      meth = options;
-      options = {};
-    }
-
-    var opts = $.extend({}, $.expander.defaults, options),
-        rSelfClose = /^<(?:area|br|col|embed|hr|img|input|link|meta|param).*>$/i,
-        rAmpWordEnd = opts.wordEnd || /(&(?:[^;]+;)?|[a-zA-Z\u00C0-\u0100]+)$/,
-        rOpenCloseTag = /<\/?(\w+)[^>]*>/g,
-        rOpenTag = /<(\w+)[^>]*>/g,
-        rCloseTag = /<\/(\w+)>/g,
-        rLastCloseTag = /(<\/[^>]+>)\s*$/,
-        rTagPlus = /^<[^>]+>.?/,
-        delayedCollapse;
-
-    var methods = {
-      init: function() {
-        this.each(function() {
-          var i, l, tmp, newChar, summTagless, summOpens, summCloses,
-              lastCloseTag, detailText, detailTagless,
-              $thisDetails, $readMore,
-              openTagsForDetails = [],
-              closeTagsForsummaryText = [],
-              defined = {},
-              thisEl = this,
-              $this = $(this),
-              $summEl = $([]),
-              o = $.meta ? $.extend({}, opts, $this.data()) : opts,
-              hasDetails = !!$this.find('.' + o.detailClass).length,
-              hasBlocks = !!$this.find('*').filter(function() {
-                var display = $(this).css('display');
-                return (/^block|table|list/).test(display);
-              }).length,
-              el = hasBlocks ? 'div' : 'span',
-              detailSelector = el + '.' + o.detailClass,
-              moreSelector = 'span.' + o.moreClass,
-              expandSpeed = o.expandSpeed || 0,
-              allHtml = $.trim( $this.html() ),
-              allText = $.trim( $this.text() ),
-              summaryText = allHtml.slice(0, o.slicePoint);
-
-          // bail out if we've already set up the expander on this element
-          if ( $.data(this, 'expander') ) {
-            return;
-          }
-
-          $.data(this, 'expander', true);
-
-          // determine which callback functions are defined
-          $.each(['onSlice','beforeExpand', 'afterExpand', 'onCollapse'], function(index, val) {
-            defined[val] = $.isFunction(o[val]);
-          });
-
-          // back up if we're in the middle of a tag or word
-          summaryText = backup(summaryText);
-
-          // summary text sans tags length
-          summTagless = summaryText.replace(rOpenCloseTag, '').length;
-
-          // add more characters to the summary, one for each character in the tags
-          while (summTagless < o.slicePoint) {
-            newChar = allHtml.charAt(summaryText.length);
-            if (newChar == '<') {
-              newChar = allHtml.slice(summaryText.length).match(rTagPlus)[0];
-            }
-            summaryText += newChar;
-            summTagless++;
-          }
-
-          summaryText = backup(summaryText, o.preserveWords);
-
-          // separate open tags from close tags and clean up the lists
-          summOpens = summaryText.match(rOpenTag) || [];
-          summCloses = summaryText.match(rCloseTag) || [];
-
-          // filter out self-closing tags
-          tmp = [];
-          $.each(summOpens, function(index, val) {
-            if ( !rSelfClose.test(val) ) {
-              tmp.push(val);
-            }
-          });
-          summOpens = tmp;
-
-          // strip close tags to just the tag name
-          l = summCloses.length;
-          for (i = 0; i < l; i++) {
-            summCloses[i] = summCloses[i].replace(rCloseTag, '$1');
-          }
-
-          // tags that start in summary and end in detail need:
-          // a). close tag at end of summary
-          // b). open tag at beginning of detail
-          $.each(summOpens, function(index, val) {
-            var thisTagName = val.replace(rOpenTag, '$1');
-            var closePosition = $.inArray(thisTagName, summCloses);
-            if (closePosition === -1) {
-              openTagsForDetails.push(val);
-              closeTagsForsummaryText.push('</' + thisTagName + '>');
-
-            } else {
-              summCloses.splice(closePosition, 1);
-            }
-          });
-
-          // reverse the order of the close tags for the summary so they line up right
-          closeTagsForsummaryText.reverse();
-
-          // create necessary summary and detail elements if they don't already exist
-          if ( !hasDetails ) {
-
-            // end script if there is no detail text or if detail has fewer words than widow option
-            detailText = allHtml.slice(summaryText.length);
-            detailTagless = $.trim( detailText.replace(rOpenCloseTag, '') );
-
-            if ( detailTagless === '' || detailTagless.split(/\s+/).length < o.widow ) {
-              return;
-            }
-            // otherwise, continue...
-            lastCloseTag = closeTagsForsummaryText.pop() || '';
-            summaryText += closeTagsForsummaryText.join('');
-            detailText = openTagsForDetails.join('') + detailText;
-
-          } else {
-            // assume that even if there are details, we still need readMore/readLess/summary elements
-            // (we already bailed out earlier when readMore el was found)
-            // but we need to create els differently
-
-            // remove the detail from the rest of the content
-            detailText = $this.find(detailSelector).remove().html();
-
-            // The summary is what's left
-            summaryText = $this.html();
-
-            // allHtml is the summary and detail combined (this is needed when content has block-level elements)
-            allHtml = summaryText + detailText;
-
-            lastCloseTag = '';
-          }
-          o.moreLabel = $this.find(moreSelector).length ? '' : buildMoreLabel(o);
-
-          if (hasBlocks) {
-            detailText = allHtml;
-          }
-          summaryText += lastCloseTag;
-
-          // onSlice callback
-          o.summary = summaryText;
-          o.details = detailText;
-          o.lastCloseTag = lastCloseTag;
-
-          if (defined.onSlice) {
-            // user can choose to return a modified options object
-            // one last chance for user to change the options. sneaky, huh?
-            // but could be tricky so use at your own risk.
-            tmp = o.onSlice.call(thisEl, o);
-
-          // so, if the returned value from the onSlice function is an object with a details property, we'll use that!
-            o = tmp && tmp.details ? tmp : o;
-          }
-
-          // build the html with summary and detail and use it to replace old contents
-          var html = buildHTML(o, hasBlocks);
-
-          $this.html( html );
-
-          // set up details and summary for expanding/collapsing
-          $thisDetails = $this.find(detailSelector);
-          $readMore = $this.find(moreSelector);
-          $thisDetails.hide();
-          $readMore.find('a').unbind('click.expander').bind('click.expander', expand);
-
-          $summEl = $this.find('div.' + o.summaryClass);
-
-          if ( o.userCollapse && !$this.find('span.' + o.lessClass).length ) {
-            $this
-            .find(detailSelector)
-            .append('<span class="' + o.lessClass + '">' + o.userCollapsePrefix + '<a href="#">' + o.userCollapseText + '</a></span>');
-          }
-
-          $this
-          .find('span.' + o.lessClass + ' a')
-          .unbind('click.expander')
-          .bind('click.expander', function(event) {
-            event.preventDefault();
-            clearTimeout(delayedCollapse);
-            var $detailsCollapsed = $(this).closest(detailSelector);
-            reCollapse(o, $detailsCollapsed);
-            if (defined.onCollapse) {
-              o.onCollapse.call(thisEl, true);
-            }
-          });
-
-          function expand(event) {
-            event.preventDefault();
-            $readMore.hide();
-            $summEl.hide();
-            if (defined.beforeExpand) {
-              o.beforeExpand.call(thisEl);
-            }
-
-            $thisDetails.stop(false, true)[o.expandEffect](expandSpeed, function() {
-              $thisDetails.css({zoom: ''});
-              if (defined.afterExpand) {o.afterExpand.call(thisEl);}
-              delayCollapse(o, $thisDetails, thisEl);
-            });
-          }
-
-        }); // this.each
-      },
-      destroy: function() {
-        if ( !this.data('expander') ) {
-          return;
-        }
-        this.removeData('expander');
-        this.each(function() {
-          var $this = $(this),
-              o = $.meta ? $.extend({}, opts, $this.data()) : opts,
-              details = $this.find('.' + o.detailClass).contents();
-
-          $this.find('.' + o.moreClass).remove();
-          $this.find('.' + o.summaryClass).remove();
-          $this.find('.' + o.detailClass).after(details).remove();
-          $this.find('.' + o.lessClass).remove();
-
-        });
-      }
-    };
-
-    // run the methods (almost always "init")
-    if ( methods[meth] ) {
-      methods[ meth ].call(this);
-    }
-
-    // utility functions
-    function buildHTML(o, blocks) {
-      var el = 'span',
-          summary = o.summary;
-      if ( blocks ) {
-        el = 'div';
-        // if summary ends with a close tag, tuck the moreLabel inside it
-        if ( rLastCloseTag.test(summary) && !o.expandAfterSummary) {
-          summary = summary.replace(rLastCloseTag, o.moreLabel + '$1');
-        } else {
-        // otherwise (e.g. if ends with self-closing tag) just add moreLabel after summary
-        // fixes #19
-          summary += o.moreLabel;
-        }
-
-        // and wrap it in a div
-        summary = '<div class="' + o.summaryClass + '">' + summary + '</div>';
-      } else {
-        summary += o.moreLabel;
-      }
-
-      return [
-        summary,
-        '<',
-          el + ' class="' + o.detailClass + '"',
-        '>',
-          o.details,
-        '</' + el + '>'
-        ].join('');
-    }
-
-    function buildMoreLabel(o) {
-      var ret = '<span class="' + o.moreClass + '">' + o.expandPrefix;
-      ret += '<a href="#">' + o.expandText + '</a></span>';
-      return ret;
-    }
-
-    function backup(txt, preserveWords) {
-      if ( txt.lastIndexOf('<') > txt.lastIndexOf('>') ) {
-        txt = txt.slice( 0, txt.lastIndexOf('<') );
-      }
-      if (preserveWords) {
-        txt = txt.replace(rAmpWordEnd,'');
-      }
-
-      return $.trim(txt);
-    }
-
-    function reCollapse(o, el) {
-      el.stop(true, true)[o.collapseEffect](o.collapseSpeed, function() {
-        var prevMore = el.prev('span.' + o.moreClass).show();
-        if (!prevMore.length) {
-          el.parent().children('div.' + o.summaryClass).show()
-            .find('span.' + o.moreClass).show();
-        }
-      });
-    }
-
-    function delayCollapse(option, $collapseEl, thisEl) {
-      if (option.collapseTimer) {
-        delayedCollapse = setTimeout(function() {
-          reCollapse(option, $collapseEl);
-          if ( $.isFunction(option.onCollapse) ) {
-            option.onCollapse.call(thisEl, false);
-          }
-        }, option.collapseTimer);
-      }
-    }
-
-    return this;
-  };
-
-  // plugin defaults
-  $.fn.expander.defaults = $.expander.defaults;
-})(jQuery);
index b1a579b..5ab6d23 100644 (file)
@@ -224,6 +224,7 @@ openerp.mail = function (session) {
             this.name = datasets.name ||  false,
             this.record_name = datasets.record_name ||  false,
             this.body = datasets.body || '',
+            this.body_short = datasets.body_short || '',
             this.vote_nb = datasets.vote_nb || 0,
             this.has_voted = datasets.has_voted ||  false,
             this.is_favorite = datasets.is_favorite ||  false,
@@ -939,7 +940,6 @@ openerp.mail = function (session) {
         
         start: function () {
             this._super.apply(this, arguments);
-            this.expender();
             this.bind_events();
             if(this.thread_level < this.options.display_indented_thread) {
                 this.create_thread();
@@ -962,6 +962,8 @@ openerp.mail = function (session) {
             this.$('.oe_reply').on('click', this.on_message_reply);
             this.$('.oe_star').on('click', this.on_star);
             this.$('.oe_msg_vote').on('click', this.on_vote);
+            this.$('.oe_mail_expand').on('click', this.on_expand);
+            this.$('.oe_mail_reduce').on('click', this.on_expand);
         },
 
         /* Call the on_compose_message on the thread of this message. */
@@ -972,15 +974,9 @@ openerp.mail = function (session) {
             return false;
         },
 
-        expender: function () {
-            this.$('.oe_msg_body:first').expander({
-                slicePoint: this.options.truncate_limit,
-                expandText: 'read more',
-                userCollapseText: 'read less',
-                detailClass: 'oe_msg_tail',
-                moreClass: 'oe_mail_expand',
-                lessClass: 'oe_mail_reduce',
-                });
+        on_expand: function (event) {
+            this.$('.oe_msg_body_short:first').toggle();
+            this.$('.oe_msg_body_long:first').toggle();
         },
 
         /**
index 7e7d772..9958cb5 100644 (file)
                         <t t-if="widget.subject" t-raw="widget.subject"/>
                     </h1>
                     <div class="oe_msg_body">
-                        <t t-raw="widget.body"/>
+                        <t t-if="widget.body_short">
+                            <div class="oe_msg_body_short"><t t-raw="widget.body_short"/><span class="oe_mail_expand"><a href="#">... read more</a></span></div>
+                            <div class="oe_msg_body_long" style="display: none;"><t t-raw="widget.body"/><span class="oe_mail_reduce"><a href="#">read less</a></span></div>
+                        </t>
+                        <t t-if="! widget.body_short">
+                            <t t-raw="widget.body"/>
+                        </t>
                     </div>
                 </div>
                 <div class="oe_msg_footer">