summaryrefslogtreecommitdiff
path: root/program/js
diff options
context:
space:
mode:
Diffstat (limited to 'program/js')
-rw-r--r--program/js/app.js498
-rw-r--r--program/js/common.js130
-rw-r--r--program/js/jstz.min.js13
-rw-r--r--program/js/list.js228
-rw-r--r--program/js/treelist.js938
5 files changed, 991 insertions, 816 deletions
diff --git a/program/js/app.js b/program/js/app.js
index 4ae5cc938..41be99c0f 100644
--- a/program/js/app.js
+++ b/program/js/app.js
@@ -44,7 +44,9 @@ function rcube_webmail()
comm_path: './',
blankpage: 'program/resources/blank.gif',
recipients_separator: ',',
- recipients_delimiter: ', '
+ recipients_delimiter: ', ',
+ popup_width: 1150,
+ popup_width_small: 900
};
// create protected reference to myself
@@ -179,7 +181,8 @@ function rcube_webmail()
}
// enable general commands
- this.enable_command('close', 'logout', 'mail', 'addressbook', 'settings', 'save-pref', 'compose', 'undo', 'about', 'switch-task', true);
+ this.enable_command('close', 'logout', 'mail', 'addressbook', 'settings', 'save-pref',
+ 'compose', 'undo', 'about', 'switch-task', 'menu-open', 'menu-save', true);
if (this.env.permaurl)
this.enable_command('permaurl', 'extwin', true);
@@ -188,7 +191,7 @@ function rcube_webmail()
case 'mail':
// enable mail commands
- this.enable_command('list', 'checkmail', 'add-contact', 'search', 'reset-search', 'collapse-folder', true);
+ this.enable_command('list', 'checkmail', 'add-contact', 'search', 'reset-search', 'collapse-folder', 'import-messages', true);
if (this.gui_objects.messagelist) {
this.message_list = new rcube_list_widget(this.gui_objects.messagelist, {
@@ -205,12 +208,13 @@ function rcube_webmail()
this.message_list.addEventListener('dragend', function(e){ p.drag_end(e); });
this.message_list.addEventListener('expandcollapse', function(e){ p.msglist_expand(e); });
this.message_list.addEventListener('column_replace', function(e){ p.msglist_set_coltypes(e); });
+ this.message_list.addEventListener('listupdate', function(e){ p.triggerEvent('listupdate', e); });
document.onmouseup = function(e){ return p.doc_mouse_up(e); };
this.gui_objects.messagelist.parentNode.onmousedown = function(e){ return p.click_on_list(e); };
this.message_list.init();
- this.enable_command('toggle_status', 'toggle_flag', 'menu-open', 'menu-save', 'sort', true);
+ this.enable_command('toggle_status', 'toggle_flag', 'sort', true);
// load messages
this.command('list');
@@ -226,8 +230,8 @@ function rcube_webmail()
this.env.message_commands = ['show', 'reply', 'reply-all', 'reply-list',
'moveto', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource',
- 'print', 'load-attachment', 'show-headers', 'hide-headers', 'download',
- 'forward', 'forward-inline', 'forward-attachment'];
+ 'print', 'load-attachment', 'download-attachment', 'show-headers', 'hide-headers', 'download',
+ 'forward', 'forward-inline', 'forward-attachment', 'change-format'];
if (this.env.action == 'show' || this.env.action == 'preview') {
this.enable_command(this.env.message_commands, this.env.uid);
@@ -274,11 +278,12 @@ function rcube_webmail()
this.init_messageform();
}
// show printing dialog
- else if (this.env.action == 'print' && this.env.uid)
+ else if (this.env.action == 'print' && this.env.uid) {
if (bw.safari)
setTimeout('window.print()', 10);
else
window.print();
+ }
// get unread count for each mailbox
if (this.gui_objects.mailboxlist) {
@@ -313,7 +318,7 @@ function rcube_webmail()
}
// detect browser capabilities
- if (!this.is_framed())
+ if (!this.is_framed() && !this.env.extwin)
this.browser_capabilities_check();
break;
@@ -475,6 +480,7 @@ function rcube_webmail()
});
this.treelist.addEventListener('collapse', function(node){ ref.folder_collapsed(node) });
this.treelist.addEventListener('expand', function(node){ ref.folder_collapsed(node) });
+ this.treelist.addEventListener('select', function(node){ ref.triggerEvent('selectfolder', { folder:node.id, prefix:'rcmli' }) });
}
}
@@ -596,18 +602,36 @@ function rcube_webmail()
case 'extwin':
if (this.env.action == 'compose') {
- var prevstate = this.env.compose_extwin;
- $("input[name='_action']", this.gui_objects.messageform).val('compose');
- this.gui_objects.messageform.action = this.url('mail/compose', { _id: this.env.compose_id, _extwin: 1 });
- this.gui_objects.messageform.target = this.open_window('', 1100, 900);
- this.gui_objects.messageform.submit();
+ var form = this.gui_objects.messageform,
+ win = this.open_window('');
+
+ $("input[name='_action']", form).val('compose');
+ form.action = this.url('mail/compose', { _id: this.env.compose_id, _extwin: 1 });
+ form.target = win.name;
+ form.submit();
}
else {
- this.open_window(this.env.permaurl, 900, 900);
+ this.open_window(this.env.permaurl, true);
}
break;
+ case 'change-format':
+ url = this.env.permaurl + '&_format=' + props;
+
+ if (this.env.action == 'preview')
+ url = url.replace(/_action=show/, '_action=preview') + '&_framed=1';
+ if (this.env.extwin)
+ url += '&_extwin=1';
+
+ location.href = url;
+ break;
+
case 'menu-open':
+ if (props && props.menu == 'attachmentmenu') {
+ var mimetype = this.env.attachments[props.id];
+ this.enable_command('open-attachment', mimetype && this.env.mimetypes && $.inArray(mimetype, this.env.mimetypes) >= 0);
+ }
+
case 'menu-save':
this.triggerEvent(command, {props:props});
return false;
@@ -833,15 +857,15 @@ function rcube_webmail()
break;
case 'load-attachment':
- var qstring = '_mbox='+urlencode(this.env.mailbox)+'&_uid='+this.env.uid+'&_part='+props.part;
+ case 'open-attachment':
+ case 'download-attachment':
+ var qstring = '_mbox='+urlencode(this.env.mailbox)+'&_uid='+this.env.uid+'&_part='+props,
+ mimetype = this.env.attachments[props];
// open attachment in frame if it's of a supported mimetype
- if (this.env.uid && props.mimetype && this.env.mimetypes && $.inArray(props.mimetype, this.env.mimetypes) >= 0) {
- var attachment_win = window.open(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', 'rcubemailattachment'+this.env.uid+props.part);
- if (attachment_win) {
- setTimeout(function(){ attachment_win.focus(); }, 10);
+ if (command != 'download-attachment' && mimetype && this.env.mimetypes && $.inArray(mimetype, this.env.mimetypes) >= 0) {
+ if (this.open_window(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1', true, true))
break;
- }
}
this.goto_url('get', qstring+'&_download=1', false);
@@ -880,7 +904,7 @@ function rcube_webmail()
case 'nextmessage':
if (this.env.next_uid)
- this.show_message(this.env.next_uid, false, this.env.action=='preview');
+ this.show_message(this.env.next_uid, false, this.env.action == 'preview');
break;
case 'lastmessage':
@@ -916,16 +940,13 @@ function rcube_webmail()
url._to = props;
}
else {
- // use contact_id passed as command parameter
- var n, len, a_cids = [];
+ var a_cids = [];
+ // use contact id passed as command parameter
if (props)
a_cids.push(props);
// get selected contacts
- else if (this.contact_list) {
- var selection = this.contact_list.get_selection();
- for (n=0, len=selection.length; n<len; n++)
- a_cids.push(selection[n]);
- }
+ else if (this.contact_list)
+ a_cids = this.contact_list.get_selection();
if (a_cids.length)
this.http_post('mailto', { _cid: a_cids.join(','), _source: this.env.source }, true);
@@ -960,8 +981,8 @@ function rcube_webmail()
// Reset the auto-save timer
clearTimeout(this.save_timer);
- // compose form did not change
- if (this.cmp_hash == this.compose_field_hash()) {
+ // compose form did not change (and draft wasn't saved already)
+ if (this.env.draft_id && this.cmp_hash == this.compose_field_hash()) {
this.auto_save_start();
break;
}
@@ -983,7 +1004,7 @@ function rcube_webmail()
// Reset the auto-save timer
clearTimeout(this.save_timer);
- this.upload_file(props || this.gui_objects.uploadform);
+ this.upload_file(props || this.gui_objects.uploadform, 'upload');
break;
case 'insert-sig':
@@ -1028,9 +1049,8 @@ function rcube_webmail()
case 'print':
if (uid = this.get_single_uid()) {
- ref.printwin = window.open(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+(this.env.safemode ? '&_safe=1' : ''));
+ ref.printwin = this.open_window(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox)+(this.env.safemode ? '&_safe=1' : ''), true, true);
if (this.printwin) {
- setTimeout(function(){ ref.printwin.focus(); }, 20);
if (this.env.action != 'show')
this.mark_message('read', uid);
}
@@ -1038,11 +1058,8 @@ function rcube_webmail()
break;
case 'viewsource':
- if (uid = this.get_single_uid()) {
- ref.sourcewin = window.open(this.env.comm_path+'&_action=viewsource&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox));
- if (this.sourcewin)
- setTimeout(function(){ ref.sourcewin.focus(); }, 20);
- }
+ if (uid = this.get_single_uid())
+ this.open_window(this.env.comm_path+'&_action=viewsource&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true, true);
break;
case 'download':
@@ -1102,6 +1119,12 @@ function rcube_webmail()
}
break;
+ case 'import-messages':
+ var form = props || this.gui_objects.importform;
+ $('input[name="_unlock"]', form).val(this.set_busy(true, 'importwait'));
+ this.upload_file(form, 'import');
+ break;
+
case 'import':
if (this.env.action == 'import' && this.gui_objects.importform) {
var file = document.getElementById('rcmimportfile');
@@ -1176,6 +1199,7 @@ function rcube_webmail()
if (typeof cmd === 'string') {
this.commands[cmd] = enable;
this.set_button(cmd, (enable ? 'act' : 'pas'));
+ this.triggerEvent('enable-command', {command: cmd, status: enable});
}
// push array elements into commands array
else {
@@ -1240,7 +1264,7 @@ function rcube_webmail()
if (!url)
url = this.env.comm_path;
- return url.replace(/_task=[a-z]+/, '_task='+task);
+ return url.replace(/_task=[a-z0-9_-]+/i, '_task='+task);
};
this.reload = function(delay)
@@ -1503,7 +1527,7 @@ function rcube_webmail()
// start timer for message preview (wait for double click)
if (selected && this.env.contentframe && !list.multi_selecting && !this.dummy_select)
- this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200);
+ this.preview_timer = setTimeout(function() { ref.msglist_get_preview(); }, this.dblclick_time);
else if (this.env.contentframe)
this.show_contentframe(false);
};
@@ -1519,12 +1543,13 @@ function rcube_webmail()
var win = this.get_frame_window(this.env.contentframe);
- if (win && win.location.href.indexOf(this.env.blankpage)>=0) {
+ if (win && win.location.href.indexOf(this.env.blankpage) >= 0) {
if (this.preview_timer)
clearTimeout(this.preview_timer);
if (this.preview_read_timer)
clearTimeout(this.preview_read_timer);
- this.preview_timer = setTimeout(function(){ ref.msglist_get_preview(); }, 200);
+
+ this.preview_timer = setTimeout(function() { ref.msglist_get_preview(); }, this.dblclick_time);
}
};
@@ -1532,11 +1557,11 @@ function rcube_webmail()
{
if (this.preview_timer)
clearTimeout(this.preview_timer);
-
if (this.preview_read_timer)
clearTimeout(this.preview_read_timer);
var uid = list.get_single_selection();
+
if (uid && this.env.mailbox == this.env.drafts_mailbox)
this.open_compose_step({ _draft_uid: uid, _mbox: this.env.mailbox });
else if (uid)
@@ -1576,7 +1601,7 @@ function rcube_webmail()
this.msglist_set_coltypes = function(list)
{
- var i, found, name, cols = list.list.tHead.rows[0].cells;
+ var i, found, name, cols = list.thead.rows[0].cells;
this.env.coltypes = [];
@@ -1624,15 +1649,28 @@ function rcube_webmail()
return 0;
};
- this.open_window = function(url, width, height)
+ // open popup window
+ this.open_window = function(url, small, toolbar)
{
- var w = Math.min(width, screen.width - 10),
- h = Math.min(height, screen.height - 100),
- l = (screen.width - w) / 2 + (screen.left || 0),
- t = Math.max(0, (screen.height - h) / 2 + (screen.top || 0) - 20),
- wname = 'rcmextwin' + new Date().getTime(),
- extwin = window.open(url + '&_extwin=1', wname,
- 'width='+w+',height='+h+',top='+t+',left='+l+',resizable=yes,toolbar=no,status=no,location=no');
+ var wname = 'rcmextwin' + new Date().getTime();
+
+ url += (url.match(/\?/) ? '&' : '?') + '_extwin=1';
+
+ if (this.env.standard_windows)
+ extwin = window.open(url, wname);
+ else {
+ var win = this.is_framed() ? parent.window : window,
+ page = $(win),
+ page_width = page.width(),
+ page_height = bw.mz ? $('body', win).height() : page.height(),
+ w = Math.min(small ? this.env.popup_width_small : this.env.popup_width, page_width),
+ h = page_height, // always use same height
+ l = (win.screenLeft || win.screenX) + 20,
+ t = (win.screenTop || win.screenY) + 20,
+ extwin = window.open(url, wname,
+ 'width='+w+',height='+h+',top='+t+',left='+l+',resizable=yes,location=no,scrollbars=yes'
+ +(toolbar ? ',toolbar=yes,menubar=yes,status=yes' : ',toolbar=no,menubar=no,status=no'));
+ }
// write loading... message to empty windows
if (!url && extwin.document) {
@@ -1641,10 +1679,8 @@ function rcube_webmail()
// focus window, delayed to bring to front
window.setTimeout(function() { extwin.focus(); }, 10);
- // position window with setTimeout for Chrome (#1488931)
- window.setTimeout(function() { extwin.moveTo(l,t); }, bw.chrome ? 100 : 10);
- return wname;
+ return extwin;
};
@@ -1728,10 +1764,7 @@ function rcube_webmail()
+ (flags.flagged ? ' flagged' : '')
+ (flags.unread_children && flags.seen && !this.env.autoexpand_threads ? ' unroot' : '')
+ (message.selected ? ' selected' : ''),
- // for performance use DOM instead of jQuery here
- row = document.createElement('tr');
-
- row.id = 'rcmrow'+uid;
+ row = { cols:[], style:{}, id:'rcmrow'+uid };
// message status icons
css_class = 'msgicon';
@@ -1795,8 +1828,7 @@ function rcube_webmail()
// add each submitted col
for (n in this.env.coltypes) {
c = this.env.coltypes[n];
- col = document.createElement('td');
- col.className = String(c).toLowerCase();
+ col = { className: String(c).toLowerCase() };
if (c == 'flag') {
css_class = (flags.flagged ? 'flagged' : 'unflagged');
@@ -1841,8 +1873,7 @@ function rcube_webmail()
html = cols[c];
col.innerHTML = html;
-
- row.appendChild(col);
+ row.cols.push(col);
}
list.insert_row(row, attop);
@@ -1943,7 +1974,7 @@ function rcube_webmail()
}
else {
if (!preview && this.env.message_extwin && !this.env.extwin)
- this.open_window(this.env.comm_path+url, 1000, 1200);
+ this.open_window(this.env.comm_path+url, true);
else
this.location_href(this.env.comm_path+url, target, true);
@@ -2205,7 +2236,7 @@ function rcube_webmail()
if (root)
row = rows[root] ? rows[root].obj : null;
else
- row = this.message_list.list.tBodies[0].firstChild;
+ row = this.message_list.tbody.firstChild;
while (row) {
if (row.nodeType == 1 && (r = rows[row.uid])) {
@@ -2381,7 +2412,7 @@ function rcube_webmail()
this.delete_excessive_thread_rows = function()
{
var rows = this.message_list.rows,
- tbody = this.message_list.list.tBodies[0],
+ tbody = this.message_list.tbody,
row = tbody.firstChild,
cnt = this.env.pagesize + 1;
@@ -2969,11 +3000,12 @@ function rcube_webmail()
// open new compose window
if (this.env.compose_extwin && !this.env.extwin) {
- this.open_window(url, 1150, 900);
+ this.open_window(url);
}
else {
this.redirect(url);
- window.resizeTo(Math.max(1150, $(window).width()), Math.max(900, $(window).height()));
+ if (this.env.extwin)
+ window.resizeTo(Math.max(this.env.popup_width, $(window).width()), $(window).height() + 24);
}
};
@@ -2989,10 +3021,10 @@ function rcube_webmail()
input_message = $("[name='_message']").get(0),
html_mode = $("input[name='_is_html']").val() == '1',
ac_fields = ['cc', 'bcc', 'replyto', 'followupto'],
- ac_props;
+ ac_props, opener_rc = this.opener();
// close compose step in opener
- if (window.opener && !window.opener.closed && opener.rcmail && opener.rcmail.env.action == 'compose') {
+ if (opener_rc && opener_rc.env.action == 'compose') {
setTimeout(function(){ opener.history.back(); }, 100);
this.env.opened_extwin = true;
}
@@ -3087,7 +3119,7 @@ function rcube_webmail()
this.compose_add_recipient = function(field)
{
- var recipients = [], input = $('#_'+field);
+ var recipients = [], input = $('#_'+field), delim = this.env.recipients_delimiter;
if (this.contact_list && this.contact_list.selection.length) {
for (var id, n=0; n < this.contact_list.selection.length; n++) {
@@ -3106,8 +3138,10 @@ function rcube_webmail()
}
if (recipients.length && input.length) {
- var oldval = input.val();
- input.val((oldval ? oldval + this.env.recipients_delimiter : '') + recipients.join(this.env.recipients_delimiter));
+ var oldval = input.val(), rx = new RegExp(RegExp.escape(delim) + '\\s*$');
+ if (oldval && !rx.test(oldval))
+ oldval += delim + ' ';
+ input.val(oldval + recipients.join(delim + ' ') + delim + ' ');
this.triggerEvent('add-recipient', { field:field, recipients:recipients });
}
};
@@ -3297,6 +3331,15 @@ function rcube_webmail()
this.set_draft_id = function(id)
{
+ var rc;
+
+ if (!this.env.draft_id && id && (rc = this.opener())) {
+ // refresh the drafts folder in opener window
+ if (rc.env.task == 'mail' && rc.env.action == '' && rc.env.mailbox == this.env.drafts_mailbox)
+ rc.command('checkmail');
+ }
+
+ this.env.draft_id = id;
$("input[name='_draft_saveid']").val(id);
};
@@ -3341,12 +3384,45 @@ function rcube_webmail()
if (!show_sig)
show_sig = this.env.show_sig;
- var cursor_pos, p = -1,
+ var i, rx, cursor_pos, p = -1,
id = obj.options[obj.selectedIndex].value,
input_message = $("[name='_message']"),
message = input_message.val(),
is_html = ($("input[name='_is_html']").val() == '1'),
- sig = this.env.identity;
+ sig = this.env.identity,
+ delim = this.env.recipients_delimiter,
+ headers = ['replyto', 'bcc'];
+
+ // update reply-to/bcc fields with addresses defined in identities
+ for (i in headers) {
+ var key = headers[i],
+ old_val = sig && this.env.identities[sig] ? this.env.identities[sig][key] : '',
+ new_val = id && this.env.identities[id] ? this.env.identities[id][key] : '',
+ input = $('[name="_'+key+'"]'), input_val = input.val();
+
+ // remove old address(es)
+ if (old_val && input_val) {
+ rx = new RegExp('\\s*' + RegExp.escape(old_val) + '\\s*');
+ input_val = input_val.replace(rx, '');
+ }
+
+ // cleanup
+ rx = new RegExp(RegExp.escape(delim) + '\\s*' + RegExp(delim), 'g');
+ input_val = input_val.replace(rx, delim)
+ rx = new RegExp('^\\s*' + RegExp.escape(delim) + '\\s*$');
+ input_val = input_val.replace(rx, '')
+
+ // add new address(es)
+ if (new_val) {
+ rx = new RegExp(RegExp.escape(delim) + '\\s*$');
+ if (input_val && !rx.test(input_val))
+ input_val += delim + ' ';
+ input_val += new_val + delim + ' ';
+ }
+
+ if (old_val || new_val)
+ input.val(input_val).change();
+ }
// enable manual signature insert
if (this.env.signatures && this.env.signatures[id]) {
@@ -3362,7 +3438,7 @@ function rcube_webmail()
sig = this.env.signatures[sig].text;
sig = sig.replace(/\r\n/g, '\n');
- p = this.env.sig_above ? message.indexOf(sig) : message.lastIndexOf(sig);
+ p = this.env.top_posting ? message.indexOf(sig) : message.lastIndexOf(sig);
if (p >= 0)
message = message.substring(0, p) + message.substring(p+sig.length, message.length);
}
@@ -3371,7 +3447,7 @@ function rcube_webmail()
sig = this.env.signatures[id].text;
sig = sig.replace(/\r\n/g, '\n');
- if (this.env.sig_above) {
+ if (this.env.top_posting) {
if (p >= 0) { // in place of removed signature
message = message.substring(0, p) + sig + message.substring(p, message.length);
cursor_pos = p - 1;
@@ -3415,7 +3491,7 @@ function rcube_webmail()
sigElem = doc.createElement('div');
sigElem.setAttribute('id', '_rc_sig');
- if (this.env.sig_above) {
+ if (this.env.top_posting) {
// if no existing sig and top posting then insert at caret pos
editor.getWin().focus(); // correct focus in IE & Chrome
@@ -3446,8 +3522,8 @@ function rcube_webmail()
return true;
};
- // upload attachment file
- this.upload_file = function(form)
+ // upload (attachment) file
+ this.upload_file = function(form, action)
{
if (!form)
return false;
@@ -3474,7 +3550,7 @@ function rcube_webmail()
return;
}
- var frame_name = this.async_upload_form(form, 'upload', function(e) {
+ var frame_name = this.async_upload_form(form, action || 'upload', function(e) {
var d, content = '';
try {
if (this.contentDocument) {
@@ -3672,14 +3748,19 @@ function rcube_webmail()
this.env.search_id = null;
};
- this.sent_successfully = function(type, msg)
+ this.sent_successfully = function(type, msg, target)
{
this.display_message(msg, type);
if (this.env.extwin) {
+ var rc = this.opener();
this.lock_form(this.gui_objects.messageform);
- if (window.opener && !window.opener.closed && opener.rcmail)
- opener.rcmail.display_message(msg, type);
+ if (rc) {
+ rc.display_message(msg, type);
+ // refresh the folder where sent message was saved
+ if (target && rc.env.task == 'mail' && rc.env.action == '' && rc.env.mailbox == target)
+ rc.command('checkmail');
+ }
setTimeout(function(){ window.close() }, 1000);
}
else {
@@ -4054,6 +4135,7 @@ function rcube_webmail()
var n, id, sid, contact, ref = this, writable = false,
source = this.env.source ? this.env.address_sources[this.env.source] : null;
+ // we don't have dblclick handler here, so use 200 instead of this.dblclick_time
if (id = list.get_single_selection())
this.preview_timer = setTimeout(function(){ ref.load_contact(id, 'show'); }, 200);
else if (this.env.contentframe)
@@ -4294,7 +4376,7 @@ function rcube_webmail()
this.group_member_change('add', cid, dest, to.id);
else {
var lock = this.display_message(this.get_label('copyingcontact'), 'loading'),
- post_data = {_cid: cid, _source: source, _to: dest, _togid: to.id, _gid: group};
+ post_data = {_cid: cid, _source: this.env.source, _to: dest, _togid: to.id, _gid: group};
this.http_post('copy', post_data, lock);
}
@@ -4302,7 +4384,7 @@ function rcube_webmail()
// target is an addressbook
else if (to.id != source) {
var lock = this.display_message(this.get_label('copyingcontact'), 'loading'),
- post_data = {_cid: cid, _source: source, _to: to.id, _gid: group};
+ post_data = {_cid: cid, _source: this.env.source, _to: to.id, _gid: group};
this.http_post('copy', post_data, lock);
}
@@ -4364,23 +4446,8 @@ function rcube_webmail()
newcid = newcid+'-'+source;
}
- if (list.rows[cid] && (row = list.rows[cid].obj)) {
- for (c=0; c<cols_arr.length; c++)
- if (row.cells[c])
- $(row.cells[c]).html(cols_arr[c]);
-
- // cid change
- if (newcid) {
- newcid = this.html_identifier(newcid);
- row.id = 'rcmrow' + newcid;
- list.remove_row(cid);
- list.init_row(row);
- list.selection[0] = newcid;
- row.style.display = '';
- }
-
- list.data[cid] = data;
- }
+ list.update_row(cid, cols_arr, newcid, true);
+ list.data[cid] = data;
};
// add row to contacts list
@@ -4390,7 +4457,7 @@ function rcube_webmail()
return false;
var c, col, list = this.contact_list,
- row = document.createElement('tr');
+ row = { cols:[] };
row.id = 'rcmrow'+this.html_identifier(cid);
row.className = 'contact ' + (classes || '');
@@ -4400,10 +4467,10 @@ function rcube_webmail()
// add each submitted col
for (c in cols) {
- col = document.createElement('td');
+ col = {};
col.className = String(c).toLowerCase();
col.innerHTML = cols[c];
- row.appendChild(col);
+ row.cols.push(col);
}
// store data in list member
@@ -4491,11 +4558,8 @@ function rcube_webmail()
// callback from server upon group-delete command
this.remove_group_item = function(prop)
{
- var li, key = 'G'+prop.source+prop.id;
- if ((li = this.get_folder_li(key,'',true))) {
- this.triggerEvent('group_delete', { source:prop.source, id:prop.id, li:li });
-
- li.parentNode.removeChild(li);
+ var key = 'G'+prop.source+prop.id;
+ if (this.treelist.remove(key)) {
delete this.env.contactfolders[key];
delete this.env.contactgroups[key];
}
@@ -4514,8 +4578,22 @@ function rcube_webmail()
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) : $('ul.groups li:last', this.get_folder_li(this.env.source,'',true));
- 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();
@@ -4572,11 +4650,13 @@ function rcube_webmail()
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();
@@ -4599,14 +4679,12 @@ function rcube_webmail()
link = $('<a>').attr('href', '#')
.attr('rel', prop.source+':'+prop.id)
.click(function() { return rcmail.command('listgroup', prop, this); })
- .html(prop.name),
- li = $('<li>').attr({id: 'rcmli'+this.html_identifier(key), 'class': 'contactgroup'})
- .append(link);
+ .html(prop.name);
this.env.contactfolders[key] = this.env.contactgroups[key] = prop;
- this.add_contact_group_row(prop, li);
+ this.treelist.insert({ id:key, html:link, classes:['contactgroup'] }, prop.source, true);
- this.triggerEvent('group_insert', { id:prop.id, source:prop.source, name:prop.name, li:li[0] });
+ this.triggerEvent('group_insert', { id:prop.id, source:prop.source, name:prop.name, li:this.treelist.get_item(key) });
};
// callback for renaming a contact group
@@ -4615,15 +4693,13 @@ function rcube_webmail()
this.reset_add_input();
var key = 'G'+prop.source+prop.id,
- li = this.get_folder_li(key,'',true),
- link;
+ newnode = {};
// group ID has changed, replace link node and identifiers
- if (li && prop.newid) {
+ if (prop.newid) {
var newkey = 'G'+prop.source+prop.newid,
- newprop = $.extend({}, prop);;
+ newprop = $.extend({}, prop);
- li.id = 'rcmli' + this.html_identifier(newkey,true);
this.env.contactfolders[newkey] = this.env.contactfolders[key];
this.env.contactfolders[newkey].id = prop.newid;
this.env.group = prop.newid;
@@ -4634,46 +4710,22 @@ function rcube_webmail()
newprop.id = prop.newid;
newprop.type = 'group';
- link = $('<a>').attr('href', '#')
+ newnode.id = newkey;
+ newnode.html = $('<a>').attr('href', '#')
.attr('rel', prop.source+':'+prop.newid)
.click(function() { return rcmail.command('listgroup', newprop, this); })
.html(prop.name);
- $(li).children().replaceWith(link);
}
// update displayed group name
- else if (li && (link = li.firstChild) && link.tagName.toLowerCase() == 'a') {
- link.innerHTML = prop.name;
+ else {
+ $(this.treelist.get_item(key)).children().first().html(prop.name);
this.env.contactfolders[key].name = this.env.contactgroups[key].name = prop.name;
}
- this.add_contact_group_row(prop, $(li), true);
-
- this.triggerEvent('group_update', { id:prop.id, source:prop.source, name:prop.name, li:li[0], newid:prop.newid });
- };
-
- // add contact group row to the list, with sorting
- this.add_contact_group_row = function(prop, li, reloc)
- {
- var row, name = prop.name.toUpperCase(),
- 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) {
- row = li.clone(true);
- li.remove();
- }
- else
- row = li;
-
- $('li[id^="'+prefix+'"]', this.gui_objects.folderlist).each(function(i, elem) {
- if (name >= $(this).text().toUpperCase())
- sibling = elem;
- else
- return false;
- });
+ // update list node and re-sort it
+ this.treelist.update(key, newnode, true);
- row.insertAfter(sibling);
+ this.triggerEvent('group_update', { id:prop.id, source:prop.source, name:prop.name, li:this.treelist.get_item(key), newid:prop.newid });
};
this.update_group_commands = function()
@@ -4905,11 +4957,9 @@ function rcube_webmail()
.attr('rel', id)
.click(function() { return rcmail.command('listsearch', id, this); })
.html(name),
- li = $('<li>').attr({ id:'rcmli' + this.html_identifier(key,true), 'class':'contactsearch' })
- .append(link),
- prop = {name:name, id:id, li:li[0]};
+ prop = { name:name, id:id };
- this.add_saved_search_row(prop, li);
+ this.treelist.insert({ id:key, html:link, classes:['contactsearch'] }, null, 'contactsearch');
this.select_folder(key,'',true);
this.enable_command('search-delete', true);
this.env.search_id = id;
@@ -4917,35 +4967,6 @@ function rcube_webmail()
this.triggerEvent('abook_search_insert', prop);
};
- // add saved search row to the list, with sorting
- this.add_saved_search_row = function(prop, li, reloc)
- {
- var row, sibling, name = prop.name.toUpperCase();
-
- // When renaming groups, we need to remove it from DOM and insert it in the proper place
- if (reloc) {
- row = li.clone(true);
- li.remove();
- }
- else
- row = li;
-
- $('li[class~="contactsearch"]', this.gui_objects.folderlist).each(function(i, elem) {
- if (!sibling)
- sibling = this.previousSibling;
-
- if (name >= $(this).text().toUpperCase())
- sibling = elem;
- else
- return false;
- });
-
- if (sibling)
- row.insertAfter(sibling);
- else
- row.appendTo(this.gui_objects.folderlist);
- };
-
// creates an input for saved search name
this.search_create = function()
{
@@ -4964,10 +4985,8 @@ function rcube_webmail()
this.remove_search_item = function(id)
{
var li, key = 'S'+id;
- if ((li = this.get_folder_li(key,'',true))) {
+ if (this.treelist.remove(key)) {
this.triggerEvent('search_delete', { id:id, li:li });
-
- li.parentNode.removeChild(li);
}
this.env.search_id = null;
@@ -5065,18 +5084,16 @@ function rcube_webmail()
this.update_identity_row = function(id, name, add)
{
- var row, col, list = this.identity_list,
+ var list = this.identity_list,
rid = this.html_identifier(id);
- if (list.rows[rid] && (row = list.rows[rid].obj)) {
- $(row.cells[0]).html(name);
- }
- else if (add) {
- row = $('<tr>').attr('id', 'rcmrow'+rid).get(0);
- col = $('<td>').addClass('mail').html(name).appendTo(row);
- list.insert_row(row);
+ if (add) {
+ list.insert_row({ id:'rcmrow'+rid, cols:[ { className:'mail', innerHTML:name } ] });
list.select(rid);
}
+ else {
+ list.update_row(rid, [ name ]);
+ }
};
@@ -5701,14 +5718,15 @@ function rcube_webmail()
if (!this.gui_objects.message)
return;
- var k, n, i, msg, m = this.messages;
+ var k, n, i, o, m = this.messages;
// Hide message by object, don't use for 'loading'!
if (typeof obj === 'object') {
- $(obj)[fade?'fadeOut':'hide']();
- msg = $(obj).data('key');
- if (this.messages[msg])
- delete this.messages[msg];
+ o = $(obj);
+ k = o.data('key');
+ this.hide_message_object(o, fade);
+ if (m[k])
+ delete m[k];
}
// Hide message by id
else {
@@ -5718,7 +5736,7 @@ function rcube_webmail()
m[k].elements.splice(n, 1);
// hide DOM element if last instance is removed
if (!m[k].elements.length) {
- m[k].obj[fade?'fadeOut':'hide']();
+ this.hide_message_object(m[k].obj, fade);
delete m[k];
}
// set pending action label for 'loading' message
@@ -5728,9 +5746,9 @@ function rcube_webmail()
delete m[k].labels[i];
}
else {
- msg = m[k].labels[i].msg;
+ o = m[k].labels[i].msg;
+ m[k].obj.html(o);
}
- m[k].obj.html(msg);
}
}
}
@@ -5739,6 +5757,15 @@ function rcube_webmail()
}
};
+ // hide message object and remove from the DOM
+ this.hide_message_object = function(o, fade)
+ {
+ if (fade)
+ o.fadeOut(600, function() {$(this).remove(); });
+ else
+ o.hide().remove();
+ };
+
// remove all messages immediately
this.clear_messages = function()
{
@@ -5751,17 +5778,17 @@ function rcube_webmail()
for (k in m)
for (n in m[k].elements)
if (m[k].obj)
- m[k].obj.hide();
+ this.hide_message_object(m[k].obj);
this.messages = {};
};
// open a jquery UI dialog with the given content
- this.show_popup_dialog = function(html, title)
+ this.show_popup_dialog = function(html, title, buttons)
{
// forward call to parent window
if (this.is_framed()) {
- parent.rcmail.show_popup_dialog(html, title);
+ parent.rcmail.show_popup_dialog(html, title, buttons);
return;
}
@@ -5769,17 +5796,21 @@ function rcube_webmail()
.html(html)
.dialog({
title: title,
+ buttons: buttons,
modal: true,
resizable: true,
- width: 580,
+ width: 500,
close: function(event, ui) { $(this).remove() }
});
- // resize and center popup
- var win = $(window), w = win.width(), h = win.height(),
- width = popup.width(), height = popup.height();
- popup.dialog('option', { height: Math.min(h-40, height+50), width: Math.min(w-20, width+50) })
- .dialog('option', 'position', ['center', 'center']); // only works in a separate call (!?)
+ // resize and center popup
+ var win = $(window), w = win.width(), h = win.height(),
+ width = popup.width(), height = popup.height();
+
+ popup.dialog('option', {
+ height: Math.min(h - 40, height + 75 + (buttons ? 50 : 0)),
+ width: Math.min(w - 20, width + 20)
+ });
};
// enable/disable buttons for page shifting
@@ -5792,7 +5823,10 @@ function rcube_webmail()
// mark a mailbox as selected and set environment variable
this.select_folder = function(name, prefix, encode)
{
- if (this.gui_objects.folderlist) {
+ if (this.treelist) {
+ this.treelist.select(name);
+ }
+ else if (this.gui_objects.folderlist) {
var current_li, target_li;
if ((current_li = $('li.selected', this.gui_objects.folderlist))) {
@@ -5838,7 +5872,7 @@ function rcube_webmail()
this.set_message_coltypes = function(coltypes, repl, smart_col)
{
var list = this.message_list,
- thead = list ? list.list.tHead : null,
+ thead = list ? list.thead : null,
cell, col, n, len, th, tr;
this.env.coltypes = coltypes;
@@ -5851,14 +5885,14 @@ function rcube_webmail()
for (c=0, len=repl.length; c < len; c++) {
cell = document.createElement('td');
- cell.innerHTML = repl[c].html;
+ cell.innerHTML = repl[c].html || '';
if (repl[c].id) cell.id = repl[c].id;
if (repl[c].className) cell.className = repl[c].className;
tr.appendChild(cell);
}
th.appendChild(tr);
thead.parentNode.replaceChild(th, thead);
- thead = th;
+ list.thead = thead = th;
}
for (n=0, len=this.env.coltypes.length; n<len; n++) {
@@ -6080,9 +6114,9 @@ function rcube_webmail()
var base = this.env.comm_path, k, param = {};
// overwrite task name
- if (query._action.match(/([a-z]+)\/([a-z0-9-_.]+)/)) {
+ if (query._action.match(/([a-z0-9_-]+)\/([a-z0-9-_.]+)/)) {
query._action = RegExp.$2;
- base = base.replace(/\_task=[a-z]+/, '_task='+RegExp.$1);
+ base = base.replace(/\_task=[a-z0-9_-]+/, '_task='+RegExp.$1);
}
// remove undefined values
@@ -6359,6 +6393,14 @@ function rcube_webmail()
if (location_url && this.env.action != 'compose') // don't redirect on compose screen, contents might get lost (#1488926)
this.redirect(location_url);
+ // 403 Forbidden response (CSRF prevention) - reload the page.
+ // In case there's a new valid session it will be used, otherwise
+ // login form will be presented (#1488960).
+ if (request.status == 403) {
+ (this.is_framed() ? parent : window).location.reload();
+ return;
+ }
+
// re-send keep-alive requests after 30 seconds
if (action == 'keep-alive')
setTimeout(function(){ ref.keep_alive(); ref.start_keepalive(); }, 30000);
@@ -6472,6 +6514,7 @@ function rcube_webmail()
url: ref.url(ref.env.filedrop.action||'upload', { _id:ref.env.compose_id||ref.env.cid||'', _uploadid:ts, _remote:1 }),
contentType: formdata ? false : 'multipart/form-data; boundary=' + boundary,
processData: false,
+ timeout: 0, // disable default timeout set in ajaxSetup()
data: formdata || multipart,
headers: {'X-Roundcube-Request': ref.env.request_token},
beforeSend: function(xhr, s) { if (!formdata && xhr.sendAsBinary) xhr.send = xhr.sendAsBinary; },
@@ -6611,6 +6654,17 @@ function rcube_webmail()
/********* helper methods *********/
/********************************************************/
+ // get window.opener.rcmail if available
+ this.opener = function()
+ {
+ // catch Error: Permission denied to access property rcmail
+ try {
+ if (window.opener && !opener.closed && opener.rcmail)
+ return opener.rcmail;
+ }
+ catch (e) {}
+ };
+
// check if we're in show mode or if we have a unique selection
// and return the message uid
this.get_single_uid = function()
diff --git a/program/js/common.js b/program/js/common.js
index f9e945c05..de07ec131 100644
--- a/program/js/common.js
+++ b/program/js/common.js
@@ -251,7 +251,7 @@ remove_listener: function(p)
},
/**
- * Prevent event propagation and bubbeling
+ * Prevent event propagation and bubbling
*/
cancel: function(evt)
{
@@ -298,8 +298,7 @@ addEventListener: function(evt, func, obj)
if (!this._events[evt])
this._events[evt] = [];
- var e = {func:func, obj:obj ? obj : window};
- this._events[evt][this._events[evt].length] = e;
+ this._events[evt].push({func:func, obj:obj ? obj : window});
},
/**
@@ -372,117 +371,6 @@ triggerEvent: function(evt, e)
}; // end rcube_event_engine.prototype
-
-/**
- * Roundcube generic layer (floating box) class
- *
- * @constructor
- */
-function rcube_layer(id, attributes)
-{
- this.name = id;
-
- // create a new layer in the current document
- this.create = function(arg)
- {
- var l = (arg.x) ? arg.x : 0,
- t = (arg.y) ? arg.y : 0,
- w = arg.width,
- h = arg.height,
- z = arg.zindex,
- vis = arg.vis,
- parent = arg.parent,
- obj = document.createElement('DIV');
-
- obj.id = this.name;
- obj.style.position = 'absolute';
- obj.style.visibility = (vis) ? (vis==2) ? 'inherit' : 'visible' : 'hidden';
- obj.style.left = l+'px';
- obj.style.top = t+'px';
- if (w)
- obj.style.width = w.toString().match(/\%$/) ? w : w+'px';
- if (h)
- obj.style.height = h.toString().match(/\%$/) ? h : h+'px';
- if (z)
- obj.style.zIndex = z;
-
- if (parent)
- parent.appendChild(obj);
- else
- document.body.appendChild(obj);
-
- this.elm = obj;
- };
-
- // create new layer
- if (attributes != null) {
- this.create(attributes);
- this.name = this.elm.id;
- }
- else // just refer to the object
- this.elm = document.getElementById(id);
-
- if (!this.elm)
- return false;
-
-
- // ********* layer object properties *********
-
- this.css = this.elm.style;
- this.event = this.elm;
- this.width = this.elm.offsetWidth;
- this.height = this.elm.offsetHeight;
- this.x = parseInt(this.elm.offsetLeft);
- this.y = parseInt(this.elm.offsetTop);
- this.visible = (this.css.visibility=='visible' || this.css.visibility=='show' || this.css.visibility=='inherit') ? true : false;
-
-
- // ********* layer object methods *********
-
- // move the layer to a specific position
- this.move = function(x, y)
- {
- this.x = x;
- this.y = y;
- this.css.left = Math.round(this.x)+'px';
- this.css.top = Math.round(this.y)+'px';
- };
-
- // change the layers width and height
- this.resize = function(w,h)
- {
- this.css.width = w+'px';
- this.css.height = h+'px';
- this.width = w;
- this.height = h;
- };
-
- // show or hide the layer
- this.show = function(a)
- {
- if(a == 1) {
- this.css.visibility = 'visible';
- this.visible = true;
- }
- else if(a == 2) {
- this.css.visibility = 'inherit';
- this.visible = true;
- }
- else {
- this.css.visibility = 'hidden';
- this.visible = false;
- }
- };
-
- // write new content into a Layer
- this.write = function(cont)
- {
- this.elm.innerHTML = cont;
- };
-
-};
-
-
// check if input is a valid email address
// By Cal Henderson <cal@iamcal.com>
// http://code.iamcal.com/php/rfc822/
@@ -537,7 +425,7 @@ function rcube_clone_object(obj)
for (var key in obj) {
if (obj[key] && typeof obj[key] === 'object')
- out[key] = clone_object(obj[key]);
+ out[key] = rcube_clone_object(obj[key]);
else
out[key] = obj[key];
}
@@ -717,13 +605,15 @@ if (bw.ie) {
// jQuery plugin to emulate HTML5 placeholder attributes on input elements
jQuery.fn.placeholder = function(text) {
return this.each(function() {
- var elem = $(this);
+ var active = false, elem = $(this);
this.title = text;
+ // Try HTML5 placeholder attribute first
if ('placeholder' in this) {
- elem.attr('placeholder', text); // Try HTML5 placeholder attribute first
+ elem.attr('placeholder', text);
}
- else { // Fallback to Javascript emulation of placeholder
+ // Fallback to Javascript emulation of placeholder
+ else {
this._placeholder = text;
elem.blur(function(e) {
if ($.trim(elem.val()) == "")
@@ -740,7 +630,9 @@ jQuery.fn.placeholder = function(text) {
elem[(active ? 'addClass' : 'removeClass')]('placeholder').attr('spellcheck', active);
});
- if (this != document.activeElement) // Do not blur currently focused element
+ // Do not blur currently focused element (catch exception: #1489008)
+ try { active = this == document.activeElement; } catch(e) {}
+ if (!active)
elem.blur();
}
});
diff --git a/program/js/jstz.min.js b/program/js/jstz.min.js
index 81c5057fd..d5f888cac 100644
--- a/program/js/jstz.min.js
+++ b/program/js/jstz.min.js
@@ -1,11 +1,2 @@
-var jstz=function(){var b=function(a){a=-a.getTimezoneOffset();return null!==a?a:0},c=function(){return b(new Date(2010,0,1,0,0,0,0))},f=function(){return b(new Date(2010,5,1,0,0,0,0))},e=function(){var a=c(),d=f(),b=c()-f();return new jstz.TimeZone(jstz.olson.timezones[0>b?a+",1":0<b?d+",1,s":a+",0"])};return{determine_timezone:function(){"undefined"!==typeof console&&console.log("jstz.determine_timezone() is deprecated and will be removed in an upcoming version. Please use jstz.determine() instead.");
-return e()},determine:e,date_is_dst:function(a){var d=5<a.getMonth()?f():c(),a=b(a);return 0!==d-a}}}();jstz.TimeZone=function(b){var c=null,c=b;"undefined"!==typeof jstz.olson.ambiguity_list[c]&&function(){for(var b=jstz.olson.ambiguity_list[c],e=b.length,a=0,d=b[0];a<e;a+=1)if(d=b[a],jstz.date_is_dst(jstz.olson.dst_start_dates[d])){c=d;break}}();return{name:function(){return c}}};jstz.olson={};
-jstz.olson.timezones={"-720,0":"Etc/GMT+12","-660,0":"Pacific/Pago_Pago","-600,1":"America/Adak","-600,0":"Pacific/Honolulu","-570,0":"Pacific/Marquesas","-540,0":"Pacific/Gambier","-540,1":"America/Anchorage","-480,1":"America/Los_Angeles","-480,0":"Pacific/Pitcairn","-420,0":"America/Phoenix","-420,1":"America/Denver","-360,0":"America/Guatemala","-360,1":"America/Chicago","-360,1,s":"Pacific/Easter","-300,0":"America/Bogota","-300,1":"America/New_York","-270,0":"America/Caracas","-240,1":"America/Halifax",
-"-240,0":"America/Santo_Domingo","-240,1,s":"America/Asuncion","-210,1":"America/St_Johns","-180,1":"America/Godthab","-180,0":"America/Argentina/Buenos_Aires","-180,1,s":"America/Montevideo","-120,0":"America/Noronha","-120,1":"Etc/GMT+2","-60,1":"Atlantic/Azores","-60,0":"Atlantic/Cape_Verde","0,0":"Etc/UTC","0,1":"Europe/London","60,1":"Europe/Berlin","60,0":"Africa/Lagos","60,1,s":"Africa/Windhoek","120,1":"Asia/Beirut","120,0":"Africa/Johannesburg","180,1":"Europe/Moscow","180,0":"Asia/Baghdad",
-"210,1":"Asia/Tehran","240,0":"Asia/Dubai","240,1":"Asia/Yerevan","270,0":"Asia/Kabul","300,1":"Asia/Yekaterinburg","300,0":"Asia/Karachi","330,0":"Asia/Kolkata","345,0":"Asia/Kathmandu","360,0":"Asia/Dhaka","360,1":"Asia/Omsk","390,0":"Asia/Rangoon","420,1":"Asia/Krasnoyarsk","420,0":"Asia/Jakarta","480,0":"Asia/Shanghai","480,1":"Asia/Irkutsk","525,0":"Australia/Eucla","525,1,s":"Australia/Eucla","540,1":"Asia/Yakutsk","540,0":"Asia/Tokyo","570,0":"Australia/Darwin","570,1,s":"Australia/Adelaide",
-"600,0":"Australia/Brisbane","600,1":"Asia/Vladivostok","600,1,s":"Australia/Sydney","630,1,s":"Australia/Lord_Howe","660,1":"Asia/Kamchatka","660,0":"Pacific/Noumea","690,0":"Pacific/Norfolk","720,1,s":"Pacific/Auckland","720,0":"Pacific/Tarawa","765,1,s":"Pacific/Chatham","780,0":"Pacific/Tongatapu","780,1,s":"Pacific/Apia","840,0":"Pacific/Kiritimati"};
-jstz.olson.dst_start_dates={"America/Denver":new Date(2011,2,13,3,0,0,0),"America/Mazatlan":new Date(2011,3,3,3,0,0,0),"America/Chicago":new Date(2011,2,13,3,0,0,0),"America/Mexico_City":new Date(2011,3,3,3,0,0,0),"Atlantic/Stanley":new Date(2011,8,4,7,0,0,0),"America/Asuncion":new Date(2011,9,2,3,0,0,0),"America/Santiago":new Date(2011,9,9,3,0,0,0),"America/Campo_Grande":new Date(2011,9,16,5,0,0,0),"America/Montevideo":new Date(2011,9,2,3,0,0,0),"America/Sao_Paulo":new Date(2011,9,16,5,0,0,0),"America/Los_Angeles":new Date(2011,
-2,13,8,0,0,0),"America/Santa_Isabel":new Date(2011,3,5,8,0,0,0),"America/Havana":new Date(2011,2,13,2,0,0,0),"America/New_York":new Date(2011,2,13,7,0,0,0),"Asia/Gaza":new Date(2011,2,26,23,0,0,0),"Asia/Beirut":new Date(2011,2,27,1,0,0,0),"Europe/Minsk":new Date(2011,2,27,2,0,0,0),"Europe/Helsinki":new Date(2011,2,27,4,0,0,0),"Europe/Istanbul":new Date(2011,2,28,5,0,0,0),"Asia/Damascus":new Date(2011,3,1,2,0,0,0),"Asia/Jerusalem":new Date(2011,3,1,6,0,0,0),"Africa/Cairo":new Date(2010,3,30,4,0,0,
-0),"Asia/Yerevan":new Date(2011,2,27,4,0,0,0),"Asia/Baku":new Date(2011,2,27,8,0,0,0),"Pacific/Auckland":new Date(2011,8,26,7,0,0,0),"Pacific/Fiji":new Date(2010,11,29,23,0,0,0),"America/Halifax":new Date(2011,2,13,6,0,0,0),"America/Goose_Bay":new Date(2011,2,13,2,1,0,0),"America/Miquelon":new Date(2011,2,13,5,0,0,0),"America/Godthab":new Date(2011,2,27,1,0,0,0)};
-jstz.olson.ambiguity_list={"America/Denver":["America/Denver","America/Mazatlan"],"America/Chicago":["America/Chicago","America/Mexico_City"],"America/Asuncion":["Atlantic/Stanley","America/Asuncion","America/Santiago","America/Campo_Grande"],"America/Montevideo":["America/Montevideo","America/Sao_Paulo"],"Asia/Beirut":"Asia/Gaza Asia/Beirut Europe/Minsk Europe/Helsinki Europe/Istanbul Asia/Damascus Asia/Jerusalem Africa/Cairo".split(" "),"Asia/Yerevan":["Asia/Yerevan","Asia/Baku"],"Pacific/Auckland":["Pacific/Auckland",
-"Pacific/Fiji"],"America/Los_Angeles":["America/Los_Angeles","America/Santa_Isabel"],"America/New_York":["America/Havana","America/New_York"],"America/Halifax":["America/Goose_Bay","America/Halifax"],"America/Godthab":["America/Miquelon","America/Godthab"]}; \ No newline at end of file
+/*! jsTimezoneDetect - v1.0.5 - 2013-04-01 */
+(function(e){var t=function(){"use strict";var e="s",n=2011,r=function(e){var t=-e.getTimezoneOffset();return t!==null?t:0},i=function(e,t,n){var r=new Date;return e!==undefined&&r.setFullYear(e),r.setDate(n),r.setMonth(t),r},s=function(e){return r(i(e,0,2))},o=function(e){return r(i(e,5,2))},u=function(e){var t=e.getMonth()>7?o(e.getFullYear()):s(e.getFullYear()),n=r(e);return t-n!==0},a=function(){var t=s(n),r=o(n),i=t-r;return i<0?t+",1":i>0?r+",1,"+e:t+",0"},f=function(){var e=a();return new t.TimeZone(t.olson.timezones[e])},l=function(e){var t=new Date(2010,6,15,1,0,0,0),n={"America/Denver":new Date(2011,2,13,3,0,0,0),"America/Mazatlan":new Date(2011,3,3,3,0,0,0),"America/Chicago":new Date(2011,2,13,3,0,0,0),"America/Mexico_City":new Date(2011,3,3,3,0,0,0),"America/Asuncion":new Date(2012,9,7,3,0,0,0),"America/Santiago":new Date(2012,9,3,3,0,0,0),"America/Campo_Grande":new Date(2012,9,21,5,0,0,0),"America/Montevideo":new Date(2011,9,2,3,0,0,0),"America/Sao_Paulo":new Date(2011,9,16,5,0,0,0),"America/Los_Angeles":new Date(2011,2,13,8,0,0,0),"America/Santa_Isabel":new Date(2011,3,5,8,0,0,0),"America/Havana":new Date(2012,2,10,2,0,0,0),"America/New_York":new Date(2012,2,10,7,0,0,0),"Asia/Beirut":new Date(2011,2,27,1,0,0,0),"Europe/Helsinki":new Date(2011,2,27,4,0,0,0),"Europe/Istanbul":new Date(2011,2,28,5,0,0,0),"Asia/Damascus":new Date(2011,3,1,2,0,0,0),"Asia/Jerusalem":new Date(2011,3,1,6,0,0,0),"Asia/Gaza":new Date(2009,2,28,0,30,0,0),"Africa/Cairo":new Date(2009,3,25,0,30,0,0),"Pacific/Auckland":new Date(2011,8,26,7,0,0,0),"Pacific/Fiji":new Date(2010,10,29,23,0,0,0),"America/Halifax":new Date(2011,2,13,6,0,0,0),"America/Goose_Bay":new Date(2011,2,13,2,1,0,0),"America/Miquelon":new Date(2011,2,13,5,0,0,0),"America/Godthab":new Date(2011,2,27,1,0,0,0),"Europe/Moscow":t,"Asia/Yekaterinburg":t,"Asia/Omsk":t,"Asia/Krasnoyarsk":t,"Asia/Irkutsk":t,"Asia/Yakutsk":t,"Asia/Vladivostok":t,"Asia/Kamchatka":t,"Europe/Minsk":t,"Pacific/Apia":new Date(2010,10,1,1,0,0,0),"Australia/Perth":new Date(2008,10,1,1,0,0,0)};return n[e]};return{determine:f,date_is_dst:u,dst_start_for:l}}();t.TimeZone=function(e){"use strict";var n={"America/Denver":["America/Denver","America/Mazatlan"],"America/Chicago":["America/Chicago","America/Mexico_City"],"America/Santiago":["America/Santiago","America/Asuncion","America/Campo_Grande"],"America/Montevideo":["America/Montevideo","America/Sao_Paulo"],"Asia/Beirut":["Asia/Beirut","Europe/Helsinki","Europe/Istanbul","Asia/Damascus","Asia/Jerusalem","Asia/Gaza"],"Pacific/Auckland":["Pacific/Auckland","Pacific/Fiji"],"America/Los_Angeles":["America/Los_Angeles","America/Santa_Isabel"],"America/New_York":["America/Havana","America/New_York"],"America/Halifax":["America/Goose_Bay","America/Halifax"],"America/Godthab":["America/Miquelon","America/Godthab"],"Asia/Dubai":["Europe/Moscow"],"Asia/Dhaka":["Asia/Yekaterinburg"],"Asia/Jakarta":["Asia/Omsk"],"Asia/Shanghai":["Asia/Krasnoyarsk","Australia/Perth"],"Asia/Tokyo":["Asia/Irkutsk"],"Australia/Brisbane":["Asia/Yakutsk"],"Pacific/Noumea":["Asia/Vladivostok"],"Pacific/Tarawa":["Asia/Kamchatka"],"Pacific/Tongatapu":["Pacific/Apia"],"Africa/Johannesburg":["Asia/Gaza","Africa/Cairo"],"Asia/Baghdad":["Europe/Minsk"]},r=e,i=function(){var e=n[r],i=e.length,s=0,o=e[0];for(;s<i;s+=1){o=e[s];if(t.date_is_dst(t.dst_start_for(o))){r=o;return}}},s=function(){return typeof n[r]!="undefined"};return s()&&i(),{name:function(){return r}}},t.olson={},t.olson.timezones={"-720,0":"Pacific/Majuro","-660,0":"Pacific/Pago_Pago","-600,1":"America/Adak","-600,0":"Pacific/Honolulu","-570,0":"Pacific/Marquesas","-540,0":"Pacific/Gambier","-540,1":"America/Anchorage","-480,1":"America/Los_Angeles","-480,0":"Pacific/Pitcairn","-420,0":"America/Phoenix","-420,1":"America/Denver","-360,0":"America/Guatemala","-360,1":"America/Chicago","-360,1,s":"Pacific/Easter","-300,0":"America/Bogota","-300,1":"America/New_York","-270,0":"America/Caracas","-240,1":"America/Halifax","-240,0":"America/Santo_Domingo","-240,1,s":"America/Santiago","-210,1":"America/St_Johns","-180,1":"America/Godthab","-180,0":"America/Argentina/Buenos_Aires","-180,1,s":"America/Montevideo","-120,0":"America/Noronha","-120,1":"America/Noronha","-60,1":"Atlantic/Azores","-60,0":"Atlantic/Cape_Verde","0,0":"UTC","0,1":"Europe/London","60,1":"Europe/Berlin","60,0":"Africa/Lagos","60,1,s":"Africa/Windhoek","120,1":"Asia/Beirut","120,0":"Africa/Johannesburg","180,0":"Asia/Baghdad","180,1":"Europe/Moscow","210,1":"Asia/Tehran","240,0":"Asia/Dubai","240,1":"Asia/Baku","270,0":"Asia/Kabul","300,1":"Asia/Yekaterinburg","300,0":"Asia/Karachi","330,0":"Asia/Kolkata","345,0":"Asia/Kathmandu","360,0":"Asia/Dhaka","360,1":"Asia/Omsk","390,0":"Asia/Rangoon","420,1":"Asia/Krasnoyarsk","420,0":"Asia/Jakarta","480,0":"Asia/Shanghai","480,1":"Asia/Irkutsk","525,0":"Australia/Eucla","525,1,s":"Australia/Eucla","540,1":"Asia/Yakutsk","540,0":"Asia/Tokyo","570,0":"Australia/Darwin","570,1,s":"Australia/Adelaide","600,0":"Australia/Brisbane","600,1":"Asia/Vladivostok","600,1,s":"Australia/Sydney","630,1,s":"Australia/Lord_Howe","660,1":"Asia/Kamchatka","660,0":"Pacific/Noumea","690,0":"Pacific/Norfolk","720,1,s":"Pacific/Auckland","720,0":"Pacific/Tarawa","765,1,s":"Pacific/Chatham","780,0":"Pacific/Tongatapu","780,1,s":"Pacific/Apia","840,0":"Pacific/Kiritimati"},typeof exports!="undefined"?exports.jstz=t:e.jstz=t})(this); \ No newline at end of file
diff --git a/program/js/list.js b/program/js/list.js
index 660b74d85..8b8a7196e 100644
--- a/program/js/list.js
+++ b/program/js/list.js
@@ -3,7 +3,7 @@
| Roundcube List Widget |
| |
| This file is part of the Roundcube Webmail client |
- | Copyright (C) 2006-2009, The Roundcube Dev Team |
+ | Copyright (C) 2006-2013, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
@@ -30,6 +30,9 @@ function rcube_list_widget(list, p)
this.BACKSPACE_KEY = 8;
this.list = list ? list : null;
+ this.tagname = this.list ? this.list.nodeName.toLowerCase() : 'table';
+ this.thead;
+ this.tbody;
this.frame = null;
this.rows = [];
this.selection = [];
@@ -55,8 +58,8 @@ function rcube_list_widget(list, p)
this.in_selection_before = false;
this.focused = false;
this.drag_mouse_start = null;
- this.dblclick_time = 600;
- this.row_init = function(){};
+ this.dblclick_time = 500; // default value on MS Windows is 500
+ this.row_init = function(){}; // @deprecated; use list.addEventListener('initrow') instead
// overwrite default paramaters
if (p && typeof p === 'object')
@@ -73,11 +76,19 @@ rcube_list_widget.prototype = {
*/
init: function()
{
- if (this.list && this.list.tBodies[0]) {
+ if (this.tagname == 'table' && this.list && this.list.tBodies[0]) {
+ this.thead = this.list.tHead;
+ this.tbody = this.list.tBodies[0];
+ }
+ else if (this.tagname != 'table' && this.list) {
+ this.tbody = this.list;
+ }
+
+ if (this.tbody) {
this.rows = [];
this.rowcount = 0;
- var r, len, rows = this.list.tBodies[0].rows;
+ var r, len, rows = this.tbody.childNodes;
for (r=0, len=rows.length; r<len; r++) {
this.init_row(rows[r]);
@@ -127,7 +138,8 @@ init_row: function(row)
if (document.all)
row.onselectstart = function() { return false; };
- this.row_init(this.rows[uid]);
+ this.row_init(this.rows[uid]); // legacy support
+ this.triggerEvent('initrow', this.rows[uid]);
}
},
@@ -137,16 +149,16 @@ init_row: function(row)
*/
init_header: function()
{
- if (this.list && this.list.tHead) {
+ if (this.thead) {
this.colcount = 0;
var col, r, p = this;
// add events for list columns moving
- if (this.column_movable && this.list.tHead && this.list.tHead.rows) {
- for (r=0; r<this.list.tHead.rows[0].cells.length; r++) {
+ if (this.column_movable && this.thead && this.thead.rows) {
+ for (r=0; r<this.thead.rows[0].cells.length; r++) {
if (this.column_fixed == r)
continue;
- col = this.list.tHead.rows[0].cells[r];
+ col = this.thead.rows[0].cells[r];
col.onmousedown = function(e){ return p.drag_column(e, this); };
this.colcount++;
}
@@ -160,10 +172,16 @@ init_header: function()
*/
clear: function(sel)
{
- var tbody = document.createElement('tbody');
+ if (this.tagname == 'table') {
+ var tbody = document.createElement('tbody');
+ this.list.insertBefore(tbody, this.tbody);
+ this.list.removeChild(this.list.tBodies[1]);
+ this.tbody = tbody;
+ }
+ else {
+ $(this.row_tagname() + ':not(.thead)', this.tbody).remove();
+ }
- this.list.insertBefore(tbody, this.list.tBodies[0]);
- this.list.removeChild(this.list.tBodies[1]);
this.rows = [];
this.rowcount = 0;
@@ -181,12 +199,12 @@ clear: function(sel)
*/
remove_row: function(uid, sel_next)
{
- var obj = this.rows[uid] ? this.rows[uid].obj : null;
+ var node = this.rows[uid] ? this.rows[uid].obj : null;
- if (!obj)
+ if (!node)
return;
- obj.style.display = 'none';
+ node.style.display = 'none';
if (sel_next)
this.select_next();
@@ -199,12 +217,31 @@ remove_row: function(uid, sel_next)
/**
* Add row to the list and initialize it
*/
-insert_row: function(row, attop)
+insert_row: function(row, before)
{
- var tbody = this.list.tBodies[0];
+ var tbody = this.tbody;
+
+ // create a real dom node first
+ if (row.nodeName === undefined) {
+ // for performance reasons use DOM instead of jQuery here
+ var domrow = document.createElement(this.row_tagname());
+ if (row.id) domrow.id = row.id;
+ if (row.className) domrow.className = row.className;
+ if (row.style) $.extend(domrow.style, row.style);
+
+ for (var domcell, col, i=0; row.cols && i < row.cols.length; i++) {
+ col = row.cols[i];
+ domcell = document.createElement(this.col_tagname());
+ if (col.className) domcell.className = col.className;
+ if (col.innerHTML) domcell.innerHTML = col.innerHTML;
+ domrow.appendChild(domcell);
+ }
- if (attop && tbody.rows.length)
- tbody.insertBefore(row, tbody.firstChild);
+ row = domrow;
+ }
+
+ if (before && tbody.childNodes.length)
+ tbody.insertBefore(row, (typeof before == 'object' && before.parentNode == tbody) ? before : tbody.firstChild);
else
tbody.appendChild(row);
@@ -212,6 +249,28 @@ insert_row: function(row, attop)
this.rowcount++;
},
+/**
+ *
+ */
+update_row: function(id, cols, newid, select)
+{
+ var row = this.rows[id];
+ if (!row) return false;
+
+ var domrow = row.obj;
+ for (var domcell, col, i=0; cols && i < cols.length; i++) {
+ this.get_cell(domrow, i).html(cols[i]);
+ }
+
+ if (newid) {
+ delete this.rows[id];
+ domrow.id = 'rcmrow' + newid;
+ this.init_row(domrow);
+
+ if (select)
+ this.selection[0] = newid;
+ }
+},
/**
@@ -230,8 +289,9 @@ focus: function(e)
}
// Un-focus already focused elements (#1487123, #1487316, #1488600, #1488620)
+ // It looks that window.focus() does the job for all browsers, but not Firefox (#1489058)
$(':focus:not(body)').blur();
- $('iframe').each(function() { this.blur(); });
+ window.focus();
if (e || (e = window.event))
rcube_event.cancel(e);
@@ -270,8 +330,8 @@ drag_column: function(e, col)
this.add_dragfix();
// find selected column number
- for (var i=0; i<this.list.tHead.rows[0].cells.length; i++) {
- if (col == this.list.tHead.rows[0].cells[i]) {
+ for (var i=0; i<this.thead.rows[0].cells.length; i++) {
+ if (col == this.thead.rows[0].cells[i]) {
this.selected_column = i;
break;
}
@@ -434,6 +494,7 @@ collapse: function(row)
new_row = new_row.nextSibling;
}
+ this.triggerEvent('listupdate');
return false;
},
@@ -449,7 +510,7 @@ expand: function(row)
this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded, obj:row.obj });
}
else {
- var tbody = this.list.tBodies[0];
+ var tbody = this.tbody;
new_row = tbody.firstChild;
depth = 0;
last_expanded_parent_depth = 0;
@@ -481,6 +542,7 @@ expand: function(row)
new_row = new_row.nextSibling;
}
+ this.triggerEvent('listupdate');
return false;
},
@@ -501,7 +563,7 @@ collapse_all: function(row)
return false;
}
else {
- new_row = this.list.tBodies[0].firstChild;
+ new_row = this.tbody.firstChild;
depth = 0;
}
@@ -523,6 +585,7 @@ collapse_all: function(row)
new_row = new_row.nextSibling;
}
+ this.triggerEvent('listupdate');
return false;
},
@@ -539,7 +602,7 @@ expand_all: function(row)
this.triggerEvent('expandcollapse', { uid:row.uid, expanded:row.expanded, obj:row.obj });
}
else {
- new_row = this.list.tBodies[0].firstChild;
+ new_row = this.tbody.firstChild;
depth = 0;
}
@@ -559,6 +622,8 @@ expand_all: function(row)
}
new_row = new_row.nextSibling;
}
+
+ this.triggerEvent('listupdate');
return false;
},
@@ -605,7 +670,7 @@ get_prev_row: function()
get_first_row: function()
{
if (this.rowcount) {
- var i, len, rows = this.list.tBodies[0].rows;
+ var i, len, rows = this.tbody.childNodes;
for (i=0, len=rows.length-1; i<len; i++)
if (rows[i].id && String(rows[i].id).match(/^rcmrow([a-z0-9\-_=\+\/]+)/i) && this.rows[RegExp.$1] != null)
@@ -618,7 +683,7 @@ get_first_row: function()
get_last_row: function()
{
if (this.rowcount) {
- var i, rows = this.list.tBodies[0].rows;
+ var i, rows = this.tbody.childNodes;
for (i=rows.length-1; i>=0; i--)
if (rows[i].id && String(rows[i].id).match(/^rcmrow([a-z0-9\-_=\+\/]+)/i) && this.rows[RegExp.$1] != null)
@@ -628,6 +693,22 @@ get_last_row: function()
return null;
},
+row_tagname: function()
+{
+ var row_tagnames = { table:'tr', ul:'li', '*':'div' };
+ return row_tagnames[this.tagname] || row_tagnames['*'];
+},
+
+col_tagname: function()
+{
+ var col_tagnames = { table:'td', '*':'span' };
+ return col_tagnames[this.tagname] || col_tagnames['*'];
+},
+
+get_cell: function(row, index)
+{
+ return $(this.col_tagname(), row).eq(index);
+},
/**
* selects or unselects the proper row depending on the modifier key pressed
@@ -774,14 +855,20 @@ shift_select: function(id, control)
if (!this.rows[this.shift_start] || !this.selection.length)
this.shift_start = id;
- var n, from_rowIndex = this.rows[this.shift_start].obj.rowIndex,
- to_rowIndex = this.rows[id].obj.rowIndex,
- i = ((from_rowIndex < to_rowIndex)? from_rowIndex : to_rowIndex),
- j = ((from_rowIndex > to_rowIndex)? from_rowIndex : to_rowIndex);
+ var n, i, j, to_row = this.rows[id],
+ from_rowIndex = this._rowIndex(this.rows[this.shift_start].obj),
+ to_rowIndex = this._rowIndex(to_row.obj);
+
+ if (!to_row.expanded && to_row.has_children)
+ if (to_row = this.rows[(this.row_children(id)).pop()])
+ to_rowIndex = this._rowIndex(to_row.obj);
+
+ i = ((from_rowIndex < to_rowIndex) ? from_rowIndex : to_rowIndex),
+ j = ((from_rowIndex > to_rowIndex) ? from_rowIndex : to_rowIndex);
// iterate through the entire message list
for (n in this.rows) {
- if (this.rows[n].obj.rowIndex >= i && this.rows[n].obj.rowIndex <= j) {
+ if (this._rowIndex(this.rows[n].obj) >= i && this._rowIndex(this.rows[n].obj) <= j) {
if (!this.in_selection(n)) {
this.highlight_row(n, true);
}
@@ -794,6 +881,13 @@ shift_select: function(id, control)
}
},
+/**
+ * Helper method to emulate the rowIndex property of non-tr elements
+ */
+_rowIndex: function(obj)
+{
+ return (obj.rowIndex !== undefined) ? obj.rowIndex : $(obj).prevAll().length;
+},
/**
* Check if given id is part of the current selection
@@ -823,7 +917,7 @@ select_all: function(filter)
for (n in this.rows) {
if (!filter || this.rows[n][filter] == true) {
this.last_selected = n;
- this.highlight_row(n, true);
+ this.highlight_row(n, true, true);
}
else {
$(this.rows[n].obj).removeClass('selected').removeClass('unfocused');
@@ -918,7 +1012,7 @@ get_single_selection: function()
/**
* Highlight/unhighlight a row
*/
-highlight_row: function(id, multiple)
+highlight_row: function(id, multiple, norecur)
{
if (!this.rows[id])
return;
@@ -934,7 +1028,7 @@ highlight_row: function(id, multiple)
if (!this.in_selection(id)) { // select row
this.selection.push(id);
$(this.rows[id].obj).addClass('selected');
- if (!this.rows[id].expanded)
+ if (!norecur && !this.rows[id].expanded)
this.highlight_children(id, true);
}
else { // unselect row
@@ -944,7 +1038,7 @@ highlight_row: function(id, multiple)
this.selection = a_pre.concat(a_post);
$(this.rows[id].obj).removeClass('selected').removeClass('unfocused');
- if (!this.rows[id].expanded)
+ if (!norecur && !this.rows[id].expanded)
this.highlight_children(id, false);
}
}
@@ -962,7 +1056,7 @@ highlight_children: function(id, status)
for (i=0; i<len; i++) {
selected = this.in_selection(children[i]);
if ((status && !selected) || (!status && selected))
- this.highlight_row(children[i], true);
+ this.highlight_row(children[i], true, true);
}
},
@@ -1138,7 +1232,7 @@ drag_mouse_move: function(e)
this.draglayer.html('');
// get subjects of selected messages
- var c, i, n, subject, obj;
+ var i, n, obj, me;
for (n=0; n<this.selection.length; n++) {
// only show 12 lines
if (n>12) {
@@ -1146,38 +1240,28 @@ drag_mouse_move: function(e)
break;
}
+ me = this;
if (obj = this.rows[this.selection[n]].obj) {
- subject = '';
-
- for (c=0, i=0; i<obj.childNodes.length; i++) {
- if (obj.childNodes[i].nodeName == 'TD') {
- if (n == 0)
- this.drag_start_pos = $(obj.childNodes[i]).offset();
-
- if (this.subject_col < 0 || (this.subject_col >= 0 && this.subject_col == c)) {
- var entry, node, tmp_node, nodes = obj.childNodes[i].childNodes;
- // find text node
- for (m=0; m<nodes.length; m++) {
- if ((tmp_node = obj.childNodes[i].childNodes[m]) && (tmp_node.nodeType==3 || tmp_node.nodeName=='A'))
- node = tmp_node;
- }
-
- if (!node)
- break;
-
- subject = $(node).text();
- // remove leading spaces
+ $('> '+this.col_tagname(), obj).each(function(i,elem){
+ if (n == 0)
+ me.drag_start_pos = $(elem).offset();
+
+ if (me.subject_col < 0 || (me.subject_col >= 0 && me.subject_col == i)) {
+ var subject = $(elem).text();
+
+ if (subject) {
+ // remove leading spaces
subject = $.trim(subject);
// truncate line to 50 characters
subject = (subject.length > 50 ? subject.substring(0, 50) + '...' : subject);
- entry = $('<div>').text(subject);
- this.draglayer.append(entry);
- break;
+ var entry = $('<div>').text(subject);
+ me.draglayer.append(entry);
}
- c++;
+
+ return false; // break
}
- }
+ });
}
}
@@ -1232,7 +1316,7 @@ drag_mouse_up: function(e)
// remove temp divs
this.del_dragfix();
- this.triggerEvent('dragend');
+ this.triggerEvent('dragend', e);
return rcube_event.cancel(e);
},
@@ -1252,7 +1336,7 @@ column_drag_mouse_move: function(e)
if (!this.col_draglayer) {
var lpos = $(this.list).offset(),
- cells = this.list.tHead.rows[0].cells;
+ cells = this.thead.rows[0].cells;
// create dragging layer
this.col_draglayer = $('<div>').attr('id', 'rcmcoldraglayer')
@@ -1345,7 +1429,7 @@ column_drag_mouse_up: function(e)
}
}
- this.triggerEvent('column_dragend');
+ this.triggerEvent('column_dragend', e);
return rcube_event.cancel(e);
},
@@ -1408,7 +1492,11 @@ del_dragfix: function()
*/
column_replace: function(from, to)
{
- var len, cells = this.list.tHead.rows[0].cells,
+ // only supported for <table> lists
+ if (!this.thead || !this.thead.rows)
+ return;
+
+ var len, cells = this.thead.rows[0].cells,
elem = cells[from],
before = cells[to],
td = document.createElement('td');
@@ -1421,8 +1509,8 @@ column_replace: function(from, to)
cells[0].parentNode.replaceChild(elem, td);
// replace list cells
- for (r=0, len=this.list.tBodies[0].rows.length; r<len; r++) {
- row = this.list.tBodies[0].rows[r];
+ for (r=0, len=this.tbody.rows.length; r<len; r++) {
+ row = this.tbody.rows[r];
elem = row.cells[from];
before = row.cells[to];
diff --git a/program/js/treelist.js b/program/js/treelist.js
index 47ac0c1a0..5913f44b4 100644
--- a/program/js/treelist.js
+++ b/program/js/treelist.js
@@ -23,400 +23,550 @@
*/
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') + '">&nbsp;</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;
- }
+ // 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') + '">&nbsp;</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