From bc2c4380b5b754a3b13cc7d6663b2b81d2577e2e Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Wed, 06 Mar 2013 05:11:37 -0500
Subject: [PATCH] Add attachment menu with Open and Download options (#1488975)

---
 CHANGELOG                                   |    1 
 program/include/rcmail_output_html.php      |    4 +
 skins/larry/templates/message.html          |    8 ++
 program/steps/mail/show.inc                 |   23 +++--
 skins/larry/templates/messagepreview.html   |    8 ++
 skins/larry/images/buttons.gif              |    0 
 skins/classic/mail.css                      |   20 ++++
 skins/classic/functions.js                  |   53 +++++++++++--
 program/localization/en_US/labels.inc       |    1 
 skins/classic/templates/message.html        |    8 ++
 skins/larry/styles.css                      |   13 +++
 skins/larry/images/buttons.png              |    0 
 program/js/app.js                           |   21 +++-
 skins/larry/mail.css                        |    2 
 skins/classic/templates/messagepreview.html |   12 ++
 skins/larry/ui.js                           |   42 +++++++++-
 16 files changed, 181 insertions(+), 35 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index c5d8c76..fa5fb8e 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Add attachment menu with Open and Download options (#1488975)
 - Fix thumbnail size when GD extension is used for image resize (#1488985)
 - Display user-friendly message on IMAP "over quota" errors (#1484164)
 - Display notice that message is encrypted also for application/pkcs7-mime messages (#1488526)
diff --git a/program/include/rcmail_output_html.php b/program/include/rcmail_output_html.php
index 2babe1c..ade2bd4 100644
--- a/program/include/rcmail_output_html.php
+++ b/program/include/rcmail_output_html.php
@@ -1175,6 +1175,10 @@
             $out = sprintf('<a%s>%s</a>', $attrib_str, $btn_content);
         }
 
+        if ($attrib['wrapper']) {
+            $out = html::tag($attrib['wrapper'], null, $out);
+        }
+
         return $out;
     }
 
diff --git a/program/js/app.js b/program/js/app.js
index 4011fa5..55c71d7 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -179,7 +179,8 @@
     }
 
     // enable general commands
-    this.enable_command('close', 'logout', 'mail', 'addressbook', 'settings', 'save-pref', 'compose', 'undo', 'about', 'switch-task', true);
+    this.enable_command('close', 'logout', 'mail', 'addressbook', 'settings', 'save-pref',
+      'compose', 'undo', 'about', 'switch-task', 'menu-open', 'menu-save', true);
 
     if (this.env.permaurl)
       this.enable_command('permaurl', 'extwin', true);
@@ -211,7 +212,7 @@
           this.gui_objects.messagelist.parentNode.onmousedown = function(e){ return p.click_on_list(e); };
 
           this.message_list.init();
-          this.enable_command('toggle_status', 'toggle_flag', 'menu-open', 'menu-save', 'sort', true);
+          this.enable_command('toggle_status', 'toggle_flag', 'sort', true);
 
           // load messages
           this.command('list');
@@ -227,7 +228,7 @@
 
         this.env.message_commands = ['show', 'reply', 'reply-all', 'reply-list',
           'moveto', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource',
-          'print', 'load-attachment', 'show-headers', 'hide-headers', 'download',
+          'print', 'load-attachment', 'download-attachment', 'show-headers', 'hide-headers', 'download',
           'forward', 'forward-inline', 'forward-attachment'];
 
         if (this.env.action == 'show' || this.env.action == 'preview') {
@@ -608,6 +609,11 @@
         break;
 
       case 'menu-open':
+        if (props && props.menu == 'attachmentmenu') {
+          var mimetype = this.env.attachments[props.id];
+          this.enable_command('open-attachment', mimetype && this.env.mimetypes && $.inArray(mimetype, this.env.mimetypes) >= 0);
+        }
+
       case 'menu-save':
         this.triggerEvent(command, {props:props});
         return false;
@@ -833,11 +839,14 @@
         break;
 
       case 'load-attachment':
-        var qstring = '_mbox='+urlencode(this.env.mailbox)+'&_uid='+this.env.uid+'&_part='+props.part;
+      case 'open-attachment':
+      case 'download-attachment':
+        var qstring = '_mbox='+urlencode(this.env.mailbox)+'&_uid='+this.env.uid+'&_part='+props,
+          mimetype = this.env.attachments[props];
 
         // open attachment in frame if it's of a supported mimetype
-        if (this.env.uid && props.mimetype && this.env.mimetypes && $.inArray(props.mimetype, this.env.mimetypes) >= 0) {
-          var attachment_win = window.open(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', 'rcubemailattachment'+this.env.uid+props.part);
+        if (command != 'download-attachment' && mimetype && this.env.mimetypes && $.inArray(mimetype, this.env.mimetypes) >= 0) {
+          var attachment_win = window.open(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', 'rcubemailattachment'+this.env.uid+props);
           if (attachment_win) {
             setTimeout(function(){ attachment_win.focus(); }, 10);
             break;
diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc
index 0750e78..61a13e9 100644
--- a/program/localization/en_US/labels.inc
+++ b/program/localization/en_US/labels.inc
@@ -64,6 +64,7 @@
 $labels['move']     = 'Move';
 $labels['moveto']   = 'Move to...';
 $labels['download'] = 'Download';
+$labels['open']     = 'Open';
 $labels['showattachment'] = 'Show';
 $labels['showanyway'] = 'Show it anyway';
 
diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc
index 437dbaa..87555cb 100644
--- a/program/steps/mail/show.inc
+++ b/program/steps/mail/show.inc
@@ -147,6 +147,7 @@
   global $PRINT_MODE, $MESSAGE, $RCMAIL;
 
   $out = $ol = '';
+  $attachments = array();
 
   if (sizeof($MESSAGE->attachments)) {
     foreach ($MESSAGE->attachments as $attach_prop) {
@@ -165,21 +166,23 @@
           $title = '';
         }
 
-        $ol .= html::tag('li', rcmail_filetype2classname($attach_prop->mimetype, $filename),
-          html::a(array(
+        $mimetype = rcmail_fix_mimetype($attach_prop->mimetype);
+        $class    = rcmail_filetype2classname($mimetype, $filename);
+        $id       = 'attach' . $attach_prop->mime_id;
+        $link     = html::a(array(
             'href' => $MESSAGE->get_part_url($attach_prop->mime_id, false),
-            'onclick' => sprintf(
-              'return %s.command(\'load-attachment\',{part:\'%s\', mimetype:\'%s\'},this)',
-              JS_OBJECT_NAME,
-              $attach_prop->mime_id,
-              rcmail_fix_mimetype($attach_prop->mimetype)),
-              'title' => Q($title),
-            ),
-            Q($filename)));
+            'onclick' => sprintf('return %s.command(\'load-attachment\',\'%s\',this)',
+              JS_OBJECT_NAME, $attach_prop->mime_id),
+            'title' => Q($title),
+            ), Q($filename));
+        $ol .= html::tag('li', array('class' => $class, 'id' => $id), $link);
+
+        $attachments[$attach_prop->mime_id] = $mimetype;
       }
     }
 
     $out = html::tag('ul', $attrib, $ol, html::$common_attrib);
+    $RCMAIL->output->set_env('attachments', $attachments);
   }
 
   return $out;
diff --git a/skins/classic/functions.js b/skins/classic/functions.js
index c59ea9b..499783b 100644
--- a/skins/classic/functions.js
+++ b/skins/classic/functions.js
@@ -92,6 +92,7 @@
     forwardmenu:    {id:'forwardmenu', editable:1},
     searchmenu:     {id:'searchmenu', editable:1},
     messagemenu:    {id:'messagemenu'},
+    attachmentmenu: {id:'attachmentmenu'},
     listmenu:       {id:'listmenu', editable:1},
     dragmessagemenu:{id:'dragmessagemenu', sticky:1},
     groupmenu:      {id:'groupoptionsmenu', above:1},
@@ -133,24 +134,24 @@
 {
   var obj = this.popups[popup].obj,
     above = this.popups[popup].above,
-    ref = rcube_find_object(popup+'link');
+    ref = $(this.popups[popup].link ? this.popups[popup].link : rcube_find_object(popup+'link'));
 
   if (typeof show == 'undefined')
     show = obj.is(':visible') ? false : true;
   else if (this.popups[popup].toggle && show && this.popups[popup].obj.is(':visible') )
     show = false;
 
-  if (show && ref) {
-    var parent = $(ref).parent(),
+  if (show && ref.length) {
+    var parent = ref.parent(),
       win = $(window),
-      pos = parent.hasClass('dropbutton') ? parent.offset() : $(ref).offset();
+      pos = parent.hasClass('dropbutton') ? parent.offset() : ref.offset();
 
-    if (!above && pos.top + ref.offsetHeight + obj.height() > win.height())
+    if (!above && pos.top + ref.height() + obj.height() > win.height())
       above = true;
     if (pos.left + obj.width() > win.width())
       pos.left = win.width() - obj.width() - 30;
 
-    obj.css({ left:pos.left, top:(pos.top + (above ? -obj.height() : ref.offsetHeight)) });
+    obj.css({ left:pos.left, top:(pos.top + (above ? -obj.height() : ref.height())) });
   }
 
   obj[show?'show':'hide']();
@@ -325,7 +326,7 @@
   };
 },
 
-open_listmenu: function(e)
+open_listmenu: function()
 {
   this.listmenu();
 },
@@ -378,6 +379,35 @@
   });
 
   this.show_popupmenu('spellmenu', show);
+},
+
+show_attachmentmenu: function(elem)
+{
+  var id = elem.parentNode.id.replace(/^attach/, '');
+
+  $('#attachmenuopen').unbind('click').attr('onclick', '').click(function(e) {
+    return rcmail.command('open-attachment', id, this);
+  });
+
+  $('#attachmenudownload').unbind('click').attr('onclick', '').click(function() {
+    rcmail.command('download-attachment', id, this);
+  });
+
+  this.popups.attachmentmenu.link = elem;
+  rcmail.command('menu-open', {menu: 'attachmentmenu', id: id});
+},
+
+menu_open: function(p)
+{
+  if (p && p.props && p.props.menu == 'attachmentmenu')
+    this.show_popup('attachmentmenu');
+  else
+    this.open_listmenu();
+},
+
+menu_save: function(prop)
+{
+  this.save_listmenu();
 },
 
 body_mouseup: function(evt, p)
@@ -800,8 +830,8 @@
     .contents().mouseup(function(e){rcmail_ui.body_mouseup(e)});
 
   if (rcmail.env.task == 'mail') {
-    rcmail.addEventListener('menu-open', 'open_listmenu', rcmail_ui);
-    rcmail.addEventListener('menu-save', 'save_listmenu', rcmail_ui);
+    rcmail.addEventListener('menu-open', 'menu_open', rcmail_ui);
+    rcmail.addEventListener('menu-save', 'menu_save', rcmail_ui);
     rcmail.addEventListener('aftersend-attachment', 'uploadmenu', rcmail_ui);
     rcmail.addEventListener('aftertoggle-editor', 'resize_compose_body_ev', rcmail_ui);
     rcmail.gui_object('message_dragmenu', 'dragmessagemenu');
@@ -817,6 +847,11 @@
 
     if (rcmail.env.action == 'compose')
       rcmail_ui.init_compose_form();
+    else if (rcmail.env.action == 'show' || rcmail.env.action == 'preview')
+      // add menu link for each attachment
+      $('#attachment-list > li[id^="attach"]').each(function() {
+        $(this).append($('<a class="drop">').click(function() { rcmail_ui.show_attachmentmenu(this); }));
+      });
   }
   else if (rcmail.env.task == 'addressbook') {
     rcmail.addEventListener('afterupload-photo', function(){ rcmail_ui.show_popup('uploadform', false); });
diff --git a/skins/classic/mail.css b/skins/classic/mail.css
index 8be35aa..4d42d98 100644
--- a/skins/classic/mail.css
+++ b/skins/classic/mail.css
@@ -173,13 +173,15 @@
 }
 
 #messagemenu li a.active:hover,
+#attachmentmenu li a.active:hover,
 #markmessagemenu li a.active:hover
 {
   color: #fff;
   background-color: #c00;
 }
 
-#messagemenu li a
+#messagemenu li a,
+#attachmentmenu li a
 {
   background: url(images/messageactions.png) no-repeat 7px 0;
   background-position: 7px 20px;
@@ -190,7 +192,8 @@
   background-position: 7px 1px;
 }
 
-#messagemenu li a.downloadlink
+#messagemenu li a.downloadlink,
+#attachmentmenu li a.downloadlink
 {
   background-position: 7px -17px;
 }
@@ -200,7 +203,8 @@
   background-position: 7px -35px;
 }
 
-#messagemenu li a.openlink
+#messagemenu li a.openlink,
+#attachmentmenu li a.openlink
 {
   background-position: 7px -53px;
 }
@@ -1135,6 +1139,16 @@
   text-decoration: underline;
 }
 
+#attachment-list li a.drop {
+  background: url(images/icons/down_small.gif) no-repeat center 6px;
+  width: 12px;
+  height: 7px;
+  cursor: pointer;
+  padding: 5px 0 0;
+  margin-left: 3px;
+  display: inline-block;
+}
+
 #messagebody
 {
   position:relative;
diff --git a/skins/classic/templates/message.html b/skins/classic/templates/message.html
index d1594ea..73dfcb9 100644
--- a/skins/classic/templates/message.html
+++ b/skins/classic/templates/message.html
@@ -65,5 +65,13 @@
 </script>
 <roundcube:endif />
 
+<div id="attachmentmenu" class="popupmenu">
+  <ul class="toolbarmenu">
+    <li><roundcube:button command="open-attachment" id="attachmenuopen" type="link" label="open" class="openlink" classAct="openlink active" innerclass="openlink" /></li>
+    <li><roundcube:button command="download-attachment" id="attachmenudownload" type="link" label="download" class="downloadlink" classAct="downloadlink active" innerclass="downloadlink" /></li>
+    <roundcube:container name="attachmentmenu" id="attachmentmenu" />
+  </ul>
+</div>
+
 </body>
 </html>
diff --git a/skins/classic/templates/messagepreview.html b/skins/classic/templates/messagepreview.html
index 78b2306..935238e 100644
--- a/skins/classic/templates/messagepreview.html
+++ b/skins/classic/templates/messagepreview.html
@@ -3,8 +3,10 @@
 <head>
 <title><roundcube:object name="pagetitle" /></title>
 <roundcube:include file="/includes/links.html" />
+<script type="text/javascript" src="/splitter.js"></script>
+<script type="text/javascript" src="/functions.js"></script>
 </head>
-<body class="iframe">
+<body class="iframe" onload="rcube_init_mail_ui()">
 
 <div class="messageheaderbox">
 <roundcube:button command="extwin" image="/images/icons/extwin.png" width="15" height="15" title="openinextwin" id="openextwinlink" />
@@ -16,5 +18,13 @@
 <roundcube:object name="messageObjects" id="message-objects" />
 <roundcube:object name="messageBody" id="messagebody" />
 
+<div id="attachmentmenu" class="popupmenu">
+  <ul class="toolbarmenu">
+    <li><roundcube:button command="open-attachment" id="attachmenuopen" type="link" label="open" class="openlink" classAct="openlink active" innerclass="openlink" /></li>
+    <li><roundcube:button command="download-attachment" id="attachmenudownload" type="link" label="download" class="downloadlink" classAct="downloadlink active" innerclass="downloadlink" /></li>
+    <roundcube:container name="attachmentmenu" id="attachmentmenu" />
+  </ul>
+</div>
+
 </body>
 </html>
diff --git a/skins/larry/images/buttons.gif b/skins/larry/images/buttons.gif
index d8a33d6..8a4a78e 100644
--- a/skins/larry/images/buttons.gif
+++ b/skins/larry/images/buttons.gif
Binary files differ
diff --git a/skins/larry/images/buttons.png b/skins/larry/images/buttons.png
index 4438d9c..0ec061a 100644
--- a/skins/larry/images/buttons.png
+++ b/skins/larry/images/buttons.png
Binary files differ
diff --git a/skins/larry/mail.css b/skins/larry/mail.css
index c993708..e6529d1 100644
--- a/skins/larry/mail.css
+++ b/skins/larry/mail.css
@@ -868,7 +868,7 @@
 }
 
 div.hide-headers {
-	background-position: center -1589px;
+	background-position: center -1600px;
 }
 
 #all-headers {
