From 4764079c6af092a6dfa18306601e1b33482fb756 Mon Sep 17 00:00:00 2001 From: alecpl <alec@alec.pl> Date: Thu, 05 May 2011 03:26:50 -0400 Subject: [PATCH] - Fix bug where messages were deleted instead moved to trash folder after Shift key was used (#1487902), small code improvements --- program/js/app.js | 1135 +++++++++++++++++++++++++++++++++++------------------------ 1 files changed, 676 insertions(+), 459 deletions(-) diff --git a/program/js/app.js b/program/js/app.js index b57c447..99446d6 100644 --- a/program/js/app.js +++ b/program/js/app.js @@ -3,7 +3,7 @@ | Roundcube Webmail Client Script | | | | This file is part of the Roundcube Webmail client | - | Copyright (C) 2005-2010, Roundcube Dev, - Switzerland | + | Copyright (C) 2005-2010, The Roundcube Dev Team | | Licensed under the GNU GPL | | | +-----------------------------------------------------------------------+ @@ -16,7 +16,6 @@ $Id$ */ - function rcube_webmail() { @@ -37,7 +36,7 @@ // webmail client settings this.dblclick_time = 500; - this.message_time = 3000; + this.message_time = 2000; this.identifier_expr = new RegExp('[^0-9a-z\-_]', 'gi'); @@ -63,7 +62,7 @@ // set environment variable(s) this.set_env = function(p, value) { - if (p != null && typeof(p) == 'object' && !value) + if (p != null && typeof p === 'object' && !value) for (var n in p) this.env[n] = p[n]; else @@ -136,6 +135,14 @@ return; } + // Enable debug console + if (!window.console || !window.console.log) { + window.console = new rcube_console(); + } + else { + $('#console').hide(); + } + // find all registered gui containers for (var n in this.gui_containers) this.gui_containers[n] = $('#'+this.gui_containers[n]); @@ -148,7 +155,7 @@ this.init_buttons(); // tell parent window that this frame is loaded - if (this.env.framed && parent.rcmail && parent.rcmail.set_busy) { + if (this.is_framed()) { parent.rcmail.set_busy(false, null, parent.rcmail.env.frame_lock); parent.rcmail.env.frame_lock = null; } @@ -222,13 +229,13 @@ } // make preview/message frame visible - if (this.env.action == 'preview' && this.env.framed && parent.rcmail) { + if (this.env.action == 'preview' && this.is_framed()) { this.enable_command('compose', 'add-contact', false); parent.rcmail.show_contentframe(true); } } else if (this.env.action == 'compose') { - this.env.compose_commands = ['send-attachment', 'remove-attachment', 'send', 'toggle-editor']; + this.env.compose_commands = ['send-attachment', 'remove-attachment', 'send', 'cancel', 'toggle-editor']; if (this.env.drafts_mailbox) this.env.compose_commands.push('savedraft') @@ -317,9 +324,9 @@ } } - if ((this.env.action=='add' || this.env.action=='edit') && this.gui_objects.editform) { + if ((this.env.action == 'add' || this.env.action == 'edit') && this.gui_objects.editform) { this.enable_command('save', true); - $("input[type='text']").first().select(); + this.init_contact_form(); } else if (this.gui_objects.qsearchbox) { this.enable_command('search', 'reset-search', 'moveto', true); @@ -336,15 +343,22 @@ case 'settings': this.enable_command('preferences', 'identities', 'save', 'folders', true); - if (this.env.action=='identities') { + if (this.env.action == 'identities') { this.enable_command('add', this.env.identities_level < 2); } - else if (this.env.action=='edit-identity' || this.env.action=='add-identity') { + else if (this.env.action == 'edit-identity' || this.env.action == 'add-identity') { this.enable_command('add', this.env.identities_level < 2); this.enable_command('save', 'delete', 'edit', 'toggle-editor', true); } - else if (this.env.action=='folders') - this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', 'delete-folder', 'enable-threading', 'disable-threading', true); + else if (this.env.action == 'folders') { + this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', true); + } + else if (this.env.action == 'edit-folder' && this.gui_objects.editform) { + this.enable_command('save', 'folder-size', true); + parent.rcmail.env.messagecount = this.env.messagecount; + parent.rcmail.enable_command('purge', this.env.messagecount); + $("input[type='text']").first().select(); + } if (this.gui_objects.identitieslist) { this.identity_list = new rcube_list_widget(this.gui_objects.identitieslist, {multiselect:false, draggable:false, keyboard:false}); @@ -378,10 +392,11 @@ // detect client timezone $('#rcmlogintz').val(new Date().getTimezoneOffset() / -60); - // display 'loading' message on form submit + // display 'loading' message on form submit, lock submit button $('form').submit(function () { - rcmail.display_message(rcmail.get_label('loading'), 'loading'); - }); + $('input[type=submit]', this).attr('disabled', true); + rcmail.display_message('', 'loading'); + }); this.enable_command('login', true); break; @@ -407,9 +422,9 @@ // execute all foreign onload scripts // @deprecated for (var i in this.onloads) { - if (typeof(this.onloads[i]) == 'string') + if (typeof this.onloads[i] === 'string') eval(this.onloads[i]); - else if (typeof(this.onloads[i]) == 'function') + else if (typeof this.onloads[i] === 'function') this.onloads[i](); } @@ -434,7 +449,7 @@ // command not supported or allowed if (!this.commands[command]) { // pass command to parent window - if (this.env.framed && parent.rcmail && parent.rcmail.command) + if (this.is_framed()) parent.rcmail.command(command, props); return false; @@ -447,24 +462,24 @@ } // process external commands - if (typeof this.command_handlers[command] == 'function') { + if (typeof this.command_handlers[command] === 'function') { var ret = this.command_handlers[command](props, obj); - return ret !== null ? ret : (obj ? false : true); + return ret !== undefined ? ret : (obj ? false : true); } - else if (typeof this.command_handlers[command] == 'string') { + else if (typeof this.command_handlers[command] === 'string') { var ret = window[this.command_handlers[command]](props, obj); - return ret !== null ? ret : (obj ? false : true); + return ret !== undefined ? ret : (obj ? false : true); } // trigger plugin hooks this.triggerEvent('actionbefore', {props:props, action:command}); - var event_ret = this.triggerEvent('before'+command, props); - if (typeof event_ret != 'undefined') { + var ret = this.triggerEvent('before'+command, props); + if (ret !== undefined) { // abort if one the handlers returned false - if (event_ret === false) + if (ret === false) return false; else - props = event_ret; + props = ret; } // process internal command @@ -626,11 +641,14 @@ input_name.focus(); break; } - else if (input_email.length && !rcube_check_email(input_email.val())) { + else if (this.task == 'settings' && input_email.length && (this.env.identities_level % 2) == 0 && !rcube_check_email(input_email.val())) { alert(this.get_label('noemailwarning')); input_email.focus(); break; } + + // clear empty input fields + $('input.placeholder').each(function(){ if (this.value == this._placeholder) this.value = ''; }); } this.gui_objects.editform.submit(); @@ -639,13 +657,13 @@ case 'delete': // mail task - if (this.task=='mail') + if (this.task == 'mail') this.delete_messages(); // addressbook task - else if (this.task=='addressbook') + else if (this.task == 'addressbook') this.delete_contacts(); // user settings task - else if (this.task=='settings') + else if (this.task == 'settings') this.delete_identity(); break; @@ -790,10 +808,10 @@ case 'compose': var url = this.env.comm_path+'&_action=compose'; - if (this.task=='mail') { + if (this.task == 'mail') { url += '&_mbox='+urlencode(this.env.mailbox); - if (this.env.mailbox==this.env.drafts_mailbox) { + if (this.env.mailbox == this.env.drafts_mailbox) { var uid; if (uid = this.get_single_uid()) url += '&_draft_uid='+uid; @@ -802,7 +820,7 @@ url += '&_to='+urlencode(props); } // modify url if we're in addressbook - else if (this.task=='addressbook') { + else if (this.task == 'addressbook') { // switch to mail compose step directly if (props && props.indexOf('@') > 0) { url = this.get_task_url('mail', url); @@ -811,24 +829,21 @@ } // use contact_id passed as command parameter - var a_cids = []; + var n, len, a_cids = []; if (props) a_cids.push(props); // get selected contacts else if (this.contact_list) { var selection = this.contact_list.get_selection(); - for (var n=0; n<selection.length; n++) + for (n=0, len=selection.length; n<len; n++) a_cids.push(selection[n]); } if (a_cids.length) - this.http_request('mailto', '_cid='+urlencode(a_cids.join(','))+'&_source='+urlencode(this.env.source), true); + this.http_post('mailto', {_cid: a_cids.join(','), _source: this.env.source}, true); break; } - - // don't know if this is necessary... - url = url.replace(/&_framed=1/, ''); this.redirect(url); break; @@ -988,31 +1003,29 @@ case 'export': if (this.contact_list.rowcount > 0) { - var add_url = (this.env.source ? '_source='+urlencode(this.env.source)+'&' : ''); - if (this.env.search_request) - add_url += '_search='+this.env.search_request; - - this.goto_url('export', add_url); + this.goto_url('export', { _source:this.env.source, _gid:this.env.group, _search:this.env.search_request }); } + break; + + case 'upload-photo': + this.upload_contact_photo(props); + break; + + case 'delete-photo': + this.replace_contact_photo('-del-'); break; // user settings commands case 'preferences': - this.goto_url(''); - break; - case 'identities': - this.goto_url('settings/identities'); - break; - case 'folders': - this.goto_url('settings/folders'); + this.goto_url('settings/' + command); break; // unified command call (command name == function name) default: var func = command.replace(/-/g, '_'); - if (this[func] && typeof this[func] == 'function') + if (this[func] && typeof this[func] === 'function') this[func](props); break; } @@ -1119,12 +1132,12 @@ this.reload = function(delay) { - if (this.env.framed && parent.rcmail) + if (this.is_framed()) parent.rcmail.reload(delay); else if (delay) window.setTimeout(function(){ rcmail.reload(); }, delay); else if (window.location) - location.href = this.env.comm_path; + location.href = this.env.comm_path + (this.env.action ? '&_action='+this.env.action : ''); }; // Add variable to GET string, replace old value if exists @@ -1147,6 +1160,12 @@ else return url + '?' + name + '=' + value; }; + + this.is_framed = function() + { + return (this.env.framed && parent.rcmail && parent.rcmail != this && parent.rcmail.command); + }; + /*********************************************************/ /********* event handling methods *********/ @@ -1345,22 +1364,20 @@ this.doc_mouse_up = function(e) { - var model, list, li; + var model, list, li, id; - if (this.message_list) { - if (!rcube_mouse_is_over(e, this.message_list.list.parentNode)) - this.message_list.blur(); + if (list = this.message_list) { + if (!rcube_mouse_is_over(e, list.list.parentNode)) + list.blur(); else - this.message_list.focus(); - list = this.message_list; + list.focus(); model = this.env.mailboxes; } - else if (this.contact_list) { - if (!rcube_mouse_is_over(e, this.contact_list.list.parentNode)) - this.contact_list.blur(); + else if (list = this.contact_list) { + if (!rcube_mouse_is_over(e, list.list.parentNode)) + list.blur(); else - this.contact_list.focus(); - list = this.contact_list; + list.focus(); model = this.env.contactfolders; } else if (this.ksearch_value) { @@ -1381,8 +1398,8 @@ // reset 'pressed' buttons if (this.buttons_sel) { - for (var id in this.buttons_sel) - if (typeof id != 'function') + for (id in this.buttons_sel) + if (typeof id !== 'function') this.button_out(this.buttons_sel[id], id); this.buttons_sel = {}; } @@ -1480,8 +1497,6 @@ this.command('previouspage'); else if (list.key_pressed == 34) this.command('nextpage'); - else - list.shiftkey = false; }; this.msglist_get_preview = function() @@ -1527,7 +1542,7 @@ if (this.task == 'mail') allow = (this.env.mailboxes[id] && this.env.mailboxes[id].id != this.env.mailbox && !this.env.mailboxes[id].virtual); else if (this.task == 'settings') - allow = (id != this.env.folder); + allow = (id != this.env.mailbox); else if (this.task == 'addressbook') { if (id != this.env.source && this.env.contactfolders[id]) { if (this.env.contactfolders[id].type == 'group') { @@ -1668,7 +1683,7 @@ message.expanded = true; } else if (message.has_children) { - if (typeof(message.expanded) == 'undefined' && (this.env.autoexpand_threads == 1 || (this.env.autoexpand_threads == 2 && message.unread_children))) { + if (message.expanded === undefined && (this.env.autoexpand_threads == 1 || (this.env.autoexpand_threads == 2 && message.unread_children))) { message.expanded = true; } } @@ -1687,7 +1702,7 @@ var action = flags.mbox == this.env.drafts_mailbox ? 'compose' : 'show'; var uid_param = flags.mbox == this.env.drafts_mailbox ? '_draft_uid' : '_uid'; cols.subject = '<a href="./?_task=mail&_action='+action+'&_mbox='+urlencode(flags.mbox)+'&'+uid_param+'='+uid+'"'+ - ' onclick="return rcube_event.cancel(event)">'+cols.subject+'</a>'; + ' onclick="return rcube_event.cancel(event)" onmouseover="rcube_webmail.long_subject_title(this,'+(message.depth+1)+')">'+cols.subject+'</a>'; } // add each submitted col @@ -1756,7 +1771,7 @@ { var update, add_url = ''; - if (typeof sort_col == 'undefined') + if (sort_col === undefined) sort_col = this.env.sort_col; if (!sort_order) sort_order = this.env.sort_order; @@ -1822,9 +1837,9 @@ this.show_contentframe(true); else { if (!this.env.frame_lock) { - (parent.rcmail ? parent.rcmail : this).env.frame_lock = this.set_busy(true, 'loading'); + (this.is_framed() ? parent.rcmail : this).env.frame_lock = this.set_busy(true, 'loading'); } - target.location.href = this.env.comm_path+url; + this.location_href(this.env.comm_path+url, target); // mark as read and change mbox unread counter if (action == 'preview' && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread && this.env.preview_pane_mark_read >= 0) { @@ -1903,7 +1918,7 @@ var url = '', target = window; if (!mbox) - mbox = this.env.mailbox; + mbox = this.env.mailbox ? this.env.mailbox : 'INBOX'; if (add_url) url += add_url; @@ -1946,7 +1961,7 @@ // load message list to target frame/window if (mbox) { this.set_busy(true, 'loading'); - target.location.href = this.env.comm_path+'&_mbox='+urlencode(mbox)+(page ? '&_page='+page : '')+url; + this.location_href(this.env.comm_path+'&_mbox='+urlencode(mbox)+(page ? '&_page='+page : '')+url, target); } }; @@ -2352,7 +2367,7 @@ // copy selected messages to the specified mailbox this.copy_messages = function(mbox) { - if (mbox && typeof mbox == 'object') + if (mbox && typeof mbox === 'object') mbox = mbox.id; // exit if current or no mailbox specified or if selection is empty @@ -2372,22 +2387,24 @@ } } + add_url += '&_uid='+this.uids_to_list(a_uids); + // send request to server - this.http_post('copy', '_uid='+a_uids.join(',')+'&_mbox='+urlencode(this.env.mailbox)+add_url, lock); + this.http_post('copy', '_mbox='+urlencode(this.env.mailbox)+add_url, lock); }; // move selected messages to the specified mailbox this.move_messages = function(mbox) { - if (mbox && typeof mbox == 'object') + if (mbox && typeof mbox === 'object') mbox = mbox.id; // exit if current or no mailbox specified or if selection is empty if (!mbox || mbox == this.env.mailbox || (!this.env.uid && (!this.message_list || !this.message_list.get_selection().length))) return; - var lock = false; - var add_url = '&_target_mbox='+urlencode(mbox)+'&_from='+(this.env.action ? this.env.action : ''); + var lock = false, + add_url = '&_target_mbox='+urlencode(mbox)+'&_from='+(this.env.action ? this.env.action : ''); // show wait message if (this.env.action == 'show') { @@ -2405,17 +2422,19 @@ // delete selected messages from the current mailbox this.delete_messages = function() { - var selection = this.message_list ? $.merge([], this.message_list.get_selection()) : []; + var uid, i, len, trash = this.env.trash_mailbox, + list = this.message_list, + selection = list ? $.merge([], list.get_selection()) : []; // exit if no mailbox specified or if selection is empty if (!this.env.uid && !selection.length) return; // also select childs of collapsed rows - for (var uid, i=0, len=selection.length; i<len; i++) { + for (i=0, len=selection.length; i<len; i++) { uid = selection[i]; - if (this.message_list.rows[uid].has_children && !this.message_list.rows[uid].expanded) - this.message_list.select_childs(uid); + if (list.rows[uid].has_children && !list.rows[uid].expanded) + list.select_childs(uid); } // if config is set to flag for deletion @@ -2424,17 +2443,18 @@ return false; } // if there isn't a defined trash mailbox or we are in it - else if (!this.env.trash_mailbox || this.env.mailbox == this.env.trash_mailbox) + // @TODO: we should check if defined trash mailbox exists + else if (!trash || this.env.mailbox == trash) this.permanently_remove_messages(); // if there is a trash mailbox defined and we're not currently in it else { // if shift was pressed delete it immediately - if (this.message_list && this.message_list.shiftkey) { + if (list && list.shiftkey) { if (confirm(this.get_label('deletemessagesconfirm'))) this.permanently_remove_messages(); } else - this.move_messages(this.env.trash_mailbox); + this.move_messages(trash); } return true; @@ -2728,24 +2748,23 @@ this.expunge_mailbox = function(mbox) { - var lock = false; - var add_url = ''; + var lock = false, + url = '_mbox='+urlencode(mbox); // lock interface if it's the active mailbox if (mbox == this.env.mailbox) { lock = this.set_busy(true, 'loading'); - add_url = '&_reload=1'; + url += '&_reload=1'; } // send request to server - var url = '_mbox='+urlencode(mbox); - this.http_post('expunge', url+add_url, lock); + this.http_post('expunge', url, lock); }; this.purge_mailbox = function(mbox) { - var lock = false; - var add_url = ''; + var lock = false, + url = '_mbox='+urlencode(mbox); if (!confirm(this.get_label('purgefolderconfirm'))) return false; @@ -2753,13 +2772,11 @@ // lock interface if it's the active mailbox if (mbox == this.env.mailbox) { lock = this.set_busy(true, 'loading'); - add_url = '&_reload=1'; + url += '&_reload=1'; } // send request to server - var url = '_mbox='+urlencode(mbox); - this.http_post('purge', url+add_url, lock); - return true; + this.http_post('purge', url, lock); }; // test if purge command is allowed @@ -2806,7 +2823,7 @@ input_subject = $("input[name='_subject']"), input_message = $("[name='_message']").get(0), html_mode = $("input[name='_is_html']").val() == '1', - ac_fields = ['cc', 'bcc', 'replyto', 'mailreplyto', 'mailfollowupto']; + ac_fields = ['cc', 'bcc', 'replyto', 'followupto']; // init live search events this.init_address_input_events(input_to); @@ -2873,7 +2890,7 @@ // check if all files has been uploaded for (var key in this.env.attachments) { - if (typeof this.env.attachments[key] == 'object' && !this.env.attachments[key].complete) { + if (typeof this.env.attachments[key] === 'object' && !this.env.attachments[key].complete) { alert(this.get_label('notuploadedwarning')); return false; } @@ -2987,11 +3004,11 @@ this.compose_field_hash = function(save) { // check input fields - var value_to = $("[name='_to']").val(); - var value_cc = $("[name='_cc']").val(); - var value_bcc = $("[name='_bcc']").val(); - var value_subject = $("[name='_subject']").val(); - var str = ''; + var ed, str = '', + value_to = $("[name='_to']").val(), + value_cc = $("[name='_cc']").val(), + value_bcc = $("[name='_bcc']").val(), + value_subject = $("[name='_subject']").val(); if (value_to) str += value_to+':'; @@ -3002,9 +3019,8 @@ if (value_subject) str += value_subject+':'; - var editor = tinyMCE.get(this.env.composebody); - if (editor) - str += editor.getContent(); + if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody))) + str += ed.getContent(); else str += $("[name='_message']").val(); @@ -3154,37 +3170,13 @@ if (!form) return false; - // get file input fields - var send = false; - for (var n=0; n<form.elements.length; n++) - if (form.elements[n].type=='file' && form.elements[n].value) { - send = true; - break; - } + // get file input field, count files on capable browser + var field = $('input[type=file]', form).get(0), + files = field.files ? field.files.length : field.value ? 1 : 0; // create hidden iframe and post upload form - if (send) { - var ts = new Date().getTime(); - var frame_name = 'rcmupload'+ts; - - // have to do it this way for IE - // otherwise the form will be posted to a new window - if (document.all) { - var html = '<iframe name="'+frame_name+'" src="program/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>'; - document.body.insertAdjacentHTML('BeforeEnd',html); - } - else { // for standards-compilant browsers - var frame = document.createElement('iframe'); - frame.name = frame_name; - frame.style.border = 'none'; - frame.style.width = 0; - frame.style.height = 0; - frame.style.visibility = 'hidden'; - document.body.appendChild(frame); - } - - // handle upload errors, parsing iframe content in onload - $(frame_name).bind('load', {ts:ts}, function(e) { + if (files) { + var frame_name = this.async_upload_form(form, 'upload', function(e) { var d, content = ''; try { if (this.contentDocument) { @@ -3193,7 +3185,7 @@ d = this.contentWindow.document; } content = d.childNodes[0].innerHTML; - } catch (e) {} + } catch (err) {} if (!content.match(/add2attachment/) && (!bw.opera || (rcmail.env.uploadframe && rcmail.env.uploadframe == e.data.ts))) { if (!content.match(/display_message/)) @@ -3205,13 +3197,10 @@ rcmail.env.uploadframe = e.data.ts; }); - form.target = frame_name; - form.action = this.env.comm_path+'&_action=upload&_uploadid='+ts; - form.setAttribute('enctype', 'multipart/form-data'); - form.submit(); - // display upload indicator and cancel button - var content = this.get_label('uploading'); + var content = this.get_label('uploading' + (files > 1 ? 'many' : '')), + ts = frame_name.replace(/^rcmupload/, ''); + if (this.env.loadingicon) content = '<img src="'+this.env.loadingicon+'" alt="" />'+content; if (this.env.cancelicon) @@ -3231,8 +3220,7 @@ if (!this.gui_objects.attachmentlist) return false; - var li = $('<li>').attr('id', name).html(att.html); - var indicator; + var indicator, li = $('<li>').attr('id', name).html(att.html); // replace indicator's li if (upload_id && (indicator = document.getElementById(upload_id))) { @@ -3259,7 +3247,7 @@ return false; var list = this.gui_objects.attachmentlist.getElementsByTagName("li"); - for (i=0;i<list.length;i++) + for (i=0; i<list.length; i++) if (list[i].id == name) this.gui_objects.attachmentlist.removeChild(list[i]); }; @@ -3267,7 +3255,7 @@ this.remove_attachment = function(name) { if (name && this.env.attachments[name]) - this.http_post('remove-attachment', '_file='+urlencode(name)); + this.http_post('remove-attachment', { _id:this.env.compose_id, _file:name }); return true; }; @@ -3436,13 +3424,13 @@ end = inp_value.substring(p+this.ksearch_value.length, inp_value.length); // insert all members of a group - if (typeof this.env.contacts[id] == 'object' && this.env.contacts[id].id) { + if (typeof this.env.contacts[id] === 'object' && this.env.contacts[id].id) { insert += this.env.contacts[id].name + ', '; this.group2expand = $.extend({}, this.env.contacts[id]); this.group2expand.input = this.ksearch_input; this.http_request('group-expand', '_source='+urlencode(this.env.contacts[id].source)+'&_gid='+urlencode(this.env.contacts[id].id), false); } - else if (typeof this.env.contacts[id] == 'string') + else if (typeof this.env.contacts[id] === 'string') insert = this.env.contacts[id] + ', '; this.ksearch_input.value = pre + insert + end; @@ -3479,21 +3467,20 @@ min = this.env.autocomplete_min_length; // trim query string - q = q.replace(/(^\s+|\s+$)/g, ''); + q = $.trim(q); // Don't (re-)search if the last results are still active if (q == this.ksearch_value) return; - if (q.length < min) { + if (q.length && q.length < min) { if (!this.env.acinfo) { - var label = this.get_label('autocompletechars'); - label = label.replace('$min', min); - this.env.acinfo = this.display_message(label); + this.env.acinfo = this.display_message( + this.get_label('autocompletechars').replace('$min', min)); } return; } - else if (this.env.acinfo && q.length == min) { + else if (this.env.acinfo) { this.hide_message(this.env.acinfo); } @@ -3541,7 +3528,7 @@ // add each result line to list for (i=0; i < a_results.length; i++) { - text = typeof a_results[i] == 'object' ? a_results[i].name : a_results[i]; + text = typeof a_results[i] === 'object' ? a_results[i].name : a_results[i]; li = document.createElement('LI'); li.innerHTML = text.replace(new RegExp('('+RegExp.escape(s_val)+')', 'ig'), '##$1%%').replace(/</g, '<').replace(/>/g, '>').replace(/##([^%]+)%%/g, '<b>$1</b>'); li.onmouseover = function(){ ref.ksearch_select(this); }; @@ -3589,7 +3576,7 @@ if (this.ksearch_pane) this.ksearch_pane.hide(); - }; + }; /*********************************************************/ @@ -3664,7 +3651,7 @@ add_url += '&_search='+this.env.search_request; this.set_busy(true, 'loading'); - target.location.href = this.env.comm_path + (src ? '&_source='+urlencode(src) : '') + add_url; + this.location_href(this.env.comm_path + (src ? '&_source='+urlencode(src) : '') + add_url, target); }; // send remote request to load contacts list @@ -3710,7 +3697,7 @@ add_url += '&_gid='+urlencode(this.env.group); this.set_busy(true); - target.location.href = this.env.comm_path+'&_action='+action+'&_source='+urlencode(this.env.source)+'&_cid='+urlencode(cid) + add_url; + this.location_href(this.env.comm_path+'&_action='+action+'&_source='+urlencode(this.env.source)+'&_cid='+urlencode(cid) + add_url, target); } return true; }; @@ -3764,6 +3751,9 @@ this.show_contentframe(false); } + if (this.env.group) + qs += '&_gid='+urlencode(this.env.group); + // also send search request to get the right records from the next page if (this.env.search_request) qs += '&_search='+this.env.search_request; @@ -3777,25 +3767,25 @@ // update a contact record in the list this.update_contact_row = function(cid, cols_arr, newcid) { - var row; - if (this.contact_list.rows[cid] && (row = this.contact_list.rows[cid].obj)) { - for (var c=0; c<cols_arr.length; c++) + var c, row, list = this.contact_list; + + cid = String(cid).replace(this.identifier_expr, '_'); + + 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 = String(newcid).replace(this.identifier_expr, '_'); row.id = 'rcmrow' + newcid; - this.contact_list.remove_row(cid); - this.contact_list.init_row(row); - this.contact_list.selection[0] = newcid; + list.remove_row(cid); + list.init_row(row); + list.selection[0] = newcid; row.style.display = ''; } - - return true; } - - return false; }; // add row to contacts list @@ -3809,7 +3799,7 @@ even = rowcount%2, row = document.createElement('tr'); - row.id = 'rcmrow'+cid; + row.id = 'rcmrow'+String(cid).replace(this.identifier_expr, '_'); row.className = 'contact '+(even ? 'even' : 'odd'); if (this.contact_list.in_selection(cid)) @@ -3826,6 +3816,28 @@ this.contact_list.insert_row(row); this.enable_command('export', (this.contact_list.rowcount > 0)); + }; + + this.init_contact_form = function() + { + var ref = this, col; + + this.set_photo_actions($('#ff_photo').val()); + + for (col in this.env.coltypes) + this.init_edit_field(col, null); + + $('.contactfieldgroup .row a.deletebutton').click(function() { + ref.delete_edit_field(this); + return false; + }); + + $('select.addfieldmenu').change(function(e) { + ref.insert_edit_field($(this).val(), $(this).attr('rel'), this); + this.selectedIndex = 0; + }); + + $("input[type='text']:visible").first().focus(); }; this.group_create = function() @@ -3941,9 +3953,10 @@ this.env.contactfolders[key] = this.env.contactgroups[key] = prop; var link = $('<a>').attr('href', '#') + .attr('rel', prop.source+':'+prop.id) .bind('click', function() { return rcmail.command('listgroup', prop, this);}) .html(prop.name); - var li = $('<li>').attr('id', 'rcmli'+key) + var li = $('<li>').attr('id', 'rcmli'+key.replace(this.identifier_expr, '_')) .addClass('contactgroup') .append(link) .insertAfter(this.get_folder_li(prop.source)); @@ -3956,13 +3969,198 @@ { this.reset_add_input(); - var key = 'G'+prop.source+prop.id, link, li = this.get_folder_li(key); + var key = 'G'+prop.source+prop.id, + li = this.get_folder_li(key), + link; - if (li && (link = li.firstChild) && link.tagName.toLowerCase() == 'a') + // group ID has changed, replace link node and identifiers + if (li && prop.newid) { + var newkey = 'G'+prop.source+prop.newid; + li.id = String('rcmli'+newkey).replace(this.identifier_expr, '_'); + this.env.contactfolders[newkey] = this.env.contactfolders[key]; + this.env.contactfolders[newkey].id = prop.newid; + this.env.group = prop.newid; + + var newprop = $.extend({}, prop); + newprop.id = prop.newid; + newprop.type = 'group'; + + link = $('<a>').attr('href', '#') + .attr('rel', prop.source+':'+prop.newid) + .bind('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.triggerEvent('group_update', { id:prop.id, source:prop.source, name:prop.name, li:li[0] }); + this.triggerEvent('group_update', { id:prop.id, source:prop.source, name:prop.name, li:li[0], newid:prop.newid }); + }; + + + this.init_edit_field = function(col, elem) + { + if (!elem) + elem = $('.ff_' + col); + + elem.focus(function(){ ref.focus_textfield(this); }) + .blur(function(){ ref.blur_textfield(this); }) + .each(function(){ this._placeholder = this.title = ref.env.coltypes[col].label; ref.blur_textfield(this); }); + }; + + this.insert_edit_field = function(col, section, menu) + { + // just make pre-defined input field visible + var elem = $('#ff_'+col); + if (elem.length) { + elem.show().focus(); + $(menu).children('option[value="'+col+'"]').attr('disabled', true); + } + else { + var lastelem = $('.ff_'+col), + appendcontainer = $('#contactsection'+section+' .contactcontroller'+col); + + if (!appendcontainer.length) + appendcontainer = $('<fieldset>').addClass('contactfieldgroup contactcontroller'+col).insertAfter($('#contactsection'+section+' .contactfieldgroup').last()); + + if (appendcontainer.length && appendcontainer.get(0).nodeName == 'FIELDSET') { + var input, colprop = this.env.coltypes[col], + row = $('<div>').addClass('row'), + cell = $('<div>').addClass('contactfieldcontent data'), + label = $('<div>').addClass('contactfieldlabel label'); + + if (colprop.subtypes_select) + label.html(colprop.subtypes_select); + else + label.html(colprop.label); + + var name_suffix = colprop.limit != 1 ? '[]' : ''; + if (colprop.type == 'text' || colprop.type == 'date') { + input = $('<input>') + .addClass('ff_'+col) + .attr('type', 'text') + .attr('name', '_'+col+name_suffix) + .attr('size', colprop.size) + .appendTo(cell); + + this.init_edit_field(col, input); + } + else if (colprop.type == 'composite') { + var childcol, cp, first; + for (var childcol in colprop.childs) { + cp = colprop.childs[childcol]; + input = $('<input>') + .addClass('ff_'+childcol) + .attr('type', 'text') + .attr('name', '_'+childcol+name_suffix) + .attr('size', cp.size) + .appendTo(cell); + cell.append(" "); + this.init_edit_field(childcol, input); + if (!first) first = input; + } + input = first; // set focus to the first of this composite fields + } + else if (colprop.type == 'select') { + input = $('<select>') + .addClass('ff_'+col) + .attr('name', '_'+col+name_suffix) + .appendTo(cell); + + var options = input.attr('options'); + options[options.length] = new Option('---', ''); + if (colprop.options) + $.each(colprop.options, function(i, val){ options[options.length] = new Option(val, i); }); + } + + if (input) { + var delbutton = $('<a href="#del"></a>') + .addClass('contactfieldbutton deletebutton') + .attr('title', this.get_label('delete')) + .attr('rel', col) + .html(this.env.delbutton) + .click(function(){ ref.delete_edit_field(this); return false }) + .appendTo(cell); + + row.append(label).append(cell).appendTo(appendcontainer.show()); + input.first().focus(); + + // disable option if limit reached + if (!colprop.count) colprop.count = 0; + if (++colprop.count == colprop.limit && colprop.limit) + $(menu).children('option[value="'+col+'"]').attr('disabled', true); + } + } + } + }; + + this.delete_edit_field = function(elem) + { + var col = $(elem).attr('rel'), + colprop = this.env.coltypes[col], + fieldset = $(elem).parents('fieldset.contactfieldgroup'), + addmenu = fieldset.parent().find('select.addfieldmenu'); + + // just clear input but don't hide the last field + if (--colprop.count <= 0 && colprop.visible) + $(elem).parent().children('input').val('').blur(); + else { + $(elem).parents('div.row').remove(); + // hide entire fieldset if no more rows + if (!fieldset.children('div.row').length) + fieldset.hide(); + } + + // enable option in add-field selector or insert it if necessary + if (addmenu.length) { + var option = addmenu.children('option[value="'+col+'"]'); + if (option.length) + option.attr('disabled', false); + else + option = $('<option>').attr('value', col).html(colprop.label).appendTo(addmenu); + addmenu.show(); + } + }; + + + this.upload_contact_photo = function(form) + { + if (form && form.elements._photo.value) { + this.async_upload_form(form, 'upload-photo', function(e) { + rcmail.set_busy(false, null, rcmail.photo_upload_id); + }); + + // display upload indicator + this.photo_upload_id = this.set_busy(true, 'uploading'); + } + }; + + this.replace_contact_photo = function(id) + { + var img_src = id == '-del-' ? this.env.photo_placeholder : + this.env.comm_path + '&_action=photo&_source=' + this.env.source + '&_cid=' + this.env.cid + '&_photo=' + id; + + this.set_photo_actions(id); + $(this.gui_objects.contactphoto).children('img').attr('src', img_src); + }; + + this.photo_upload_end = function() + { + this.set_busy(false, null, this.photo_upload_id); + delete this.photo_upload_id; + }; + + this.set_photo_actions = function(id) + { + var n, buttons = this.buttons['upload-photo']; + for (n=0; n < buttons.length; n++) + $('#'+buttons[n].id).html(this.get_label(id == '-del-' ? 'addphoto' : 'replacephoto')); + + $('#ff_photo').val(id); + this.enable_command('upload-photo', this.env.coltypes.photo ? true : false); + this.enable_command('delete-photo', this.env.coltypes.photo && id != '-del-'); }; @@ -3973,16 +4171,12 @@ this.init_subscription_list = function() { var p = this; - this.subscription_list = new rcube_list_widget(this.gui_objects.subscriptionlist, {multiselect:false, draggable:true, keyboard:false, toggleselect:true}); + this.subscription_list = new rcube_list_widget(this.gui_objects.subscriptionlist, + {multiselect:false, draggable:true, keyboard:false, toggleselect:true}); this.subscription_list.addEventListener('select', function(o){ p.subscription_select(o); }); this.subscription_list.addEventListener('dragstart', function(o){ p.drag_active = true; }); this.subscription_list.addEventListener('dragend', function(o){ p.subscription_move_folder(o); }); this.subscription_list.row_init = function (row) { - var anchors = row.obj.getElementsByTagName('a'); - if (anchors[0]) - anchors[0].onclick = function() { p.command('rename-folder', row.id); return false; }; - if (anchors[1]) - anchors[1].onclick = function() { p.command('delete-folder', row.id); return false; }; row.obj.onmouseover = function() { p.focus_subscription(row.id); }; row.obj.onmouseout = function() { p.unfocus_subscription(row.id); }; }; @@ -4002,7 +4196,7 @@ add_url = '&_framed=1'; target = window.frames[this.env.contentframe]; } - target.location.href = this.env.comm_path+'&_action=edit-prefs&_section='+id+add_url; + this.location_href(this.env.comm_path+'&_action=edit-prefs&_section='+id+add_url, target); } return true; @@ -4031,7 +4225,7 @@ if (action && (id || action=='add-identity')) { this.set_busy(true); - target.location.href = this.env.comm_path+'&_action='+action+'&_iid='+id+add_url; + this.location_href(this.env.comm_path+'&_action='+action+'&_iid='+id+add_url, target); } return true; @@ -4056,20 +4250,21 @@ this.focus_subscription = function(id) { var row, folder, - reg = RegExp('['+RegExp.escape(this.env.delimiter)+']?[^'+RegExp.escape(this.env.delimiter)+']+$'); + delim = RegExp.escape(this.env.delimiter), + reg = RegExp('['+delim+']?[^'+delim+']+$'); - if (this.drag_active && this.env.folder && (row = document.getElementById(id))) + if (this.drag_active && this.env.mailbox && (row = document.getElementById(id))) if (this.env.subscriptionrows[id] && (folder = this.env.subscriptionrows[id][0])) { if (this.check_droptarget(folder) && - !this.env.subscriptionrows[this.get_folder_row_id(this.env.folder)][2] && - (folder != this.env.folder.replace(reg, '')) && - (!folder.match(new RegExp('^'+RegExp.escape(this.env.folder+this.env.delimiter))))) { + !this.env.subscriptionrows[this.get_folder_row_id(this.env.mailbox)][2] && + (folder != this.env.mailbox.replace(reg, '')) && + (!folder.match(new RegExp('^'+RegExp.escape(this.env.mailbox+this.env.delimiter))))) { this.set_env('dstfolder', folder); $(row).addClass('droptarget'); } } - else if (this.env.folder.match(new RegExp(RegExp.escape(this.env.delimiter)))) { + else if (this.env.mailbox.match(new RegExp(delim))) { this.set_env('dstfolder', this.env.delimiter); $(this.subscription_list.frame).addClass('droptarget'); } @@ -4089,141 +4284,55 @@ this.subscription_select = function(list) { var id, folder; - if ((id = list.get_single_selection()) && - this.env.subscriptionrows['rcmrow'+id] && - (folder = this.env.subscriptionrows['rcmrow'+id][0])) - this.set_env('folder', folder); - else - this.set_env('folder', null); - if (this.gui_objects.createfolderhint) - $(this.gui_objects.createfolderhint).html(this.env.folder ? this.get_label('addsubfolderhint') : ''); + if (list && (id = list.get_single_selection()) && + (folder = this.env.subscriptionrows['rcmrow'+id]) + ) { + this.set_env('mailbox', folder[0]); + this.show_folder(folder[0]); + this.enable_command('delete-folder', !folder[2]); + } + else { + this.env.mailbox = null; + this.show_contentframe(false); + this.enable_command('delete-folder', 'purge', false); + } }; this.subscription_move_folder = function(list) { - var reg = RegExp('['+RegExp.escape(this.env.delimiter)+']?[^'+RegExp.escape(this.env.delimiter)+']+$'); - if (this.env.folder && this.env.dstfolder && (this.env.dstfolder != this.env.folder) && - (this.env.dstfolder != this.env.folder.replace(reg, ''))) { - var reg = new RegExp('[^'+RegExp.escape(this.env.delimiter)+']*['+RegExp.escape(this.env.delimiter)+']', 'g'); - var basename = this.env.folder.replace(reg, ''); - var newname = this.env.dstfolder==this.env.delimiter ? basename : this.env.dstfolder+this.env.delimiter+basename; + var delim = RegExp.escape(this.env.delimiter), + reg = RegExp('['+delim+']?[^'+delim+']+$'); - var lock = this.set_busy(true, 'foldermoving'); - this.http_post('rename-folder', '_folder_oldname='+urlencode(this.env.folder)+'&_folder_newname='+urlencode(newname), lock); + if (this.env.mailbox && this.env.dstfolder && (this.env.dstfolder != this.env.mailbox) && + (this.env.dstfolder != this.env.mailbox.replace(reg, '')) + ) { + reg = new RegExp('[^'+delim+']*['+delim+']', 'g'); + var lock = this.set_busy(true, 'foldermoving'), + basename = this.env.mailbox.replace(reg, ''), + newname = this.env.dstfolder==this.env.delimiter ? basename : this.env.dstfolder+this.env.delimiter+basename; + + this.http_post('rename-folder', '_folder_oldname='+urlencode(this.env.mailbox)+'&_folder_newname='+urlencode(newname), lock); } this.drag_active = false; this.unfocus_subscription(this.get_folder_row_id(this.env.dstfolder)); }; // tell server to create and subscribe a new mailbox - this.create_folder = function(name) + this.create_folder = function() { - if (this.edit_folder) - this.reset_folder_rename(); - - var form; - if ((form = this.gui_objects.editform) && form.elements['_folder_name']) { - name = form.elements['_folder_name'].value; - - if (name.indexOf(this.env.delimiter)>=0) { - alert(this.get_label('forbiddencharacter')+' ('+this.env.delimiter+')'); - return false; - } - - if (this.env.folder && name != '') - name = this.env.folder+this.env.delimiter+name; - - var lock = this.set_busy(true, 'foldercreating'); - this.http_post('create-folder', '_name='+urlencode(name), lock); - } - else if (form.elements['_folder_name']) - form.elements['_folder_name'].focus(); - }; - - // start renaming the mailbox name. - // this will replace the name string with an input field - this.rename_folder = function(id) - { - var temp, row, form; - - // reset current renaming - if (temp = this.edit_folder) { - this.reset_folder_rename(); - if (temp == id) - return; - } - - if (id && this.env.subscriptionrows[id] && (row = document.getElementById(id))) { - var reg = new RegExp('.*['+RegExp.escape(this.env.delimiter)+']'); - this.name_input = document.createElement('input'); - this.name_input.type = 'text'; - this.name_input.value = this.env.subscriptionrows[id][0].replace(reg, ''); - - reg = new RegExp('['+RegExp.escape(this.env.delimiter)+']?[^'+RegExp.escape(this.env.delimiter)+']+$'); - this.name_input.__parent = this.env.subscriptionrows[id][0].replace(reg, ''); - this.name_input.onkeydown = function(e){ rcmail.name_input_keydown(e); }; - - row.cells[0].replaceChild(this.name_input, row.cells[0].firstChild); - this.edit_folder = id; - this.name_input.select(); - - if (form = this.gui_objects.editform) - form.onsubmit = function(){ return false; }; - } - }; - - // remove the input field and write the current mailbox name to the table cell - this.reset_folder_rename = function() - { - var cell = this.name_input ? this.name_input.parentNode : null; - - if (cell && this.edit_folder && this.env.subscriptionrows[this.edit_folder]) - $(cell).html(this.env.subscriptionrows[this.edit_folder][1]); - - this.edit_folder = null; - }; - - // handler for keyboard events on the input field - this.name_input_keydown = function(e) - { - var key = rcube_event.get_keycode(e); - - // enter - if (key==13) { - var newname = this.name_input ? this.name_input.value : null; - if (this.edit_folder && newname) { - if (newname.indexOf(this.env.delimiter)>=0) { - alert(this.get_label('forbiddencharacter')+' ('+this.env.delimiter+')'); - return false; - } - - if (this.name_input.__parent) - newname = this.name_input.__parent + this.env.delimiter + newname; - - var lock = this.set_busy(true, 'folderrenaming'); - this.http_post('rename-folder', '_folder_oldname='+urlencode(this.env.subscriptionrows[this.edit_folder][0])+'&_folder_newname='+urlencode(newname), lock); - } - } - // escape - else if (key==27) - this.reset_folder_rename(); + this.show_folder('', this.env.mailbox); }; // delete a specific mailbox with all its messages - this.delete_folder = function(id) + this.delete_folder = function(name) { - var folder = this.env.subscriptionrows[id][0]; - - if (this.edit_folder) - this.reset_folder_rename(); + var id = this.get_folder_row_id(name ? name : this.env.mailbox), + folder = this.env.subscriptionrows[id][0]; if (folder && confirm(this.get_label('deletefolderconfirm'))) { var lock = this.set_busy(true, 'folderdeleting'); - this.http_post('delete-folder', '_mboxes='+urlencode(folder), lock); - this.set_env('folder', null); - - $(this.gui_objects.createfolderhint).html(''); + this.http_post('delete-folder', '_mbox='+urlencode(folder), lock); } }; @@ -4280,18 +4389,8 @@ // set messages count to zero row.cells[1].innerHTML = '*'; - // update subscription/threading checkboxes + // update subscription checkbox $('input[name="_subscribed[]"]', row).val(name).attr('checked', true); - $('input[name="_threaded[]"]', row).val(name).attr('checked', false); - - var elem; - // add new folder to rename-folder list and clear input field - if (form = this.gui_objects.editform) { - if (elem = form.elements['_folder_oldname']) - elem.options[elem.options.length] = new Option(name, name); - if (elem = form.elements['_folder_name']) - elem.value = ''; - } } this.init_subscription_list(); @@ -4305,72 +4404,37 @@ // replace an existing table row with a new folder line this.replace_folder_row = function(oldfolder, newfolder, display_name, before) { - var form, elm, - id = this.get_folder_row_id(oldfolder), + var id = this.get_folder_row_id(oldfolder), row = document.getElementById(id); // replace an existing table row (if found) this.add_folder_row(newfolder, display_name, row, before); - - // rename folder in rename-folder dropdown - if ((form = this.gui_objects.editform) && (elm = form.elements['_folder_oldname'])) { - for (var i=0; i<elm.options.length; i++) { - if (elm.options[i].value == oldfolder) { - elm.options[i].text = display_name; - elm.options[i].value = newfolder; - break; - } - } - - form.elements['_folder_newname'].value = ''; - } }; // remove the table row of a specific mailbox from the table // (the row will not be removed, just hidden) this.remove_folder_row = function(folder) { - var form, elm, row, id = this.get_folder_row_id(folder); + var row, id = this.get_folder_row_id(folder); if (id && (row = document.getElementById(id))) row.style.display = 'none'; - - // remove folder from rename-folder list - if ((form = this.gui_objects.editform) && (elm = form.elements['_folder_oldname'])) { - for (var i=0; i<elm.options.length; i++) { - if (elm.options[i].value == folder) { - elm.options[i] = null; - break; - } - } - } - - if (form && (elm = form.elements['_folder_newname'])) - elm.value = ''; }; this.subscribe = function(folder) { - if (folder) - this.http_post('subscribe', '_mbox='+urlencode(folder)); + if (folder) { + var lock = this.display_message(this.get_label('foldersubscribing'), 'loading'); + this.http_post('subscribe', '_mbox='+urlencode(folder), lock); + } }; this.unsubscribe = function(folder) { - if (folder) - this.http_post('unsubscribe', '_mbox='+urlencode(folder)); - }; - - this.enable_threading = function(folder) - { - if (folder) - this.http_post('enable-threading', '_mbox='+urlencode(folder)); - }; - - this.disable_threading = function(folder) - { - if (folder) - this.http_post('disable-threading', '_mbox='+urlencode(folder)); + if (folder) { + var lock = this.display_message(this.get_label('folderunsubscribing'), 'loading'); + this.http_post('unsubscribe', '_mbox='+urlencode(folder), lock); + } }; // helper method to find a specific mailbox row ID @@ -4389,7 +4453,7 @@ var cell, td, new_row = document.createElement('tr'); - for(var n=0; n<row.cells.length; n++) { + for (var n=0; n<row.cells.length; n++) { cell = row.cells[n]; td = document.createElement('td'); @@ -4403,6 +4467,50 @@ } return new_row; + }; + + // when user select a folder in manager + this.show_folder = function(folder, path, force) + { + var target = window, + url = '&_action=edit-folder&_mbox='+urlencode(folder); + + if (path) + url += '&_path='+urlencode(path); + + if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) { + target = window.frames[this.env.contentframe]; + url += '&_framed=1'; + } + + if (String(target.location.href).indexOf(url) >= 0 && !force) { + this.show_contentframe(true); + } + else { + if (!this.env.frame_lock) { + (parent.rcmail ? parent.rcmail : this).env.frame_lock = this.set_busy(true, 'loading'); + } + this.location_href(this.env.comm_path+url, target); + } + }; + + // disables subscription checkbox (for protected folder) + this.disable_subscription = function(folder) + { + var id = this.get_folder_row_id(folder); + if (id) + $('input[name="_subscribed[]"]', $('#'+id)).attr('disabled', true); + }; + + this.folder_size = function(folder) + { + var lock = this.set_busy(true, 'loading'); + this.http_post('folder-size', '_mbox='+urlencode(folder), lock); + }; + + this.folder_size_update = function(size) + { + $('#folder-size').replaceWith(size); }; @@ -4421,7 +4529,7 @@ this.init_buttons = function() { for (var cmd in this.buttons) { - if (typeof cmd != 'string') + if (typeof cmd !== 'string') continue; for (var i=0; i< this.buttons[cmd].length; i++) { @@ -4482,7 +4590,7 @@ obj.src = button[state]; } // set class name according to button state - else if (obj && typeof(button[state])!='undefined') { + else if (obj && button[state] !== undefined) { button.status = state; obj.className = button[state]; } @@ -4582,6 +4690,23 @@ } }; + + this.focus_textfield = function(elem) + { + elem._hasfocus = true; + var $elem = $(elem); + if ($elem.hasClass('placeholder') || $elem.val() == elem._placeholder) + $elem.val('').removeClass('placeholder').attr('spellcheck', true); + }; + + this.blur_textfield = function(elem) + { + elem._hasfocus = false; + var $elem = $(elem); + if (elem._placeholder && (!$elem.val() || $elem.val() == elem._placeholder)) + $elem.addClass('placeholder').attr('spellcheck', false).val(elem._placeholder); + }; + // write to the document/window title this.set_pagetitle = function(title) { @@ -4593,7 +4718,7 @@ this.display_message = function(msg, type) { // pass command to parent window - if (this.env.framed && parent.rcmail) + if (this.is_framed()) return parent.rcmail.display_message(msg, type); if (!this.gui_objects.message) { @@ -4605,58 +4730,89 @@ type = type ? type : 'notice'; - var date = new Date(), - id = type + date.getTime(); - + var ref = this, + key = msg, + date = new Date(), + id = type + date.getTime(), + timeout = this.message_time * (type == 'error' || type == 'warning' ? 2 : 1); + if (type == 'loading') { + key = 'loading'; + timeout = this.env.request_timeout * 1000; if (!msg) msg = this.get_label('loading'); - - // The same message of type 'loading' is already displayed - if (this.messages[msg]) { - this.messages[msg].elements.push(id); - return id; - } } - var ref = this, - obj = $('<div>').addClass(type).html(msg), - cont = $(this.gui_objects.message).show(); - - if (type == 'loading') { - obj.appendTo(cont); - this.messages[msg] = {'obj': obj, 'elements': [id]}; - window.setTimeout(function() { rcmail.hide_message(id); }, this.env.request_timeout * 1000); + // The same message is already displayed + if (this.messages[key]) { + // replace label + if (this.messages[key].obj) + this.messages[key].obj.html(msg); + // store label in stack + if (type == 'loading') { + this.messages[key].labels.push({'id': id, 'msg': msg}); + } + // add element and set timeout + this.messages[key].elements.push(id); + window.setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout); return id; } - else { - obj.appendTo(cont).bind('mousedown', function(){ return ref.hide_message(obj); }); - window.setTimeout(function(){ ref.hide_message(obj, true); }, this.message_time); - return obj; + + // create DOM object and display it + var obj = $('<div>').addClass(type).html(msg).data('key', key), + cont = $(this.gui_objects.message).append(obj).show(); + + this.messages[key] = {'obj': obj, 'elements': [id]}; + + if (type == 'loading') { + this.messages[key].labels = [{'id': id, 'msg': msg}]; } + else { + obj.click(function() { return ref.hide_message(obj); }); + } + + window.setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout); + return id; }; // make a message to disapear this.hide_message = function(obj, fade) { // pass command to parent window - if (this.env.framed && parent.rcmail) + if (this.is_framed()) return parent.rcmail.hide_message(obj, fade); - if (typeof(obj) == 'object') { - // custom message + var k, n, i, msg, 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]; } + // Hide message by id else { - // 'loading' message - var k, n, m = this.messages; for (k in m) { for (n in m[k].elements) { if (m[k] && m[k].elements[n] == obj) { 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'](); delete m[k]; + } + // set pending action label for 'loading' message + else if (k == 'loading') { + for (i in m[k].labels) { + if (m[k].labels[i].id == obj) { + delete m[k].labels[i]; + } + else { + msg = m[k].labels[i].msg; + } + m[k].obj.html(msg); + } } } } @@ -4671,7 +4827,7 @@ var current_li, target_li; if ((current_li = this.get_folder_li(old, prefix))) { - $(current_li).removeClass('selected').removeClass('unfocused'); + $(current_li).removeClass('selected').addClass('unfocused'); } if ((target_li = this.get_folder_li(name, prefix))) { $(target_li).removeClass('unfocused').addClass('selected'); @@ -4700,17 +4856,18 @@ // and for setting some message list global variables this.set_message_coltypes = function(coltypes, repl) { - this.env.coltypes = coltypes; + var list = this.message_list, + thead = list ? list.list.tHead : null, + cell, col, n, len, th, tr; - // set correct list titles - var thead = this.gui_objects.messagelist ? this.gui_objects.messagelist.tHead : null, - cell, col, n, len; + this.env.coltypes = coltypes; // replace old column headers if (thead) { if (repl) { - var th = document.createElement('thead'), - tr = document.createElement('tr'); + th = document.createElement('thead'); + tr = document.createElement('tr'); + for (c=0, len=repl.length; c < len; c++) { cell = document.createElement('td'); cell.innerHTML = repl[c].html; @@ -4720,6 +4877,7 @@ } th.appendChild(tr); thead.parentNode.replaceChild(th, thead); + thead = th; } for (n=0, len=this.env.coltypes.length; n<len; n++) { @@ -4743,15 +4901,16 @@ if ((n = $.inArray('subject', this.env.coltypes)) >= 0) { this.set_env('subject_col', n); - if (this.message_list) - this.message_list.subject_col = n; + if (list) + list.subject_col = n; } if ((n = $.inArray('flag', this.env.coltypes)) >= 0) this.set_env('flagged_col', n); if ((n = $.inArray('status', this.env.coltypes)) >= 0) this.set_env('status_col', n); - this.message_list.init_header(); + if (list) + list.init_header(); }; // replace content of row count display @@ -4774,7 +4933,7 @@ this.set_quota = function(content) { if (content && this.gui_objects.quotadisplay) { - if (typeof(content) == 'object' && content.type == 'image') + if (typeof content === 'object' && content.type == 'image') this.percent_indicator(this.gui_objects.quotadisplay, content); else $(this.gui_objects.quotadisplay).html(content); @@ -4794,11 +4953,14 @@ // update the mailbox count display this.set_unread_count_display = function(mbox, set_title) { - var reg, text_obj, item, mycount, childcount, div; + var reg, link, text_obj, item, mycount, childcount, div; if (item = this.get_folder_li(mbox)) { mycount = this.env.unread_counts[mbox] ? this.env.unread_counts[mbox] : 0; - text_obj = item.getElementsByTagName('a')[0]; + link = $(item).children('a').eq(0); + text_obj = link.children('span.unreadcount'); + if (!text_obj.length && mycount) + text_obj = $('<span>').addClass('unreadcount').appendTo(link); reg = /\s+\([0-9]+\)$/i; childcount = 0; @@ -4810,12 +4972,10 @@ childcount += this.env.unread_counts[k]; } - if (mycount && text_obj.innerHTML.match(reg)) - text_obj.innerHTML = text_obj.innerHTML.replace(reg, ' ('+mycount+')'); - else if (mycount) - text_obj.innerHTML += ' ('+mycount+')'; - else - text_obj.innerHTML = text_obj.innerHTML.replace(reg, ''); + if (mycount && text_obj.length) + text_obj.html(' ('+mycount+')'); + else if (text_obj.length) + text_obj.remove(); // set parent's display reg = new RegExp(RegExp.escape(this.env.delimiter) + '[^' + RegExp.escape(this.env.delimiter) + ']+$'); @@ -4919,8 +5079,9 @@ quota_width = parseInt(quota / 100 * width), pos = $(obj).position(); - // Opera bug? + // workarounds for Opera and Webkit bugs pos.top = Math.max(0, pos.top); + pos.left = Math.max(0, pos.left); this.env.indicator_width = width; this.env.indicator_height = height; @@ -4958,7 +5119,7 @@ bar1.addClass('quota_mid'); } else { - main.addClass(' quota_text_normal'); + main.addClass(' quota_text_low'); bar1.addClass('quota_low'); } @@ -4997,47 +5158,74 @@ /********************************************************/ /********* remote request methods *********/ /********************************************************/ + + // compose a valid url with the given parameters + this.url = function(action, query) + { + var querystring = typeof query === 'string' ? '&' + query : ''; + + if (typeof action !== 'string') + query = action; + else if (!query || typeof query !== 'object') + query = {}; + + if (action) + query._action = action; + else + query._action = this.env.action; + + var base = this.env.comm_path; + + // overwrite task name + if (query._action.match(/([a-z]+)\/([a-z-_]+)/)) { + query._action = RegExp.$2; + base = base.replace(/\_task=[a-z]+/, '_task='+RegExp.$1); + } + + // remove undefined values + var param = {}; + for (var k in query) { + if (query[k] !== undefined && query[k] !== null) + param[k] = query[k]; + } + + return base + '&' + $.param(param) + querystring; + }; this.redirect = function(url, lock) { if (lock || lock === null) this.set_busy(true); - if (this.env.framed && window.parent) - parent.location.href = url; + if (this.is_framed()) + parent.rcmail.redirect(url, lock); else - location.href = url; + this.location_href(url, window); }; this.goto_url = function(action, query, lock) { - var url = this.env.comm_path, - querystring = query ? '&'+query : ''; + this.redirect(this.url(action, query)); + }; - // overwrite task name - if (action.match(/([a-z]+)\/([a-z-_]+)/)) { - action = RegExp.$2; - url = url.replace(/\_task=[a-z]+/, '_task='+RegExp.$1); - } - - this.redirect(url+'&_action='+action+querystring, lock); + this.location_href = function(url, target) + { + // simulate real link click to force IE to send referer header + if (bw.ie && target == window) + $('<a>').attr('href', url).appendTo(document.body).get(0).click(); + else + target.location.href = url; }; // send a http request to the server this.http_request = function(action, query, lock) { - var url = this.env.comm_path; - - // overwrite task name - if (action.match(/([a-z]+)\/([a-z-_]+)/)) { - action = RegExp.$2; - url = url.replace(/\_task=[a-z]+/, '_task='+RegExp.$1); - } + var url = this.url(action, query); // trigger plugin hook var result = this.triggerEvent('request'+action, query); - if (typeof result != 'undefined') { + if (result !== undefined) { // abort if one the handlers returned false if (result === false) return false; @@ -5045,7 +5233,7 @@ query = result; } - url += '&_remote=1&_action=' + action + (query ? '&' : '') + query; + url += '&_remote=1'; // send request console.log('HTTP GET: ' + url); @@ -5059,17 +5247,9 @@ // send a http POST request to the server this.http_post = function(action, postdata, lock) { - var url = this.env.comm_path; + var url = this.url(action); - // overwrite task name - if (action.match(/([a-z]+)\/([a-z-_]+)/)) { - action = RegExp.$2; - url = url.replace(/\_task=[a-z]+/, '_task='+RegExp.$1); - } - - url += '&_action=' + action; - - if (postdata && typeof(postdata) == 'object') { + if (postdata && typeof postdata === 'object') { postdata._remote = 1; postdata._unlock = (lock ? lock : 0); } @@ -5078,7 +5258,7 @@ // trigger plugin hook var result = this.triggerEvent('request'+action, postdata); - if (typeof result != 'undefined') { + if (result !== undefined) { // abort if one the handlers returned false if (result === false) return false; @@ -5112,9 +5292,9 @@ this.set_env(response.env); // we have labels to add - if (typeof response.texts == 'object') { + if (typeof response.texts === 'object') { for (var name in response.texts) - if (typeof response.texts[name] == 'string') + if (typeof response.texts[name] === 'string') this.add_label(name, response.texts[name]); } @@ -5210,10 +5390,43 @@ this.set_busy(false, null, lock); request.abort(); - if (errmsg) + if (request.status && errmsg) this.display_message(this.get_label('servererror') + ' (' + errmsg + ')', 'error'); }; + // post the given form to a hidden iframe + this.async_upload_form = function(form, action, onload) + { + var ts = new Date().getTime(), + frame_name = 'rcmupload'+ts; + + // have to do it this way for IE + // otherwise the form will be posted to a new window + if (document.all) { + var html = '<iframe name="'+frame_name+'" src="program/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>'; + document.body.insertAdjacentHTML('BeforeEnd', html); + } + else { // for standards-compilant browsers + var frame = document.createElement('iframe'); + frame.name = frame_name; + frame.style.border = 'none'; + frame.style.width = 0; + frame.style.height = 0; + frame.style.visibility = 'hidden'; + document.body.appendChild(frame); + } + + // handle upload errors, parsing iframe content in onload + $(frame_name).bind('load', {ts:ts}, onload); + + form.target = frame_name; + form.action = this.url(action, { _id:this.env.compose_id||'', _uploadid:ts }); + form.setAttribute('enctype', 'multipart/form-data'); + form.submit(); + + return frame_name; + }; + // starts interval for keep-alive/check-recent signal this.start_keepalive = function() { @@ -5223,14 +5436,7 @@ if (this.env.keep_alive && !this.env.framed && this.task == 'mail' && this.gui_objects.mailboxlist) this._int = setInterval(function(){ ref.check_for_recent(false); }, this.env.keep_alive * 1000); else if (this.env.keep_alive && !this.env.framed && this.task != 'login' && this.env.action != 'print') - this._int = setInterval(function(){ ref.send_keep_alive(); }, this.env.keep_alive * 1000); - }; - - // sends keep-alive signal to the server - this.send_keep_alive = function() - { - var d = new Date(); - this.http_request('keep-alive', '_t='+d.getTime()); + this._int = setInterval(function(){ ref.http_request('keep-alive'); }, this.env.keep_alive * 1000); }; // sends request to check for recent messages @@ -5239,7 +5445,7 @@ if (this.busy) return; - var lock, addurl = '_t=' + (new Date().getTime()) + '&_mbox=' + urlencode(this.env.mailbox); + var lock, addurl = '_mbox=' + urlencode(this.env.mailbox); if (refresh) { lock = this.set_busy(true, 'checkingmail'); @@ -5279,7 +5485,7 @@ // gets cursor position this.get_caret_pos = function(obj) { - if (typeof(obj.selectionEnd)!='undefined') + if (obj.selectionEnd !== undefined) return obj.selectionEnd; else if (document.selection && document.selection.createRange) { var range = document.selection.createRange(); @@ -5342,6 +5548,17 @@ } // end object rcube_webmail + +// some static methods +rcube_webmail.long_subject_title = function(elem, indent) +{ + if (!elem.title) { + var $elem = $(elem); + if ($elem.width() + indent * 15 > $elem.parent().width()) + elem.title = $elem.html(); + } +}; + // copy event engine prototype rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener; rcube_webmail.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener; -- Gitblit v1.9.1