From 9a0153324eeb1f0e808cb1a063d1f37d49ad48e2 Mon Sep 17 00:00:00 2001
From: Aleksander Machniak <alec@alec.pl>
Date: Sun, 15 Dec 2013 04:01:38 -0500
Subject: [PATCH] Implemented menu actions to copy/move messages, added folder-selector widget (#1484086)

---
 CHANGELOG                                  |    1 
 skins/larry/includes/mailtoolbar.html      |    2 
 skins/larry/templates/message.html         |    4 
 skins/classic/common.css                   |   51 ++++++++++
 skins/classic/mail.css                     |   17 ---
 skins/larry/images/listicons.png           |    0 
 skins/classic/functions.js                 |    1 
 program/include/rcmail.php                 |   25 +++-
 program/localization/en_US/labels.inc      |    1 
 skins/classic/templates/message.html       |    4 
 skins/larry/styles.css                     |   32 ++++++
 program/js/app.js                          |  113 +++++++++++++++++++++-
 skins/larry/mail.css                       |    6 -
 skins/classic/includes/messagetoolbar.html |    5 
 skins/larry/ui.js                          |    5 
 15 files changed, 224 insertions(+), 43 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index c5574b1..a4088cc 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,7 @@
 CHANGELOG Roundcube Webmail
 ===========================
 
+- Implemented menu actions to copy/move messages, added folder-selector widget (#1484086)
 - Fix regression where only first new folder was placed in correct place on the list (#1489472)
 - Fix issue where children of selected and collapsed thread were skipped on various actions (#1489457)
 - Fix issue where groups were not deleted when "Replace entire addressbook" option on contacts import was used (#1489420)
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index 35ade8a..cefbc4d 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -1282,13 +1282,22 @@
         }
         else {
             $js_mailboxlist = array();
-            $out = html::tag('ul', $attrib, $rcmail->render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
+            $tree = $rcmail->render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib);
 
-            $rcmail->output->include_script('treelist.js');
-            $rcmail->output->add_gui_object('mailboxlist', $attrib['id']);
+            if ($type != 'js') {
+                $out = html::tag('ul', $attrib, $tree, html::$common_attrib);
+
+                $rcmail->output->include_script('treelist.js');
+                $rcmail->output->add_gui_object('mailboxlist', $attrib['id']);
+                $rcmail->output->set_env('unreadwrap', $attrib['unreadwrap']);
+                $rcmail->output->set_env('collapsed_folders', (string)$rcmail->config->get('collapsed_folders'));
+            }
+
             $rcmail->output->set_env('mailboxes', $js_mailboxlist);
-            $rcmail->output->set_env('unreadwrap', $attrib['unreadwrap']);
-            $rcmail->output->set_env('collapsed_folders', (string)$rcmail->config->get('collapsed_folders'));
+
+            // we can't use object keys in javascript because they are unordered
+            // we need sorted folders list for folder-selector widget
+            $rcmail->output->set_env('mailboxes_list', array_keys($js_mailboxlist));
         }
 
         return $out;
@@ -1473,9 +1482,13 @@
             $jslist[$folder['id']] = array(
                 'id'      => $folder['id'],
                 'name'    => $foldername,
-                'virtual' => $folder['virtual']
+                'virtual' => $folder['virtual'],
             );
 
+            if (!empty($folder_class)) {
+                $jslist[$folder['id']]['class'] = $folder_class;
+            }
+
             if (!empty($folder['folders'])) {
                 $out .= html::tag('ul', array('style' => ($is_collapsed ? "display:none;" : null)),
                     $this->render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
diff --git a/program/js/app.js b/program/js/app.js
index cbe61ab..138fcbb 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -842,14 +842,14 @@
       case 'move':
       case 'moveto': // deprecated
         if (this.task == 'mail')
-          this.move_messages(props);
+          this.move_messages(props, obj);
         else if (this.task == 'addressbook')
           this.move_contacts(props);
         break;
 
       case 'copy':
         if (this.task == 'mail')
-          this.copy_messages(props);
+          this.copy_messages(props, obj);
         else if (this.task == 'addressbook')
           this.copy_contacts(props);
         break;
@@ -1411,8 +1411,6 @@
 
   this.drag_start = function(list)
   {
-    var model = this.task == 'mail' ? this.env.mailboxes : this.env.contactfolders;
-
     this.drag_active = true;
 
     if (this.preview_timer)
@@ -2612,10 +2610,12 @@
   };
 
   // copy selected messages to the specified mailbox
-  this.copy_messages = function(mbox)
+  this.copy_messages = function(mbox, obj)
   {
     if (mbox && typeof mbox === 'object')
       mbox = mbox.id;
+    else if (!mbox)
+      return this.folder_selector(obj, function(folder) { ref.command('copy', folder); });
 
     // exit if current or no mailbox specified
     if (!mbox || mbox == this.env.mailbox)
@@ -2632,10 +2632,12 @@
   };
 
   // move selected messages to the specified mailbox
-  this.move_messages = function(mbox)
+  this.move_messages = function(mbox, obj)
   {
     if (mbox && typeof mbox === 'object')
       mbox = mbox.id;
+    else if (!mbox)
+      return this.folder_selector(obj, function(folder) { ref.command('move', folder); });
 
     // exit if current or no mailbox specified
     if (!mbox || mbox == this.env.mailbox)
@@ -6553,6 +6555,105 @@
     elem.onclick = function() { rcmail.command('show-headers', '', elem); };
   };
 
+  // create folder selector popup, position and display it
+  this.folder_selector = function(obj, callback)
+  {
+    var container = this.folder_selector_element;
+
+    if (!container) {
+      var rows = [],
+        delim = this.env.delimiter,
+        ul = $('<ul class="toolbarmenu iconized">'),
+        li = document.createElement('li'),
+        link = document.createElement('a'),
+        span = document.createElement('span');
+
+      container = $('<div id="folder-selector" class="popupmenu"></div>');
+      link.href = '#';
+      link.className = 'icon';
+
+      // loop over sorted folders list
+      $.each(this.env.mailboxes_list, function() {
+        var tmp, n = 0, s = 0,
+          folder = ref.env.mailboxes[this],
+          id = folder.id,
+          a = link.cloneNode(false), row = li.cloneNode(false);
+
+        if (folder.virtual)
+          a.className += ' virtual';
+        else {
+          a.className += ' active';
+          a.onclick = function() { container.hide().data('callback')(folder.id); };
+        }
+
+        if (folder['class'])
+          a.className += ' ' + folder['class'];
+
+        // calculate/set indentation level
+        while ((s = id.indexOf(delim, s)) >= 0) {
+          n++; s++;
+        }
+        a.style.paddingLeft =  n ? (n * 16) + 'px' : 0;
+
+        // add folder name element
+        tmp = span.cloneNode(false);
+        $(tmp).text(folder.name);
+        a.appendChild(tmp);
+
+        row.appendChild(a);
+        rows.push(row);
+      });
+
+      ul.append(rows).appendTo(container);
+
+      // temporarily show element to calculate its size
+      container.css({left: '-1000px', top: '-1000px'})
+        .appendTo($('body')).show();
+
+      // set max-height if the list is long
+      if (rows.length > 10)
+        container.css('max-height', $('li', container)[0].offsetHeight * 10 + 9)
+
+      // hide selector on click out of selector element
+      var fn = function(e) { if (e.target != container.get(0)) container.hide(); };
+      $(document.body).on('mouseup', fn);
+      $('iframe').contents().on('mouseup', fn)
+        .load(function(e) { try { $(this).contents().on('mouseup', fn); } catch(e) {}; });
+
+      this.folder_selector_element = container;
+    }
+
+    // position menu on the screen
+    this.element_position(container, obj);
+
+    container.show().data('callback', callback);
+  };
+
+  // position a menu element on the screen in relation to other object
+  this.element_position = function(element, obj)
+  {
+    var obj = $(obj), win = $(window),
+      width = obj.width(),
+      height = obj.height(),
+      win_height = win.height(),
+      elem_height = $(element).height(),
+      elem_width = $(element).width(),
+      pos = obj.offset(),
+      top = pos.top,
+      left = pos.left + width;
+
+    if (top + elem_height > win_height) {
+      top -= elem_height - height;
+      if (top < 0)
+        top = Math.max(0, (win_height - elem_height) / 2);
+    }
+
+    if (left + elem_width > win.width())
+      left -= elem_width + width;
+
+    element.css({left: left + 'px', top: top + 'px'});
+  };
+
 
   /********************************************************/
   /*********  html to text conversion functions   *********/
diff --git a/program/localization/en_US/labels.inc b/program/localization/en_US/labels.inc
index 92ec826..61890a6 100644
--- a/program/localization/en_US/labels.inc
+++ b/program/localization/en_US/labels.inc
@@ -64,6 +64,7 @@
 $labels['copy']     = 'Copy';
 $labels['move']     = 'Move';
 $labels['moveto']   = 'Move to...';
+$labels['copyto']   = 'Copy to...';
 $labels['download'] = 'Download';
 $labels['open']     = 'Open';
 $labels['showattachment'] = 'Show';
diff --git a/skins/classic/common.css b/skins/classic/common.css
index 186be24..10bc91c 100644
--- a/skins/classic/common.css
+++ b/skins/classic/common.css
@@ -965,6 +965,57 @@
 }
 
 
+/*** folder selector ***/
+
+#folder-selector li a
+{
+  padding: 0;
+}
+
+#folder-selector li a span
+{
+  background: url(images/icons/folders.png) no-repeat 6px 0;
+  display: block;
+  height: 15px;
+  min-height: 14px;
+  padding: 2px 4px 2px 28px;
+  overflow: hidden;
+  max-width: 120px;
+  text-overflow: ellipsis;
+}
+
+#folder-selector li a.virtual
+{
+  color: #A0A0A0;
+}
+
+#folder-selector li a.active:hover span
+{
+  color: white;
+}
+
+#folder-selector li a.inbox span
+{
+  background-position: 6px -18px;
+}
+#folder-selector li a.drafts span
+{
+  background-position: 6px -37px;
+}
+#folder-selector li a.sent span
+{
+  background-position: 6px -54px;
+}
+#folder-selector li a.trash span
+{
+  background-position: 6px -91px;
+}
+#folder-selector li a.junk span
+{
+  background-position: 6px -73px;
+}
+
+
 /***** tabbed interface elements *****/
 
 div.tabsbar,
diff --git a/skins/classic/functions.js b/skins/classic/functions.js
index 2b7886d..4312d57 100644
--- a/skins/classic/functions.js
+++ b/skins/classic/functions.js
@@ -419,6 +419,7 @@
       && !this.popups[i].toggle
       && (!this.popups[i].editable || !this.target_overlaps(target, this.popups[i].id))
       && (!this.popups[i].sticky || !rcube_mouse_is_over(evt, rcube_find_object(this.popups[i].id)))
+      && !$(target).is('.folder-selector-link') && !$(target).children('.folder-selector-link').length
     ) {
       window.setTimeout('rcmail_ui.show_popup("'+i+'",false);', 50);
     }
diff --git a/skins/classic/includes/messagetoolbar.html b/skins/classic/includes/messagetoolbar.html
index 8f8efd2..8d98c7e 100644
--- a/skins/classic/includes/messagetoolbar.html
+++ b/skins/classic/includes/messagetoolbar.html
@@ -20,9 +20,6 @@
 <roundcube:container name="toolbar" id="messagetoolbar" />
 <roundcube:button name="markmenulink" id="markmenulink" type="link" class="button markmessage" title="markmessages" onclick="rcmail_ui.show_popup('markmenu');return false" content=" " />
 <roundcube:button name="messagemenulink" id="messagemenulink" type="link" class="button messagemenu" title="moreactions" onclick="rcmail_ui.show_popup('messagemenu');return false" content=" " />
-<roundcube:if condition="template:name == 'message'" />
-<roundcube:object name="mailboxlist" type="select" noSelection="moveto" maxlength="25" onchange="rcmail.command('move', this.options[this.selectedIndex].value)" class="mboxlist" folder_filter="mail" />
-<roundcube:endif />
 </div>
 
 <div id="forwardmenu" class="popupmenu">
@@ -46,6 +43,8 @@
     <li><roundcube:button class="printlink" command="print" label="printmessage" classAct="printlink active" /></li>
     <li><roundcube:button class="downloadlink" command="download" label="emlsave" classAct="downloadlink active" /></li>
     <li><roundcube:button class="editlink" command="edit" prop="new" label="editasnew" classAct="editlink active" /></li>
+    <li><roundcube:button class="movelink" command="move" label="moveto" classAct="copylink active" innerclass="folder-selector-link" /></li>
+    <li><roundcube:button class="copylink" command="copy" label="copyto" classAct="movelink active" innerclass="folder-selector-link" /></li>
     <li class="separator_below"><roundcube:button class="sourcelink" command="viewsource" label="viewsource" classAct="sourcelink active" /></li>
     <li><roundcube:button class="openlink" command="open" label="openinextwin" target="_blank" classAct="openlink active" /></li>
     <roundcube:container name="messagemenu" id="messagemenu" />
diff --git a/skins/classic/mail.css b/skins/classic/mail.css
index e5d55e0..c887ab3 100644
--- a/skins/classic/mail.css
+++ b/skins/classic/mail.css
@@ -175,23 +175,6 @@
   background-position: -512px 0;
 }
 
-#messagetoolbar select.mboxlist
-{
-  position: relative;
-  margin: 0 8px;
-  top: 7px;
-}
-
-#messagetoolbar select.mboxlist option
-{
-  padding-left: 15px;
-}
-
-#messagetoolbar select.mboxlist option[value=""]
-{
-  padding-left: 2px;
-}
-
 #messagemenu li a.active:hover,
 #attachmentmenu li a.active:hover,
 #markmessagemenu li a.active:hover
diff --git a/skins/classic/templates/message.html b/skins/classic/templates/message.html
index bd4fbf2..3ab0a2e 100644
--- a/skins/classic/templates/message.html
+++ b/skins/classic/templates/message.html
@@ -30,11 +30,13 @@
 <div id="mailboxlist-container">
 <div id="mailboxlist-title" class="boxtitle"><roundcube:label name="mailboxlist" /></div>
 <div class="boxlistcontent">
-    <roundcube:object name="mailboxlist" id="mailboxlist" class="treelist" maxlength="25" />
+    <roundcube:object name="mailboxlist" id="mailboxlist" class="treelist" folder_filter="mail" />
 </div>
 <div class="boxfooter"></div>
 </div>
 </div>
+<roundcube:else />
+<roundcube:object name="mailboxlist" folder_filter="mail" type="js" />
 <roundcube:endif />
 
 <div id="messageframe">
diff --git a/skins/larry/images/listicons.png b/skins/larry/images/listicons.png
index 8a17cc5..49342a3 100644
--- a/skins/larry/images/listicons.png
+++ b/skins/larry/images/listicons.png
Binary files differ
diff --git a/skins/larry/includes/mailtoolbar.html b/skins/larry/includes/mailtoolbar.html
index 59f2d58..5708a94 100644
--- a/skins/larry/includes/mailtoolbar.html
+++ b/skins/larry/includes/mailtoolbar.html
@@ -39,6 +39,8 @@
 	<li><roundcube:button command="download" label="emlsave" class="icon" classAct="icon active" innerclass="icon download" /></li>
 	<li><roundcube:button command="edit" prop="new" label="editasnew" class="icon" classAct="icon active" innerclass="icon edit" /></li>
 	<li><roundcube:button command="viewsource" label="viewsource" class="icon" classAct="icon active" innerclass="icon viewsource" /></li>
+	<li><roundcube:button command="move" label="moveto" class="icon" classAct="icon active" innerclass="icon move folder-selector-link" /></li>
+	<li><roundcube:button command="copy" label="copyto" class="icon" classAct="icon active" innerclass="icon copy folder-selector-link" /></li>
 	<li><roundcube:button command="open" label="openinextwin" target="_blank" class="icon" classAct="icon active" innerclass="icon extwin" /></li>
 	<roundcube:container name="messagemenu" id="messagemenu" />
   </ul>
diff --git a/skins/larry/mail.css b/skins/larry/mail.css
index 8e1634a..d3b09c0 100644
--- a/skins/larry/mail.css
+++ b/skins/larry/mail.css
@@ -417,12 +417,6 @@
 	right: 0;
 }
 
-#messagetoolbar .toolbarselect {
-	position: absolute;
-	bottom: 6px;
-	right: 3px;
-}
-
 #messagesearchtools {
 	position: absolute;
 	right: 0;
diff --git a/skins/larry/styles.css b/skins/larry/styles.css
index 61f35b0..21d93c1 100644
--- a/skins/larry/styles.css
+++ b/skins/larry/styles.css
@@ -2329,6 +2329,38 @@
 	z-index: 255;
 }
 
