/* +-----------------------------------------------------------------------+ | RoundCube Webmail Client Script | | | | This file is part of the RoundCube Webmail client | | Copyright (C) 2005-2006, RoundCube Dev, - Switzerland | | Licensed under the GNU GPL | | | +-----------------------------------------------------------------------+ | Authors: Thomas Bruederli | | Charles McNulty | +-----------------------------------------------------------------------+ | Requires: common.js, list.js | +-----------------------------------------------------------------------+ $Id$ */ var rcube_webmail_client; function rcube_webmail() { this.env = new Object(); this.labels = new Object(); this.buttons = new Object(); this.gui_objects = new Object(); this.commands = new Object(); // create public reference to myself rcube_webmail_client = this; this.ref = 'rcube_webmail_client'; // webmail client settings this.dblclick_time = 600; this.message_time = 5000; this.mbox_expression = new RegExp('[^0-9a-z\-_]', 'gi'); // mimetypes supported by the browser (default settings) this.mimetypes = new Array('text/plain', 'text/html', 'text/xml', 'image/jpeg', 'image/gif', 'image/png', 'application/x-javascript', 'application/pdf', 'application/x-shockwave-flash'); // default environment vars this.env.keep_alive = 60; // seconds this.env.request_timeout = 180; // seconds this.env.draft_autosave = 0; // seconds // set environment variable this.set_env = function(name, value) { this.env[name] = value; }; // add a localized label to the client environment this.add_label = function(key, value) { this.labels[key] = value; }; // add a button to the button list this.register_button = function(command, id, type, act, sel, over) { if (!this.buttons[command]) this.buttons[command] = new Array(); var button_prop = {id:id, type:type}; if (act) button_prop.act = act; if (sel) button_prop.sel = sel; if (over) button_prop.over = over; this.buttons[command][this.buttons[command].length] = button_prop; }; // register a specific gui object this.gui_object = function(name, id) { this.gui_objects[name] = id; }; // initialize webmail client this.init = function() { var p = this; this.task = this.env.task; // check browser if (!bw.dom || !bw.xmlhttp_test()) { this.goto_url('error', '_code=0x199'); return; } // find all registered gui objects for (var n in this.gui_objects) this.gui_objects[n] = rcube_find_object(this.gui_objects[n]); // tell parent window that this frame is loaded if (this.env.framed && parent.rcmail && parent.rcmail.set_busy) parent.rcmail.set_busy(false); // enable general commands this.enable_command('logout', 'mail', 'addressbook', 'settings', true); switch (this.task) { case 'mail': if (this.gui_objects.messagelist) { this.message_list = new rcube_list_widget(this.gui_objects.messagelist, {multiselect:true, draggable:true, keyboard:true, dblclick_time:this.dblclick_time}); this.message_list.row_init = function(o){ p.init_message_row(o); }; this.message_list.addEventListener('dblclick', function(o){ p.msglist_dbl_click(o); }); this.message_list.addEventListener('keypress', function(o){ p.msglist_keypress(o); }); this.message_list.addEventListener('select', function(o){ p.msglist_select(o); }); this.message_list.addEventListener('dragstart', function(o){ p.drag_active = true; }); this.message_list.addEventListener('dragend', function(o){ p.drag_active = false; }); this.message_list.init(); this.enable_command('toggle_status', true); if (this.gui_objects.mailcontframe) { this.gui_objects.mailcontframe.onmousedown = function(e){ return p.click_on_list(e); }; document.onmouseup = function(e){ return p.doc_mouse_up(e); }; } else this.message_list.focus(); } // enable mail commands this.enable_command('list', 'checkmail', 'compose', 'add-contact', 'search', 'reset-search', true); if (this.env.action=='show') { this.enable_command('show', 'reply', 'reply-all', 'forward', 'moveto', 'delete', 'viewsource', 'print', 'load-attachment', true); if (this.env.next_uid) this.enable_command('nextmessage', true); if (this.env.prev_uid) this.enable_command('previousmessage', true); } if (this.env.action=='show' && this.env.blockedobjects) { if (this.gui_objects.remoteobjectsmsg) this.gui_objects.remoteobjectsmsg.style.display = 'block'; this.enable_command('load-images', true); } if (this.env.action=='compose') { this.enable_command('add-attachment', 'send-attachment', 'remove-attachment', 'send', true); if (this.env.spellcheck) { this.env.spellcheck.spelling_state_observer = function(s){ rcube_webmail_client.set_spellcheck_state(s); }; this.set_spellcheck_state('ready'); } if (this.env.drafts_mailbox) this.enable_command('savedraft', true); } if (this.env.messagecount) this.enable_command('select-all', 'select-none', 'sort', 'expunge', true); if (this.env.messagecount && (this.env.mailbox==this.env.trash_mailbox || this.env.mailbox==this.env.junk_mailbox)) this.enable_command('purge', true); this.set_page_buttons(); // focus this window window.focus(); // init message compose form if (this.env.action=='compose') this.init_messageform(); // show printing dialog if (this.env.action=='print') window.print(); // get unread count for each mailbox if (this.gui_objects.mailboxlist) this.http_request('getunread', ''); break; case 'addressbook': if (this.gui_objects.contactslist) { this.contact_list = new rcube_list_widget(this.gui_objects.contactslist, {multiselect:true, draggable:false, keyboard:true}); this.contact_list.addEventListener('keypress', function(o){ p.contactlist_keypress(o); }); this.contact_list.addEventListener('select', function(o){ p.contactlist_select(o); }); this.contact_list.init(); if (this.env.cid) this.contact_list.highlight_row(this.env.cid); if (this.gui_objects.contactslist.parentNode) { this.gui_objects.contactslist.parentNode.onmousedown = function(e){ return p.click_on_list(e); }; document.onmouseup = function(e){ return p.doc_mouse_up(e); }; } else this.contact_list.focus(); } this.set_page_buttons(); if (this.env.cid) this.enable_command('show', 'edit', true); if ((this.env.action=='add' || this.env.action=='edit') && this.gui_objects.editform) this.enable_command('save', true); this.enable_command('list', 'add', true); // this.enable_command('ldappublicsearch', this.env.ldappublicsearch); break; case 'settings': this.enable_command('preferences', 'identities', 'save', 'folders', true); if (this.env.action=='identities' || this.env.action=='edit-identity' || this.env.action=='add-identity') this.enable_command('edit', 'add', 'delete', true); if (this.env.action=='edit-identity' || this.env.action=='add-identity') this.enable_command('save', true); if (this.env.action=='folders') this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', 'delete-folder', true); if (this.gui_objects.identitieslist) { this.identity_list = new rcube_list_widget(this.gui_objects.identitieslist, {multiselect:false, draggable:false, keyboard:false}); this.identity_list.addEventListener('select', function(o){ p.identity_select(o); }); this.identity_list.init(); this.identity_list.focus(); if (this.env.iid) this.identity_list.highlight_row(this.env.iid); } break; case 'login': var input_user = rcube_find_object('_user'); var input_pass = rcube_find_object('_pass'); if (input_user && input_user.value=='') input_user.focus(); else if (input_pass) input_pass.focus(); this.enable_command('login', true); break; default: break; } // enable basic commands this.enable_command('logout', true); // flag object as complete this.loaded = true; // show message if (this.pending_message) this.display_message(this.pending_message[0], this.pending_message[1]); // start keep-alive interval this.start_keepalive(); }; // start interval for keep-alive/recent_check signal this.start_keepalive = function() { if (this.env.keep_alive && this.task=='mail' && this.gui_objects.messagelist) this._int = setInterval(this.ref+'.check_for_recent()', this.env.keep_alive * 1000); else if (this.env.keep_alive && this.task!='login') this._int = setInterval(this.ref+'.send_keep_alive()', this.env.keep_alive * 1000); } this.init_message_row = function(row) { var uid = row.uid; if (uid && this.env.messages[uid]) { row.deleted = this.env.messages[uid].deleted; row.unread = this.env.messages[uid].unread; row.replied = this.env.messages[uid].replied; } // set eventhandler to message icon if ((row.icon = row.obj.cells[0].childNodes[0]) && row.icon.nodeName=='IMG') { var p = this; row.icon.id = 'msgicn_'+row.uid; row.icon._row = row.obj; row.icon.onmousedown = function(e) { p.command('toggle_status', this); }; } }; // init message compose form: set focus and eventhandlers this.init_messageform = function() { if (!this.gui_objects.messageform) return false; //this.messageform = this.gui_objects.messageform; var input_from = rcube_find_object('_from'); var input_to = rcube_find_object('_to'); var input_cc = rcube_find_object('_cc'); var input_bcc = rcube_find_object('_bcc'); var input_replyto = rcube_find_object('_replyto'); var input_subject = rcube_find_object('_subject'); var input_message = rcube_find_object('_message'); // init live search events if (input_to) this.init_address_input_events(input_to); if (input_cc) this.init_address_input_events(input_cc); if (input_bcc) this.init_address_input_events(input_bcc); // add signature according to selected identity if (input_from && input_from.type=='select-one') this.change_identity(input_from); if (input_to && input_to.value=='') input_to.focus(); else if (input_subject && input_subject.value=='') input_subject.focus(); else if (input_message) this.set_caret2start(input_message); // get summary of all field values this.cmp_hash = this.compose_field_hash(); // start the auto-save timer this.auto_save_start(); }; this.init_address_input_events = function(obj) { var handler = function(e){ return rcube_webmail_client.ksearch_keypress(e,this); }; var handler2 = function(e){ return rcube_webmail_client.ksearch_blur(e,this); }; if (bw.safari) obj.addEventListener('keydown', handler, false); else if (bw.mz) { obj.addEventListener('keypress', handler, false); obj.addEventListener('blur', handler2, false); } else if (bw.ie) obj.onkeydown = handler; obj.setAttribute('autocomplete', 'off'); }; /*********************************************************/ /********* client command interface *********/ /*********************************************************/ // execute a specific command on the web client this.command = function(command, props, obj) { if (obj && obj.blur) obj.blur(); if (this.busy) return false; // command not supported or allowed if (!this.commands[command]) { // pass command to parent window if (this.env.framed && parent.rcmail && parent.rcmail.command) parent.rcmail.command(command, props); return false; } // check input before leaving compose step if (this.task=='mail' && this.env.action=='compose' && (command=='list' || command=='mail' || command=='addressbook' || command=='settings')) { if (this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning'))) return false; } // process command switch (command) { case 'login': if (this.gui_objects.loginform) this.gui_objects.loginform.submit(); break; case 'logout': this.goto_url('logout'); break; // commands to switch task case 'mail': case 'addressbook': case 'settings': this.switch_task(command); break; // misc list commands case 'list': if (this.task=='mail') { if (this.env.search_request<0 || (this.env.search_request && props != this.env.mailbox)) this.reset_qsearch(); this.list_mailbox(props); } else if (this.task=='addressbook') this.list_contacts(); break; case 'sort': // get the type of sorting var a_sort = props.split('_'); var sort_col = a_sort[0]; var sort_order = a_sort[1] ? a_sort[1].toUpperCase() : null; var header; // no sort order specified: toggle if (sort_order==null) { if (this.env.sort_col==sort_col) sort_order = this.env.sort_order=='ASC' ? 'DESC' : 'ASC'; else sort_order = this.env.sort_order; } if (this.env.sort_col==sort_col && this.env.sort_order==sort_order) break; // set table header class if (header = document.getElementById('rcmHead'+this.env.sort_col)) this.set_classname(header, 'sorted'+(this.env.sort_order.toUpperCase()), false); if (header = document.getElementById('rcmHead'+sort_col)) this.set_classname(header, 'sorted'+sort_order, true); // save new sort properties this.env.sort_col = sort_col; this.env.sort_order = sort_order; // reload message list this.list_mailbox('', '', sort_col+'_'+sort_order); break; case 'nextpage': this.list_page('next'); break; case 'previouspage': this.list_page('prev'); break; case 'expunge': if (this.env.messagecount) this.expunge_mailbox(this.env.mailbox); break; case 'purge': case 'empty-mailbox': if (this.env.messagecount) this.purge_mailbox(this.env.mailbox); break; // common commands used in multiple tasks case 'show': if (this.task=='mail') { var uid = this.get_single_uid(); if (uid && (!this.env.uid || uid != this.env.uid)) { if (this.env.mailbox == this.env.drafts_mailbox) this.goto_url('compose', '_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true); else this.show_message(uid); } } else if (this.task=='addressbook') { var cid = props ? props : this.get_single_cid(); if (cid && !(this.env.action=='show' && cid==this.env.cid)) this.load_contact(cid, 'show'); } break; case 'add': if (this.task=='addressbook') this.load_contact(0, 'add'); /* LDAP stuff, has to be re-written with new address book if (!window.frames[this.env.contentframe].rcmail) this.load_contact(0, 'add'); else { if (window.frames[this.env.contentframe].rcmail.selection.length) this.add_ldap_contacts(); else this.load_contact(0, 'add'); } */ else if (this.task=='settings') { this.identity_list.clear_selection(); this.load_identity(0, 'add-identity'); } break; case 'edit': var cid; if (this.task=='addressbook' && (cid = this.get_single_cid())) this.load_contact(cid, 'edit'); else if (this.task=='settings' && props) this.load_identity(props, 'edit-identity'); break; case 'save-identity': case 'save': if (this.gui_objects.editform) { var input_pagesize = rcube_find_object('_pagesize'); var input_name = rcube_find_object('_name'); var input_email = rcube_find_object('_email'); // user prefs if (input_pagesize && isNaN(input_pagesize.value)) { alert(this.get_label('nopagesizewarning')); input_pagesize.focus(); break; } // contacts/identities else { if (input_name && input_name.value == '') { alert(this.get_label('nonamewarning')); input_name.focus(); break; } else if (input_email && !rcube_check_email(input_email.value)) { alert(this.get_label('noemailwarning')); input_email.focus(); break; } } this.gui_objects.editform.submit(); } break; case 'delete': // mail task if (this.task=='mail') this.delete_messages(); // addressbook task else if (this.task=='addressbook') this.delete_contacts(); // user settings task else if (this.task=='settings') this.delete_identity(); break; // mail task commands case 'move': case 'moveto': this.move_messages(props); break; case 'toggle_status': if (props && !props._row) break; var uid; var flag = 'read'; if (props._row.uid) { uid = props._row.uid; this.message_list.dont_select = true; // toggle read/unread if (this.message_list.rows[uid].deleted) { flag = 'undelete'; } else if (!this.message_list.rows[uid].unread) flag = 'unread'; } this.mark_message(flag, uid); break; case 'load-images': if (this.env.uid) this.show_message(this.env.uid, true); break; case 'load-attachment': var qstring = '_mbox='+this.env.mailbox+'&_uid='+this.env.uid+'&_part='+props.part; // open attachment in frame if it's of a supported mimetype if (this.env.uid && props.mimetype && find_in_array(props.mimetype, this.mimetypes)>=0) { this.attachment_win = window.open(this.env.comm_path+'&_action=get'+url+'&_frame=1', 'rcubemailattachment'); if (this.attachment_win) { setTimeout(this.ref+'.attachment_win.focus()', 10); break; } } this.goto_url('get', qstring+'&_download=1'); break; case 'select-all': this.message_list.select_all(props); break; case 'select-none': this.message_list.clear_selection(); break; case 'nextmessage': if (this.env.next_uid) this.show_message(this.env.next_uid); break; case 'previousmessage': if (this.env.prev_uid) this.show_message(this.env.prev_uid); break; case 'checkmail': this.check_for_recent(); break; case 'compose': var url = this.env.comm_path+'&_action=compose'; if (this.task=='mail' && this.env.mailbox==this.env.drafts_mailbox) { var uid; if (uid = this.get_single_uid()) url += '&_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox); } // modify url if we're in addressbook else if (this.task=='addressbook') { url = this.get_task_url('mail', url); var a_cids = new Array(); // use contact_id passed as command parameter if (props) a_cids[a_cids.length] = props; // get selected contacts else { var selection = this.contact_list.get_selection(); for (var n=0; n0 ? true : false); } else { this.enable_command('show', 'reply', 'reply-all', 'forward', 'print', selected); this.enable_command('delete', 'moveto', list.selection.length>0 ? true : false); } }; this.msglist_dbl_click = function(list) { var uid = list.get_single_selection(); if (uid && this.env.mailbox == this.env.drafts_mailbox) this.goto_url('compose', '_draft_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true); else if (uid) this.show_message(uid); }; this.msglist_keypress = function(list) { if (list.key_pressed == list.ENTER_KEY) this.command('show'); else if (list.key_pressed == list.DELETE_KEY) this.command('delete'); }; /*********************************************************/ /********* (message) list functionality *********/ /*********************************************************/ // when user doble-clicks on a row this.show_message = function(id, safe) { var add_url = ''; var target = window; if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) { target = window.frames[this.env.contentframe]; add_url = '&_framed=1'; } if (safe) add_url = '&_safe=1'; if (id) { this.set_busy(true, 'loading'); target.location.href = this.env.comm_path+'&_action=show&_uid='+id+'&_mbox='+urlencode(this.env.mailbox)+add_url; } }; // list a specific page this.list_page = function(page) { if (page=='next') page = this.env.current_page+1; if (page=='prev' && this.env.current_page>1) page = this.env.current_page-1; if (page > 0 && page <= this.env.pagecount) { this.env.current_page = page; if (this.task=='mail') this.list_mailbox(this.env.mailbox, page); else if (this.task=='addressbook') this.list_contacts(page); } }; // list messages of a specific mailbox this.list_mailbox = function(mbox, page, sort) { this.last_selected = 0; var add_url = ''; var target = window; if (!mbox) mbox = this.env.mailbox; // add sort to url if set if (sort) add_url += '&_sort=' + sort; // set page=1 if changeing to another mailbox if (!page && mbox != this.env.mailbox) { page = 1; add_url += '&_refresh=1'; this.env.current_page = page; this.message_list.clear_selection(); } // also send search request to get the right messages if (this.env.search_request) add_url += '&_search='+this.env.search_request; this.select_mailbox(mbox); // load message list remotely if (this.gui_objects.messagelist) { this.list_mailbox_remote(mbox, page, add_url); return; } if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) { target = window.frames[this.env.contentframe]; add_url += '&_framed=1'; } // 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 : '')+add_url; } }; // send remote request to load message list this.list_mailbox_remote = function(mbox, page, add_url) { // clear message list first this.message_list.clear(); // send request to server var url = '_mbox='+urlencode(mbox)+(page ? '&_page='+page : ''); this.set_busy(true, 'loading'); this.http_request('list', url+add_url, true); }; this.expunge_mailbox = function(mbox) { var lock = false; var add_url = ''; // lock interface if it's the active mailbox if (mbox == this.env.mailbox) { lock = true; this.set_busy(true, 'loading'); add_url = '&_reload=1'; } // send request to server var url = '_mbox='+urlencode(mbox); this.http_request('expunge', url+add_url, lock); }; this.purge_mailbox = function(mbox) { var lock = false; var add_url = ''; if (!confirm(this.get_label('purgefolderconfirm'))) return false; // lock interface if it's the active mailbox if (mbox == this.env.mailbox) { lock = true; this.set_busy(true, 'loading'); add_url = '&_reload=1'; } // send request to server var url = '_mbox='+urlencode(mbox); this.http_request('purge', url+add_url, lock); return true; }; this.focus_mailbox = function(mbox) { var mbox_li; if (this.drag_active && mbox != this.env.mailbox && (mbox_li = this.get_mailbox_li(mbox))) this.set_classname(mbox_li, 'droptarget', true); } this.unfocus_mailbox = function(mbox) { var mbox_li; if (this.drag_active && (mbox_li = this.get_mailbox_li(mbox))) this.set_classname(mbox_li, 'droptarget', false); } // move selected messages to the specified mailbox this.move_messages = function(mbox) { // exit if no mailbox specified or if selection is empty var selection = this.message_list.get_selection(); if (!mbox || !(selection.length || this.env.uid) || mbox==this.env.mailbox) return; var a_uids = new Array(); if (this.env.uid) a_uids[a_uids.length] = this.env.uid; else { var id; for (var n=0; n 0) { rows[uid].classname = rows[uid].classname.replace(/\s*deleted/, ''); this.set_classname(rows[uid].obj, 'deleted', false); } if (rows[uid].unread && this.env.unreadicon) icn_src = this.env.unreadicon; else if (rows[uid].replied && this.env.repliedicon) icn_src = this.env.repliedicon; else if (this.env.messageicon) icn_src = this.env.messageicon; if (rows[uid].icon && icn_src) rows[uid].icon.src = icn_src; } } this.http_request('mark', '_uid='+a_uids.join(',')+'&_flag=undelete'); return true; }; this.flag_as_deleted = function(a_uids) { // if deleting message from "view message" don't bother with delete icon if (this.env.action == "show") return false; var rows = this.message_list.rows; for (var i=0; i=0) message = message.substring(0, p-1) + message.substring(p+sig.length, message.length); } // add the new signature string if (this.env.signatures && this.env.signatures[id]) { sig = this.env.signatures[id]['text']; if (sig.indexOf('--')!=0) sig = '--\n'+sig; message += '\n'+sig; } } else { var eid = tinyMCE.getEditorId('_message'); // editor is a TinyMCE_Control object var editor = tinyMCE.getInstanceById(eid); var msgDoc = editor.getDoc(); var msgBody = msgDoc.body; if (this.env.signatures && this.env.signatures[id]) { // Append the signature as a span within the body var sigElem = msgDoc.getElementById("_rc_sig"); if (!sigElem) { sigElem = msgDoc.createElement("span"); sigElem.setAttribute("id", "_rc_sig"); msgBody.appendChild(sigElem); } if (this.env.signatures[id]['is_html']) { sigElem.innerHTML = this.env.signatures[id]['text']; } else { sigElem.innerHTML = '
' + this.env.signatures[id]['text'] + '
'; } } } if (input_message) input_message.value = message; this.env.identity = id; return true; }; this.show_attachment_form = function(a) { if (!this.gui_objects.uploadbox) return false; var elm, list; if (elm = this.gui_objects.uploadbox) { if (a && (list = this.gui_objects.attachmentlist)) { var pos = rcube_get_object_pos(list); var left = pos.x; var top = pos.y + list.offsetHeight + 10; elm.style.top = top+'px'; elm.style.left = left+'px'; } elm.style.visibility = a ? 'visible' : 'hidden'; } // clear upload form if (!a && this.gui_objects.attachmentform && this.gui_objects.attachmentform!=this.gui_objects.messageform) this.gui_objects.attachmentform.reset(); return true; }; // upload attachment file this.upload_file = function(form) { if (!form) return false; // get file input fields var send = false; for (var n=0; n