program/include/rcmail.php | ●●●●● patch | view | raw | blame | history | |
program/js/app.js | ●●●●● patch | view | raw | blame | history | |
program/js/treelist.js | ●●●●● patch | view | raw | blame | history | |
program/steps/addressbook/func.inc | ●●●●● patch | view | raw | blame | history | |
skins/classic/addressbook.css | ●●●●● patch | view | raw | blame | history | |
skins/classic/templates/addressbook.html | ●●●●● patch | view | raw | blame | history | |
skins/larry/addressbook.css | ●●●●● patch | view | raw | blame | history | |
skins/larry/templates/addressbook.html | ●●●●● patch | view | raw | blame | history |
program/include/rcmail.php
@@ -1406,6 +1406,7 @@ $js_mailboxlist = array(); $out = html::tag('ul', $attrib, $rcmail->render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib); $rcmail->output->include_script('treelist.js'); $rcmail->output->add_gui_object('mailboxlist', $attrib['id']); $rcmail->output->set_env('mailboxes', $js_mailboxlist); $rcmail->output->set_env('unreadwrap', $attrib['unreadwrap']); @@ -1584,14 +1585,13 @@ 'id' => "rcmli".$folder_id, 'class' => join(' ', $classes), 'noclose' => true), html::a($link_attrib, $html_name) . (!empty($folder['folders']) ? html::div(array( 'class' => ($is_collapsed ? 'collapsed' : 'expanded'), 'style' => "position:absolute", 'onclick' => sprintf("%s.command('collapse-folder', '%s')", rcmail_output::JS_OBJECT_NAME, $js_name) ), ' ') : '')); html::a($link_attrib, $html_name)); $jslist[$folder_id] = array( if (!empty($folder['folders'])) { $out .= html::div('treetoggle ' . ($is_collapsed ? 'collapsed' : 'expanded'), ' '); } $jslist[$folder['id']] = array( 'id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual'] program/js/app.js
@@ -4,7 +4,7 @@ | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2013, The Roundcube Dev Team | | Copyright (C) 2011-2012, Kolab Systems AG | | Copyright (C) 2011-2013, Kolab Systems AG | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -459,8 +459,21 @@ this.display_message(this.pending_message[0], this.pending_message[1], this.pending_message[2]); // map implicit containers if (this.gui_objects.folderlist) if (this.gui_objects.folderlist) { this.gui_containers.foldertray = $(this.gui_objects.folderlist); // init treelist widget if (window.rcube_treelist_widget) { this.treelist = new rcube_treelist_widget(this.gui_objects.folderlist, { id_prefix: 'rcmli', id_encode: this.html_identifier_encode, id_decode: this.html_identifier_decode, check_droptarget: function(node){ return !node.virtual && ref.check_droptarget(node.id) } }); this.treelist.addEventListener('collapse', function(node){ ref.folder_collapsed(node) }); this.treelist.addEventListener('expand', function(node){ ref.folder_collapsed(node) }); } } // activate html5 file drop feature (if browser supports it and if configured) if (this.gui_objects.filedrop && this.env.filedrop && ((window.XMLHttpRequest && XMLHttpRequest.prototype && XMLHttpRequest.prototype.sendAsBinary) || window.FormData)) { @@ -1263,11 +1276,12 @@ this.html_identifier = function(str, encode) { str = String(str); if (encode) return Base64.encode(str).replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_'); else return str.replace(this.identifier_expr, '_'); return encode ? this.html_identifier_encode(str) : String(str).replace(this.identifier_expr, '_'); }; this.html_identifier_encode = function(str) { return Base64.encode(String(str)).replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_'); }; this.html_identifier_decode = function(str) @@ -1320,29 +1334,9 @@ if (this.preview_read_timer) clearTimeout(this.preview_read_timer); // save folderlist and folders location/sizes for droptarget calculation in drag_move() if (this.gui_objects.folderlist && model) { this.initialBodyScrollTop = bw.ie ? 0 : window.pageYOffset; this.initialListScrollTop = this.gui_objects.folderlist.parentNode.scrollTop; var k, li, height, list = $(this.gui_objects.folderlist); pos = list.offset(); this.env.folderlist_coords = { x1:pos.left, y1:pos.top, x2:pos.left + list.width(), y2:pos.top + list.height() }; this.env.folder_coords = []; for (k in model) { if (li = this.get_folder_li(k)) { // only visible folders if (height = li.firstChild.offsetHeight) { pos = $(li.firstChild).offset(); this.env.folder_coords[k] = { x1:pos.left, y1:pos.top, x2:pos.left + li.firstChild.offsetWidth, y2:pos.top + height, on:0 }; } } } } // prepare treelist widget for dragging interactions if (this.treelist) this.treelist.drag_start(); }; this.drag_end = function(e) @@ -1350,87 +1344,28 @@ this.drag_active = false; this.env.last_folder_target = null; if (this.folder_auto_timer) { clearTimeout(this.folder_auto_timer); this.folder_auto_timer = null; this.folder_auto_expand = null; } // over the folders if (this.gui_objects.folderlist && this.env.folder_coords) { for (var k in this.env.folder_coords) { if (this.env.folder_coords[k].on) $(this.get_folder_li(k)).removeClass('droptarget'); } } if (this.treelist) this.treelist.drag_end(); }; this.drag_move = function(e) { if (this.gui_objects.folderlist && this.env.folder_coords) { var k, li, div, check, oldclass, if (this.gui_objects.folderlist) { var drag_target, oldclass, layerclass = 'draglayernormal', mouse = rcube_event.get_mouse_pos(e), pos = this.env.folderlist_coords, // offsets to compensate for scrolling while dragging a message boffset = bw.ie ? -document.documentElement.scrollTop : this.initialBodyScrollTop, moffset = this.initialListScrollTop-this.gui_objects.folderlist.parentNode.scrollTop; mouse = rcube_event.get_mouse_pos(e); if (this.contact_list && this.contact_list.draglayer) oldclass = this.contact_list.draglayer.attr('class'); mouse.y += -moffset-boffset; // if mouse pointer is outside of folderlist if (mouse.x < pos.x1 || mouse.x >= pos.x2 || mouse.y < pos.y1 || mouse.y >= pos.y2) { if (this.env.last_folder_target) { $(this.get_folder_li(this.env.last_folder_target)).removeClass('droptarget'); this.env.folder_coords[this.env.last_folder_target].on = 0; this.env.last_folder_target = null; // mouse intersects a valid drop target on the treelist if (this.treelist && (drag_target = this.treelist.intersects(mouse, true))) { this.env.last_folder_target = drag_target; layerclass = 'draglayer' + (this.check_droptarget(drag_target) > 1 ? 'copy' : 'normal'); } if (layerclass != oldclass && this.contact_list && this.contact_list.draglayer) this.contact_list.draglayer.attr('class', layerclass); return; } // over the folders for (k in this.env.folder_coords) { pos = this.env.folder_coords[k]; if (mouse.x >= pos.x1 && mouse.x < pos.x2 && mouse.y >= pos.y1 && mouse.y < pos.y2) { if (check = this.check_droptarget(k)) { li = this.get_folder_li(k); div = $(li.getElementsByTagName('div')[0]); // if the folder is collapsed, expand it after 1sec and restart the drag & drop process. if (div.hasClass('collapsed')) { if (this.folder_auto_timer) clearTimeout(this.folder_auto_timer); this.folder_auto_expand = this.env.mailboxes[k].id; this.folder_auto_timer = setTimeout(function() { rcmail.command('collapse-folder', rcmail.folder_auto_expand); rcmail.drag_start(null); }, 1000); } else if (this.folder_auto_timer) { clearTimeout(this.folder_auto_timer); this.folder_auto_timer = null; this.folder_auto_expand = null; } $(li).addClass('droptarget'); this.env.folder_coords[k].on = 1; this.env.last_folder_target = k; layerclass = 'draglayer' + (check > 1 ? 'copy' : 'normal'); } else { // Clear target, otherwise drag end will trigger move into last valid droptarget else this.env.last_folder_target = null; } else if (pos.on) { $(this.get_folder_li(k)).removeClass('droptarget'); this.env.folder_coords[k].on = 0; } } if (layerclass != oldclass && this.contact_list && this.contact_list.draglayer) @@ -1440,40 +1375,33 @@ this.collapse_folder = function(name) { var li = this.get_folder_li(name, '', true), div = $('div:first', li), ul = $('ul:first', li); if (this.treelist) this.treelist.toggle(name); }; if (div.hasClass('collapsed')) { ul.show(); div.removeClass('collapsed').addClass('expanded'); var reg = new RegExp('&'+urlencode(name)+'&'); this.env.collapsed_folders = this.env.collapsed_folders.replace(reg, ''); } else if (div.hasClass('expanded')) { ul.hide(); div.removeClass('expanded').addClass('collapsed'); this.env.collapsed_folders = this.env.collapsed_folders+'&'+urlencode(name)+'&'; this.folder_collapsed = function(node) { var prefname = this.env.task == 'addressbook' ? 'collapsed_abooks' : 'collapsed_folders'; if (node.collapsed) { this.env[prefname] = this.env[prefname] + '&'+urlencode(node.id)+'&'; // select the folder if one of its childs is currently selected // don't select if it's virtual (#1488346) if (this.env.mailbox.indexOf(name + this.env.delimiter) == 0 && !$(li).hasClass('virtual')) if (this.env.mailbox && this.env.mailbox.indexOf(name + this.env.delimiter) == 0 && !node.virtual) this.command('list', name); } else return; // Work around a bug in IE6 and IE7, see #1485309 if (bw.ie6 || bw.ie7) { var siblings = li.nextSibling ? li.nextSibling.getElementsByTagName('ul') : null; if (siblings && siblings.length && (li = siblings[0]) && li.style && li.style.display != 'none') { li.style.display = 'none'; li.style.display = ''; } else { var reg = new RegExp('&'+urlencode(node.id)+'&'); this.env[prefname] = this.env[prefname].replace(reg, ''); } this.command('save-pref', { name: 'collapsed_folders', value: this.env.collapsed_folders }); this.set_unread_count_display(name, false); if (!this.drag_active) { this.command('save-pref', { name: prefname, value: this.env[prefname] }); if (this.env.unread_counts) this.set_unread_count_display(node.id, false); } }; this.doc_mouse_up = function(e) @@ -1498,9 +1426,9 @@ if (this.drag_active && model && this.env.last_folder_target) { var target = model[this.env.last_folder_target]; $(this.get_folder_li(this.env.last_folder_target)).removeClass('droptarget'); this.env.last_folder_target = null; list.draglayer.hide(); this.drag_end(e); if (!this.drag_menu(e, target)) this.command('moveto', target); @@ -4164,7 +4092,7 @@ else if (!this.env.search_request) folder = group ? 'G'+src+group : src; this.select_folder(folder); this.select_folder(folder, '', true); this.env.source = src; this.env.group = group; @@ -4468,7 +4396,7 @@ this.name_input.bind('keydown', function(e){ return rcmail.add_input_keydown(e); }); this.env.group_renaming = true; var link, li = this.get_folder_li(this.env.source+this.env.group, 'rcmliG'); var link, li = this.get_folder_li('G'+this.env.source+this.env.group,'',true); if (li && (link = li.firstChild)) { $(link).hide().before(this.name_input); } @@ -4489,7 +4417,7 @@ this.remove_group_item = function(prop) { var li, key = 'G'+prop.source+prop.id; if ((li = this.get_folder_li(key))) { if ((li = this.get_folder_li(key,'',true))) { this.triggerEvent('group_delete', { source:prop.source, id:prop.id, li:li }); li.parentNode.removeChild(li); @@ -4511,7 +4439,7 @@ this.name_input.bind('keydown', function(e){ return rcmail.add_input_keydown(e); }); this.name_input_li = $('<li>').addClass(type).append(this.name_input); var li = type == 'contactsearch' ? $('li:last', this.gui_objects.folderlist) : this.get_folder_li(this.env.source); var li = type == 'contactsearch' ? $('li:last', this.gui_objects.folderlist) : $('ul.groups li:last', this.get_folder_li(this.env.source,'',true)); this.name_input_li.insertAfter(li); } @@ -4612,7 +4540,7 @@ this.reset_add_input(); var key = 'G'+prop.source+prop.id, li = this.get_folder_li(key), li = this.get_folder_li(key,'',true), link; // group ID has changed, replace link node and identifiers @@ -4651,8 +4579,8 @@ this.add_contact_group_row = function(prop, li, reloc) { var row, name = prop.name.toUpperCase(), sibling = this.get_folder_li(prop.source), prefix = 'rcmliG' + this.html_identifier(prop.source); sibling = this.get_folder_li(prop.source,'',true), prefix = 'rcmli' + this.html_identifier('G'+prop.source, true); // When renaming groups, we need to remove it from DOM and insert it in the proper place if (reloc) { @@ -4901,12 +4829,12 @@ .attr('rel', id) .click(function() { return rcmail.command('listsearch', id, this); }) .html(name), li = $('<li>').attr({id: 'rcmli' + this.html_identifier(key), 'class': 'contactsearch'}) li = $('<li>').attr({ id:'rcmli' + this.html_identifier(key,true), 'class':'contactsearch' }) .append(link), prop = {name:name, id:id, li:li[0]}; this.add_saved_search_row(prop, li); this.select_folder('S'+id); this.select_folder(key,'',true); this.enable_command('search-delete', true); this.env.search_id = id; @@ -4960,7 +4888,7 @@ this.remove_search_item = function(id) { var li, key = 'S'+id; if ((li = this.get_folder_li(key))) { if ((li = this.get_folder_li(key,'',true))) { this.triggerEvent('search_delete', { id:id, li:li }); li.parentNode.removeChild(li); @@ -4982,7 +4910,7 @@ } this.reset_qsearch(); this.select_folder('S'+id); this.select_folder('S'+id, '', true); // reset vars this.env.current_page = 1; program/js/treelist.js
New file @@ -0,0 +1,425 @@ /* +-----------------------------------------------------------------------+ | Roundcube Treelist widget | | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2013, The Roundcube Dev Team | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | | See the README file for a full license statement. | | | +-----------------------------------------------------------------------+ | Authors: Thomas Bruederli <roundcube@gmail.com> | +-----------------------------------------------------------------------+ | Requires: common.js | +-----------------------------------------------------------------------+ */ /** * Roundcube Treelist widget class * @contructor */ function rcube_treelist_widget(node, p) { // apply some defaults to p p = $.extend({ id_prefix: '', autoexpand: 1000, selectable: false, check_droptarget: function(node){ return !node.virtual } }, p || {}); var container = $(node); var data = p.data || []; var indexbyid = {}; var selection = null; var drag_active = false; var box_coords = {}; var item_coords = []; var autoexpand_timer; var autoexpand_item; var body_scroll_top = 0; var list_scroll_top = 0; var me = this; /////// export public members and methods this.container = container; this.expand = expand; this.collapse = collapse; this.select = select; this.render = render; this.drag_start = drag_start; this.drag_end = drag_end; this.intersects = intersects; /////// startup code (constructor) // abort if node not found if (!container.length) return; if (p.data) { index_data({ children:data }); } // load data from DOM else { data = walk_list(container); // console.log(data); } // register click handlers on list container.on('click', 'div.treetoggle', function(e){ toggle(dom2id($(this).parent())); }); container.on('click', 'li', function(e){ var node = p.selectable ? indexbyid[dom2id($(this))] : null; if (node && !node.virtual) { select(node.id); e.stopPropagation(); } }); /////// private methods /** * Collaps a the node with the given ID */ function collapse(id, recursive, set) { var node; if (node = indexbyid[id]) { node.collapsed = typeof set == 'undefined' || set; update_dom(node); // Work around a bug in IE6 and IE7, see #1485309 if (window.bw && (bw.ie6 || bw.ie7) && node.collapsed) { id2dom(node.id).next().children('ul:visible').hide().show(); } if (recursive && node.children) { for (var i=0; i < node.children.length; i++) { collapse(node.children[i].id, recursive, set); } } me.triggerEvent(node.collapsed ? 'collapse' : 'expand', node); } } /** * Expand a the node with the given ID */ function expand(id, recursive) { collapse(id, recursive, false); } /** * Toggle collapsed state of a list node */ function toggle(id, recursive) { var node; if (node = indexbyid[id]) { collapse(id, recursive, !node.collapsed); } } /** * Select a tree node by it's ID */ function select(id) { if (selection) { id2dom(selection).removeClass('selected'); selection = null; } var li = id2dom(id); if (li.length) { li.addClass('selected'); selection = id; // TODO: expand all parent nodes if collapsed scroll_to_node(li); } me.triggerEvent('select', indexbyid[id]); } /** * Getter for the currently selected node ID */ function get_selection() { return selection; } /** * Return the DOM element of the list item with the given ID */ function get_item(id) { return id2dom(id).get(0); } /** * Apply the 'collapsed' status of the data node to the corresponding DOM element(s) */ function update_dom(node) { var li = id2dom(node.id); li.children('ul').first()[(node.collapsed ? 'hide' : 'show')](); li.children('div.treetoggle').removeClass('collapsed expanded').addClass(node.collapsed ? 'collapsed' : 'expanded'); me.triggerEvent('toggle', node); } /** * Render the tree list from the internal data structure */ function render() { if (me.triggerEvent('renderBefore', data) === false) return; // remove all child nodes container.html(''); // render child nodes for (var i=0; i < data.length; i++) { render_node(data[i], container); } me.triggerEvent('renderAfter', container); } /** * Render a specific node into the DOM list */ function render_node(node, parent) { var li = $('<li>' + node.html + '</li>') .attr('id', p.id_prefix + node.id) .addClass((node.classes || []).join(' ')) .appendTo(parent); if (node.virtual) li.addClass('virtual'); if (node.id == selection) li.addClass('selected'); // add child list and toggle icon if (node.children && node.children.length) { $('<div class="treetoggle '+(node.collapsed ? 'collapsed' : 'expanded') + '"> </div>').appendTo(li); var ul = $('<ul>').appendTo(li); if (node.collapsed) ul.hide(); for (var i=0; i < node.children.length; i++) { render_node(node.children[i], ul); } } } /** * Recursively walk the DOM tree and build an internal data structure * representing the skeleton of this tree list. */ function walk_list(ul) { var result = []; ul.children('li').each(function(i,e){ var li = $(e); var node = { id: dom2id(li), classes: li.attr('class').split(' '), virtual: li.hasClass('virtual'), html: li.children().first().get(0).outerHTML, children: walk_list(li.children('ul')) } if (node.children.length) { node.collapsed = li.children('ul').css('display') == 'none'; } if (li.hasClass('selected')) { selection = node.id; } result.push(node); indexbyid[node.id] = node; }) return result; } /** * Recursively walk the data tree and index nodes by their ID */ function index_data(node) { if (node.id) { indexbyid[node.id] = node; } for (var c=0; node.children && c < node.children.length; c++) { index_data(node.children[c]); } } /** * Get the (stripped) node ID from the given DOM element */ function dom2id(li) { var domid = li.attr('id').replace(new RegExp('^' + (p.id_prefix) || '%'), ''); return p.id_decode ? p.id_decode(domid) : domid; } /** * Get the <li> element for the given node ID */ function id2dom(id) { var domid = p.id_encode ? p.id_encode(id) : id; return $('#' + p.id_prefix + domid); } /** * Scroll the parent container to make the given list item visible */ function scroll_to_node(li) { var scroller = container.parent(); scroller.scrollTop(li.offset().top - scroller.offset().top + scroller.scrollTop()); } ///// drag & drop support /** * When dragging starts, compute absolute bounding boxes of the list and it's items * for faster comparisons while mouse is moving */ function drag_start() { var li, item, height, pos = container.offset(); body_scroll_top = bw.ie ? 0 : window.pageYOffset; list_scroll_top = container.parent().scrollTop(); drag_active = true; box_coords = { x1: pos.left, y1: pos.top, x2: pos.left + container.width(), y2: pos.top + container.height() }; item_coords = []; for (var id in indexbyid) { li = id2dom(id); item = li.children().first().get(0); if (height = item.offsetHeight) { pos = $(item).offset(); item_coords[id] = { x1: pos.left, y1: pos.top, x2: pos.left + item.offsetWidth, y2: pos.top + height, on: id == autoexpand_item }; } } } /** * Signal that dragging has stopped */ function drag_end() { drag_active = false; if (autoexpand_timer) { clearTimeout(autoexpand_timer); autoexpand_timer = null; autoexpand_item = null; } $('li.droptarget', container).removeClass('droptarget'); } /** * Determine if the given mouse coords intersect the list and one if its items */ function intersects(mouse, highlight) { // offsets to compensate for scrolling while dragging a message var boffset = bw.ie ? -document.documentElement.scrollTop : body_scroll_top, moffset = list_scroll_top - container.parent().scrollTop(), result = null; mouse.top = mouse.y + -moffset - boffset; // no intersection with list bounding box if (mouse.x < box_coords.x1 || mouse.x >= box_coords.x2 || mouse.top < box_coords.y1 || mouse.top >= box_coords.y2) { // TODO: optimize performance for this operation $('li.droptarget', container).removeClass('droptarget'); return result; } // check intersection with visible list items var pos, node; for (var 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]; // if the folder is collapsed, expand it after the configured time if (node.children && node.children.length && node.collapsed && p.autoexpand && autoexpand_item != id) { if (autoexpand_timer) clearTimeout(autoexpand_timer); autoexpand_item = id; autoexpand_timer = setTimeout(function() { expand(autoexpand_item); drag_start(); // re-calculate item coords autoexpand_item = null; }, p.autoexpand); } else if (autoexpand_timer && autoexpand_item != id) { clearTimeout(autoexpand_timer); autoexpand_item = null; autoexpand_timer = null; } // check if this item is accepted as drop target if (p.check_droptarget(node)) { if (highlight) { id2dom(id).addClass('droptarget'); pos.on = true; } result = id; } else { result = null; } } else if (pos.on) { id2dom(id).removeClass('droptarget'); pos.on = false; } } return result; } } // use event processing functions from Roundcube's rcube_event_engine rcube_treelist_widget.prototype.addEventListener = rcube_event_engine.prototype.addEventListener; rcube_treelist_widget.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener; rcube_treelist_widget.prototype.triggerEvent = rcube_event_engine.prototype.triggerEvent; program/steps/addressbook/func.inc
@@ -187,7 +187,7 @@ $jsdata = array(); $line_templ = html::tag('li', array( 'id' => 'rcmli%s', 'class' => '%s'), 'id' => 'rcmli%s', 'class' => '%s', 'noclose' => true), html::a(array('href' => '%s', 'rel' => '%s', 'onclick' => "return ".JS_OBJECT_NAME.".command('list','%s',this)"), '%s')); @@ -213,7 +213,7 @@ $name = !empty($source['name']) ? $source['name'] : $id; $out .= sprintf($line_templ, html_identifier($id), rcube_utils::html_identifier($id, true), $class_name, Q(rcmail_url(null, array('_source' => $id))), $source['id'], @@ -224,10 +224,11 @@ $groupdata = rcmail_contact_groups($groupdata); $jsdata = $groupdata['jsdata']; $out = $groupdata['out']; $out .= '</li>'; } $line_templ = html::tag('li', array( 'id' => 'rcmliS%s', 'class' => '%s'), 'id' => 'rcmli%s', 'class' => '%s'), html::a(array('href' => '#', 'rel' => 'S%s', 'onclick' => "return ".JS_OBJECT_NAME.".command('listsearch', '%s', this)"), '%s')); @@ -245,14 +246,17 @@ $class_name .= ' ' . $source['class_name']; $out .= sprintf($line_templ, html_identifier($id), rcube_utils::html_identifier('S'.$id, true), $class_name, $id, $js_id, (!empty($source['name']) ? Q($source['name']) : Q($id))); } $OUTPUT->set_env('contactgroups', $jsdata); $OUTPUT->set_env('collapsed_abooks', (string)$RCMAIL->config->get('collapsed_abooks','')); $OUTPUT->add_gui_object('folderlist', $attrib['id']); $OUTPUT->include_script('treelist.js'); // add some labels to client $OUTPUT->add_label('deletegroupconfirm', 'groupdeleting', 'addingmember', 'removingmember'); @@ -265,18 +269,24 @@ global $RCMAIL; $groups = $RCMAIL->get_address_book($args['source'])->list_groups(); $js_id = $RCMAIL->JQ($args['source']); if (!empty($groups)) { $line_templ = html::tag('li', array( 'id' => 'rcmliG%s', 'class' => 'contactgroup'), 'id' => 'rcmli%s', 'class' => 'contactgroup'), html::a(array('href' => '#', 'rel' => '%s:%s', 'onclick' => "return ".JS_OBJECT_NAME.".command('listgroup',{'source':'%s','id':'%s'},this)"), '%s')); // append collapse/expand toggle and open a new <ul> $is_collapsed = strpos($RCMAIL->config->get('collapsed_abooks',''), '&'.rawurlencode($args['source']).'&') !== false; $args['out'] .= html::div('treetoggle ' . ($is_collapsed ? 'collapsed' : 'expanded'), ' '); $jsdata = array(); $groups_html = ''; foreach ($groups as $group) { $args['out'] .= sprintf($line_templ, html_identifier($args['source'] . $group['ID']), $groups_html .= sprintf($line_templ, rcube_utils::html_identifier('G' . $args['source'] . $group['ID'], true), $args['source'], $group['ID'], $args['source'], $group['ID'], Q($group['name']) ); @@ -286,6 +296,10 @@ } } $args['out'] .= html::tag('ul', array('class' => 'groups', 'style' => ($is_collapsed ? "display:none;" : null)), $groups_html); return $args; } skins/classic/addressbook.css
@@ -118,7 +118,7 @@ #directorylistbox input { margin: 0px; margin: 0 0 0 20px; font-size: 11px; width: 90%; } @@ -144,7 +144,8 @@ width: 280px; } #directorylist #directorylist, #directorylist li ul { list-style: none; margin: 0; @@ -152,11 +153,15 @@ background-color: #FFFFFF; } #directorylist li ul { border-top: 1px solid #EBEBEB; } #directorylist li { display: block; font-size: 11px; background: url(images/icons/folders.png) 5px -108px no-repeat; border-bottom: 1px solid #EBEBEB; white-space: nowrap; } @@ -168,31 +173,37 @@ padding-left: 25px; padding-top: 2px; padding-bottom: 2px; height: 16px; text-decoration: none; white-space: nowrap; background: url(images/icons/folders.png) 5px -108px no-repeat; } #directorylist li.contactgroup #directorylist li ul li a { padding-left: 15px; background-position: 20px -143px; padding-left: 45px; } #directorylist li.contactsearch #directorylist li ul li:last-child { border-bottom: 0; } #directorylist li.contactgroup a { background-position: 22px -143px; } #directorylist li.contactsearch a { background-position: 6px -162px; } #directorylist li.selected { background-color: #929292; border-bottom: 1px solid #898989; } #directorylist li.selected a #directorylist li.selected > a { color: #FFF; font-weight: bold; background-color: #929292; } #directorylist li.droptarget skins/classic/templates/addressbook.html
@@ -63,8 +63,7 @@ <div id="directorylistbox"> <div id="directorylist-title" class="boxtitle"><roundcube:label name="groups" /></div> <div id="directorylist-content" class="boxlistcontent"> <roundcube:object name="directorylist" id="directorylist" /> <roundcube:object name="groupslist" id="contactgroupslist" /> <roundcube:object name="directorylist" id="directorylist" class="treelist" /> </div> <div id="directorylist-footer" class="boxfooter"> <roundcube:button command="group-create" type="link" title="newcontactgroup" class="buttonPas addgroup" classAct="button addgroup" content=" " /> skins/larry/addressbook.css
@@ -83,8 +83,21 @@ background-position: 6px -766px; } #directorylist li.addressbook.selected a { #directorylist li.addressbook.selected > a { background-position: 6px -791px; } #directorylist li.addressbook ul li:last-child { border-bottom: 0; } #directorylist li.addressbook ul.groups { margin: 0; padding: 0; } #directorylist li.addressbook ul.groups li { width: 100%; } #directorylist li.contactgroup a { @@ -112,6 +125,12 @@ margin-left: 8px; } #directorylist li.addressbook div.collapsed, #directorylist li.addressbook div.expanded { top: 15px; left: 20px; } #contacts-table .contact td.name { background-position: 6px -1603px; } skins/larry/templates/addressbook.html
@@ -30,7 +30,7 @@ <div id="directorylistbox" class="uibox listbox"> <h2 id="directorylist-header" class="boxtitle"><roundcube:label name="groups" /></h2> <div id="directorylist-content" class="scroller withfooter"> <roundcube:object name="directorylist" id="directorylist" class="listing" /> <roundcube:object name="directorylist" id="directorylist" class="treelist listing" /> </div> <div id="directorylist-footer" class="boxfooter"> <roundcube:button command="group-create" type="link" title="newcontactgroup" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="groupoptions" id="groupoptionslink" type="link" title="moreactions" class="listbutton groupactions" onclick="UI.show_popup('groupoptions');return false" innerClass="inner" content="⚙" />