+/*** folder selector ***/
+
+#folder-selector li a span {
+	background: url("images/listicons.png") 4px -2021px no-repeat;
+	display: block;
+	height: 17px;
+	min-height: 14px;
+	padding: 4px 4px 1px 28px;
+	overflow: hidden;
+	max-width: 120px;
+	text-overflow: ellipsis;
+}
+
+#folder-selector li a.virtual {
+	opacity: .2;
+}
+
+#folder-selector li a.inbox span {
+	background-position: 4px -2049px;
+}
+#folder-selector li a.drafts span {
+	background-position: 4px -1388px;
+}
+#folder-selector li a.sent span {
+	background-position: 4px -2074px;
+}
+#folder-selector li a.trash span {
+	background-position: 4px -1508px;
+}
+#folder-selector li a.junk span {
+	background-position: 4px -2100px;
+}
 
 /*** attachment list ***/
 
diff --git a/skins/larry/templates/message.html b/skins/larry/templates/message.html
index e63705f..df92b75 100644
--- a/skins/larry/templates/message.html
+++ b/skins/larry/templates/message.html
@@ -16,9 +16,6 @@
 	<roundcube:button command="list" type="link" class="button back disabled" classAct="button back" classSel="button back pressed" label="back" />
 <roundcube:endif />
 	<roundcube:include file="/includes/mailtoolbar.html" />