diff --git a/skins/larry/styles.css b/skins/larry/styles.css
index 044a09e..1e3e6f7 100644
--- a/skins/larry/styles.css
+++ b/skins/larry/styles.css
@@ -2219,7 +2219,7 @@
 	display: block;
 	color: #333;
 	font-weight: bold;
-	padding: 8px 4px 3px 30px;
+	padding: 8px 15px 3px 30px;
 	text-shadow: 0px 1px 1px #fff;
 	text-decoration: none;
 	white-space: nowrap;
@@ -2227,6 +2227,17 @@
 	text-overflow: ellipsis;
 }
 
+.attachmentslist li a.drop {
+	background: url(images/buttons.png) no-repeat scroll center -1570px;
+	width: 14px;
+	height: 26px;
+	cursor: pointer;
+	position: absolute;
+	right: 0;
+	top: 0;
+	padding: 0;
+}
+
 #compose-attachments ul li {
 	padding-right: 28px;
 }
diff --git a/skins/larry/templates/message.html b/skins/larry/templates/message.html
index b4ceb6a..36e0efa 100644
--- a/skins/larry/templates/message.html
+++ b/skins/larry/templates/message.html
@@ -73,6 +73,14 @@
 
 </div><!-- end mainscreen -->
 
+<div id="attachmentmenu" class="popupmenu dropdown">
+	<ul class="toolbarmenu">
+		<li><roundcube:button command="open-attachment" id="attachmenuopen" type="link" label="open" class="icon" classAct="icon active" innerclass="icon extwin" /></li>
+		<li><roundcube:button command="download-attachment" id="attachmenudownload" type="link" label="download" class="icon" classAct="icon active" innerclass="icon download" /></li>
+		<roundcube:container name="attachmentmenu" id="attachmentmenu" />
+	</ul>
+</div>
+
 <roundcube:include file="/includes/footer.html" />
 
 </body>
