Backported the treelist refactoring to 0.9
1 files added
19 files modified
| | |
| | | $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']); |
| | |
| | | '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'] |
| | |
| | | | | |
| | | | 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. | |
| | |
| | | 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)) { |
| | |
| | | |
| | | 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) |
| | |
| | | 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) |
| | |
| | | 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; |
| | | } |
| | | if (layerclass != oldclass && this.contact_list && this.contact_list.draglayer) |
| | | this.contact_list.draglayer.attr('class', layerclass); |
| | | return; |
| | | // 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'); |
| | | } |
| | | |
| | | // 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'); |
| | | } |
| | | // 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; |
| | | } |
| | | else { |
| | | // Clear target, otherwise drag end will trigger move into last valid droptarget |
| | | this.env.last_folder_target = null; |
| | | } |
| | | |
| | | if (layerclass != oldclass && this.contact_list && this.contact_list.draglayer) |
| | |
| | | |
| | | 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) |
| | |
| | | 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); |
| | |
| | | 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; |
| | |
| | | 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); |
| | | } |
| | |
| | | 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); |
| | |
| | | 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); |
| | | this.name_input_li.insertAfter(li); |
| | | var ul, li; |
| | | |
| | | // find list (UL) element |
| | | if (type == 'contactsearch') |
| | | ul = this.gui_objects.folderlist; |
| | | else |
| | | ul = $('ul.groups', this.get_folder_li(this.env.source,'',true)); |
| | | |
| | | // append to the list |
| | | li = $('li:last', ul); |
| | | if (li.length) |
| | | this.name_input_li.insertAfter(li); |
| | | else { |
| | | this.name_input_li.appendTo(ul); |
| | | ul.show(); // make sure the list is visible |
| | | } |
| | | } |
| | | |
| | | this.name_input.select().focus(); |
| | |
| | | this.reset_add_input = function() |
| | | { |
| | | if (this.name_input) { |
| | | var li = this.name_input.parent(); |
| | | if (this.env.group_renaming) { |
| | | var li = this.name_input.parent(); |
| | | li.children().last().show(); |
| | | this.env.group_renaming = false; |
| | | } |
| | | else if ($('li', li.parent()).length == 1) |
| | | li.parent().hide(); |
| | | |
| | | this.name_input.remove(); |
| | | |
| | |
| | | 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 |
| | |
| | | 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) { |
| | |
| | | .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; |
| | | |
| | |
| | | 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); |
| | |
| | | } |
| | | |
| | | this.reset_qsearch(); |
| | | this.select_folder('S'+id); |
| | | this.select_folder('S'+id, '', true); |
| | | |
| | | // reset vars |
| | | this.env.current_page = 1; |
New file |
| | |
| | | /* |
| | | +-----------------------------------------------------------------------+ |
| | | | 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), |
| | | data = p.data || [], |
| | | indexbyid = {}, |
| | | selection = null, |
| | | drag_active = false, |
| | | box_coords = {}, |
| | | item_coords = [], |
| | | autoexpand_timer, |
| | | autoexpand_item, |
| | | body_scroll_top = 0, |
| | | list_scroll_top = 0, |
| | | 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; |
| | | this.update = update_node; |
| | | this.insert = insert; |
| | | this.remove = remove; |
| | | this.get_item = get_item; |
| | | this.get_selection = get_selection; |
| | | |
| | | /////// startup code (constructor) |
| | | |
| | | // abort if node not found |
| | | if (!container.length) |
| | | return; |
| | | |
| | | if (p.data) |
| | | index_data({ children:data }); |
| | | // load data from DOM |
| | | else |
| | | update_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); |
| | | } |
| | | |
| | | /** |
| | | * Insert the given node |
| | | */ |
| | | function insert(node, parent_id, sort) |
| | | { |
| | | var li, parent_li, |
| | | parent_node = parent_id ? indexbyid[parent_id] : null; |
| | | |
| | | // insert as child of an existing node |
| | | if (parent_node) { |
| | | if (!parent_node.children) |
| | | parent_node.children = []; |
| | | |
| | | parent_node.children.push(node); |
| | | parent_li = id2dom(parent_id); |
| | | |
| | | // re-render the entire subtree |
| | | if (parent_node.children.length == 1) { |
| | | render_node(parent_node, parent_li.parent(), parent_li); |
| | | li = id2dom(node.id); |
| | | } |
| | | else { |
| | | // append new node to parent's child list |
| | | li = render_node(node, parent_li.children('ul').first()); |
| | | } |
| | | } |
| | | // insert at top level |
| | | else { |
| | | data.push(node); |
| | | li = render_node(node, container); |
| | | } |
| | | |
| | | indexbyid[node.id] = node; |
| | | |
| | | if (sort) { |
| | | resort_node(li, typeof sort == 'string' ? '[class~="' + sort + '"]' : ''); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Update properties of an existing node |
| | | */ |
| | | function update_node(id, updates, sort) |
| | | { |
| | | var li, node = indexbyid[id]; |
| | | |
| | | if (node) { |
| | | li = id2dom(id); |
| | | |
| | | if (updates.id || updates.html || updates.children || updates.classes) { |
| | | $.extend(node, updates); |
| | | render_node(node, li.parent(), li); |
| | | } |
| | | |
| | | if (node.id != id) { |
| | | delete indexbyid[id]; |
| | | indexbyid[node.id] = node; |
| | | } |
| | | |
| | | if (sort) { |
| | | resort_node(li, typeof sort == 'string' ? '[class~="' + sort + '"]' : ''); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Helper method to sort the list of the given item |
| | | */ |
| | | function resort_node(li, filter) |
| | | { |
| | | var first, sibling, |
| | | myid = li.get(0).id, |
| | | sortname = li.children().first().text().toUpperCase(); |
| | | |
| | | li.parent().children('li' + filter).each(function(i, elem) { |
| | | if (i == 0) |
| | | first = elem; |
| | | if (elem.id == myid) { |
| | | // skip |
| | | } |
| | | else if (elem.id != myid && sortname >= $(elem).children().first().text().toUpperCase()) { |
| | | sibling = elem; |
| | | } |
| | | else { |
| | | return false; |
| | | } |
| | | }); |
| | | |
| | | if (sibling) { |
| | | li.insertAfter(sibling); |
| | | } |
| | | else if (first.id != myid) { |
| | | li.insertBefore(first); |
| | | } |
| | | |
| | | // reload data from dom |
| | | update_data(); |
| | | } |
| | | |
| | | /** |
| | | * Remove the item with the given ID |
| | | */ |
| | | function remove(id) |
| | | { |
| | | var node, li; |
| | | |
| | | if (node = indexbyid[id]) { |
| | | li = id2dom(id); |
| | | li.remove(); |
| | | |
| | | node.deleted = true; |
| | | delete indexbyid[id]; |
| | | |
| | | return true; |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | /** |
| | | * (Re-)read tree data from DOM |
| | | */ |
| | | function update_data() |
| | | { |
| | | data = walk_list(container); |
| | | } |
| | | |
| | | /** |
| | | * 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, replace) |
| | | { |
| | | if (node.deleted) |
| | | return; |
| | | |
| | | var li = $('<li>') |
| | | .attr('id', p.id_prefix + (p.id_encode ? p.id_encode(node.id) : node.id)) |
| | | .addClass((node.classes || []).join(' ')); |
| | | |
| | | if (replace) |
| | | replace.replaceWith(li); |
| | | else |
| | | li.appendTo(parent); |
| | | |
| | | if (typeof node.html == 'string') |
| | | li.html(node.html); |
| | | else if (typeof node.html == 'object') |
| | | li.append(node.html); |
| | | |
| | | 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).attr('class', node.childlistclass); |
| | | if (node.collapsed) |
| | | ul.hide(); |
| | | |
| | | for (var i=0; i < node.children.length; i++) { |
| | | render_node(node.children[i], ul); |
| | | } |
| | | } |
| | | |
| | | return li; |
| | | } |
| | | |
| | | /** |
| | | * 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), sublist = li.children('ul'); |
| | | var node = { |
| | | id: dom2id(li), |
| | | classes: li.attr('class').split(' '), |
| | | virtual: li.hasClass('virtual'), |
| | | html: li.children().first().get(0).outerHTML, |
| | | children: walk_list(sublist) |
| | | } |
| | | |
| | | if (sublist.length) { |
| | | node.childlistclass = sublist.attr('class'); |
| | | } |
| | | if (node.children.length) { |
| | | node.collapsed = sublist.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(), |
| | | current_offset = scroller.scrollTop(), |
| | | rel_offset = li.offset().top - scroller.offset().top; |
| | | |
| | | if (rel_offset < 0 || rel_offset + li.height() > scroller.height()) |
| | | scroller.scrollTop(rel_offset + current_offset); |
| | | } |
| | | |
| | | ///// 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; |
| | |
| | | $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')); |
| | |
| | | |
| | | $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'], |
| | |
| | | $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')); |
| | | |
| | |
| | | $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'); |
| | | |
| | |
| | | global $RCMAIL; |
| | | |
| | | $groups = $RCMAIL->get_address_book($args['source'])->list_groups(); |
| | | $js_id = $RCMAIL->JQ($args['source']); |
| | | $groups_html = ''; |
| | | |
| | | 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(); |
| | | 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']) |
| | | ); |
| | |
| | | } |
| | | } |
| | | |
| | | $args['out'] .= html::tag('ul', |
| | | array('class' => 'groups', 'style' => ($is_collapsed || empty($groups) ? "display:none;" : null)), |
| | | $groups_html); |
| | | |
| | | return $args; |
| | | } |
| | | |
| | |
| | | |
| | | #directorylistbox input |
| | | { |
| | | margin: 0px; |
| | | margin: 0 0 0 20px; |
| | | font-size: 11px; |
| | | width: 90%; |
| | | } |
| | |
| | | width: 280px; |
| | | } |
| | | |
| | | #directorylist |
| | | #directorylist, |
| | | #directorylist li ul |
| | | { |
| | | list-style: none; |
| | | margin: 0; |
| | |
| | | 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; |
| | | } |
| | |
| | | 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 |
| | |
| | | background-color: #929292; |
| | | } |
| | | |
| | | ul.treelist li |
| | | { |
| | | position: relative; |
| | | } |
| | | |
| | | ul.treelist li div.treetoggle |
| | | { |
| | | position: absolute; |
| | | left: 8px !important; |
| | | left: -16px; |
| | | top: 1px; |
| | | width: 14px; |
| | | height: 16px; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | ul.treelist li div.collapsed |
| | | { |
| | | background: url(images/icons/collapsed.png) bottom right no-repeat; |
| | | } |
| | | |
| | | ul.treelist li div.expanded |
| | | { |
| | | background: url(images/icons/expanded.png) bottom right no-repeat; |
| | | } |
| | | |
| | | |
| | | /***** mac-style quicksearch field *****/ |
| | | |
| | |
| | | font-size: 11px; |
| | | padding: 0px; |
| | | border: none; |
| | | outline: none; |
| | | } |
| | | |
| | | /***** roundcube webmail pre-defined classes *****/ |
| | |
| | | border-bottom: none; |
| | | } |
| | | |
| | | #mailboxlist li div |
| | | { |
| | | position: absolute; |
| | | left: 8px !important; |
| | | left: -16px; |
| | | top: 1px; |
| | | width: 14px; |
| | | height: 16px; |
| | | } |
| | | |
| | | #mailboxlist li div.collapsed, |
| | | #mailboxlist li div.expanded |
| | | { |
| | | cursor: pointer; |
| | | } |
| | | |
| | | #mailboxlist li div.collapsed |
| | | { |
| | | background: url(images/icons/collapsed.png) bottom right no-repeat; |
| | | } |
| | | |
| | | #mailboxlist li div.expanded |
| | | { |
| | | background: url(images/icons/expanded.png) bottom right no-repeat; |
| | | } |
| | | |
| | | #mailboxlist li.inbox |
| | | { |
| | | background-position: 5px -18px; |
| | |
| | | <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=" " /> |
| | |
| | | <div id="mailboxlist-container"> |
| | | <div id="mailboxlist-title" class="boxtitle"><roundcube:label name="mailboxlist" /></div> |
| | | <div id="mailboxlist-content" class="boxlistcontent"> |
| | | <roundcube:object name="mailboxlist" id="mailboxlist" folder_filter="mail" /> |
| | | <roundcube:object name="mailboxlist" id="mailboxlist" class="treelist" folder_filter="mail" /> |
| | | </div> |
| | | <div id="mailboxlist-footer" class="boxfooter"> |
| | | <roundcube:button name="mailboxmenulink" id="mailboxmenulink" type="link" title="folderactions" class="button groupactions" onclick="rcmail_ui.show_popup('mailboxmenu');return false" content=" " /> |
| | |
| | | <div id="mailboxlist-container"> |
| | | <div id="mailboxlist-title" class="boxtitle"><roundcube:label name="mailboxlist" /></div> |
| | | <div class="boxlistcontent"> |
| | | <roundcube:object name="mailboxlist" id="mailboxlist" maxlength="25" /> |
| | | <roundcube:object name="mailboxlist" id="mailboxlist" class="treelist" maxlength="25" /> |
| | | </div> |
| | | <div class="boxfooter"></div> |
| | | </div> |
| | |
| | | <div id="mailboxlist-container"> |
| | | <div class="boxtitle"><roundcube:label name="mailboxlist" /></div> |
| | | <div class="boxlistcontent"> |
| | | <roundcube:object name="mailboxlist" id="mailboxlist" folder_filter="mail" /> |
| | | <roundcube:object name="mailboxlist" id="mailboxlist" class="treelist" folder_filter="mail" /> |
| | | </div> |
| | | <div class="boxfooter"></div> |
| | | </div> |
| | |
| | | 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 { |
| | |
| | | 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; |
| | | } |
| | |
| | | }); |
| | | |
| | | </script> |
| | | <!--[if lte IE 8]> |
| | | <script type="text/javascript"> |
| | | |
| | | // fix missing :last-child selectors |
| | | $(document).ready(function(){ |
| | | $('ul.treelist ul').each(function(i,ul){ |
| | | $('li:last-child', ul).css('border-bottom', 0); |
| | | }); |
| | | }); |
| | | |
| | | </script> |
| | | <![endif]--> |
| | | |
| | |
| | | background-position: 6px 2px; |
| | | } |
| | | |
| | | #mailboxlist li:first-child { |
| | | #mailboxlist > li:first-child { |
| | | border-radius: 4px 4px 0 0; |
| | | border-top: 0; |
| | | } |
| | |
| | | background-position: 6px 3px; |
| | | } |
| | | |
| | | #mailboxlist li.mailbox.unread a { |
| | | #mailboxlist li.mailbox.unread > a { |
| | | padding-right: 36px; |
| | | } |
| | | |
| | |
| | | color: #017cb4; |
| | | } |
| | | |
| | | #mailboxlist li.mailbox div.treetoggle { |
| | | top: 13px; |
| | | left: 19px; |
| | | } |
| | | |
| | | #mailboxlist li.mailbox ul li:last-child { |
| | | border-bottom: 0; |
| | | } |
| | | |
| | | /* nested mailboxes */ |
| | | |
| | | #mailboxlist li.mailbox ul { |
| | | list-style: none; |
| | | margin: 0; |
| | |
| | | border-top: 1px solid #bbd3da; |
| | | } |
| | | |
| | | #mailboxlist li.mailbox ul li { |
| | | padding-left: 26px; |
| | | } |
| | | |
| | | #mailboxlist li.mailbox ul li a { |
| | | background-position: 6px -93px; |
| | | padding-left: 52px; /* 36 + 1 x 16 */ |
| | | background-position: 22px -93px; /* 6 + 1 x 16 */ |
| | | } |
| | | |
| | | #mailboxlist li.mailbox ul li.selected > a { |
| | | background-position: 6px -117px; |
| | | background-position: 22px -117px; |
| | | } |
| | | |
| | | #mailboxlist li.mailbox ul li:last-child { |
| | | border-bottom: 0; |
| | | } |
| | | |
| | | #mailboxlist li.mailbox div.collapsed, |
| | | #mailboxlist li.mailbox div.expanded { |
| | | position: absolute; |
| | | top: 13px; |
| | | left: 19px; |
| | | width: 13px; |
| | | height: 13px; |
| | | background: url(images/listicons.png) -3px -144px no-repeat; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | #mailboxlist li.mailbox div.expanded { |
| | | background-position: -3px -168px; |
| | | } |
| | | |
| | | #mailboxlist li.mailbox.selected > div.collapsed { |
| | | background-position: -23px -144px; |
| | | } |
| | | |
| | | #mailboxlist li.mailbox.selected > div.expanded { |
| | | background-position: -23px -168px; |
| | | } |
| | | |
| | | |
| | | #mailboxlist li.mailbox ul li div.collapsed, |
| | | #mailboxlist li.mailbox ul li div.expanded { |
| | | left: 43px; |
| | | #mailboxlist li.mailbox ul li div.treetoggle { |
| | | left: 33px; |
| | | top: 14px; |
| | | } |
| | | |
| | | #mailboxlist li.mailbox ul ul li.mailbox a { |
| | | padding-left: 68px; /* 2x */ |
| | | background-position: 38px -93px; |
| | | } |
| | | #mailboxlist li.mailbox ul ul li.selected > a { |
| | | background-position: 38px -117px; |
| | | } |
| | | #mailboxlist li.mailbox ul ul li div.treetoggle { |
| | | left: 48px; |
| | | } |
| | | |
| | | #mailboxlist li.mailbox ul ul ul li.mailbox a { |
| | | padding-left: 84px; /* 3x */ |
| | | background-position: 54px -93px; |
| | | } |
| | | #mailboxlist li.mailbox ul ul ul li.selected > a { |
| | | background-position: 54px -117px; |
| | | } |
| | | #mailboxlist li.mailbox ul ul ul li div.treetoggle { |
| | | left: 64px; |
| | | } |
| | | |
| | | #mailboxlist li.mailbox ul ul ul ul li.mailbox a { |
| | | padding-left: 100px; /* 4x */ |
| | | background-position: 70px -93px; |
| | | } |
| | | #mailboxlist li.mailbox ul ul ul ul li.selected > a { |
| | | background-position: 70px -117px; |
| | | } |
| | | #mailboxlist li.mailbox ul ul ul ul li div.treetoggle { |
| | | left: 80px; |
| | | } |
| | | |
| | | /* indent folders on levels > 4 */ |
| | | #mailboxlist li.mailbox ul ul ul ul ul li { |
| | | padding-left: 16px; |
| | | } |
| | | #mailboxlist li.mailbox ul ul ul ul ul li div.treetoggle { |
| | | left: 96px; |
| | | } |
| | | |
| | | #mailboxlist li.mailbox .unreadcount { |
| | | position: absolute; |
| | | top: 3px; |
| | |
| | | background-color: #d9ecf4; |
| | | } |
| | | |
| | | ul.listing li ul { |
| | | border-top: 1px solid #bbd3da; |
| | | } |
| | | |
| | | ul.listing li.droptarget, |
| | | table.listing tr.droptarget td { |
| | | background-color: #e8e798; |
| | |
| | | vertical-align: top; |
| | | } |
| | | |
| | | ul.treelist li { |
| | | position: relative; |
| | | } |
| | | |
| | | ul.treelist li div.treetoggle { |
| | | position: absolute; |
| | | top: 13px; |
| | | left: 19px; |
| | | width: 13px; |
| | | height: 13px; |
| | | background: url(images/listicons.png) -3px -144px no-repeat; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | ul.treelist li div.treetoggle.expanded { |
| | | background-position: -3px -168px; |
| | | } |
| | | |
| | | ul.treelist li.selected > div.collapsed { |
| | | background-position: -23px -144px; |
| | | } |
| | | |
| | | ul.treelist li.selected > div.expanded { |
| | | background-position: -23px -168px; |
| | | } |
| | | |
| | | .listbox .boxfooter { |
| | | position: absolute; |
| | | bottom: 0; |
| | |
| | | <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="⚙" /> |
| | |
| | | <div id="folderlist-header"></div> |
| | | <div id="mailboxcontainer" class="uibox listbox"> |
| | | <div id="folderlist-content" class="scroller withfooter"> |
| | | <roundcube:object name="mailboxlist" id="mailboxlist" class="listing" folder_filter="mail" unreadwrap="%s" /> |
| | | <roundcube:object name="mailboxlist" id="mailboxlist" class="treelist listing" folder_filter="mail" unreadwrap="%s" /> |
| | | </div> |
| | | <div id="folderlist-footer" class="boxfooter"> |
| | | <roundcube:button name="mailboxmenulink" id="mailboxmenulink" type="link" title="folderactions" class="listbutton groupactions" onclick="UI.show_popup('mailboxmenu');return false" innerClass="inner" content="⚙" /> |
| | |
| | | <!-- folders list --> |
| | | <div id="mailboxcontainer" class="uibox listbox"> |
| | | <div class="scroller"> |
| | | <roundcube:object name="mailboxlist" id="mailboxlist" class="listing" folder_filter="mail" unreadwrap="%s" /> |
| | | <roundcube:object name="mailboxlist" id="mailboxlist" class="treelist listing" folder_filter="mail" unreadwrap="%s" /> |
| | | </div> |
| | | </div> |
| | | |
| | |
| | | <!-- folders list --> |
| | | <div id="mailboxcontainer" class="uibox listbox"> |
| | | <div class="scroller"> |
| | | <roundcube:object name="mailboxlist" id="mailboxlist" class="listing" folder_filter="mail" unreadwrap="%s" /> |
| | | <roundcube:object name="mailboxlist" id="mailboxlist" class="treelist listing" folder_filter="mail" unreadwrap="%s" /> |
| | | </div> |
| | | </div> |
| | | |
| | |
| | | /** |
| | | * Roundcube functions for default skin interface |
| | | * |
| | | * Copyright (c) 2013, The Roundcube Dev Team |
| | | * Copyright (c) 2011, The Roundcube Dev Team |
| | | * |
| | | * The contents are subject to the Creative Commons Attribution-ShareAlike |
| | | * License. It is allowed to copy, distribute, transmit and to adapt the work |
| | |
| | | this.delay = 500; |
| | | |
| | | this.top |
| | | .mouseenter(function() { if (rcmail.drag_active) ref.ts = window.setTimeout(function() { ref.scroll('down'); }, ref.delay); }) |
| | | .mouseenter(function() { ref.ts = window.setTimeout(function() { ref.scroll('down'); }, ref.delay); }) |
| | | .mouseout(function() { if (ref.ts) window.clearTimeout(ref.ts); }); |
| | | |
| | | this.bottom |
| | | .mouseenter(function() { if (rcmail.drag_active) ref.ts = window.setTimeout(function() { ref.scroll('up'); }, ref.delay); }) |
| | | .mouseenter(function() { ref.ts = window.setTimeout(function() { ref.scroll('up'); }, ref.delay); }) |
| | | .mouseout(function() { if (ref.ts) window.clearTimeout(ref.ts); }); |
| | | |
| | | this.scroll = function(dir) |