From 3cb61e7528c2a8544083bf14e02ea4b9387671fb Mon Sep 17 00:00:00 2001 From: Aleksander Machniak Date: Wed, 16 Jul 2014 11:08:11 +0200 Subject: Collapsible (and iconized) folders tree in folder manager (#1489648) --- program/js/app.js | 306 +++++++++++++++++---------------- program/js/treelist.js | 36 +++- program/steps/settings/edit_folder.inc | 36 ++-- program/steps/settings/folders.inc | 112 +++++++----- program/steps/settings/func.inc | 17 +- program/steps/settings/save_folder.inc | 10 +- skins/classic/common.css | 154 ++++++++++++++++- skins/classic/mail.css | 146 ---------------- skins/classic/settings.css | 30 +--- skins/classic/templates/folders.html | 2 +- skins/classic/templates/mail.html | 2 +- skins/larry/mail.css | 218 +---------------------- skins/larry/settings.css | 13 +- skins/larry/styles.css | 218 +++++++++++++++++++++++ skins/larry/templates/folders.html | 2 +- skins/larry/templates/mail.html | 2 +- 16 files changed, 673 insertions(+), 631 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index a47d971d6..895671a86 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -544,7 +544,7 @@ function rcube_webmail() // select first input field in an edit form if (this.gui_objects.editform) $("input,select,textarea", this.gui_objects.editform) - .not(':hidden').not(':disabled').first().select(); + .not(':hidden').not(':disabled').first().select().focus(); // unset contentframe variable if preview_pane is enabled if (this.env.contentframe && !$('#' + this.env.contentframe).is(':visible')) @@ -5793,39 +5793,52 @@ function rcube_webmail() this.last_sub_rx = RegExp('['+delim+']?[^'+delim+']+$'); this.subscription_list = new rcube_treelist_widget(this.gui_objects.subscriptionlist, { - selectable: true + selectable: true, + id_prefix: 'rcmli', + id_encode: this.html_identifier_encode, + id_decode: this.html_identifier_decode }); this.subscription_list .addEventListener('select', function(node) { ref.subscription_select(node.id); }) - .draggable({cancel: '#mailboxroot'}) + .addEventListener('collapse', function(node) { ref.folder_collapsed(node) }) + .addEventListener('expand', function(node) { ref.folder_collapsed(node) }) + .draggable({cancel: 'li.mailbox.root'}) .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); + var source_folder = ref.folder_id2name($(node).attr('id')), + dest_folder = ref.folder_id2name(this.id), + source = ref.env.subscriptionrows[source_folder], + dest = ref.env.subscriptionrows[dest_folder]; + + return source && !source[2] + && dest_folder != source_folder.replace(ref.last_sub_rx, '') + && !dest_folder.startsWith(source_folder + ref.env.delimiter); }, drop: function(e, ui) { - ref.subscription_move_folder(ui.draggable.attr('id'), this.id); + var source = ref.folder_id2name(ui.draggable.attr('id')), + dest = ref.folder_id2name(this.id); + + ref.subscription_move_folder(source, dest); } }); }; + this.folder_id2name = function(id) + { + return ref.html_identifier_decode(id.replace(/^rcmli/, '')); + }; + this.subscription_select = function(id) { var folder; - if (id && id != 'mailboxroot' && (folder = this.env.subscriptionrows[id])) { - this.env.mailbox = folder[0]; - this.show_folder(folder[0]); + if (id && id != '*' && (folder = this.env.subscriptionrows[id])) { + this.env.mailbox = id; + this.show_folder(id); this.enable_command('delete-folder', !folder[2]); } else { @@ -5837,16 +5850,13 @@ function rcube_webmail() this.subscription_move_folder = function(from, to) { - 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), + if (from && to !== null && from != to && to != from.replace(this.last_sub_rx, '')) { + var path = from.split(this.env.delimiter), basename = path.pop(), - newname = dest === '' ? basename : dest + this.env.delimiter + basename; + newname = to === '' || to === '*' ? basename : to + this.env.delimiter + basename; - if (newname != source) { - this.http_post('rename-folder', {_folder_oldname: source, _folder_newname: newname}, + if (newname != from) { + this.http_post('rename-folder', {_folder_oldname: from, _folder_newname: newname}, this.set_busy(true, 'foldermoving')); } } @@ -5861,50 +5871,51 @@ function rcube_webmail() // delete a specific mailbox with all its messages this.delete_folder = function(name) { - var id = this.get_folder_row_id(name ? name : this.env.mailbox), - folder = this.env.subscriptionrows[id][0]; + if (!name) + name = this.env.mailbox; - if (folder && confirm(this.get_label('deletefolderconfirm'))) { - this.http_post('delete-folder', {_mbox: folder}, this.set_busy(true, 'folderdeleting')); + if (name && confirm(this.get_label('deletefolderconfirm'))) { + this.http_post('delete-folder', {_mbox: name}, this.set_busy(true, 'folderdeleting')); } }; // Add folder row to the table and initialize it - this.add_folder_row = function (name, display_name, is_protected, subscribed, skip_init, class_name) + this.add_folder_row = function (id, name, display_name, is_protected, subscribed, class_name, refrow, subfolders) { if (!this.gui_objects.subscriptionlist) return false; - var row, n, tmp, tmp_name, rowid, collator, + var row, n, tmp, tmp_name, rowid, collator, pos, p, parent = '', folders = [], list = [], slist = [], - list_element = $(this.gui_objects.subscriptionlist), - refrow = $('li', list_element).get(1), - id = 'rcmli'+((new Date).getTime()); + list_element = $(this.gui_objects.subscriptionlist); + row = refrow ? refrow : $($('li', list_element).get(1)).clone(true); - if (!refrow) { + if (!row.length) { // Refresh page if we don't have a table row to clone this.goto_url('folders'); return false; } - // clone a table row if there are existing rows - row = $(refrow).clone(true); - // set ID, reset css class - row.attr({id: id, 'class': class_name}); + row.attr({id: 'rcmli' + this.html_identifier_encode(id), 'class': class_name}); + + if (!refrow || !refrow.length) { + // remove old subfolders and toggle + $('ul,div.treetoggle', row).remove(); + } // set folder name - $('.name', row).html(display_name); + $('a:first', row).text(display_name); // update subscription checkbox - $('input[name="_subscribed[]"]', row).val(name) + $('input[name="_subscribed[]"]:first', row).val(id) .prop({checked: subscribed ? true : false, disabled: is_protected ? true : false}); // add to folder/row-ID map this.env.subscriptionrows[id] = [name, display_name, false]; // copy folders data to an array for sorting - $.each(this.env.subscriptionrows, function(k, v) { folders.push(v); }); + $.each(this.env.subscriptionrows, function(k, v) { v[3] = k; folders.push(v); }); try { // use collator if supported (FF29, IE11, Opera15, Chrome24) @@ -5916,64 +5927,106 @@ function rcube_webmail() folders.sort(function(a, b) { var i, f1, f2, path1 = a[0].split(ref.env.delimiter), - path2 = b[0].split(ref.env.delimiter); + path2 = b[0].split(ref.env.delimiter), + len = path1.length; - for (i=0; i ').addClass('treetoggle collapsed').appendTo(parent); + } + if (!$('ul', parent).length) { + $('