diff --git a/skins/larry/templates/messagepreview.html b/skins/larry/templates/messagepreview.html
index aef282a..dbfe2dc 100644
--- a/skins/larry/templates/messagepreview.html
+++ b/skins/larry/templates/messagepreview.html
@@ -51,6 +51,14 @@
 </div>
 </div>
 
+<div id="attachmentmenu" class="popupmenu dropdown">
+	<ul class="toolbarmenu">
+		<li><roundcube:button command="open-attachment" id="attachmenuopen" type="link" label="open" class="icon" classAct="icon active" innerclass="icon extwin" /></li>
+		<li><roundcube:button command="download-attachment" id="attachmenudownload" type="link" label="download" class="icon" classAct="icon active" innerclass="icon download" /></li>
+		<roundcube:container name="attachmentmenu" id="attachmentmenu" />
+	</ul>
+</div>
+
 <roundcube:include file="/includes/footer.html" />
 
 </body>
diff --git a/skins/larry/ui.js b/skins/larry/ui.js
index d2638bb..6b2a5c7 100644
--- a/skins/larry/ui.js
+++ b/skins/larry/ui.js
@@ -17,6 +17,7 @@
   var popupconfig = {
     forwardmenu:        { editable:1 },
     searchmenu:         { editable:1, callback:searchmenu },
+    attachmentmenu:     { },
     listoptions:        { editable:1 },
     dragmessagemenu:    { sticky:1 },
     groupmenu:          { above:1 },
@@ -81,8 +82,8 @@
 
     /***  mail task  ***/
     if (rcmail.env.task == 'mail') {
-      rcmail.addEventListener('menu-open', show_listoptions);
-      rcmail.addEventListener('menu-save', save_listoptions);
+      rcmail.addEventListener('menu-open', menu_open);
+      rcmail.addEventListener('menu-save', menu_save);
       rcmail.addEventListener('responseafterlist', function(e){ switch_view_mode(rcmail.env.threading ? 'thread' : 'list') });
 
       var dragmenu = $('#dragmessagemenu');
@@ -95,6 +96,11 @@
         rcmail.addEventListener('aftershow-headers', function() { layout_messageview(); });
         rcmail.addEventListener('afterhide-headers', function() { layout_messageview(); });
         $('#previewheaderstoggle').click(function(e){ toggle_preview_headers(this); return false });
+
+        // add menu link for each attachment
+        $('#attachment-list > li').each(function() {
+          $(this).append($('<a class="drop">').click(function() { attachmentmenu(this); }));
+        });
       }
       else if (rcmail.env.action == 'compose') {
         rcmail.addEventListener('aftertoggle-editor', function(){ window.setTimeout(function(){ layout_composeview() }, 200); });
@@ -436,7 +442,7 @@
   {
     var obj = popups[popup],
       config = popupconfig[popup],
-      ref = $('#'+popup+'link'),
+      ref = $(config.link ? config.link : '#'+popup+'link'),
       above = config.above;
 
     if (!obj) {
@@ -452,7 +458,7 @@
     else if (config.toggle && show && obj.is(':visible'))
       show = false;
 
-    if (show && ref) {
+    if (show && ref.length) {
       var parent = ref.parent(),
         win = $(window),
         pos;
@@ -575,6 +581,19 @@
 
   /**** popup callbacks ****/
 
+  function menu_open(p)
+  {
+    if (p && p.props && p.props.menu == 'attachmentmenu')
+      show_popupmenu('attachmentmenu');
+    else
+      show_listoptions();
+  }
+
+  function menu_save(prop)
+  {
+    save_listoptions();
+  }
+
   function searchmenu(show)
   {
     if (show && rcmail.env.search_mods) {
@@ -605,6 +624,21 @@
     }
   }
 
+  function attachmentmenu(elem)
+  {
+    var id = elem.parentNode.id.replace(/^attach/, '');
+
+    $('#attachmenuopen').unbind('click').attr('onclick', '').click(function(e) {
+      return rcmail.command('open-attachment', id, this);
+    });
+
+    $('#attachmenudownload').unbind('click').attr('onclick', '').click(function() {
+      rcmail.command('download-attachment', id, this);
+    });
+
+    popupconfig.attachmentmenu.link = elem;
+    rcmail.command('menu-open', {menu: 'attachmentmenu', id: id});
+  }
 
   function spellmenu(show)
   {

--
Gitblit v1.9.1