-	<div class="toolbarselect">
-		<roundcube:object name="mailboxlist" type="select" noSelection="moveto" maxlength="25" onchange="rcmail.command('move', this.options[this.selectedIndex].value)" class="mailboxlist decorated" folder_filter="mail" />
-	</div>
 </div>
 
 <roundcube:if condition="!env:extwin" />
@@ -36,6 +33,7 @@
 
 <div id="mailview-right" class="offset uibox">
 <roundcube:else />
+<roundcube:object name="mailboxlist" folder_filter="mail" type="js" />
 
 <div id="mailview-right" class="offset fullwidth uibox">
 <roundcube:endif />
diff --git a/skins/larry/ui.js b/skins/larry/ui.js
index d203acf..d661310 100644
--- a/skins/larry/ui.js
+++ b/skins/larry/ui.js
@@ -369,8 +369,10 @@
   function body_mouseup(e)
   {
     var config, obj, target = e.target;
+
     if (target.className == 'inner')
         target = e.target.parentNode;
+
     for (var id in popups) {
       obj = popups[id];
       config = popupconfig[id];
@@ -379,9 +381,10 @@
         && !config.toggle
         && (!config.editable || !target_overlaps(target, obj.get(0)))
         && (!config.sticky || !rcube_mouse_is_over(e, obj.get(0)))
+        && !$(target).is('.folder-selector-link')
       ) {
         var myid = id+'';
-        window.setTimeout(function(){ show_popupmenu(myid, false) }, 10);
+        window.setTimeout(function() { show_popupmenu(myid, false); }, 10);
       }
     }
   }

--
Gitblit v1.9.1