diff plugins/contextmenu/contextmenu.js @ 0:1e000243b222

vanilla 1.3.3 distro, I hope
author Charlie Root
date Thu, 04 Jan 2018 15:50:29 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/plugins/contextmenu/contextmenu.js	Thu Jan 04 15:50:29 2018 -0500
@@ -0,0 +1,1027 @@
+/**
+ * ContextMenu plugin script
+ *
+ * @licstart  The following is the entire license notice for the
+ * JavaScript code in this file.
+ *
+ * Copyright (C) 2009-2014 Philip Weir
+ *
+ * The JavaScript code in this page is free software: you can redistribute it
+ * and/or modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, either version 3 of
+ * the License, or (at your option) any later version.
+ *
+ * @licend  The above is the entire license notice
+ * for the JavaScript code in this file.
+ */
+
+rcube_webmail.prototype.context_menu_skip_commands = new Array('mail-checkmail', 'mail-compose', 'addressbook-add', 'addressbook-import', 'addressbook-advanced-search', 'addressbook-search-create');
+rcube_webmail.prototype.context_menu_overload_commands = new Array('move', 'copy');
+rcube_webmail.prototype.context_menu_commands = new Array();
+rcube_webmail.prototype.context_menu_popup_menus = new Array();
+rcube_webmail.prototype.context_menu_popup_commands = {};
+
+rcube_webmail.prototype.context_menu_command_pattern = /rcmail\.command\(\'([^\']+)\',\s?\'((?:\\\'|[^\'])*)\'/;
+
+function rcm_listmenu_init(row, props, events) {
+	if (!events)
+		events = {};
+
+	var menu = rcm_callbackmenu_init(props, $.extend({
+		'beforeactivate': function(p) {
+			rcmail.env.contextmenu_selection = p.ref.list_selection(true);
+		},
+		'afteractivate': function(p) {
+			p.ref.menu_selection = p.ref.list_object.get_selection();
+			p.ref.list_selection(false, rcmail.env.contextmenu_selection);
+		}
+	}, events));
+
+	var list_object = props.list_object ? props.list_object : rcmail.message_list;
+	$("#" + row).on("contextmenu", function(e) {
+		if (uid = list_object.get_row_uid(this)) {
+			rcm_show_menu(e, this, uid, menu);
+		}
+	});
+}
+
+function rcm_foldermenu_init(el, props, events) {
+	if (!events)
+		events = {};
+
+	var menu = rcm_callbackmenu_init($.extend({'menu_name': 'folderlist', 'list_object': null}, props), $.extend({
+		'beforeactivate': function(p) {
+			if (rcmail.env.contextmenu_messagecount_request) {
+				rcmail.env.contextmenu_messagecount_request.abort();
+			}
+			rcmail.env.contextmenu_messagecount_request = null;
+		},
+		'activate': function(p) {
+			if ($.inArray(p.command, Array('expunge', 'purge', 'mark-all-read')) >= 0) {
+				// disable the commands by default
+				$(p.el).addClass('disabled').removeClass('active');
+
+				// if menu is opened on current folder (or special mark-all-read command) then enable the commands same as in UI
+				if ((rcmail.env.context_menu_source_id == rcmail.env.mailbox || p.command == 'mark-all-read') && rcm_check_button_state(p.btn, true)) {
+					$(p.el).addClass('active').removeClass('disabled');
+				}
+				// if menu is opened on difference folder then get message count for the folder
+				else if (rcmail.env.context_menu_source_id != rcmail.env.mailbox && !rcmail.env.contextmenu_messagecount_request) {
+					// folder check called async to prevent slowdown on menu load
+					rcmail.env.contextmenu_messagecount_request = $.ajax({
+						type: 'POST', url: rcmail.url('plugin.contextmenu.messagecount'), data: {'_mbox': rcmail.env.context_menu_source_id}, dataType: 'json', async: true,
+						success: function(data) {
+							if (data.messagecount > 0 && $('#rcm_folderlist').is(':visible')) {
+								// override the environment to check if commands should be abled
+								var temp_exists = rcmail.env.exists;
+								var temp_mailbox = rcmail.env.mailbox;
+								rcmail.env.exists = data.messagecount;
+								rcmail.env.mailbox = rcmail.env.context_menu_source_id;
+
+								$('#rcm_folderlist').find('a.cmd_expunge').addClass('active').removeClass('disabled');
+								if (rcmail.purge_mailbox_test()) {
+									$('#rcm_folderlist').find('a.cmd_purge').addClass('active').removeClass('disabled');
+								}
+
+								rcmail.env.exists = temp_exists;
+								rcmail.env.mailbox = temp_mailbox;
+							}
+						}
+					});
+				}
+			}
+		},
+		'beforecommand': function(p) {
+			if (rcmail.env.context_menu_source_id != rcmail.env.mailbox && $.inArray(p.command, Array('expunge', 'purge')) >= 0) {
+				var result = rcmail[p.command + '_mailbox'](rcmail.env.context_menu_source_id);
+
+				// update the unread count and trash icon
+				if (p.command == 'purge' && result !== false) {
+					rcmail.set_unread_count(rcmail.env.context_menu_source_id, 0, false);
+
+					if (rcmail.env.context_menu_source_id == rcmail.env.trash_mailbox)
+						rcmail.set_trash_count(0);
+				}
+
+				return {'abort': true, 'result': true};
+			}
+			else if (rcmail.env.context_menu_source_id != rcmail.env.mailbox && p.command == 'mark-all-read') {
+				rcmail.mark_all_read(rcmail.env.context_menu_source_id);
+				return {'abort': true, 'result': true};
+			}
+		}
+	}, events));
+
+	$(el).click(function(e) {
+		// hide menu when changing folder
+		menu.hide(e);
+	})
+	.on("contextmenu", function(e) {
+		var source = $(this).children('a');
+
+		// remove focus (and keyboard nav highlighting) from A
+		source.blur();
+
+		if (source.attr('rel') && source.attr('onclick') && source.attr('onclick').match(rcmail.context_menu_command_pattern)) {
+			rcm_show_menu(e, this, source.attr('rel'), menu);
+		}
+	});
+}
+
+function rcm_abookmenu_init(el, props, events) {
+	if (!events)
+		events = {};
+
+	var menu = rcm_callbackmenu_init($.extend({'menu_name': 'abooklist'}, props), $.extend({
+		'beforeactivate': function(p) {
+			p.ref.container.find('li.submenu').remove();
+		},
+		'activate': function(p) {
+			var ids = rcmail.env.context_menu_source_id.split(':', 2);
+			cur_source = ids[0];
+
+			if (p.command == 'group-create') {
+				// addressbook
+				if ($(p.source).hasClass('addressbook') && rcmail.env.address_sources[cur_source].groups && !rcmail.env.address_sources[cur_source].readonly) {
+					$(p.el).addClass('active').removeClass('disabled');
+				}
+				else {
+					$(p.el).addClass('disabled').removeClass('active');
+				}
+			}
+			else if (p.command == 'group-rename' || p.command == 'group-delete') {
+				// group
+				if ($(p.source).hasClass('contactgroup') && !rcmail.env.address_sources[cur_source].readonly) {
+					$(p.el).addClass('active').removeClass('disabled');
+				}
+				else {
+					$(p.el).addClass('disabled').removeClass('active');
+				}
+			}
+			else if (p.command == 'search-delete') {
+				// saved search
+				if ($(p.source).hasClass('contactsearch')) {
+					$(p.el).addClass('active').removeClass('disabled');
+				}
+				else {
+					$(p.el).addClass('disabled').removeClass('active');
+				}
+			}
+		},
+		'command': function(p) {
+			if (!$(p.el).hasClass('active'))
+				return;
+
+			var prev_source = rcmail.env.source;
+			var prev_group = rcmail.env.group;
+			var result = false;
+
+			var ids = rcmail.env.context_menu_source_id.split(':', 2);
+			cur_source = ids[0];
+			cur_id = ids[1];
+
+			rcmail.env.source = cur_source;
+			rcmail.env.group = cur_id;
+
+			// enable the required command
+			var prev_command = rcmail.commands[p.command];
+			rcmail.enable_command(p.command, true);
+
+			switch (p.command) {
+				case 'search-delete':
+					var result = false;
+
+					if ($(p.ref.selected_object).children('a').attr('rel')) {
+						var prev_search_id = rcmail.env.search_id;
+						var prev_search_request = rcmail.env.search_request;
+						rcmail.env.search_request = true;
+						rcmail.env.search_id = $(p.ref.selected_object).children('a').attr('rel').replace('S', '');
+
+						result = rcmail.command(p.command, p.args, p.el, p.evt);
+
+						rcmail.env.search_request = prev_search_request;
+						rcmail.env.search_id = prev_search_id;
+					}
+					break;
+				default:
+					result = rcmail.command(p.command, p.args, p.el, p.evt);
+					break;
+			}
+
+			rcmail.enable_command(p.command, prev_command);
+			rcmail.env.source = prev_source;
+			rcmail.env.group = prev_group;
+
+			return result;
+		}
+	}, events));
+
+	$(el).click(function(e) {
+		// hide menu when changing address book
+		menu.hide(e);
+	})
+	.on("contextmenu",function(e) {
+		var source = $(this).children('a');
+
+		// remove focus (and keyboard nav highlighting) from A
+		source.blur();
+
+		if (source.attr('rel') && source.attr('rel').match(/([A-Z0-9\-_]+(:[A-Z0-9\-_]+)?)/i)) {
+			rcm_show_menu(e, this, RegExp.$1, menu);
+		}
+	});
+}
+
+function rcm_callbackmenu_init(props, events) {
+	var std_events = {
+		'command': function(p) {
+			if (!$(p.el).hasClass('active'))
+				return;
+
+			if (p.ref.list_object) {
+				var prev_display_next = rcmail.env.display_next;
+
+				if (!(p.ref.list_object.selection.length == 1 && p.ref.list_object.in_selection(rcmail.env.context_menu_source_id)))
+					rcmail.env.display_next = false;
+
+				var prev_sel = p.ref.list_selection(true);
+			}
+
+			// enable the required command
+			var prev_command = rcmail.commands[p.command];
+			rcmail.enable_command(p.command, true);
+			var result = rcmail.command(p.command, p.args, p.el, p.evt);
+			rcmail.enable_command(p.command, prev_command);
+
+			if (p.ref.list_object) {
+				p.ref.list_selection(false, prev_sel);
+				rcmail.env.display_next = prev_display_next;
+			}
+
+			if ($.inArray(p.command, rcmail.context_menu_overload_commands) >= 0) {
+				rcmail.context_menu_commands[p.command] = rcmail.commands[p.command];
+				rcmail.enable_command(p.command, true);
+			}
+
+			return result;
+		},
+		'activate': function(p) {
+			$(p.el).addClass(p.enabled ? 'active' : 'disabled');
+		}
+	}
+
+	if (events)
+		$.extend(std_events, events);
+
+	if (!rcmail.env.contextmenus[props.menu_name]) {
+		var menu = new rcube_context_menu(props);
+		$.each(std_events, function(trigger, func) {
+			menu.addEventListener(trigger, function(p) { return func(p); });
+		});
+		menu.init();
+		rcmail.env.contextmenus[props.menu_name] = menu;
+	}
+	else {
+		var menu = rcmail.env.contextmenus[props.menu_name];
+	}
+
+	return menu;
+}
+
+function rcm_show_menu(e, obj, id, menu) {
+	// if contextmenus have been disabled then show browser context menu as normal
+	if (!rcmail.env.contextmenu)
+		return true;
+
+	e.preventDefault();
+	e.cancelBubble = true;
+	if (e.stopPropagation)
+		e.stopPropagation();
+
+	// hide any other open menus
+	for (var i = 0; i < rcmail.menu_stack.length; i++) {
+		rcmail.hide_menu(rcmail.menu_stack[i], e);
+	}
+
+	rcmail.env.context_menu_source_id = id;
+	menu.show(obj, e);
+}
+
+function rcm_hide_menu(e, sub_only) {
+	$.each($(sub_only ? '.rcmsubmenu' : 'div.contextmenu'), function() {
+		if ($(this).is(':visible')) {
+			$(this).hide();
+			rcmail.triggerEvent('menu-close', { name: $(this).attr('id'), props:{ menu: $(this).attr('id') }, originalEvent: e });
+		}
+	});
+
+	// close popup menus opened by the contextmenu
+	for (var i = rcmail.context_menu_popup_menus.length - 1; i >= 0; i--) {
+		rcmail.hide_menu(rcmail.context_menu_popup_menus[i], e);
+		rcmail.context_menu_popup_menus.pop();
+	}
+}
+
+function rcube_context_menu(p) {
+	this.menu_name = null;
+	this.menu_source = null;
+	this.list_object = rcmail.message_list;
+	this.source_class = 'contextRow';
+	this.mouseover_timeout = 400;
+
+	this.is_submenu = false;
+	this.parent_menu = this;
+	this.parent_object = null;
+	this.selected_object = null
+	this.container = null;
+	this.original_selection = new Array();
+	this.menu_selection = new Array();
+	this.submenus = new Array();
+	this.timers = new Array();
+
+	// overwrite default parameters
+	if (p && typeof p === 'object')
+		for (var n in p)
+			this[n] = p[n];
+
+	var ref = this;
+
+	this.init = function() {
+		if (!this.container) {
+			rcmail.triggerEvent('contextmenu_init', this);
+
+			this.container = $('<div id="rcm_'+ this.menu_name +'" style="display: none;"></div>');
+			this.container.addClass('contextmenu popupmenu');
+			this.container.addClass(this.is_submenu ? 'rcmsubmenu' : 'rcmmainmenu');
+
+			var rows = [], ul = $('<ul role="menu">'),
+			li = $('<li>'),	link = $('<a>'), span = $('<span>');
+
+			ul.addClass('toolbarmenu iconized');
+			li.attr('role', 'menuitem');
+
+			link.attr('href', '#');
+			link.addClass('icon active');
+			link.attr('role', 'button');
+			link.attr('tabindex', '-1');
+			link.attr('aria-disabled', 'true');
+
+			span.addClass('icon');
+
+			// loop over possible menu elements and build settings object
+			sources = typeof this.menu_source == 'string' ? [this.menu_source] : this.menu_source;
+			this.menu_source = {}
+			$.each(sources, function(i) {
+				var source_elements;
+				if (typeof sources[i] == 'string') {
+					ref.menu_source[sources[i]] = {
+						'toggle': !$(sources[i]).is(':visible')
+					};
+					source_elements = $(sources[i]).children();
+				}
+				else {
+					ref.menu_source[i] = {
+						'toggle': false
+					};
+					source_elements = $(sources[i]);
+				}
+
+				ul.attr('aria-labelledby', $(sources[i]).attr('aria-labelledby'));
+
+				$.each(source_elements, function() {
+					var elem, command, args;
+
+					if ($(this).is('a')) {
+						elem = $(this).clone();
+					}
+					else if ($(this).is('span') && $(this).children().length == 2) {
+						elem = $(this).children(':first').clone();
+
+						if ($(this).children(':last').attr('onclick').match(rcmail.context_menu_popup_pattern)) {
+							$(elem).attr('onclick', $(this).children(':last').attr('onclick'));
+						}
+					}
+					else if ($(this).is('li') && $(this).children('a').length == 1) {
+						elem = $(this).children('a').clone();
+
+						if (!elem.attr('onclick') || !elem.attr('onclick').match(rcmail.context_menu_command_pattern))
+							return;
+					}
+					else if ($(this).parent().is('a')) {
+						elem = $(this).parent().clone();
+					}
+					else if (this.command && this.label) {
+						elem = $('<a>').attr('href', '#')
+								.attr('id', 'rcmjs')
+								.attr('onclick', "return rcmail.command('"+ this.command +"','"+ this.props +"',this,event)")
+								.addClass(this.classes)
+								.html(this.label);
+					}
+					else {
+						return;
+					}
+
+					// skip any element that does not look like a Roundcube button
+					if (!elem.attr('onclick')) {
+						return;
+					}
+
+					if (elem.attr('onclick').match(rcmail.context_menu_command_pattern)) {
+						command = RegExp.$1;
+						args = RegExp.$2;
+					}
+
+					// skip elements we don't need
+					if ($.inArray(rcmail.env.task + '-' + command, rcmail.context_menu_skip_commands) > -1 || elem.hasClass('rcm_ignore')) {
+						return;
+					}
+
+					var a = link.clone(), row = li.clone();
+
+					// add command name element
+					tmp = span.clone();
+					tmp.text($.trim(elem.text()).length > 0 ? $.trim(elem.text()) : elem.attr('title'));
+					tmp.addClass(elem.children('span').attr('class'));
+					a.append(tmp);
+					a.addClass(elem.attr('class'));
+					a.removeClass('button').removeClass('disabled');
+					a.addClass('rcm_elem_' + elem.attr('id'));
+
+					if (elem.attr('onclick').match(rcmail.context_menu_popup_pattern)) {
+						a.data('command', RegExp.$1);
+						a.append($('<span>').addClass('right-arrow'));
+						row.addClass('submenu');
+						a.click(function(e) {
+							if (!$(this).hasClass('active'))
+								return;
+
+							ref.submenu(a, e);
+							return false;
+						});
+
+						if (ref.mouseover_timeout > -1) {
+							a.mouseover(function(e) {
+								if (!$(this).hasClass('active'))
+									return;
+
+								ref.timers['submenu_show'] = window.setTimeout(function(a, e) {
+									ref.submenu(a, e);
+								}, ref.mouseover_timeout, a, e);
+							});
+
+							a.mouseout(function(e) {
+								if (!$(this).hasClass('active'))
+									return;
+
+								$(this).blur(); clearTimeout(ref.timers['submenu_show']);
+							});
+						}
+					}
+					else {
+						a.addClass('cmd_' + command);
+						a.data('command', command);
+						if (elem.attr('target'))
+							a.attr('target', elem.attr('target'));
+
+						a.click(function(e) {
+							if ($(this).parents('.rcmsubmenu').length == 0) {
+								rcm_hide_menu(e, true);
+								clearTimeout(ref.timers['submenu_hide']);
+							}
+
+							var cur_popups = rcmail.menu_stack.length;
+							var result;
+
+							var callback = ref.parent_menu.triggerEvent('beforecommand', {ref: ref, el: this, command: command, args: args});
+							if (!callback || !callback.abort) {
+								result = ref.parent_menu.triggerEvent('command', {ref: ref, el: this, command: command, args: args, evt: e});
+							}
+							else {
+								result = callback.result;
+							}
+
+							if (!callback || !callback.skipaftercommand)
+								ref.parent_menu.triggerEvent('aftercommand', {ref: ref, el: this, command: command, args: args});
+
+							if (rcmail.menu_stack.length > cur_popups) {
+								var popup_name = rcmail.menu_stack[rcmail.menu_stack.length - 1];
+								rcmail.context_menu_popup_menus.push(popup_name);
+
+								// make sure enabled commands match context menu message selection
+								$.each(rcmail.context_menu_popup_commands[popup_name], function(cmd, state) {
+									rcmail.enable_command(cmd, state);
+								});
+							}
+
+							// ensure menu is always hidden after action (for Safari)
+							ref.hide(e);
+
+							return result;
+						});
+
+						if (ref.mouseover_timeout > -1 && !ref.is_submenu) {
+							a.mouseover(function(e) {
+								ref.timers['submenu_hide'] = window.setTimeout(function(e) {
+									rcm_hide_menu(e, true);
+								}, ref.mouseover_timeout, e);
+							});
+
+							a.mouseout(function(e) { clearTimeout(ref.timers['submenu_hide']); });
+						}
+					}
+
+					row.append(a);
+					ref.parent_menu.triggerEvent('insertitem', {item: row});
+					rows.push(row);
+				});
+			});
+
+			ul.append(rows).appendTo(this.container);
+			this.parent_menu.triggerEvent('init', {ref: this});
+			this.container.appendTo($('body'));
+		}
+	};
+
+	this.show = function(obj, e) {
+		if (obj) {
+			this.hide(e);
+		}
+
+		var callback = this.parent_menu.triggerEvent('beforeactivate', {ref: this, source: obj});
+		if (!callback || !callback.abort) {
+			if (obj) {
+				$(obj).addClass(this.source_class);
+			}
+
+			$.each(ref.menu_source, function(id, props) {
+				if (props.toggle) {
+					$(id).parent().show();
+				}
+			});
+
+			$.each(this.container.find('a'), function() {
+				if ($(this).hasClass('rcm_active')) {
+					$(this).addClass('active');
+				}
+				else if (btn = $(this).attr('class').match(/rcm_elem_([a-z0-9]+)/)) {
+					$(this).parent('li')[(btn[1] == 'rcmjs' || $('#' + btn[1]).is(':visible')) ? 'show' : 'hide']();
+					$(this).removeClass('active').removeClass('disabled');
+
+					var enabled = false;
+					if (!rcm_check_button_state(btn[1], false) && (!ref.is_submenu || rcm_check_button_state(btn[1], true))) {
+						enabled = true;
+					}
+
+					var ret = ref.parent_menu.triggerEvent('activate', {el: this, btn: btn[1], source: obj, command: $(this).data('command'), enabled: enabled});
+					if (ret === true) {
+						$(this).addClass('active').removeClass('disabled');
+					}
+					else if (ret === false) {
+						$(this).addClass('disabled').removeClass('active');
+					}
+				}
+			});
+
+			$.each(ref.menu_source, function(id, props) {
+				if (props.toggle) {
+					$(id).parent().hide();
+				}
+			});
+
+			this.parent_menu.triggerEvent('afteractivate', {ref: this, source: obj});
+		}
+
+		// position menu on the screen
+		if (this.is_submenu) {
+			rcmail.element_position(this.container, this.parent_object);
+		}
+		else {
+			this.position(e, this.container);
+		}
+
+		if (!callback || callback.show !== false) {
+			this.selected_object = obj;
+			this.container.show();
+			rcmail.triggerEvent('menu-open', { name: this.container.attr('id'), props:{ menu: this.container.attr('id') }, originalEvent: e });
+		}
+	};
+
+	this.hide = function(e) {
+		// use window.event when e is not defined (legacy support for IE8)
+		var target = e ? e.target : window.event.srcElement;
+
+		if ($('div.contextmenu').is(':visible') && (rcmail.context_menu_popup_menus.length == 0 || $(target).parents('div.contextmenu').length == 0)) {
+			this.selected_object = null;
+			$('.' + this.source_class).removeClass(this.source_class);
+			rcm_hide_menu(e);
+
+			for (var i in rcmail.context_menu_commands) {
+				if (!rcmail.context_menu_commands[i]) {
+					rcmail.enable_command(i, false);
+				}
+			}
+
+			rcmail.context_menu_commands = new Array();
+		}
+	};
+
+	this.submenu = function(link, e) {
+		// use window.event when e is not defined (legacy support for IE8)
+		if (!e)
+			e = window.event;
+
+		if (e) {
+			e.cancelBubble = true;
+			if (e.stopPropagation)
+				e.stopPropagation();
+		}
+
+		rcm_hide_menu(e, true);
+
+		var id = rcmail.gui_containers[$(link).data('command')] ? rcmail.gui_containers[$(link).data('command')].attr('id') : $(link).data('command');
+		if (!this.submenus[id]) {
+			var elem = !$('#' + id).is('ul') ? '#' + id + ' ul' : '#' + id; // check if the container returned is a ul else there should be one directly beneath it
+			this.submenus[id] = new rcube_context_menu({'menu_name': id, 'menu_source': elem, 'parent_menu': this, 'parent_object': link, 'is_submenu': true, 'list_object': this.list_object});
+			this.submenus[id].init();
+		}
+
+		this.submenus[id].show(null, e);
+	};
+
+	this.position = function(e, menu) {
+		// temporarily show element to calculate its size
+		menu.css({left: '-1000px', top: '-1000px'}).show();
+
+		var win = $(window),
+		win_height = win.height(),
+		elem_height = $(menu).height(),
+		elem_width = $(menu).width(),
+		top = e.pageY,
+		left = e.pageX;
+
+		if (top + elem_height > win_height) {
+			top -= elem_height;
+
+			if (top < 0)
+				top = Math.max(0, (win_height - elem_height) / 2);
+		}
+
+		if (left + elem_width > win.width())
+			left -= elem_width;
+
+		menu.hide();
+		menu.css({left: left + 'px', top: top + 'px'});
+	};
+
+	this.list_selection = function(show, prev_sel) {
+		// make the system think no preview pane exists while we do some fake message selects
+		// to enable/disable relevent commands for current selection
+		var prev_contentframe = rcmail.env.contentframe;
+		rcmail.env.contentframe = null;
+
+		if (show) {
+			if (this.list_object.selection.length == 0 || !this.list_object.in_selection(rcmail.env.context_menu_source_id)) {
+				prev_sel = this.list_object.get_selection();
+				this.list_object.highlight_row(rcmail.env.context_menu_source_id, true);
+
+				for (var i in prev_sel)
+					this.list_object.highlight_row(prev_sel[i], true);
+
+				this.list_object.triggerEvent('select');
+			}
+			else {
+				// trigger a select event to update active commands
+				// use case: select multiple message, open contextmenu; open contextmenu on a message not in selection; open contextmenu on selection
+				this.list_object.triggerEvent('select');
+			}
+		}
+		else if (prev_sel) {
+			for (var i in prev_sel)
+				this.list_object.highlight_row(prev_sel[i], true);
+
+			this.list_object.highlight_row(rcmail.env.context_menu_source_id, true);
+			this.list_object.triggerEvent('select');
+		}
+
+		rcmail.env.contentframe = prev_contentframe;
+
+		return prev_sel;
+	};
+
+	this.addEventListener = rcube_event_engine.prototype.addEventListener;
+	this.removeEventListener = rcube_event_engine.prototype.removeEventListener;
+	this.triggerEvent = rcube_event_engine.prototype.triggerEvent;
+};
+
+function rcm_override_mailbox_command(menu, props, before) {
+	if ($('div.contextmenu').is(':visible') && $.inArray(props.action, rcmail.context_menu_overload_commands) >= 0) {
+		if (before) {
+			rcmail.env.context_menu_prev_display_next = rcmail.env.display_next;
+			if (!(menu.list_object.selection.length == 1 && menu.list_object.in_selection(rcmail.env.context_menu_source_id)))
+				rcmail.env.display_next = false;
+
+			rcmail.env.context_menu_prev_sel = menu.list_selection(true);
+		}
+		else if (rcmail.env.context_menu_prev_sel) {
+			menu.list_selection(false, rcmail.env.context_menu_prev_sel);
+			rcmail.env.display_next = rcmail.env.context_menu_prev_display_next;
+		}
+	}
+}
+
+function rcm_check_button_state(btn, active) {
+	var classes = active ? rcmail.context_menu_button_active_class : rcmail.context_menu_button_disabled_class;
+	var found = false;
+
+	$.each(classes, function(i) {
+		if ($('#' + btn).hasClass(classes[i])) {
+			found = true;
+
+			// stop processing
+			return false;
+		}
+	});
+
+	return found;
+}
+
+function rcm_addressbook_selector(event, command, callback) {
+	var container = rcmail.rcm_addressbook_selector_element;
+
+	if (!container) {
+		var rows = [],
+			ul = $('<ul class="toolbarmenu">');
+
+		container = $('<div id="addressbook-selector" class="popupmenu"></div>');
+
+		// loop over address books
+		$.each(rcmail.env.address_sources, function() {
+			if (!this.readonly) {
+				rows.push(rcm_addressbook_selector_item(this));
+
+				if (this.groups) {
+					var ref = this;
+					$.each(rcmail.env.contactgroups, function() {
+						rows.push(rcm_addressbook_selector_item(this, ref.id));
+					});
+				}
+			}
+		});
+
+		ul.append(rows).appendTo(container);
+
+		// temporarily show element to calculate its size
+		container.css({left: '-1000px', top: '-1000px'})
+			.appendTo($('body')).show();
+
+		// set max-height if the list is long
+		if (rows.length > 10)
+			container.css('max-height', $('li', container)[0].offsetHeight * 10 + 9);
+
+		// register delegate event handler for folder item clicks
+		container.on('click', 'a.active', function(e) {
+			container.data('callback')(this, container.data('command'), e);
+			return false;
+		});
+
+		rcmail.rcm_addressbook_selector_element = container;
+	}
+
+	container.data('command', command);
+	container.data('callback', callback);
+
+	// customize menu for move or copy
+	container.find('li').show();
+
+	// search result may contain contacts from many sources, but if there is only one...
+	var source = rcmail.env.source;
+	if (source == '' && rcmail.env.selection_sources.length == 1)
+		source = rcmail.env.selection_sources[0];
+
+	// hide currently open address book from menu
+	if (source) {
+		$.each(container.find('a'), function() {
+			if (($(this).data('source') && $(this).data('source') == source) || $(this).data('id') == source)
+				$(this).parent('li').hide();
+		});
+	}
+
+	// position menu on the screen
+	rcmail.show_menu('addressbook-selector', true, event);
+}
+
+function rcm_group_selector(event, command, callback) {
+	var container = rcmail.rcm_addressgroup_selector_element;
+
+	if (!container) {
+		var rows = [],
+			ul = $('<ul class="toolbarmenu">');
+
+		container = $('<div id="addressgroup-selector" class="popupmenu"></div>');
+
+		// loop over address books
+		$.each(rcmail.env.address_sources, function() {
+			if (this.id === rcmail.env.source) {
+				var ref = this;
+				$.each(rcmail.env.contactgroups, function() {
+					rows.push(rcm_addressbook_selector_item(this, ref.id));
+				});
+			}
+		});
+
+		ul.append(rows).appendTo(container);
+
+		// remove indent added by rcm_addressbook_selector_item()
+		$(ul).find('a').removeAttr('style');
+
+		// temporarily show element to calculate its size
+		container.css({left: '-1000px', top: '-1000px'})
+			.appendTo($('body')).show();
+
+		// set max-height if the list is long
+		if (rows.length > 10)
+			container.css('max-height', $('li', container)[0].offsetHeight * 10 + 9);
+
+		// register delegate event handler for folder item clicks
+		container.on('click', 'a.active', {cmd: command}, function(e) {
+			container.data('callback')(this, e);
+			return false;
+		});
+
+		rcmail.rcm_addressgroup_selector_element = container;
+	}
+
+	container.data('callback', callback);
+
+	// customize menu for move or copy
+	container.find('li').show();
+
+	// hide currently open group from menu
+	if (rcmail.env.group) {
+		$.each(container.find('a'), function() {
+			if ($(this).data('id') == rcmail.env.group)
+				$(this).parent('li').hide();
+		});
+	}
+
+	// position menu on the screen
+	rcmail.show_menu('addressgroup-selector', true, event);
+}
+
+function rcm_addressbook_selector_item(obj, abook_id) {
+	if (abook_id && abook_id === obj.source || !abook_id) {
+		var a = $('<a>').attr('href', '#').addClass('icon'),
+			row = $('<li>');
+
+		if (obj.type == 'group') {
+			a.addClass('active contactgroup')
+			a.data('source', obj.source);
+			a.data('id', obj.id);
+			a.css('padding-left', '16px');
+		}
+		else {
+			a.addClass('addressbook active').data('id', obj.id);
+		}
+
+		// add address book name element
+		a.append($('<span>').text(obj.name));
+
+		return row.append(a);
+	}
+}
+
+$(document).ready(function() {
+	if (window.rcmail) {
+		rcmail.env.contextmenus = {};
+
+		rcmail.addEventListener('init', function() {
+			// no need to reattach events inside iframe
+			if (rcmail.is_framed())
+				return;
+
+			var body_mouseup = function(e) { $.each(rcmail.env.contextmenus, function() { this.hide(e); }); };
+			$(document.body).on('click contextmenu', body_mouseup);
+
+			// Hide menu after clicks in iframes (eg. preview pane)
+			$('iframe').on('load', function(e) {
+				try { $(this.contentDocument || this.contentWindow).on('mouseup', body_mouseup) }
+				catch (e) { /* catch possible "Permission denied" error in IE */ }
+			})
+			.contents().on('mouseup', body_mouseup);
+		});
+
+		if ((rcmail.env.task == 'mail' || rcmail.env.task == 'addressbook') && rcmail.env.action == '') {
+			// special handeling for move/copy functions (folder/address book selector)
+			rcmail.addEventListener('actionbefore', function(props) {
+				var menu = rcmail.env.task == 'addressbook' ? rcmail.env.contextmenus['contactlist'] : rcmail.env.contextmenus['messagelist'];
+				rcm_override_mailbox_command(menu, props, true);
+			});
+
+			rcmail.addEventListener('actionafter', function(props) {
+				var menu = rcmail.env.task == 'addressbook' ? rcmail.env.contextmenus['contactlist'] : rcmail.env.contextmenus['messagelist'];
+				rcm_override_mailbox_command(menu, props, false);
+			});
+		}
+
+		if (rcmail.env.task == 'mail' && rcmail.env.action == '') {
+			rcmail.register_command('plugin.contextmenu.collapseall', function(props, obj) {
+				$("#mailboxlist div.expanded").each(function() { $(this).click(); });
+			}, false);
+
+			rcmail.register_command('plugin.contextmenu.expandall', function(props, obj) {
+				$("#mailboxlist div.collapsed").each(function() { $(this).click(); });
+			}, false);
+
+			rcmail.register_command('plugin.contextmenu.openfolder', function(props, obj) {
+				var button_id = rcmail.buttons['plugin.contextmenu.openfolder'][0].id;
+
+				rcube_find_object(button_id).href = '?_task=mail&_mbox='+urlencode(rcmail.env.context_menu_source_id);
+				rcmail.sourcewin = window.open(rcube_find_object(button_id).href);
+				if (rcmail.sourcewin)
+					window.setTimeout(function() { rcmail.sourcewin.focus(); }, 20);
+
+				rcube_find_object(button_id).href = '#';
+			}, false);
+		}
+
+		if (rcmail.env.task == 'addressbook' && rcmail.env.action == '') {
+			// address book selector
+			rcmail.addEventListener('actionbefore', function(props) {
+				if ((props.action == 'move' || props.action == 'copy') && props.props == '') {
+					rcm_addressbook_selector(props.originalEvent, props.action, function(obj, cmd, evt) {
+						// search result may contain contacts from many sources, but if there is only one...
+						var source = rcmail.env.source;
+						if (source == '' && rcmail.env.selection_sources.length == 1)
+							source = rcmail.env.selection_sources[0];
+
+						if ($(obj).data('source')) {
+							rcmail.command(cmd, rcmail.env.contactgroups['G' + $(obj).data('source') + $(obj).data('id')], evt);
+						}
+						else {
+							rcmail.command(cmd, rcmail.env.address_sources[$(obj).data('id')], evt);
+						}
+					});
+
+					return false;
+				}
+			});
+
+			// address book group selector
+			rcmail.register_command('plugin.contextmenu.assigngroup', function(props, obj, event) {
+				rcm_group_selector(event, props, function(obj, evt) {
+					// search result may contain contacts from many sources, but if there is only one...
+					rcm_override_mailbox_command(rcmail.env.contextmenus['contactlist'], { action: 'copy' } , true);
+					rcmail.group_member_change('add', rcmail.contact_list.get_selection().join(','), rcmail.env.source, $(obj).data('id'));
+					rcm_override_mailbox_command(rcmail.env.contextmenus['contactlist'], { action: 'copy' } , false);
+				});
+			}, false);
+
+			// reset address book selector when groups change
+			rcmail.addEventListener('group_insert', function() { $("#addressbook-selector").remove(); $("#addressgroup-selector").remove(); rcmail.rcm_addressbook_selector_element = undefined; rcmail.rcm_addressgroup_selector_element  = undefined; } );
+			rcmail.addEventListener('group_update', function() { $("#addressbook-selector").remove(); $("#addressgroup-selector").remove(); rcmail.rcm_addressbook_selector_element = undefined; rcmail.rcm_addressgroup_selector_element  = undefined; } );
+			rcmail.addEventListener('group_delete', function() { $("#addressbook-selector").remove(); $("#addressgroup-selector").remove(); rcmail.rcm_addressbook_selector_element = undefined; rcmail.rcm_addressgroup_selector_element  = undefined; } );
+		}
+
+		// special event listeners for intreacting with plugins which open popup menus (eg: zipdownload)
+		rcmail.addEventListener('menu-open', function(p) {
+			// check for popupmenus that arent part of contextmenu
+			if ($('div.contextmenu').is(':visible') && p.name.indexOf('rcm_') != 0) {
+				rcmail.context_menu_popup_commands[p.name] = {};
+				$('#' + p.name).find('a').each(function() {
+					if ($(this).attr('onclick') && $(this).attr('onclick').match(rcmail.context_menu_command_pattern)) {
+						var cmd = RegExp.$1;
+						rcmail.context_menu_popup_commands[p.name][cmd] = rcmail.commands[cmd];
+					}
+				});
+			}
+		});
+
+		rcmail.addEventListener('menu-close', function(p) {
+			// check required args are present, other plugins trigger this event too
+			if (!p.originalEvent) {
+				return;
+			}
+
+			// check for popupmenus that arent part of contextmenu
+			var e = p.originalEvent.currentTarget ? p.originalEvent.currentTarget : p.originalEvent.srcElement;
+			if ($('div.contextmenu').is(':visible') && p.name.indexOf('rcm_') != 0 && $(e).attr('class').indexOf('rcm_elem_') == -1) {
+				rcm_hide_menu(p.originalEvent);
+			}
+		});
+
+		rcmail.addEventListener('get_single_uid', function() {
+			if ($('#rcm_messagelist').is(':visible') && rcmail.env.contextmenus['messagelist'].menu_selection.length == 1) {
+				return rcmail.env.contextmenus['messagelist'].menu_selection[0];
+			}
+		});
+
+		rcmail.addEventListener('get_single_cid', function() {
+			if ($('#rcm_contactlist').is(':visible') && rcmail.env.contextmenus['contactlist'].menu_selection.length == 1) {
+				return rcmail.env.contextmenus['contactlist'].menu_selection[0];
+			}
+			else if ($('#rcm_composeto').is(':visible') && rcmail.env.contextmenus['composeto'].menu_selection.length == 1) {
+				return rcmail.env.contextmenus['composeto'].menu_selection[0];
+			}
+		});
+	}
+});
\ No newline at end of file