From c6447e2ce289188493590ec0d5449fa3692eed08 Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 2 Jul 2014 13:03:22 +0200 Subject: Use treelist widget for folders list in Settings/Folders (#1489648) --- .../subscriptions_option/subscriptions_option.php | 4 +- program/js/app.js | 129 ++++++++------------- program/js/treelist.js | 67 +++++++++-- program/steps/settings/folders.inc | 70 ++++++----- program/steps/settings/func.inc | 2 + skins/classic/common.css | 1 + skins/classic/functions.js | 6 +- skins/classic/mail.css | 8 -- skins/classic/settings.css | 55 ++++----- skins/classic/templates/folders.html | 4 +- skins/larry/settings.css | 23 ++-- skins/larry/templates/folders.html | 2 +- skins/larry/ui.js | 4 +- 13 files changed, 193 insertions(+), 182 deletions(-) diff --git a/plugins/subscriptions_option/subscriptions_option.php b/plugins/subscriptions_option/subscriptions_option.php index 130f16a8b..5b926f2af 100644 --- a/plugins/subscriptions_option/subscriptions_option.php +++ b/plugins/subscriptions_option/subscriptions_option.php @@ -86,7 +86,9 @@ class subscriptions_option extends rcube_plugin { $rcmail = rcmail::get_instance(); if (!$rcmail->config->get('use_subscriptions', true)) { - $args['table']->remove_column('subscribed'); + foreach ($args['list'] as $idx => $data) { + $args['list'][$idx]['content'] = preg_replace('/]+>/', '', $data['content']); + } } return $args; } diff --git a/program/js/app.js b/program/js/app.js index db9a2ff5f..2b9c3f0d7 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -1856,9 +1856,6 @@ function rcube_webmail() && !this.env.mailboxes[id].virtual && (this.env.mailboxes[id].id != this.env.mailbox || this.is_multifolder_listing())) ? 1 : 0; - case 'settings': - return id != this.env.mailbox ? 1 : 0; - case 'addressbook': var target; if (id != this.env.source && (target = this.env.contactfolders[id])) { @@ -5765,62 +5762,38 @@ function rcube_webmail() this.last_sub_rx = RegExp('['+delim+']?[^'+delim+']+$'); - this.subscription_list = new rcube_list_widget(this.gui_objects.subscriptionlist, - {multiselect:false, draggable:true, keyboard:true, toggleselect:true}); - this.subscription_list - .addEventListener('select', function(o){ ref.subscription_select(o); }) - .addEventListener('dragstart', function(o){ ref.drag_active = true; }) - .addEventListener('dragend', function(o){ ref.subscription_move_folder(o); }) - .addEventListener('initrow', function (row) { - row.obj.onmouseover = function() { ref.focus_subscription(row.id); }; - row.obj.onmouseout = function() { ref.unfocus_subscription(row.id); }; - }) - .init() - .focus(); - - $('#mailboxroot') - .mouseover(function(){ ref.focus_subscription(this.id); }) - .mouseout(function(){ ref.unfocus_subscription(this.id); }) - }; - - this.focus_subscription = function(id) - { - var row, folder; + this.subscription_list = new rcube_treelist_widget(this.gui_objects.subscriptionlist, { + selectable: true + }); - if (this.drag_active && this.env.mailbox && (row = document.getElementById(id))) - if (this.env.subscriptionrows[id] && - (folder = this.env.subscriptionrows[id][0]) !== null - ) { - if (this.check_droptarget(folder) && - !this.env.subscriptionrows[this.get_folder_row_id(this.env.mailbox)][2] && - folder != this.env.mailbox.replace(this.last_sub_rx, '') && - !folder.startsWith(this.env.mailbox + this.env.delimiter) - ) { - this.env.dstfolder = folder; - $(row).addClass('droptarget'); + this.subscription_list + .addEventListener('select', function(node) { ref.subscription_select(node.id); }) + .draggable({cancel: '#mailboxroot'}) + .droppable({ + // @todo: find better way, accept callback is executed for every folder + // on the list when dragging starts (and stops), this is slow, but + // I didn't find a method to check droptarget on over event + accept: function(node) { + var source = ref.env.subscriptionrows[$(node).attr('id')], + dest = ref.env.subscriptionrows[this.id], + source_name = source[0], + dest_name = dest[0]; + + return !source[2] + && dest_name != source_name.replace(ref.last_sub_rx, '') + && !dest_name.startsWith(source_name + ref.env.delimiter); + }, + drop: function(e, ui) { + ref.subscription_move_folder(ui.draggable.attr('id'), this.id); } - } - }; - - this.unfocus_subscription = function(id) - { - var row = $('#'+id); - - this.env.dstfolder = null; - - if (row.length && this.env.subscriptionrows[id]) - row.removeClass('droptarget'); - else - $(this.subscription_list.frame).removeClass('droptarget'); + }); }; - this.subscription_select = function(list) + this.subscription_select = function(id) { - var id, folder; + var folder; - if (list && (id = list.get_single_selection()) && - (folder = this.env.subscriptionrows['rcmrow'+id]) - ) { + if (id && id != 'mailboxroot' && (folder = this.env.subscriptionrows[id])) { this.env.mailbox = folder[0]; this.show_folder(folder[0]); this.enable_command('delete-folder', !folder[2]); @@ -5832,24 +5805,21 @@ function rcube_webmail() } }; - this.subscription_move_folder = function(list) + this.subscription_move_folder = function(from, to) { - if (this.env.mailbox && this.env.dstfolder !== null && - this.env.dstfolder != this.env.mailbox && - this.env.dstfolder != this.env.mailbox.replace(this.last_sub_rx, '') - ) { - var path = this.env.mailbox.split(this.env.delimiter), + var source = this.env.subscriptionrows[from][0]; + dest = this.env.subscriptionrows[to][0]; + + if (source && dest !== null && source != dest && dest != source.replace(this.last_sub_rx, '')) { + var path = source.split(this.env.delimiter), basename = path.pop(), - newname = this.env.dstfolder === '' ? basename : this.env.dstfolder + this.env.delimiter + basename; + newname = dest === '' ? basename : dest + this.env.delimiter + basename; - if (newname != this.env.mailbox) { - this.http_post('rename-folder', {_folder_oldname: this.env.mailbox, _folder_newname: newname}, this.set_busy(true, 'foldermoving')); - this.subscription_list.draglayer.hide(); + if (newname != source) { + this.http_post('rename-folder', {_folder_oldname: source, _folder_newname: newname}, + this.set_busy(true, 'foldermoving')); } } - - this.drag_active = false; - this.unfocus_subscription(this.get_folder_row_id(this.env.dstfolder)); }; // tell server to create and subscribe a new mailbox @@ -5865,8 +5835,7 @@ function rcube_webmail() folder = this.env.subscriptionrows[id][0]; if (folder && confirm(this.get_label('deletefolderconfirm'))) { - var lock = this.set_busy(true, 'folderdeleting'); - this.http_post('delete-folder', {_mbox: folder}, lock); + this.http_post('delete-folder', {_mbox: folder}, this.set_busy(true, 'folderdeleting')); } }; @@ -5878,9 +5847,9 @@ function rcube_webmail() var row, n, tmp, tmp_name, rowid, collator, folders = [], list = [], slist = [], - tbody = this.gui_objects.subscriptionlist.tBodies[0], - refrow = $('tr', tbody).get(1), - id = 'rcmrow'+((new Date).getTime()); + list_element = $(this.gui_objects.subscriptionlist), + refrow = $('li', list_element).get(1), + id = 'rcmli'+((new Date).getTime()); if (!refrow) { // Refresh page if we don't have a table row to clone @@ -5895,7 +5864,7 @@ function rcube_webmail() row.attr({id: id, 'class': class_name}); // set folder name - row.find('td:first').html(display_name); + $('.name', row).html(display_name); // update subscription checkbox $('input[name="_subscribed[]"]', row).val(name) @@ -5966,12 +5935,13 @@ function rcube_webmail() // add row to the table if (rowid) - $('#'+rowid).after(row); + $('#' + rowid).after(row); else - row.appendTo(tbody); + list_element.append(row); // update list widget - this.subscription_list.clear_selection(); + this.subscription_list.select(); + if (!skip_init) this.init_subscription_list(); @@ -5988,11 +5958,11 @@ function rcube_webmail() if (!this.gui_objects.subscriptionlist) { if (this.is_framed) return parent.rcmail.replace_folder_row(oldfolder, newfolder, display_name, is_protected, class_name); + return false; } var i, n, len, name, dispname, oldrow, tmprow, row, level, - tbody = this.gui_objects.subscriptionlist.tBodies[0], folders = this.env.subscriptionrows, id = this.get_folder_row_id(oldfolder), prefix_len = oldfolder.length, @@ -6003,7 +5973,6 @@ function rcube_webmail() // no renaming, only update class_name if (oldfolder == newfolder) { $('#'+id).attr('class', class_name || ''); - this.subscription_list.focus(); return; } @@ -6040,7 +6009,7 @@ function rcube_webmail() for (i=level; i<0; i++) dispname = '    ' + dispname; } - row.find('td:first').html(dispname); + $('.name', row).html(dispname); this.env.subscriptionrows[id][1] = dispname; } } @@ -6068,8 +6037,8 @@ function rcube_webmail() this._remove_folder_row = function(id) { - this.subscription_list.remove_row(id.replace(/^rcmrow/, '')); - $('#'+id).remove(); + this.subscription_list.remove(id.replace(/^rcmli/, '')); + $('#' + id).remove(); delete this.env.subscriptionrows[id]; }; diff --git a/program/js/treelist.js b/program/js/treelist.js index b2d838e13..1e6061770 100644 --- a/program/js/treelist.js +++ b/program/js/treelist.js @@ -46,7 +46,7 @@ function rcube_treelist_widget(node, p) scroll_speed: 20, save_state: false, keyboard: true, - check_droptarget: function(node){ return !node.virtual } + check_droptarget: function(node) { return !node.virtual; } }, p || {}); var container = $(node), @@ -67,6 +67,7 @@ function rcube_treelist_widget(node, p) searchfield, tree_state, ui_droppable, + ui_draggable, list_id = (container.attr('id') || p.id_prefix || '0'), me = this; @@ -83,6 +84,7 @@ function rcube_treelist_widget(node, p) this.drag_end = drag_end; this.intersects = intersects; this.droppable = droppable; + this.draggable = draggable; this.update = update_node; this.insert = insert; this.remove = remove; @@ -108,7 +110,11 @@ function rcube_treelist_widget(node, p) e.stopPropagation(); }); - container.on('click', 'li', function(e){ + container.on('click', 'li', function(e) { + // do not select record on checkbox/input click + if ($(e.target).is('input')) + return true; + var node = p.selectable ? indexbyid[dom2id($(this))] : null; if (node && !node.virtual) { select(node.id); @@ -232,6 +238,9 @@ function rcube_treelist_widget(node, p) selection = null; } + if (!id) + return; + var li = id2dom(id, true); if (li.length) { li.addClass('selected').attr('aria-selected', 'true'); @@ -708,6 +717,7 @@ function rcube_treelist_widget(node, p) { var domid = p.id_encode ? p.id_encode(id) : id, suffix = search_active && !real ? '--xsR' : ''; + return $('#' + p.id_prefix + domid + suffix, container); } @@ -850,6 +860,11 @@ function rcube_treelist_widget(node, p) */ function drag_start() { + if (drag_active) + return; + + drag_active = true; + var li, item, height, pos = container.offset(); @@ -857,7 +872,6 @@ function rcube_treelist_widget(node, p) list_scroll_top = container.parent().scrollTop(); pos.top += list_scroll_top; - drag_active = true; box_coords = { x1: pos.left, y1: pos.top, @@ -920,6 +934,9 @@ function rcube_treelist_widget(node, p) */ function drag_end() { + if (!drag_active) + return; + drag_active = false; scroll_timer = null; @@ -950,7 +967,7 @@ function rcube_treelist_widget(node, p) } /** - * Determine if the given mouse coords intersect the list and one if its items + * Determine if the given mouse coords intersect the list and one of its items */ function intersects(mouse, highlight) { @@ -970,8 +987,8 @@ function rcube_treelist_widget(node, p) } // check intersection with visible list items - var pos, node; - for (var id in item_coords) { + var id, pos, node; + for (id in item_coords) { pos = item_coords[id]; if (mouse.x >= pos.x1 && mouse.x < pos.x2 && mouse.top >= pos.y1 && mouse.top < pos.y2) { node = indexbyid[id]; @@ -1024,7 +1041,14 @@ function rcube_treelist_widget(node, p) */ function droppable(opts) { - var my_opts = $.extend({ greedy: true, hoverClass: 'droptarget', addClasses:false }, opts); + if (!opts) opts = {}; + + var my_opts = $.extend({ + greedy: true, + tolerance: 'pointer', + hoverClass: 'droptarget', + addClasses: false + }, opts); my_opts.activate = function(e, ui) { drag_start(); @@ -1046,7 +1070,34 @@ function rcube_treelist_widget(node, p) opts.over(e, ui); }; - $('li:not(.virtual)', container).droppable(my_opts); + $(selector ? selector : 'li:not(.virtual)', container).droppable(my_opts); + + return this; + } + + /** + * Wrapper for jQuery.UI.draggable() activation on this widget + * + * @param object Options as passed to regular .draggable() function + */ + function draggable(opts) + { + if (!opts) opts = {}; + + var my_opts = $.extend({ + appendTo: 'body', + iframeFix: true, + addClasses: false, + cursorAt: {left: -20, top: 5}, + helper: function(e) { + return $('
').attr('id', 'rcmdraglayer') + .text($.trim($(e.target).first().text())); + } + }, opts); + + $('li:not(.virtual)', container).draggable(my_opts); + + return this; } } diff --git a/program/steps/settings/folders.inc b/program/steps/settings/folders.inc index 33b2b0624..ad5f37d95 100644 --- a/program/steps/settings/folders.inc +++ b/program/steps/settings/folders.inc @@ -177,11 +177,9 @@ if ($OUTPUT->ajax_call) { } $OUTPUT->set_pagetitle($RCMAIL->gettext('folders')); -$OUTPUT->include_script('list.js'); $OUTPUT->set_env('prefix_ns', $STORAGE->get_namespace('prefix')); -if ($STORAGE->get_capability('QUOTA')) { - $OUTPUT->set_env('quota', true); -} +$OUTPUT->set_env('quota', (bool) $STORAGE->get_capability('QUOTA')); +$OUTPUT->include_script('treelist.js'); // add some labels to client $OUTPUT->add_label('deletefolderconfirm', 'purgefolderconfirm', 'folderdeleting', @@ -205,15 +203,8 @@ function rcube_subscription_form($attrib) list($form_start, $form_end) = get_form_tags($attrib, 'folders'); unset($attrib['form']); - if (!$attrib['id']) + if (!$attrib['id']) { $attrib['id'] = 'rcmSubscriptionlist'; - - $table = new html_table(); - - if ($attrib['noheader'] !== true && $attrib['noheader'] != "true") { - // add table header - $table->add_header('name', $RCMAIL->gettext('foldername')); - $table->add_header('subscribed', ''); } $STORAGE = $RCMAIL->get_storage(); @@ -227,7 +218,6 @@ function rcube_subscription_form($attrib) $namespace = $STORAGE->get_namespace(); $special_folders = array_flip(array_merge(array('inbox' => 'INBOX'), $STORAGE->get_special_folders())); $protect_default = $RCMAIL->config->get('protect_default_folders'); - $a_js_folders = array(); $seen = array(); $list_folders = array(); @@ -272,19 +262,15 @@ function rcube_subscription_form($attrib) unset($seen); - // add drop-target representing 'root' - $table->add_row(array('id' => 'mailboxroot', 'class' => 'virtual root')); - $table->add('name', ' '); - $table->add(null, ' '); - - $a_js_folders['mailboxroot'] = array('', '', true); - $checkbox_subscribe = new html_checkbox(array( 'name' => '_subscribed[]', 'title' => $RCMAIL->gettext('changesubscription'), 'onclick' => rcmail_output::JS_OBJECT_NAME.".command(this.checked?'subscribe':'unsubscribe',this.value)", )); + $js_folders = array(); + $folders = array(); + // create list of available folders foreach ($list_folders as $i => $folder) { $idx = $i + 1; @@ -292,7 +278,7 @@ function rcube_subscription_form($attrib) $subscribed = $sub_key !== false; $protected = $protect_default && isset($special_folders[$folder['id']]); $noselect = false; - $classes = array($i%2 ? 'even' : 'odd'); + $classes = array('listitem'); $folder_utf8 = rcube_charset::convert($folder['id'], 'UTF7-IMAP'); $display_folder = str_repeat('    ', $folder['level']) @@ -352,25 +338,45 @@ function rcube_subscription_form($attrib) } } - $table->add_row(array('id' => 'rcmrow'.$idx, 'class' => join(' ', $classes), - 'foldername' => $folder['id'])); + $row_id = 'rcmli' . $idx; + $folders[$row_id] = array( + 'folder' => $folder_utf8, + 'display' => $display_folder, + 'class' => join(' ', $classes), + 'folder_imap' => $folder['id'], + 'subscribed' => $subscribed, + 'protected' => $protected || $folder['virtual'], + 'content' => html::a(array('class' => 'name', 'href' => '#_' . $row_id), $display_folder) + . $checkbox_subscribe->show(($subscribed ? $folder_utf8 : ''), + array('value' => $folder_utf8, 'disabled' => $disabled ? 'disabled' : '')) + ); + } - $table->add('name', $display_folder); - $table->add('subscribed', $checkbox_subscribe->show(($subscribed ? $folder_utf8 : ''), - array('value' => $folder_utf8, 'disabled' => $disabled ? 'disabled' : ''))); + $plugin = $RCMAIL->plugins->exec_hook('folders_list', array('list' => $folders)); - $a_js_folders['rcmrow'.$idx] = array($folder_utf8, - $display_folder, $protected || $folder['virtual']); + // add drop-target representing 'root' + $roots = array( + 'mailboxroot' => array( + 'folder' => '', + 'display' => '', + 'protected' => true, + 'class' => 'root', + 'content' => html::span('name', ' ') + ) + ); + $folders = array_merge($roots, $plugin['list']); + + while (list($key, $data) = each($folders)) { + $js_folders[$key] = array($data['folder'], $data['display'], $data['protected']); + $folders[$key] = html::tag('li', array('id' => $key, 'class' => $data['class']), $data['content']); } - $RCMAIL->plugins->exec_hook('folders_list', array('table' => $table)); - $OUTPUT->add_gui_object('subscriptionlist', $attrib['id']); - $OUTPUT->set_env('subscriptionrows', $a_js_folders); + $OUTPUT->set_env('subscriptionrows', $js_folders); $OUTPUT->set_env('defaultfolders', array_keys($special_folders)); $OUTPUT->set_env('delimiter', $delimiter); - return $form_start . $table->show($attrib) . $form_end; + return $form_start . html::tag('ul', $attrib, implode("\n", $folders)) . $form_end; } function rcmail_folder_frame($attrib) diff --git a/program/steps/settings/func.inc b/program/steps/settings/func.inc index 7ccbfa4a5..40b70b119 100644 --- a/program/steps/settings/func.inc +++ b/program/steps/settings/func.inc @@ -1309,6 +1309,8 @@ function rcmail_update_folder_row($name, $oldname=null, $subscribe=false, $class $display_name = str_repeat('    ', $level) . rcube::Q($protected ? $RCMAIL->localize_foldername($name) : rcube_charset::convert($foldersplit[$level], 'UTF7-IMAP')); + $class_name = trim($class_name . ' listitem'); + if ($oldname === null) { $OUTPUT->command('add_folder_row', $name_utf8, $display_name, $protected, $subscribe, false, $class_name); diff --git a/skins/classic/common.css b/skins/classic/common.css index d28f2871c..13f4e6483 100644 --- a/skins/classic/common.css +++ b/skins/classic/common.css @@ -723,6 +723,7 @@ ul.treelist li font-size: 11px; border-bottom: 1px solid #EBEBEB; white-space: nowrap; + overflow: hidden; } ul.treelist li a diff --git a/skins/classic/functions.js b/skins/classic/functions.js index 7f2b8b4fb..b521be3bb 100644 --- a/skins/classic/functions.js +++ b/skins/classic/functions.js @@ -751,6 +751,8 @@ function rcube_layer(id, attributes) /** * Scroller + * + * @deprecated Use treelist widget */ function rcmail_scroller(list, top, bottom) { @@ -1010,9 +1012,5 @@ function rcube_init_mail_ui() rcmail.addEventListener('afterupload-photo', function(){ rcmail_ui.show_popup('uploadform', false); }) .gui_object('dragmenu', 'dragmenu'); } - else if (rcmail.env.task == 'settings') { - if (rcmail.gui_objects.subscriptionlist) - new rcmail_scroller('#folderlist-content', '#folderlist-title', '#folderlist-footer'); - } }); } diff --git a/skins/classic/mail.css b/skins/classic/mail.css index f9e5e4b9c..58db795cb 100644 --- a/skins/classic/mail.css +++ b/skins/classic/mail.css @@ -409,14 +409,6 @@ background-color: #FFF; } -#mailboxlist li -{ - display: block; - position: relative; - font-size: 11px; - border-bottom: 1px solid #EBEBEB; -} - #mailboxlist li ul li:last-child { border-bottom: 0 none; diff --git a/skins/classic/settings.css b/skins/classic/settings.css index acd0b9fd0..3b084de87 100644 --- a/skins/classic/settings.css +++ b/skins/classic/settings.css @@ -12,64 +12,53 @@ } #identities-table, -#subscription-table, #sections-table { width: 100%; table-layout: fixed; } -#subscription-table input -{ - font: inherit; -} - -#subscription-table tbody td, -#identities-table tbody td, -#sections-table tbody td -{ - cursor: default; - text-overflow: ellipsis; - -o-text-overflow: ellipsis; - height: 18px; -} - #identities-table tbody tr.readonly td { font-style: italic; } -#subscription-table tr.virtual td +#subscription-table li.selected a { - color: #666; + color: #FFF; + background-color: #CC3333; } -#subscription-table tr.root td +#subscription-table li.root { - font-size: 10%; + font-size: 5%; + line-height: 5px; height: 5px; + padding: 2px; } -#subscription-table tr.selected td -{ - color: #FFFFFF; - background-color: #CC3333; -} - -#subscription-table tr.droptarget td +#subscription-table li a.name { - background-color: #FFFFA6; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + display: block; + float: left; + padding: 0 0 0 5px; + height: 24px; + line-height: 24px; } -#subscription-table td.name +#subscription-table li input { - width: auto; + position: absolute; + right: 0; } -#subscription-table td.subscribed +html.chrome #subscription-table li input, +html.opera #subscription-table li input { - text-align: right; - padding-right: 12px; + margin-top: 6px; } #folder-box, diff --git a/skins/classic/templates/folders.html b/skins/classic/templates/folders.html index f00c23b22..66bec6243 100644 --- a/skins/classic/templates/folders.html +++ b/skins/classic/templates/folders.html @@ -21,8 +21,8 @@
- +