diff options
Diffstat (limited to 'program/js/app.js')
-rw-r--r-- | program/js/app.js | 708 |
1 files changed, 354 insertions, 354 deletions
diff --git a/program/js/app.js b/program/js/app.js index c2456ac4f..cb08ce29d 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -4,7 +4,7 @@ | | | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2013, The Roundcube Dev Team | - | Copyright (C) 2011-2012, Kolab Systems AG | + | Copyright (C) 2011-2013, Kolab Systems AG | | | | Licensed under the GNU General Public License version 3 or | | any later version with exceptions for skins & plugins. | @@ -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); @@ -273,11 +277,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) { @@ -312,7 +317,7 @@ function rcube_webmail() } // detect browser capabilities - if (!this.is_framed()) + if (!this.is_framed() && !this.env.extwin) this.browser_capabilities_check(); break; @@ -459,9 +464,23 @@ function rcube_webmail() this.display_message(this.pending_message[0], this.pending_message[1], this.pending_message[2]); // map implicit containers - if (this.gui_objects.folderlist) + if (this.gui_objects.folderlist) { this.gui_containers.foldertray = $(this.gui_objects.folderlist); + // init treelist widget + if (window.rcube_treelist_widget) { + this.treelist = new rcube_treelist_widget(this.gui_objects.folderlist, { + id_prefix: 'rcmli', + id_encode: this.html_identifier_encode, + id_decode: this.html_identifier_decode, + check_droptarget: function(node){ return !node.virtual && ref.check_droptarget(node.id) } + }); + this.treelist.addEventListener('collapse', function(node){ ref.folder_collapsed(node) }); + this.treelist.addEventListener('expand', function(node){ ref.folder_collapsed(node) }); + this.treelist.addEventListener('select', function(node){ ref.triggerEvent('selectfolder', { folder:node.id, prefix:'rcmli' }) }); + } + } + // activate html5 file drop feature (if browser supports it and if configured) if (this.gui_objects.filedrop && this.env.filedrop && ((window.XMLHttpRequest && XMLHttpRequest.prototype && XMLHttpRequest.prototype.sendAsBinary) || window.FormData)) { $(document.body).bind('dragover dragleave drop', function(e){ return ref.document_drag_hover(e, e.type == 'dragover'); }); @@ -580,18 +599,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; @@ -756,7 +793,7 @@ function rcube_webmail() case 'moveto': if (this.task == 'mail') this.move_messages(props); - else if (this.task == 'addressbook' && this.drag_active) + else if (this.task == 'addressbook') this.copy_contact(null, props); break; @@ -817,15 +854,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); @@ -864,7 +901,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': @@ -900,16 +937,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); @@ -944,8 +978,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; } @@ -967,7 +1001,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': @@ -1012,9 +1046,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); } @@ -1022,11 +1055,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': @@ -1072,6 +1102,12 @@ function rcube_webmail() this.list_contacts(props.source, props.id); 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'); @@ -1146,6 +1182,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 { @@ -1210,7 +1247,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) @@ -1263,11 +1300,12 @@ function rcube_webmail() this.html_identifier = function(str, encode) { - str = String(str); - if (encode) - return Base64.encode(str).replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_'); - else - return str.replace(this.identifier_expr, '_'); + return encode ? this.html_identifier_encode(str) : String(str).replace(this.identifier_expr, '_'); + }; + + this.html_identifier_encode = function(str) + { + return Base64.encode(String(str)).replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_'); }; this.html_identifier_decode = function(str) @@ -1320,29 +1358,9 @@ function rcube_webmail() if (this.preview_read_timer) clearTimeout(this.preview_read_timer); - // save folderlist and folders location/sizes for droptarget calculation in drag_move() - if (this.gui_objects.folderlist && model) { - this.initialBodyScrollTop = bw.ie ? 0 : window.pageYOffset; - this.initialListScrollTop = this.gui_objects.folderlist.parentNode.scrollTop; - - var k, li, height, - list = $(this.gui_objects.folderlist); - pos = list.offset(); - - this.env.folderlist_coords = { x1:pos.left, y1:pos.top, x2:pos.left + list.width(), y2:pos.top + list.height() }; - - this.env.folder_coords = []; - for (k in model) { - if (li = this.get_folder_li(k)) { - // only visible folders - if (height = li.firstChild.offsetHeight) { - pos = $(li.firstChild).offset(); - this.env.folder_coords[k] = { x1:pos.left, y1:pos.top, - x2:pos.left + li.firstChild.offsetWidth, y2:pos.top + height, on:0 }; - } - } - } - } + // prepare treelist widget for dragging interactions + if (this.treelist) + this.treelist.drag_start(); }; this.drag_end = function(e) @@ -1350,87 +1368,28 @@ function rcube_webmail() this.drag_active = false; this.env.last_folder_target = null; - if (this.folder_auto_timer) { - clearTimeout(this.folder_auto_timer); - this.folder_auto_timer = null; - this.folder_auto_expand = null; - } - - // over the folders - if (this.gui_objects.folderlist && this.env.folder_coords) { - for (var k in this.env.folder_coords) { - if (this.env.folder_coords[k].on) - $(this.get_folder_li(k)).removeClass('droptarget'); - } - } + if (this.treelist) + this.treelist.drag_end(); }; this.drag_move = function(e) { - if (this.gui_objects.folderlist && this.env.folder_coords) { - var k, li, div, check, oldclass, + if (this.gui_objects.folderlist) { + var drag_target, oldclass, layerclass = 'draglayernormal', - mouse = rcube_event.get_mouse_pos(e), - pos = this.env.folderlist_coords, - // offsets to compensate for scrolling while dragging a message - boffset = bw.ie ? -document.documentElement.scrollTop : this.initialBodyScrollTop, - moffset = this.initialListScrollTop-this.gui_objects.folderlist.parentNode.scrollTop; + mouse = rcube_event.get_mouse_pos(e); if (this.contact_list && this.contact_list.draglayer) oldclass = this.contact_list.draglayer.attr('class'); - mouse.y += -moffset-boffset; - - // if mouse pointer is outside of folderlist - if (mouse.x < pos.x1 || mouse.x >= pos.x2 || mouse.y < pos.y1 || mouse.y >= pos.y2) { - if (this.env.last_folder_target) { - $(this.get_folder_li(this.env.last_folder_target)).removeClass('droptarget'); - this.env.folder_coords[this.env.last_folder_target].on = 0; - this.env.last_folder_target = null; - } - if (layerclass != oldclass && this.contact_list && this.contact_list.draglayer) - this.contact_list.draglayer.attr('class', layerclass); - return; + // mouse intersects a valid drop target on the treelist + if (this.treelist && (drag_target = this.treelist.intersects(mouse, true))) { + this.env.last_folder_target = drag_target; + layerclass = 'draglayer' + (this.check_droptarget(drag_target) > 1 ? 'copy' : 'normal'); } - - // over the folders - for (k in this.env.folder_coords) { - pos = this.env.folder_coords[k]; - if (mouse.x >= pos.x1 && mouse.x < pos.x2 && mouse.y >= pos.y1 && mouse.y < pos.y2) { - if (check = this.check_droptarget(k)) { - li = this.get_folder_li(k); - div = $(li.getElementsByTagName('div')[0]); - - // if the folder is collapsed, expand it after 1sec and restart the drag & drop process. - if (div.hasClass('collapsed')) { - if (this.folder_auto_timer) - clearTimeout(this.folder_auto_timer); - - this.folder_auto_expand = this.env.mailboxes[k].id; - this.folder_auto_timer = setTimeout(function() { - rcmail.command('collapse-folder', rcmail.folder_auto_expand); - rcmail.drag_start(null); - }, 1000); - } - else if (this.folder_auto_timer) { - clearTimeout(this.folder_auto_timer); - this.folder_auto_timer = null; - this.folder_auto_expand = null; - } - - $(li).addClass('droptarget'); - this.env.folder_coords[k].on = 1; - this.env.last_folder_target = k; - layerclass = 'draglayer' + (check > 1 ? 'copy' : 'normal'); - } - // Clear target, otherwise drag end will trigger move into last valid droptarget - else - this.env.last_folder_target = null; - } - else if (pos.on) { - $(this.get_folder_li(k)).removeClass('droptarget'); - this.env.folder_coords[k].on = 0; - } + else { + // Clear target, otherwise drag end will trigger move into last valid droptarget + this.env.last_folder_target = null; } if (layerclass != oldclass && this.contact_list && this.contact_list.draglayer) @@ -1440,40 +1399,33 @@ function rcube_webmail() this.collapse_folder = function(name) { - var li = this.get_folder_li(name, '', true), - div = $('div:first', li), - ul = $('ul:first', li); + if (this.treelist) + this.treelist.toggle(name); + }; - if (div.hasClass('collapsed')) { - ul.show(); - div.removeClass('collapsed').addClass('expanded'); - var reg = new RegExp('&'+urlencode(name)+'&'); - this.env.collapsed_folders = this.env.collapsed_folders.replace(reg, ''); - } - else if (div.hasClass('expanded')) { - ul.hide(); - div.removeClass('expanded').addClass('collapsed'); - this.env.collapsed_folders = this.env.collapsed_folders+'&'+urlencode(name)+'&'; + this.folder_collapsed = function(node) + { + var prefname = this.env.task == 'addressbook' ? 'collapsed_abooks' : 'collapsed_folders'; + + if (node.collapsed) { + this.env[prefname] = this.env[prefname] + '&'+urlencode(node.id)+'&'; // select the folder if one of its childs is currently selected // don't select if it's virtual (#1488346) - if (this.env.mailbox.indexOf(name + this.env.delimiter) == 0 && !$(li).hasClass('virtual')) + if (this.env.mailbox && this.env.mailbox.indexOf(name + this.env.delimiter) == 0 && !node.virtual) this.command('list', name); } - else - return; - - // Work around a bug in IE6 and IE7, see #1485309 - if (bw.ie6 || bw.ie7) { - var siblings = li.nextSibling ? li.nextSibling.getElementsByTagName('ul') : null; - if (siblings && siblings.length && (li = siblings[0]) && li.style && li.style.display != 'none') { - li.style.display = 'none'; - li.style.display = ''; - } + else { + var reg = new RegExp('&'+urlencode(node.id)+'&'); + this.env[prefname] = this.env[prefname].replace(reg, ''); } - this.command('save-pref', { name: 'collapsed_folders', value: this.env.collapsed_folders }); - this.set_unread_count_display(name, false); + if (!this.drag_active) { + this.command('save-pref', { name: prefname, value: this.env[prefname] }); + + if (this.env.unread_counts) + this.set_unread_count_display(node.id, false); + } }; this.doc_mouse_up = function(e) @@ -1498,9 +1450,9 @@ function rcube_webmail() if (this.drag_active && model && this.env.last_folder_target) { var target = model[this.env.last_folder_target]; - $(this.get_folder_li(this.env.last_folder_target)).removeClass('droptarget'); this.env.last_folder_target = null; list.draglayer.hide(); + this.drag_end(e); if (!this.drag_menu(e, target)) this.command('moveto', target); @@ -1558,7 +1510,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); }; @@ -1574,12 +1526,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); } }; @@ -1587,11 +1540,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) @@ -1631,7 +1584,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 = []; @@ -1679,15 +1632,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) { @@ -1696,10 +1662,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; }; @@ -1783,10 +1747,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'; @@ -1850,8 +1811,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'); @@ -1896,8 +1856,7 @@ function rcube_webmail() html = cols[c]; col.innerHTML = html; - - row.appendChild(col); + row.cols.push(col); } list.insert_row(row, attop); @@ -1998,7 +1957,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); @@ -2260,7 +2219,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])) { @@ -2436,7 +2395,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; @@ -3024,11 +2983,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); } }; @@ -3044,10 +3004,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; } @@ -3119,6 +3079,13 @@ function rcube_webmail() form._draft.value = draft ? '1' : ''; form.action = this.add_url(form.action, '_unlock', msgid); form.action = this.add_url(form.action, '_lang', lang); + + // register timer to notify about connection timeout + this.submit_timer = setTimeout(function(){ + ref.set_busy(false, null, msgid); + ref.display_message(ref.get_label('requesttimedout'), 'error'); + }, this.env.request_timeout * 1000); + form.submit(); }; @@ -3129,7 +3096,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++) { @@ -3148,8 +3115,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 }); } }; @@ -3339,6 +3308,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); }; @@ -3383,12 +3361,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]) { @@ -3404,7 +3415,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); } @@ -3413,7 +3424,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; @@ -3457,7 +3468,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 @@ -3488,8 +3499,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; @@ -3516,7 +3527,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) { @@ -3714,14 +3725,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 { @@ -4096,6 +4112,7 @@ function rcube_webmail() var n, id, sid, 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) @@ -4157,7 +4174,7 @@ function rcube_webmail() else if (!this.env.search_request) folder = group ? 'G'+src+group : src; - this.select_folder(folder); + this.select_folder(folder, '', true); this.env.source = src; this.env.group = group; @@ -4288,7 +4305,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); } @@ -4296,7 +4313,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); } @@ -4358,21 +4375,7 @@ 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.update_row(cid, cols_arr, newcid, true); }; // add row to contacts list @@ -4382,7 +4385,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 || ''); @@ -4392,10 +4395,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); } list.insert_row(row); @@ -4461,7 +4464,7 @@ function rcube_webmail() this.name_input.bind('keydown', function(e){ return rcmail.add_input_keydown(e); }); this.env.group_renaming = true; - var link, li = this.get_folder_li(this.env.source+this.env.group, 'rcmliG'); + var link, li = this.get_folder_li('G'+this.env.source+this.env.group,'',true); if (li && (link = li.firstChild)) { $(link).hide().before(this.name_input); } @@ -4481,11 +4484,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))) { - 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]; } @@ -4504,8 +4504,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) : this.get_folder_li(this.env.source); - this.name_input_li.insertAfter(li); + var ul, li; + + // find list (UL) element + if (type == 'contactsearch') + ul = this.gui_objects.folderlist; + else + ul = $('ul.groups', this.get_folder_li(this.env.source,'',true)); + + // append to the list + li = $('li:last', ul); + if (li.length) + this.name_input_li.insertAfter(li); + else { + this.name_input_li.appendTo(ul); + ul.show(); // make sure the list is visible + } } this.name_input.select().focus(); @@ -4562,11 +4576,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(); @@ -4589,14 +4605,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 @@ -4605,15 +4619,13 @@ function rcube_webmail() this.reset_add_input(); var key = 'G'+prop.source+prop.id, - li = this.get_folder_li(key), - 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); this.env.contactfolders[newkey] = this.env.contactfolders[key]; this.env.contactfolders[newkey].id = prop.newid; this.env.group = prop.newid; @@ -4624,45 +4636,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; - - 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), - prefix = 'rcmliG' + this.html_identifier(prop.source); - - // 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 { + $(this.treelist.get_item(key)).children().first().html(prop.name); + this.env.contactfolders[key].name = this.env.contactgroups[key].name = prop.name; } - 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() @@ -4894,47 +4883,16 @@ 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), '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.select_folder('S'+id); + 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; 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() { @@ -4953,10 +4911,8 @@ function rcube_webmail() this.remove_search_item = function(id) { var li, key = 'S'+id; - if ((li = this.get_folder_li(key))) { + if (this.treelist.remove(key)) { this.triggerEvent('search_delete', { id:id, li:li }); - - li.parentNode.removeChild(li); } this.env.search_id = null; @@ -4975,7 +4931,7 @@ function rcube_webmail() } this.reset_qsearch(); - this.select_folder('S'+id); + this.select_folder('S'+id, '', true); // reset vars this.env.current_page = 1; @@ -5054,18 +5010,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 ]); + } }; @@ -5690,14 +5644,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 { @@ -5707,7 +5662,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 @@ -5717,9 +5672,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); } } } @@ -5728,6 +5683,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() { @@ -5740,17 +5704,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; } @@ -5758,17 +5722,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 @@ -5781,7 +5749,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))) { @@ -5827,7 +5798,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; @@ -5840,14 +5811,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++) { @@ -6069,9 +6040,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 @@ -6345,14 +6316,31 @@ function rcube_webmail() // redirect to url specified in location header if not empty var location_url = request.getResponseHeader("Location"); - if (location_url) + 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); }; + // callback when an iframe finished loading + this.iframe_loaded = function(unlock) + { + this.set_busy(false, null, unlock); + + if (this.submit_timer) + clearTimeout(this.submit_timer); + }; + // post the given form to a hidden iframe this.async_upload_form = function(form, action, onload) { @@ -6452,6 +6440,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; }, @@ -6591,6 +6580,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() |