mirror of
https://github.com/roundcube/roundcubemail.git
synced 2026-03-04 07:14:02 +01:00
Now instead of <pre> we use <div class="pre"> styled with monospace font. We replace whitespace characters with non-breaking spaces where needed. I.e. plain text is always unwrappable, until it uses format=flowed, in such a case only flowed paragraphs are wrappable. Also conversion of text to HTML in compose editor was modified in the same way.
7877 lines
240 KiB
JavaScript
7877 lines
240 KiB
JavaScript
/**
|
|
* Roundcube Webmail Client Script
|
|
*
|
|
* This file is part of the Roundcube Webmail client
|
|
*
|
|
* @licstart The following is the entire license notice for the
|
|
* JavaScript code in this file.
|
|
*
|
|
* Copyright (C) 2005-2014, The Roundcube Dev Team
|
|
* Copyright (C) 2011-2014, Kolab Systems AG
|
|
*
|
|
* The JavaScript code in this page is free software: you can
|
|
* redistribute it and/or modify it under the terms of the GNU
|
|
* General Public License (GNU GPL) as published by the Free Software
|
|
* Foundation, either version 3 of the License, or (at your option)
|
|
* any later version. The code is distributed WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
* FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
|
|
*
|
|
* As additional permission under GNU GPL version 3 section 7, you
|
|
* may distribute non-source (e.g., minimized or compacted) forms of
|
|
* that code without the copy of the GNU GPL normally required by
|
|
* section 4, provided you include this license notice and a URL
|
|
* through which recipients can access the Corresponding Source.
|
|
*
|
|
* @licend The above is the entire license notice
|
|
* for the JavaScript code in this file.
|
|
*
|
|
* @author Thomas Bruederli <roundcube@gmail.com>
|
|
* @author Aleksander 'A.L.E.C' Machniak <alec@alec.pl>
|
|
* @author Charles McNulty <charles@charlesmcnulty.com>
|
|
*
|
|
* @requires jquery.js, common.js, list.js
|
|
*/
|
|
|
|
function rcube_webmail()
|
|
{
|
|
this.labels = {};
|
|
this.buttons = {};
|
|
this.buttons_sel = {};
|
|
this.gui_objects = {};
|
|
this.gui_containers = {};
|
|
this.commands = {};
|
|
this.command_handlers = {};
|
|
this.onloads = [];
|
|
this.messages = {};
|
|
this.group2expand = {};
|
|
this.http_request_jobs = {};
|
|
|
|
// webmail client settings
|
|
this.dblclick_time = 500;
|
|
this.message_time = 5000;
|
|
this.identifier_expr = new RegExp('[^0-9a-z\-_]', 'gi');
|
|
|
|
// environment defaults
|
|
this.env = {
|
|
request_timeout: 180, // seconds
|
|
draft_autosave: 0, // seconds
|
|
comm_path: './',
|
|
blankpage: 'program/resources/blank.gif',
|
|
recipients_separator: ',',
|
|
recipients_delimiter: ', ',
|
|
popup_width: 1150,
|
|
popup_width_small: 900
|
|
};
|
|
|
|
// create protected reference to myself
|
|
this.ref = 'rcmail';
|
|
var ref = this;
|
|
|
|
// set jQuery ajax options
|
|
$.ajaxSetup({
|
|
cache: false,
|
|
timeout: this.env.request_timeout * 1000,
|
|
error: function(request, status, err){ ref.http_error(request, status, err); },
|
|
beforeSend: function(xmlhttp){ xmlhttp.setRequestHeader('X-Roundcube-Request', ref.env.request_token); }
|
|
});
|
|
|
|
// unload fix
|
|
$(window).bind('beforeunload', function() { rcmail.unload = true; });
|
|
|
|
// set environment variable(s)
|
|
this.set_env = function(p, value)
|
|
{
|
|
if (p != null && typeof p === 'object' && !value)
|
|
for (var n in p)
|
|
this.env[n] = p[n];
|
|
else
|
|
this.env[p] = value;
|
|
};
|
|
|
|
// add a localized label to the client environment
|
|
this.add_label = function(p, value)
|
|
{
|
|
if (typeof p == 'string')
|
|
this.labels[p] = value;
|
|
else if (typeof p == 'object')
|
|
$.extend(this.labels, p);
|
|
};
|
|
|
|
// add a button to the button list
|
|
this.register_button = function(command, id, type, act, sel, over)
|
|
{
|
|
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;
|
|
|
|
if (!this.buttons[command])
|
|
this.buttons[command] = [];
|
|
|
|
this.buttons[command].push(button_prop);
|
|
|
|
if (this.loaded)
|
|
init_button(command, button_prop);
|
|
};
|
|
|
|
// register a specific gui object
|
|
this.gui_object = function(name, id)
|
|
{
|
|
this.gui_objects[name] = this.loaded ? rcube_find_object(id) : id;
|
|
};
|
|
|
|
// register a container object
|
|
this.gui_container = function(name, id)
|
|
{
|
|
this.gui_containers[name] = id;
|
|
};
|
|
|
|
// add a GUI element (html node) to a specified container
|
|
this.add_element = function(elm, container)
|
|
{
|
|
if (this.gui_containers[container] && this.gui_containers[container].jquery)
|
|
this.gui_containers[container].append(elm);
|
|
};
|
|
|
|
// register an external handler for a certain command
|
|
this.register_command = function(command, callback, enable)
|
|
{
|
|
this.command_handlers[command] = callback;
|
|
|
|
if (enable)
|
|
this.enable_command(command, true);
|
|
};
|
|
|
|
// execute the given script on load
|
|
this.add_onload = function(f)
|
|
{
|
|
this.onloads.push(f);
|
|
};
|
|
|
|
// initialize webmail client
|
|
this.init = function()
|
|
{
|
|
var n;
|
|
this.task = this.env.task;
|
|
|
|
// check browser
|
|
if (this.env.server_error != 409 && (!bw.dom || !bw.xmlhttp_test() || (bw.mz && bw.vendver < 1.9) || (bw.ie && bw.vendver < 7))) {
|
|
this.goto_url('error', '_code=0x199');
|
|
return;
|
|
}
|
|
|
|
// find all registered gui containers
|
|
for (n in this.gui_containers)
|
|
this.gui_containers[n] = $('#'+this.gui_containers[n]);
|
|
|
|
// find all registered gui objects
|
|
for (n in this.gui_objects)
|
|
this.gui_objects[n] = rcube_find_object(this.gui_objects[n]);
|
|
|
|
// clickjacking protection
|
|
if (this.env.x_frame_options) {
|
|
try {
|
|
// bust frame if not allowed
|
|
if (this.env.x_frame_options == 'deny' && top.location.href != self.location.href)
|
|
top.location.href = self.location.href;
|
|
else if (top.location.hostname != self.location.hostname)
|
|
throw 1;
|
|
} catch (e) {
|
|
// possible clickjacking attack: disable all form elements
|
|
$('form').each(function(){ ref.lock_form(this, true); });
|
|
this.display_message("Blocked: possible clickjacking attack!", 'error');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// init registered buttons
|
|
this.init_buttons();
|
|
|
|
// tell parent window that this frame is loaded
|
|
if (this.is_framed()) {
|
|
parent.rcmail.set_busy(false, null, parent.rcmail.env.frame_lock);
|
|
parent.rcmail.env.frame_lock = null;
|
|
}
|
|
|
|
// enable general commands
|
|
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);
|
|
|
|
switch (this.task) {
|
|
|
|
case 'mail':
|
|
// enable mail commands
|
|
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, {
|
|
multiselect:true, multiexpand:true, draggable:true, keyboard:true,
|
|
column_movable:this.env.col_movable, dblclick_time:this.dblclick_time
|
|
});
|
|
this.message_list
|
|
.addEventListener('initrow', function(o) { ref.init_message_row(o); })
|
|
.addEventListener('dblclick', function(o) { ref.msglist_dbl_click(o); })
|
|
.addEventListener('click', function(o) { ref.msglist_click(o); })
|
|
.addEventListener('keypress', function(o) { ref.msglist_keypress(o); })
|
|
.addEventListener('select', function(o) { ref.msglist_select(o); })
|
|
.addEventListener('dragstart', function(o) { ref.drag_start(o); })
|
|
.addEventListener('dragmove', function(e) { ref.drag_move(e); })
|
|
.addEventListener('dragend', function(e) { ref.drag_end(e); })
|
|
.addEventListener('expandcollapse', function(o) { ref.msglist_expand(o); })
|
|
.addEventListener('column_replace', function(o) { ref.msglist_set_coltypes(o); })
|
|
.addEventListener('listupdate', function(o) { ref.triggerEvent('listupdate', o); })
|
|
.init();
|
|
|
|
// TODO: this should go into the list-widget code
|
|
$(this.message_list.thead).on('click', 'a.sortcol', function(e){
|
|
return ref.command('sort', $(this).attr('rel'), this);
|
|
});
|
|
|
|
document.onmouseup = function(e){ return ref.doc_mouse_up(e); };
|
|
this.gui_objects.messagelist.parentNode.onmousedown = function(e){ return ref.click_on_list(e); };
|
|
|
|
this.enable_command('toggle_status', 'toggle_flag', 'sort', true);
|
|
this.enable_command('set-listmode', this.env.threads && !this.is_multifolder_listing());
|
|
|
|
// load messages
|
|
this.command('list');
|
|
|
|
$(this.gui_objects.qsearchbox).val(this.env.search_text).focusin(function() { rcmail.message_list.blur(); });
|
|
}
|
|
|
|
this.set_button_titles();
|
|
|
|
this.env.message_commands = ['show', 'reply', 'reply-all', 'reply-list',
|
|
'move', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource',
|
|
'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);
|
|
this.enable_command('reply-list', this.env.list_post);
|
|
|
|
if (this.env.action == 'show') {
|
|
this.http_request('pagenav', {_uid: this.env.uid, _mbox: this.env.mailbox, _search: this.env.search_request},
|
|
this.display_message('', 'loading'));
|
|
}
|
|
|
|
if (this.env.blockedobjects) {
|
|
if (this.gui_objects.remoteobjectsmsg)
|
|
this.gui_objects.remoteobjectsmsg.style.display = 'block';
|
|
this.enable_command('load-images', 'always-load', true);
|
|
}
|
|
|
|
// make preview/message frame visible
|
|
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.address_group_stack = [];
|
|
this.env.compose_commands = ['send-attachment', 'remove-attachment', 'send', 'cancel',
|
|
'toggle-editor', 'list-adresses', 'pushgroup', 'search', 'reset-search', 'extwin',
|
|
'insert-response', 'save-response'];
|
|
|
|
if (this.env.drafts_mailbox)
|
|
this.env.compose_commands.push('savedraft')
|
|
|
|
this.enable_command(this.env.compose_commands, 'identities', 'responses', true);
|
|
|
|
// add more commands (not enabled)
|
|
$.merge(this.env.compose_commands, ['add-recipient', 'firstpage', 'previouspage', 'nextpage', 'lastpage']);
|
|
|
|
if (this.env.spellcheck) {
|
|
this.env.spellcheck.spelling_state_observer = function(s) { ref.spellcheck_state(); };
|
|
this.env.compose_commands.push('spellcheck')
|
|
this.enable_command('spellcheck', true);
|
|
}
|
|
|
|
// init canned response functions
|
|
if (this.gui_objects.responseslist) {
|
|
$('a.insertresponse', this.gui_objects.responseslist)
|
|
.attr('unselectable', 'on')
|
|
.mousedown(function(e){ return rcube_event.cancel(e); })
|
|
.mouseup(function(e){
|
|
ref.command('insert-response', $(this).attr('rel'));
|
|
$(document.body).trigger('mouseup'); // hides the menu
|
|
return rcube_event.cancel(e);
|
|
});
|
|
|
|
// avoid textarea loosing focus when hitting the save-response button/link
|
|
for (var i=0; this.buttons['save-response'] && i < this.buttons['save-response'].length; i++) {
|
|
$('#'+this.buttons['save-response'][i].id).mousedown(function(e){ return rcube_event.cancel(e); })
|
|
}
|
|
}
|
|
|
|
document.onmouseup = function(e){ return ref.doc_mouse_up(e); };
|
|
|
|
// init message compose form
|
|
this.init_messageform();
|
|
}
|
|
else if (this.env.action == 'get')
|
|
this.enable_command('download', 'print', true);
|
|
// show printing dialog
|
|
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) {
|
|
this.env.unread_counts = {};
|
|
this.gui_objects.folderlist = this.gui_objects.mailboxlist;
|
|
this.http_request('getunread');
|
|
}
|
|
|
|
// init address book widget
|
|
if (this.gui_objects.contactslist) {
|
|
this.contact_list = new rcube_list_widget(this.gui_objects.contactslist,
|
|
{ multiselect:true, draggable:false, keyboard:false });
|
|
this.contact_list
|
|
.addEventListener('initrow', function(o) { ref.triggerEvent('insertrow', { cid:o.uid, row:o }); })
|
|
.addEventListener('select', function(o) { ref.compose_recipient_select(o); })
|
|
.addEventListener('dblclick', function(o) { ref.compose_add_recipient('to'); })
|
|
.init();
|
|
}
|
|
|
|
if (this.gui_objects.addressbookslist) {
|
|
this.gui_objects.folderlist = this.gui_objects.addressbookslist;
|
|
this.enable_command('list-adresses', true);
|
|
}
|
|
|
|
// ask user to send MDN
|
|
if (this.env.mdn_request && this.env.uid) {
|
|
var postact = 'sendmdn',
|
|
postdata = {_uid: this.env.uid, _mbox: this.env.mailbox};
|
|
if (!confirm(this.get_label('mdnrequest'))) {
|
|
postdata._flag = 'mdnsent';
|
|
postact = 'mark';
|
|
}
|
|
this.http_post(postact, postdata);
|
|
}
|
|
|
|
// detect browser capabilities
|
|
if (!this.is_framed() && !this.env.extwin)
|
|
this.browser_capabilities_check();
|
|
|
|
break;
|
|
|
|
case 'addressbook':
|
|
this.env.address_group_stack = [];
|
|
|
|
if (this.gui_objects.folderlist)
|
|
this.env.contactfolders = $.extend($.extend({}, this.env.address_sources), this.env.contactgroups);
|
|
|
|
this.enable_command('add', 'import', this.env.writable_source);
|
|
this.enable_command('list', 'listgroup', 'pushgroup', 'popgroup', 'listsearch', 'search', 'reset-search', 'advanced-search', true);
|
|
|
|
if (this.gui_objects.contactslist) {
|
|
this.contact_list = new rcube_list_widget(this.gui_objects.contactslist,
|
|
{multiselect:true, draggable:this.gui_objects.folderlist?true:false, keyboard:true});
|
|
this.contact_list
|
|
.addEventListener('initrow', function(o) { ref.triggerEvent('insertrow', { cid:o.uid, row:o }); })
|
|
.addEventListener('keypress', function(o) { ref.contactlist_keypress(o); })
|
|
.addEventListener('select', function(o) { ref.contactlist_select(o); })
|
|
.addEventListener('dragstart', function(o) { ref.drag_start(o); })
|
|
.addEventListener('dragmove', function(e) { ref.drag_move(e); })
|
|
.addEventListener('dragend', function(e) { ref.drag_end(e); })
|
|
.init();
|
|
|
|
if (this.env.cid)
|
|
this.contact_list.highlight_row(this.env.cid);
|
|
|
|
this.gui_objects.contactslist.parentNode.onmousedown = function(e){ return ref.click_on_list(e); };
|
|
document.onmouseup = function(e){ return ref.doc_mouse_up(e); };
|
|
|
|
$(this.gui_objects.qsearchbox).focusin(function() { ref.contact_list.blur(); });
|
|
|
|
this.update_group_commands();
|
|
this.command('list');
|
|
}
|
|
|
|
this.set_page_buttons();
|
|
|
|
if (this.env.cid) {
|
|
this.enable_command('show', 'edit', true);
|
|
// register handlers for group assignment via checkboxes
|
|
if (this.gui_objects.editform) {
|
|
$('input.groupmember').change(function() {
|
|
ref.group_member_change(this.checked ? 'add' : 'del', ref.env.cid, ref.env.source, this.value);
|
|
});
|
|
}
|
|
}
|
|
|
|
if (this.gui_objects.editform) {
|
|
this.enable_command('save', true);
|
|
if (this.env.action == 'add' || this.env.action == 'edit' || this.env.action == 'search')
|
|
this.init_contact_form();
|
|
}
|
|
|
|
break;
|
|
|
|
case 'settings':
|
|
this.enable_command('preferences', 'identities', 'responses', 'save', 'folders', true);
|
|
|
|
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') {
|
|
this.enable_command('save', 'edit', 'toggle-editor', true);
|
|
this.enable_command('delete', this.env.identities_level < 2);
|
|
}
|
|
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.exists = this.env.messagecount;
|
|
parent.rcmail.enable_command('purge', this.env.messagecount);
|
|
}
|
|
else if (this.env.action == 'responses') {
|
|
this.enable_command('add', 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) { ref.identity_select(o); })
|
|
.init()
|
|
.focus();
|
|
|
|
if (this.env.iid)
|
|
this.identity_list.highlight_row(this.env.iid);
|
|
}
|
|
else if (this.gui_objects.sectionslist) {
|
|
this.sections_list = new rcube_list_widget(this.gui_objects.sectionslist, {multiselect:false, draggable:false, keyboard:false});
|
|
this.sections_list
|
|
.addEventListener('select', function(o) { ref.section_select(o); })
|
|
.init()
|
|
.focus();
|
|
}
|
|
else if (this.gui_objects.subscriptionlist) {
|
|
this.init_subscription_list();
|
|
}
|
|
else if (this.gui_objects.responseslist) {
|
|
this.responses_list = new rcube_list_widget(this.gui_objects.responseslist, {multiselect:false, draggable:false, keyboard:false});
|
|
this.responses_list
|
|
.addEventListener('select', function(list) {
|
|
var win, id = list.get_single_selection();
|
|
ref.enable_command('delete', !!id && $.inArray(id, ref.env.readonly_responses) < 0);
|
|
if (id && (win = ref.get_frame_window(ref.env.contentframe))) {
|
|
ref.set_busy(true);
|
|
ref.location_href({ _action:'edit-response', _key:id, _framed:1 }, win);
|
|
}
|
|
})
|
|
.init()
|
|
.focus();
|
|
}
|
|
|
|
break;
|
|
|
|
case 'login':
|
|
var input_user = $('#rcmloginuser');
|
|
input_user.bind('keyup', function(e){ return rcmail.login_user_keyup(e); });
|
|
|
|
if (input_user.val() == '')
|
|
input_user.focus();
|
|
else
|
|
$('#rcmloginpwd').focus();
|
|
|
|
// detect client timezone
|
|
if (window.jstz) {
|
|
var timezone = jstz.determine();
|
|
if (timezone.name())
|
|
$('#rcmlogintz').val(timezone.name());
|
|
}
|
|
else {
|
|
$('#rcmlogintz').val(new Date().getStdTimezoneOffset() / -60);
|
|
}
|
|
|
|
// display 'loading' message on form submit, lock submit button
|
|
$('form').submit(function () {
|
|
$('input[type=submit]', this).prop('disabled', true);
|
|
rcmail.clear_messages();
|
|
rcmail.display_message('', 'loading');
|
|
});
|
|
|
|
this.enable_command('login', true);
|
|
break;
|
|
}
|
|
|
|
// select first input field in an edit form
|
|
if (this.gui_objects.editform)
|
|
$("input,select,textarea", this.gui_objects.editform)
|
|
.not(':hidden').not(':disabled').first().select();
|
|
|
|
// unset contentframe variable if preview_pane is enabled
|
|
if (this.env.contentframe && !$('#' + this.env.contentframe).is(':visible'))
|
|
this.env.contentframe = null;
|
|
|
|
// prevent from form submit with Enter key in file input fields
|
|
if (bw.ie)
|
|
$('input[type=file]').keydown(function(e) { if (e.keyCode == '13') e.preventDefault(); });
|
|
|
|
// flag object as complete
|
|
this.loaded = true;
|
|
this.env.lastrefresh = new Date();
|
|
|
|
// show message
|
|
if (this.pending_message)
|
|
this.display_message(this.pending_message[0], this.pending_message[1], this.pending_message[2]);
|
|
|
|
// init treelist widget
|
|
if (this.gui_objects.folderlist && 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) })
|
|
.addEventListener('expand', function(node) { ref.folder_collapsed(node) })
|
|
.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'); });
|
|
$(this.gui_objects.filedrop).addClass('droptarget')
|
|
.bind('dragover dragleave', function(e){ return ref.file_drag_hover(e, e.type == 'dragover'); })
|
|
.get(0).addEventListener('drop', function(e){ return ref.file_dropped(e); }, false);
|
|
}
|
|
|
|
// trigger init event hook
|
|
this.triggerEvent('init', { task:this.task, action:this.env.action });
|
|
|
|
// execute all foreign onload scripts
|
|
// @deprecated
|
|
for (var i in this.onloads) {
|
|
if (typeof this.onloads[i] === 'string')
|
|
eval(this.onloads[i]);
|
|
else if (typeof this.onloads[i] === 'function')
|
|
this.onloads[i]();
|
|
}
|
|
|
|
// start keep-alive and refresh intervals
|
|
this.start_refresh();
|
|
this.start_keepalive();
|
|
};
|
|
|
|
this.log = function(msg)
|
|
{
|
|
if (window.console && console.log)
|
|
console.log(msg);
|
|
};
|
|
|
|
/*********************************************************/
|
|
/********* client command interface *********/
|
|
/*********************************************************/
|
|
|
|
// execute a specific command on the web client
|
|
this.command = function(command, props, obj, event)
|
|
{
|
|
var ret, uid, cid, url, flag, aborted = false;
|
|
|
|
if (obj && obj.blur)
|
|
obj.blur();
|
|
|
|
// do nothing if interface is locked by other command (with exception for searching reset)
|
|
if (this.busy && !(command == 'reset-search' && this.last_command == 'search'))
|
|
return false;
|
|
|
|
// let the browser handle this click (shift/ctrl usually opens the link in a new window/tab)
|
|
if ((obj && obj.href && String(obj.href).indexOf('#') < 0) && rcube_event.get_modifier(event)) {
|
|
return true;
|
|
}
|
|
|
|
// command not supported or allowed
|
|
if (!this.commands[command]) {
|
|
// pass command to parent window
|
|
if (this.is_framed())
|
|
parent.rcmail.command(command, props);
|
|
|
|
return false;
|
|
}
|
|
|
|
// check input before leaving compose step
|
|
if (this.task == 'mail' && this.env.action == 'compose' && $.inArray(command, this.env.compose_commands) < 0 && !this.env.server_error) {
|
|
if (this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning')))
|
|
return false;
|
|
|
|
// remove copy from local storage if compose screen is left intentionally
|
|
this.remove_compose_data(this.env.compose_id);
|
|
}
|
|
|
|
this.last_command = command;
|
|
|
|
// process external commands
|
|
if (typeof this.command_handlers[command] === 'function') {
|
|
ret = this.command_handlers[command](props, obj);
|
|
return ret !== undefined ? ret : (obj ? false : true);
|
|
}
|
|
else if (typeof this.command_handlers[command] === 'string') {
|
|
ret = window[this.command_handlers[command]](props, obj);
|
|
return ret !== undefined ? ret : (obj ? false : true);
|
|
}
|
|
|
|
// trigger plugin hooks
|
|
this.triggerEvent('actionbefore', {props:props, action:command});
|
|
ret = this.triggerEvent('before'+command, props);
|
|
if (ret !== undefined) {
|
|
// abort if one of the handlers returned false
|
|
if (ret === false)
|
|
return false;
|
|
else
|
|
props = ret;
|
|
}
|
|
|
|
ret = undefined;
|
|
|
|
// process internal command
|
|
switch (command) {
|
|
|
|
case 'login':
|
|
if (this.gui_objects.loginform)
|
|
this.gui_objects.loginform.submit();
|
|
break;
|
|
|
|
// commands to switch task
|
|
case 'logout':
|
|
case 'mail':
|
|
case 'addressbook':
|
|
case 'settings':
|
|
this.switch_task(command);
|
|
break;
|
|
|
|
case 'about':
|
|
this.redirect('?_task=settings&_action=about', false);
|
|
break;
|
|
|
|
case 'permaurl':
|
|
if (obj && obj.href && obj.target)
|
|
return true;
|
|
else if (this.env.permaurl)
|
|
parent.location.href = this.env.permaurl;
|
|
break;
|
|
|
|
case 'extwin':
|
|
if (this.env.action == 'compose') {
|
|
var form = this.gui_objects.messageform,
|
|
win = this.open_window('');
|
|
|
|
if (win) {
|
|
this.save_compose_form_local();
|
|
$("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.display_message(this.get_label('windowopenerror'), 'error');
|
|
}
|
|
}
|
|
else {
|
|
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;
|
|
|
|
case 'open':
|
|
if (uid = this.get_single_uid()) {
|
|
obj.href = this.url('show', {_mbox: this.get_message_mailbox(uid), _uid: uid});
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case 'close':
|
|
if (this.env.extwin)
|
|
window.close();
|
|
break;
|
|
|
|
case 'list':
|
|
if (props && props != '') {
|
|
this.reset_qsearch();
|
|
}
|
|
if (this.env.action == 'compose' && this.env.extwin) {
|
|
window.close();
|
|
}
|
|
else if (this.task == 'mail') {
|
|
this.list_mailbox(props);
|
|
this.set_button_titles();
|
|
}
|
|
else if (this.task == 'addressbook')
|
|
this.list_contacts(props);
|
|
break;
|
|
|
|
case 'set-listmode':
|
|
this.set_list_options(null, undefined, undefined, props == 'threads' ? 1 : 0);
|
|
break;
|
|
|
|
case 'sort':
|
|
var sort_order = this.env.sort_order,
|
|
sort_col = !this.env.disabled_sort_col ? props : this.env.sort_col;
|
|
|
|
if (!this.env.disabled_sort_order)
|
|
sort_order = this.env.sort_col == sort_col && sort_order == 'ASC' ? 'DESC' : 'ASC';
|
|
|
|
// set table header and update env
|
|
this.set_list_sorting(sort_col, sort_order);
|
|
|
|
// reload message list
|
|
this.list_mailbox('', '', sort_col+'_'+sort_order);
|
|
break;
|
|
|
|
case 'nextpage':
|
|
this.list_page('next');
|
|
break;
|
|
|
|
case 'lastpage':
|
|
this.list_page('last');
|
|
break;
|
|
|
|
case 'previouspage':
|
|
this.list_page('prev');
|
|
break;
|
|
|
|
case 'firstpage':
|
|
this.list_page('first');
|
|
break;
|
|
|
|
case 'expunge':
|
|
if (this.env.exists)
|
|
this.expunge_mailbox(this.env.mailbox);
|
|
break;
|
|
|
|
case 'purge':
|
|
case 'empty-mailbox':
|
|
if (this.env.exists)
|
|
this.purge_mailbox(this.env.mailbox);
|
|
break;
|
|
|
|
// common commands used in multiple tasks
|
|
case 'show':
|
|
if (this.task == 'mail') {
|
|
uid = this.get_single_uid();
|
|
if (uid && (!this.env.uid || uid != this.env.uid)) {
|
|
if (this.env.mailbox == this.env.drafts_mailbox)
|
|
this.open_compose_step({ _draft_uid: uid, _mbox: this.env.mailbox });
|
|
else
|
|
this.show_message(uid);
|
|
}
|
|
}
|
|
else if (this.task == 'addressbook') {
|
|
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');
|
|
else if (this.task == 'settings' && this.env.action == 'responses') {
|
|
var frame;
|
|
if ((frame = this.get_frame_window(this.env.contentframe))) {
|
|
this.set_busy(true);
|
|
this.location_href({ _action:'add-response', _framed:1 }, frame);
|
|
}
|
|
}
|
|
else if (this.task == 'settings') {
|
|
this.identity_list.clear_selection();
|
|
this.load_identity(0, 'add-identity');
|
|
}
|
|
break;
|
|
|
|
case 'edit':
|
|
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');
|
|
else if (this.task == 'mail' && (uid = this.get_single_uid())) {
|
|
url = { _mbox: this.get_message_mailbox(uid) };
|
|
url[this.env.mailbox == this.env.drafts_mailbox && props != 'new' ? '_draft_uid' : '_uid'] = uid;
|
|
this.open_compose_step(url);
|
|
}
|
|
break;
|
|
|
|
case 'save':
|
|
var input, form = this.gui_objects.editform;
|
|
if (form) {
|
|
// adv. search
|
|
if (this.env.action == 'search') {
|
|
}
|
|
// user prefs
|
|
else if ((input = $("input[name='_pagesize']", form)) && input.length && isNaN(parseInt(input.val()))) {
|
|
alert(this.get_label('nopagesizewarning'));
|
|
input.focus();
|
|
break;
|
|
}
|
|
// contacts/identities
|
|
else {
|
|
// reload form
|
|
if (props == 'reload') {
|
|
form.action += '?_reload=1';
|
|
}
|
|
else if (this.task == 'settings' && (this.env.identities_level % 2) == 0 &&
|
|
(input = $("input[name='_email']", form)) && input.length && !rcube_check_email(input.val())
|
|
) {
|
|
alert(this.get_label('noemailwarning'));
|
|
input.focus();
|
|
break;
|
|
}
|
|
|
|
// clear empty input fields
|
|
$('input.placeholder').each(function(){ if (this.value == this._placeholder) this.value = ''; });
|
|
}
|
|
|
|
// add selected source (on the list)
|
|
if (parent.rcmail && parent.rcmail.env.source)
|
|
form.action = this.add_url(form.action, '_orig_source', parent.rcmail.env.source);
|
|
|
|
form.submit();
|
|
}
|
|
break;
|
|
|
|
case 'delete':
|
|
// mail task
|
|
if (this.task == 'mail')
|
|
this.delete_messages(event);
|
|
// addressbook task
|
|
else if (this.task == 'addressbook')
|
|
this.delete_contacts();
|
|
// settings: canned response
|
|
else if (this.task == 'settings' && this.env.action == 'responses')
|
|
this.delete_response();
|
|
// settings: user identities
|
|
else if (this.task == 'settings')
|
|
this.delete_identity();
|
|
break;
|
|
|
|
// mail task commands
|
|
case 'move':
|
|
case 'moveto': // deprecated
|
|
if (this.task == 'mail')
|
|
this.move_messages(props, obj);
|
|
else if (this.task == 'addressbook')
|
|
this.move_contacts(props);
|
|
break;
|
|
|
|
case 'copy':
|
|
if (this.task == 'mail')
|
|
this.copy_messages(props, obj);
|
|
else if (this.task == 'addressbook')
|
|
this.copy_contacts(props);
|
|
break;
|
|
|
|
case 'mark':
|
|
if (props)
|
|
this.mark_message(props);
|
|
break;
|
|
|
|
case 'toggle_status':
|
|
case 'toggle_flag':
|
|
flag = command == 'toggle_flag' ? 'flagged' : 'read';
|
|
|
|
if (uid = props) {
|
|
// toggle flagged/unflagged
|
|
if (flag == 'flagged') {
|
|
if (this.message_list.rows[uid].flagged)
|
|
flag = 'unflagged';
|
|
}
|
|
// toggle read/unread
|
|
else 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 'always-load':
|
|
if (this.env.uid && this.env.sender) {
|
|
this.add_contact(this.env.sender);
|
|
setTimeout(function(){ ref.command('load-images'); }, 300);
|
|
break;
|
|
}
|
|
|
|
case 'load-images':
|
|
if (this.env.uid)
|
|
this.show_message(this.env.uid, true, this.env.action=='preview');
|
|
break;
|
|
|
|
case 'load-attachment':
|
|
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 (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'))
|
|
break;
|
|
}
|
|
|
|
this.goto_url('get', qstring+'&_download=1', false);
|
|
break;
|
|
|
|
case 'select-all':
|
|
this.select_all_mode = props ? false : true;
|
|
this.dummy_select = true; // prevent msg opening if there's only one msg on the list
|
|
if (props == 'invert')
|
|
this.message_list.invert_selection();
|
|
else
|
|
this.message_list.select_all(props == 'page' ? '' : props);
|
|
this.dummy_select = null;
|
|
break;
|
|
|
|
case 'select-none':
|
|
this.select_all_mode = false;
|
|
this.message_list.clear_selection();
|
|
break;
|
|
|
|
case 'expand-all':
|
|
this.env.autoexpand_threads = 1;
|
|
this.message_list.expand_all();
|
|
break;
|
|
|
|
case 'expand-unread':
|
|
this.env.autoexpand_threads = 2;
|
|
this.message_list.collapse_all();
|
|
this.expand_unread();
|
|
break;
|
|
|
|
case 'collapse-all':
|
|
this.env.autoexpand_threads = 0;
|
|
this.message_list.collapse_all();
|
|
break;
|
|
|
|
case 'nextmessage':
|
|
if (this.env.next_uid)
|
|
this.show_message(this.env.next_uid, false, this.env.action == 'preview');
|
|
break;
|
|
|
|
case 'lastmessage':
|
|
if (this.env.last_uid)
|
|
this.show_message(this.env.last_uid);
|
|
break;
|
|
|
|
case 'previousmessage':
|
|
if (this.env.prev_uid)
|
|
this.show_message(this.env.prev_uid, false, this.env.action == 'preview');
|
|
break;
|
|
|
|
case 'firstmessage':
|
|
if (this.env.first_uid)
|
|
this.show_message(this.env.first_uid);
|
|
break;
|
|
|
|
case 'compose':
|
|
url = {};
|
|
|
|
if (this.task == 'mail') {
|
|
url._mbox = this.env.mailbox;
|
|
if (props)
|
|
url._to = props;
|
|
// also send search request so we can go back to search result after message is sent
|
|
if (this.env.search_request)
|
|
url._search = this.env.search_request;
|
|
}
|
|
// modify url if we're in addressbook
|
|
else if (this.task == 'addressbook') {
|
|
// switch to mail compose step directly
|
|
if (props && props.indexOf('@') > 0) {
|
|
url._to = props;
|
|
}
|
|
else {
|
|
var a_cids = [];
|
|
// use contact id passed as command parameter
|
|
if (props)
|
|
a_cids.push(props);
|
|
// get selected contacts
|
|
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);
|
|
else if (this.env.group)
|
|
this.http_post('mailto', { _gid: this.env.group, _source: this.env.source }, true);
|
|
|
|
break;
|
|
}
|
|
}
|
|
else if (props)
|
|
url._to = props;
|
|
|
|
this.open_compose_step(url);
|
|
break;
|
|
|
|
case 'spellcheck':
|
|
if (this.spellcheck_state()) {
|
|
this.stop_spellchecking();
|
|
}
|
|
else {
|
|
if (window.tinyMCE && tinyMCE.get(this.env.composebody)) {
|
|
tinyMCE.execCommand('mceSpellCheck', true);
|
|
}
|
|
else if (this.env.spellcheck && this.env.spellcheck.spellCheck) {
|
|
this.env.spellcheck.spellCheck();
|
|
}
|
|
}
|
|
this.spellcheck_state();
|
|
break;
|
|
|
|
case 'savedraft':
|
|
// Reset the auto-save timer
|
|
clearTimeout(this.save_timer);
|
|
|
|
// 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;
|
|
}
|
|
|
|
this.submit_messageform(true);
|
|
break;
|
|
|
|
case 'send':
|
|
if (!props.nocheck && !this.check_compose_input(command))
|
|
break;
|
|
|
|
// Reset the auto-save timer
|
|
clearTimeout(this.save_timer);
|
|
|
|
this.submit_messageform();
|
|
break;
|
|
|
|
case 'send-attachment':
|
|
// Reset the auto-save timer
|
|
clearTimeout(this.save_timer);
|
|
|
|
if (!(flag = this.upload_file(props || this.gui_objects.uploadform, 'upload'))) {
|
|
if (flag !== false)
|
|
alert(this.get_label('selectimportfile'));
|
|
aborted = true;
|
|
}
|
|
break;
|
|
|
|
case 'insert-sig':
|
|
this.change_identity($("[name='_from']")[0], true);
|
|
break;
|
|
|
|
case 'list-adresses':
|
|
this.list_contacts(props);
|
|
this.enable_command('add-recipient', false);
|
|
break;
|
|
|
|
case 'add-recipient':
|
|
this.compose_add_recipient(props);
|
|
break;
|
|
|
|
case 'reply-all':
|
|
case 'reply-list':
|
|
case 'reply':
|
|
if (uid = this.get_single_uid()) {
|
|
url = {_reply_uid: uid, _mbox: this.get_message_mailbox(uid)};
|
|
if (command == 'reply-all')
|
|
// do reply-list, when list is detected and popup menu wasn't used
|
|
url._all = (!props && this.env.reply_all_mode == 1 && this.commands['reply-list'] ? 'list' : 'all');
|
|
else if (command == 'reply-list')
|
|
url._all = 'list';
|
|
|
|
this.open_compose_step(url);
|
|
}
|
|
break;
|
|
|
|
case 'forward-attachment':
|
|
case 'forward-inline':
|
|
case 'forward':
|
|
var uids = this.env.uid ? [this.env.uid] : (this.message_list ? this.message_list.get_selection() : []);
|
|
if (uids.length) {
|
|
url = { _forward_uid: this.uids_to_list(uids), _mbox: this.env.mailbox, _search: this.env.search_request };
|
|
if (command == 'forward-attachment' || (!props && this.env.forward_attachment) || uids.length > 1)
|
|
url._attachment = 1;
|
|
this.open_compose_step(url);
|
|
}
|
|
break;
|
|
|
|
case 'print':
|
|
if (this.env.action == 'get') {
|
|
this.gui_objects.messagepartframe.contentWindow.print();
|
|
}
|
|
else if (uid = this.get_single_uid()) {
|
|
ref.printwin = this.open_window(this.env.comm_path+'&_action=print&_uid='+uid+'&_mbox='+urlencode(this.get_message_mailbox(uid))+(this.env.safemode ? '&_safe=1' : ''), true, true);
|
|
if (this.printwin) {
|
|
if (this.env.action != 'show')
|
|
this.mark_message('read', uid);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'viewsource':
|
|
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':
|
|
if (this.env.action == 'get') {
|
|
location.href = location.href.replace(/_frame=/, '_download=');
|
|
}
|
|
else if (uid = this.get_single_uid()) {
|
|
this.goto_url('viewsource', { _uid: uid, _mbox: this.get_message_mailbox(uid), _save: 1 });
|
|
}
|
|
break;
|
|
|
|
// quicksearch
|
|
case 'search':
|
|
if (!props && this.gui_objects.qsearchbox)
|
|
props = this.gui_objects.qsearchbox.value;
|
|
if (props) {
|
|
this.qsearch(props);
|
|
break;
|
|
}
|
|
|
|
// reset quicksearch
|
|
case 'reset-search':
|
|
var n, s = this.env.search_request || this.env.qsearch;
|
|
|
|
this.reset_qsearch();
|
|
this.select_all_mode = false;
|
|
|
|
if (s && this.env.action == 'compose') {
|
|
if (this.contact_list)
|
|
this.list_contacts_clear();
|
|
}
|
|
else if (s && this.env.mailbox) {
|
|
this.list_mailbox(this.env.mailbox, 1);
|
|
}
|
|
else if (s && this.task == 'addressbook') {
|
|
if (this.env.source == '') {
|
|
for (n in this.env.address_sources) break;
|
|
this.env.source = n;
|
|
this.env.group = '';
|
|
}
|
|
this.list_contacts(this.env.source, this.env.group, 1);
|
|
}
|
|
break;
|
|
|
|
case 'pushgroup':
|
|
// add group ID to stack
|
|
this.env.address_group_stack.push(props.id);
|
|
if (obj && event)
|
|
rcube_event.cancel(event);
|
|
|
|
case 'listgroup':
|
|
this.reset_qsearch();
|
|
this.list_contacts(props.source, props.id);
|
|
break;
|
|
|
|
case 'popgroup':
|
|
if (this.env.address_group_stack.length > 1) {
|
|
this.env.address_group_stack.pop();
|
|
this.reset_qsearch();
|
|
this.list_contacts(props.source, this.env.address_group_stack[this.env.address_group_stack.length-1]);
|
|
}
|
|
break;
|
|
|
|
case 'import-messages':
|
|
var form = props || this.gui_objects.importform,
|
|
importlock = this.set_busy(true, 'importwait');
|
|
|
|
$('input[name="_unlock"]', form).val(importlock);
|
|
|
|
if (!(flag = this.upload_file(form, 'import'))) {
|
|
this.set_busy(false, null, importlock);
|
|
if (flag !== false)
|
|
alert(this.get_label('selectimportfile'));
|
|
aborted = true;
|
|
}
|
|
break;
|
|
|
|
case 'import':
|
|
if (this.env.action == 'import' && this.gui_objects.importform) {
|
|
var file = document.getElementById('rcmimportfile');
|
|
if (file && !file.value) {
|
|
alert(this.get_label('selectimportfile'));
|
|
aborted = true;
|
|
break;
|
|
}
|
|
this.gui_objects.importform.submit();
|
|
this.set_busy(true, 'importwait');
|
|
this.lock_form(this.gui_objects.importform, true);
|
|
}
|
|
else
|
|
this.goto_url('import', (this.env.source ? '_target='+urlencode(this.env.source)+'&' : ''));
|
|
break;
|
|
|
|
case 'export':
|
|
if (this.contact_list.rowcount > 0) {
|
|
this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _search: this.env.search_request });
|
|
}
|
|
break;
|
|
|
|
case 'export-selected':
|
|
if (this.contact_list.rowcount > 0) {
|
|
this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _cid: this.contact_list.get_selection().join(',') });
|
|
}
|
|
break;
|
|
|
|
case 'upload-photo':
|
|
this.upload_contact_photo(props || this.gui_objects.uploadform);
|
|
break;
|
|
|
|
case 'delete-photo':
|
|
this.replace_contact_photo('-del-');
|
|
break;
|
|
|
|
// user settings commands
|
|
case 'preferences':
|
|
case 'identities':
|
|
case 'responses':
|
|
case 'folders':
|
|
this.goto_url('settings/' + command);
|
|
break;
|
|
|
|
case 'undo':
|
|
this.http_request('undo', '', this.display_message('', 'loading'));
|
|
break;
|
|
|
|
// unified command call (command name == function name)
|
|
default:
|
|
var func = command.replace(/-/g, '_');
|
|
if (this[func] && typeof this[func] === 'function') {
|
|
ret = this[func](props, obj);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!aborted && this.triggerEvent('after'+command, props) === false)
|
|
ret = false;
|
|
this.triggerEvent('actionafter', { props:props, action:command, aborted:aborted });
|
|
|
|
return ret === false ? false : obj ? false : true;
|
|
};
|
|
|
|
// set command(s) enabled or disabled
|
|
this.enable_command = function()
|
|
{
|
|
var i, n, args = Array.prototype.slice.call(arguments),
|
|
enable = args.pop(), cmd;
|
|
|
|
for (n=0; n<args.length; n++) {
|
|
cmd = args[n];
|
|
// argument of type array
|
|
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 {
|
|
for (i in cmd)
|
|
args.push(cmd[i]);
|
|
}
|
|
}
|
|
};
|
|
|
|
this.command_enabled = function(cmd)
|
|
{
|
|
return this.commands[cmd];
|
|
}
|
|
|
|
// lock/unlock interface
|
|
this.set_busy = function(a, message, id)
|
|
{
|
|
if (a && message) {
|
|
var msg = this.get_label(message);
|
|
if (msg == message)
|
|
msg = 'Loading...';
|
|
|
|
id = this.display_message(msg, 'loading');
|
|
}
|
|
else if (!a && id) {
|
|
this.hide_message(id);
|
|
}
|
|
|
|
this.busy = a;
|
|
//document.body.style.cursor = a ? 'wait' : 'default';
|
|
|
|
if (this.gui_objects.editform)
|
|
this.lock_form(this.gui_objects.editform, a);
|
|
|
|
return id;
|
|
};
|
|
|
|
// return a localized string
|
|
this.get_label = function(name, domain)
|
|
{
|
|
if (domain && this.labels[domain+'.'+name])
|
|
return this.labels[domain+'.'+name];
|
|
else if (this.labels[name])
|
|
return this.labels[name];
|
|
else
|
|
return name;
|
|
};
|
|
|
|
// alias for convenience reasons
|
|
this.gettext = this.get_label;
|
|
|
|
// switch to another application task
|
|
this.switch_task = function(task)
|
|
{
|
|
if (this.task===task && task!='mail')
|
|
return;
|
|
|
|
var url = this.get_task_url(task);
|
|
if (task == 'mail')
|
|
url += '&_mbox=INBOX';
|
|
else if (task == 'logout')
|
|
this.clear_compose_data();
|
|
|
|
this.redirect(url);
|
|
};
|
|
|
|
this.get_task_url = function(task, url)
|
|
{
|
|
if (!url)
|
|
url = this.env.comm_path;
|
|
|
|
return url.replace(/_task=[a-z0-9_-]+/i, '_task='+task);
|
|
};
|
|
|
|
this.reload = function(delay)
|
|
{
|
|
if (this.is_framed())
|
|
parent.rcmail.reload(delay);
|
|
else if (delay)
|
|
setTimeout(function(){ rcmail.reload(); }, delay);
|
|
else if (window.location)
|
|
location.href = this.env.comm_path + (this.env.action ? '&_action='+this.env.action : '');
|
|
};
|
|
|
|
// Add variable to GET string, replace old value if exists
|
|
this.add_url = function(url, name, value)
|
|
{
|
|
value = urlencode(value);
|
|
|
|
if (/(\?.*)$/.test(url)) {
|
|
var urldata = RegExp.$1,
|
|
datax = RegExp('((\\?|&)'+RegExp.escape(name)+'=[^&]*)');
|
|
|
|
if (datax.test(urldata)) {
|
|
urldata = urldata.replace(datax, RegExp.$2 + name + '=' + value);
|
|
}
|
|
else
|
|
urldata += '&' + name + '=' + value
|
|
|
|
return url.replace(/(\?.*)$/, urldata);
|
|
}
|
|
|
|
return url + '?' + name + '=' + value;
|
|
};
|
|
|
|
this.is_framed = function()
|
|
{
|
|
return (this.env.framed && parent.rcmail && parent.rcmail != this && parent.rcmail.command);
|
|
};
|
|
|
|
this.save_pref = function(prop)
|
|
{
|
|
var request = {'_name': prop.name, '_value': prop.value};
|
|
|
|
if (prop.session)
|
|
request['_session'] = prop.session;
|
|
if (prop.env)
|
|
this.env[prop.env] = prop.value;
|
|
|
|
this.http_post('save-pref', request);
|
|
};
|
|
|
|
this.html_identifier = function(str, encode)
|
|
{
|
|
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)
|
|
{
|
|
str = String(str).replace(/-/g, '+').replace(/_/g, '/');
|
|
|
|
while (str.length % 4) str += '=';
|
|
|
|
return Base64.decode(str);
|
|
};
|
|
|
|
|
|
/*********************************************************/
|
|
/********* event handling methods *********/
|
|
/*********************************************************/
|
|
|
|
this.drag_menu = function(e, target)
|
|
{
|
|
var modkey = rcube_event.get_modifier(e),
|
|
menu = this.gui_objects.dragmenu;
|
|
|
|
if (menu && modkey == SHIFT_KEY && this.commands['copy']) {
|
|
var pos = rcube_event.get_mouse_pos(e);
|
|
this.env.drag_target = target;
|
|
$(menu).css({top: (pos.y-10)+'px', left: (pos.x-10)+'px'}).show();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
this.drag_menu_action = function(action)
|
|
{
|
|
var menu = this.gui_objects.dragmenu;
|
|
if (menu) {
|
|
$(menu).hide();
|
|
}
|
|
this.command(action, this.env.drag_target);
|
|
this.env.drag_target = null;
|
|
};
|
|
|
|
this.drag_start = function(list)
|
|
{
|
|
this.drag_active = true;
|
|
|
|
if (this.preview_timer)
|
|
clearTimeout(this.preview_timer);
|
|
if (this.preview_read_timer)
|
|
clearTimeout(this.preview_read_timer);
|
|
|
|
// prepare treelist widget for dragging interactions
|
|
if (this.treelist)
|
|
this.treelist.drag_start();
|
|
};
|
|
|
|
this.drag_end = function(e)
|
|
{
|
|
var list, model;
|
|
|
|
if (this.treelist)
|
|
this.treelist.drag_end();
|
|
|
|
// execute drag & drop action when mouse was released
|
|
if (list = this.message_list)
|
|
model = this.env.mailboxes;
|
|
else if (list = this.contact_list)
|
|
model = this.env.contactfolders;
|
|
|
|
if (this.drag_active && model && this.env.last_folder_target) {
|
|
var target = model[this.env.last_folder_target];
|
|
list.draglayer.hide();
|
|
|
|
if (this.contact_list) {
|
|
if (!this.contacts_drag_menu(e, target))
|
|
this.command('move', target);
|
|
}
|
|
else if (!this.drag_menu(e, target))
|
|
this.command('move', target);
|
|
}
|
|
|
|
this.drag_active = false;
|
|
this.env.last_folder_target = null;
|
|
};
|
|
|
|
this.drag_move = function(e)
|
|
{
|
|
if (this.gui_objects.folderlist) {
|
|
var drag_target, oldclass,
|
|
layerclass = 'draglayernormal',
|
|
mouse = rcube_event.get_mouse_pos(e);
|
|
|
|
if (this.contact_list && this.contact_list.draglayer)
|
|
oldclass = this.contact_list.draglayer.attr('class');
|
|
|
|
// 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');
|
|
}
|
|
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)
|
|
this.contact_list.draglayer.attr('class', layerclass);
|
|
}
|
|
};
|
|
|
|
this.collapse_folder = function(name)
|
|
{
|
|
if (this.treelist)
|
|
this.treelist.toggle(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 && this.env.mailbox.startsWith(name + this.env.delimiter) && !node.virtual)
|
|
this.command('list', name);
|
|
}
|
|
else {
|
|
var reg = new RegExp('&'+urlencode(node.id)+'&');
|
|
this.env[prefname] = this.env[prefname].replace(reg, '');
|
|
}
|
|
|
|
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)
|
|
{
|
|
var list, id;
|
|
|
|
// ignore event if jquery UI dialog is open
|
|
if ($(rcube_event.get_target(e)).closest('.ui-dialog, .ui-widget-overlay').length)
|
|
return;
|
|
|
|
list = this.message_list || this.contact_list;
|
|
if (list && !rcube_mouse_is_over(e, list.list.parentNode))
|
|
list.blur();
|
|
|
|
// reset 'pressed' buttons
|
|
if (this.buttons_sel) {
|
|
for (id in this.buttons_sel)
|
|
if (typeof id !== 'function')
|
|
this.button_out(this.buttons_sel[id], id);
|
|
this.buttons_sel = {};
|
|
}
|
|
};
|
|
|
|
this.click_on_list = function(e)
|
|
{
|
|
if (this.gui_objects.qsearchbox)
|
|
this.gui_objects.qsearchbox.blur();
|
|
|
|
if (this.message_list)
|
|
this.message_list.focus();
|
|
else if (this.contact_list)
|
|
this.contact_list.focus();
|
|
|
|
return true;
|
|
};
|
|
|
|
this.msglist_select = function(list)
|
|
{
|
|
if (this.preview_timer)
|
|
clearTimeout(this.preview_timer);
|
|
if (this.preview_read_timer)
|
|
clearTimeout(this.preview_read_timer);
|
|
|
|
var selected = list.get_single_selection();
|
|
|
|
this.enable_command(this.env.message_commands, selected != null);
|
|
if (selected) {
|
|
// Hide certain command buttons when Drafts folder is selected
|
|
if (this.env.mailbox == this.env.drafts_mailbox)
|
|
this.enable_command('reply', 'reply-all', 'reply-list', 'forward', 'forward-attachment', 'forward-inline', false);
|
|
// Disable reply-list when List-Post header is not set
|
|
else {
|
|
var msg = this.env.messages[selected];
|
|
if (!msg.ml)
|
|
this.enable_command('reply-list', false);
|
|
}
|
|
}
|
|
// Multi-message commands
|
|
this.enable_command('delete', 'move', 'copy', 'mark', 'forward', 'forward-attachment', list.selection.length > 0);
|
|
|
|
// reset all-pages-selection
|
|
if (selected || (list.selection.length && list.selection.length != list.rowcount))
|
|
this.select_all_mode = false;
|
|
|
|
// 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(); }, this.dblclick_time);
|
|
else if (this.env.contentframe)
|
|
this.show_contentframe(false);
|
|
};
|
|
|
|
// This allow as to re-select selected message and display it in preview frame
|
|
this.msglist_click = function(list)
|
|
{
|
|
if (list.multi_selecting || !this.env.contentframe)
|
|
return;
|
|
|
|
if (list.get_single_selection())
|
|
return;
|
|
|
|
var win = this.get_frame_window(this.env.contentframe);
|
|
|
|
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(); }, this.dblclick_time);
|
|
}
|
|
};
|
|
|
|
this.msglist_dbl_click = function(list)
|
|
{
|
|
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.messages[uid].mbox || this.env.mailbox) == this.env.drafts_mailbox)
|
|
this.open_compose_step({ _draft_uid: uid, _mbox: this.env.mailbox });
|
|
else if (uid)
|
|
this.show_message(uid, false, false);
|
|
};
|
|
|
|
this.msglist_keypress = function(list)
|
|
{
|
|
if (list.modkey == CONTROL_KEY)
|
|
return;
|
|
|
|
if (list.key_pressed == list.ENTER_KEY)
|
|
this.command('show');
|
|
else if (list.key_pressed == list.DELETE_KEY || list.key_pressed == list.BACKSPACE_KEY)
|
|
this.command('delete');
|
|
else if (list.key_pressed == 33)
|
|
this.command('previouspage');
|
|
else if (list.key_pressed == 34)
|
|
this.command('nextpage');
|
|
};
|
|
|
|
this.msglist_get_preview = function()
|
|
{
|
|
var uid = this.get_single_uid();
|
|
if (uid && this.env.contentframe && !this.drag_active)
|
|
this.show_message(uid, false, true);
|
|
else if (this.env.contentframe)
|
|
this.show_contentframe(false);
|
|
};
|
|
|
|
this.msglist_expand = function(row)
|
|
{
|
|
if (this.env.messages[row.uid])
|
|
this.env.messages[row.uid].expanded = row.expanded;
|
|
$(row.obj)[row.expanded?'addClass':'removeClass']('expanded');
|
|
};
|
|
|
|
this.msglist_set_coltypes = function(list)
|
|
{
|
|
var i, found, name, cols = list.thead.rows[0].cells;
|
|
|
|
this.env.listcols = [];
|
|
|
|
for (i=0; i<cols.length; i++)
|
|
if (cols[i].id && cols[i].id.startsWith('rcm')) {
|
|
name = cols[i].id.slice(3);
|
|
this.env.listcols.push(name);
|
|
}
|
|
|
|
if ((found = $.inArray('flag', this.env.listcols)) >= 0)
|
|
this.env.flagged_col = found;
|
|
|
|
if ((found = $.inArray('subject', this.env.listcols)) >= 0)
|
|
this.env.subject_col = found;
|
|
|
|
this.command('save-pref', { name: 'list_cols', value: this.env.listcols, session: 'list_attrib/columns' });
|
|
};
|
|
|
|
this.check_droptarget = function(id)
|
|
{
|
|
switch (this.task) {
|
|
case 'mail':
|
|
return (this.env.mailboxes[id]
|
|
&& !this.env.mailboxes[id].virtual
|
|
&& (this.env.mailboxes[id].id != this.env.mailbox || this.is_multifolder_listing())) ? 1 : 0;
|
|
|
|
case 'settings':
|
|
return id != this.env.mailbox ? 1 : 0;
|
|
|
|
case 'addressbook':
|
|
var target;
|
|
if (id != this.env.source && (target = this.env.contactfolders[id])) {
|
|
// droptarget is a group
|
|
if (target.type == 'group') {
|
|
if (target.id != this.env.group && !this.env.contactfolders[target.source].readonly) {
|
|
var is_other = this.env.selection_sources.length > 1 || $.inArray(target.source, this.env.selection_sources) == -1;
|
|
return !is_other || this.commands.move ? 1 : 2;
|
|
}
|
|
}
|
|
// droptarget is a (writable) addressbook and it's not the source
|
|
else if (!target.readonly && (this.env.selection_sources.length > 1 || $.inArray(id, this.env.selection_sources) == -1)) {
|
|
return this.commands.move ? 1 : 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
};
|
|
|
|
// open popup window
|
|
this.open_window = function(url, small, toolbar)
|
|
{
|
|
var wname = 'rcmextwin' + new Date().getTime();
|
|
|
|
url += (url.match(/\?/) ? '&' : '?') + '_extwin=1';
|
|
|
|
if (this.env.standard_windows)
|
|
var 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) {
|
|
extwin.document.write('<html><body>' + this.get_label('loading') + '</body></html>');
|
|
}
|
|
|
|
// allow plugins to grab the window reference (#1489413)
|
|
this.triggerEvent('openwindow', { url:url, handle:extwin });
|
|
|
|
// focus window, delayed to bring to front
|
|
window.setTimeout(function() { extwin && extwin.focus(); }, 10);
|
|
|
|
return extwin;
|
|
};
|
|
|
|
|
|
/*********************************************************/
|
|
/********* (message) list functionality *********/
|
|
/*********************************************************/
|
|
|
|
this.init_message_row = function(row)
|
|
{
|
|
var i, fn = {}, uid = row.uid,
|
|
status_icon = (this.env.status_col != null ? 'status' : 'msg') + 'icn' + row.id;
|
|
|
|
if (uid && this.env.messages[uid])
|
|
$.extend(row, this.env.messages[uid]);
|
|
|
|
// set eventhandler to status icon
|
|
if (row.icon = document.getElementById(status_icon)) {
|
|
fn.icon = function(e) { ref.command('toggle_status', uid); };
|
|
}
|
|
|
|
// save message icon position too
|
|
if (this.env.status_col != null)
|
|
row.msgicon = document.getElementById('msgicn'+row.id);
|
|
else
|
|
row.msgicon = row.icon;
|
|
|
|
// set eventhandler to flag icon
|
|
if (this.env.flagged_col != null && (row.flagicon = document.getElementById('flagicn'+row.id))) {
|
|
fn.flagicon = function(e) { ref.command('toggle_flag', uid); };
|
|
}
|
|
|
|
// set event handler to thread expand/collapse icon
|
|
if (!row.depth && row.has_children && (row.expando = document.getElementById('rcmexpando'+row.id))) {
|
|
fn.expando = function(e) { ref.expand_message_row(e, uid); };
|
|
}
|
|
|
|
// attach events
|
|
$.each(fn, function(i, f) {
|
|
row[i].onclick = function(e) { f(e); return rcube_event.cancel(e); };
|
|
if (bw.touch) {
|
|
row[i].addEventListener('touchend', function(e) {
|
|
if (e.changedTouches.length == 1) {
|
|
f(e);
|
|
return rcube_event.cancel(e);
|
|
}
|
|
}, false);
|
|
}
|
|
});
|
|
|
|
this.triggerEvent('insertrow', { uid:uid, row:row });
|
|
};
|
|
|
|
// create a table row in the message list
|
|
this.add_message_row = function(uid, cols, flags, attop)
|
|
{
|
|
if (!this.gui_objects.messagelist || !this.message_list)
|
|
return false;
|
|
|
|
// Prevent from adding messages from different folder (#1487752)
|
|
if (flags.mbox != this.env.mailbox && !flags.skip_mbox_check)
|
|
return false;
|
|
|
|
if (!this.env.messages[uid])
|
|
this.env.messages[uid] = {};
|
|
|
|
// merge flags over local message object
|
|
$.extend(this.env.messages[uid], {
|
|
deleted: flags.deleted?1:0,
|
|
replied: flags.answered?1:0,
|
|
unread: !flags.seen?1:0,
|
|
forwarded: flags.forwarded?1:0,
|
|
flagged: flags.flagged?1:0,
|
|
has_children: flags.has_children?1:0,
|
|
depth: flags.depth?flags.depth:0,
|
|
unread_children: flags.unread_children?flags.unread_children:0,
|
|
parent_uid: flags.parent_uid?flags.parent_uid:0,
|
|
selected: this.select_all_mode || this.message_list.in_selection(uid),
|
|
ml: flags.ml?1:0,
|
|
ctype: flags.ctype,
|
|
mbox: flags.mbox,
|
|
// flags from plugins
|
|
flags: flags.extra_flags
|
|
});
|
|
|
|
var c, n, col, html, css_class,
|
|
tree = '', expando = '',
|
|
list = this.message_list,
|
|
rows = list.rows,
|
|
message = this.env.messages[uid],
|
|
msg_id = this.html_identifier(uid,true),
|
|
row_class = 'message'
|
|
+ (!flags.seen ? ' unread' : '')
|
|
+ (flags.deleted ? ' deleted' : '')
|
|
+ (flags.flagged ? ' flagged' : '')
|
|
+ (message.selected ? ' selected' : ''),
|
|
row = { cols:[], style:{}, id:'rcmrow'+msg_id, uid:uid };
|
|
|
|
// message status icons
|
|
css_class = 'msgicon';
|
|
if (this.env.status_col === null) {
|
|
css_class += ' status';
|
|
if (flags.deleted)
|
|
css_class += ' deleted';
|
|
else if (!flags.seen)
|
|
css_class += ' unread';
|
|
else if (flags.unread_children > 0)
|
|
css_class += ' unreadchildren';
|
|
}
|
|
if (flags.answered)
|
|
css_class += ' replied';
|
|
if (flags.forwarded)
|
|
css_class += ' forwarded';
|
|
|
|
// update selection
|
|
if (message.selected && !list.in_selection(uid))
|
|
list.selection.push(uid);
|
|
|
|
// threads
|
|
if (this.env.threading) {
|
|
if (message.depth) {
|
|
// This assumes that div width is hardcoded to 15px,
|
|
tree += '<span id="rcmtab' + msg_id + '" class="branch" style="width:' + (message.depth * 15) + 'px;"> </span>';
|
|
|
|
if ((rows[message.parent_uid] && rows[message.parent_uid].expanded === false)
|
|
|| ((this.env.autoexpand_threads == 0 || this.env.autoexpand_threads == 2) &&
|
|
(!rows[message.parent_uid] || !rows[message.parent_uid].expanded))
|
|
) {
|
|
row.style.display = 'none';
|
|
message.expanded = false;
|
|
}
|
|
else
|
|
message.expanded = true;
|
|
|
|
row_class += ' thread expanded';
|
|
}
|
|
else if (message.has_children) {
|
|
if (message.expanded === undefined && (this.env.autoexpand_threads == 1 || (this.env.autoexpand_threads == 2 && message.unread_children))) {
|
|
message.expanded = true;
|
|
}
|
|
|
|
expando = '<div id="rcmexpando' + row.id + '" class="' + (message.expanded ? 'expanded' : 'collapsed') + '"> </div>';
|
|
row_class += ' thread' + (message.expanded? ' expanded' : '');
|
|
}
|
|
|
|
if (flags.unread_children && flags.seen && !message.expanded)
|
|
row_class += ' unroot';
|
|
}
|
|
|
|
tree += '<span id="msgicn'+row.id+'" class="'+css_class+'"> </span>';
|
|
row.className = row_class;
|
|
|
|
// build subject link
|
|
if (cols.subject) {
|
|
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+'='+urlencode(uid)+'"'+
|
|
' onclick="return rcube_event.cancel(event)" onmouseover="rcube_webmail.long_subject_title(this,'+(message.depth+1)+')"><span>'+cols.subject+'</span></a>';
|
|
}
|
|
|
|
// add each submitted col
|
|
for (n in this.env.listcols) {
|
|
c = this.env.listcols[n];
|
|
col = {className: String(c).toLowerCase(), events:{}};
|
|
|
|
if (this.env.coltypes[c] && this.env.coltypes[c].hidden) {
|
|
col.className += ' hidden';
|
|
}
|
|
|
|
if (c == 'flag') {
|
|
css_class = (flags.flagged ? 'flagged' : 'unflagged');
|
|
html = '<span id="flagicn'+row.id+'" class="'+css_class+'"> </span>';
|
|
}
|
|
else if (c == 'attachment') {
|
|
if (flags.attachmentClass)
|
|
html = '<span class="'+flags.attachmentClass+'"> </span>';
|
|
else if (/application\/|multipart\/(m|signed)/.test(flags.ctype))
|
|
html = '<span class="attachment"> </span>';
|
|
else if (/multipart\/report/.test(flags.ctype))
|
|
html = '<span class="report"> </span>';
|
|
else
|
|
html = ' ';
|
|
}
|
|
else if (c == 'status') {
|
|
if (flags.deleted)
|
|
css_class = 'deleted';
|
|
else if (!flags.seen)
|
|
css_class = 'unread';
|
|
else if (flags.unread_children > 0)
|
|
css_class = 'unreadchildren';
|
|
else
|
|
css_class = 'msgicon';
|
|
html = '<span id="statusicn'+row.id+'" class="'+css_class+'"> </span>';
|
|
}
|
|
else if (c == 'threads')
|
|
html = expando;
|
|
else if (c == 'subject') {
|
|
if (bw.ie)
|
|
col.events.mouseover = function() { rcube_webmail.long_subject_title_ex(this); };
|
|
html = tree + cols[c];
|
|
}
|
|
else if (c == 'priority') {
|
|
if (flags.prio > 0 && flags.prio < 6)
|
|
html = '<span class="prio'+flags.prio+'"> </span>';
|
|
else
|
|
html = ' ';
|
|
}
|
|
else if (c == 'folder') {
|
|
html = '<span onmouseover="rcube_webmail.long_subject_title(this)">' + cols[c] + '<span>';
|
|
}
|
|
else
|
|
html = cols[c];
|
|
|
|
col.innerHTML = html;
|
|
row.cols.push(col);
|
|
}
|
|
|
|
list.insert_row(row, attop);
|
|
|
|
// remove 'old' row
|
|
if (attop && this.env.pagesize && list.rowcount > this.env.pagesize) {
|
|
var uid = list.get_last_row();
|
|
list.remove_row(uid);
|
|
list.clear_selection(uid);
|
|
}
|
|
};
|
|
|
|
this.set_list_sorting = function(sort_col, sort_order)
|
|
{
|
|
// set table header class
|
|
$('#rcm'+this.env.sort_col).removeClass('sorted'+(this.env.sort_order.toUpperCase()));
|
|
if (sort_col)
|
|
$('#rcm'+sort_col).addClass('sorted'+sort_order);
|
|
|
|
this.env.sort_col = sort_col;
|
|
this.env.sort_order = sort_order;
|
|
};
|
|
|
|
this.set_list_options = function(cols, sort_col, sort_order, threads)
|
|
{
|
|
var update, post_data = {};
|
|
|
|
if (sort_col === undefined)
|
|
sort_col = this.env.sort_col;
|
|
if (!sort_order)
|
|
sort_order = this.env.sort_order;
|
|
|
|
if (this.env.sort_col != sort_col || this.env.sort_order != sort_order) {
|
|
update = 1;
|
|
this.set_list_sorting(sort_col, sort_order);
|
|
}
|
|
|
|
if (this.env.threading != threads) {
|
|
update = 1;
|
|
post_data._threads = threads;
|
|
}
|
|
|
|
if (cols && cols.length) {
|
|
// make sure new columns are added at the end of the list
|
|
var i, idx, name, newcols = [], oldcols = this.env.listcols;
|
|
for (i=0; i<oldcols.length; i++) {
|
|
name = oldcols[i];
|
|
idx = $.inArray(name, cols);
|
|
if (idx != -1) {
|
|
newcols.push(name);
|
|
delete cols[idx];
|
|
}
|
|
}
|
|
for (i=0; i<cols.length; i++)
|
|
if (cols[i])
|
|
newcols.push(cols[i]);
|
|
|
|
if (newcols.join() != oldcols.join()) {
|
|
update = 1;
|
|
post_data._cols = newcols.join(',');
|
|
}
|
|
}
|
|
|
|
if (update)
|
|
this.list_mailbox('', '', sort_col+'_'+sort_order, post_data);
|
|
};
|
|
|
|
// when user double-clicks on a row
|
|
this.show_message = function(id, safe, preview)
|
|
{
|
|
if (!id)
|
|
return;
|
|
|
|
var win, target = window,
|
|
action = preview ? 'preview': 'show',
|
|
url = '&_action='+action+'&_uid='+id+'&_mbox='+urlencode(this.get_message_mailbox(id));
|
|
|
|
if (preview && (win = this.get_frame_window(this.env.contentframe))) {
|
|
target = win;
|
|
url += '&_framed=1';
|
|
}
|
|
|
|
if (safe)
|
|
url += '&_safe=1';
|
|
|
|
// also send search request to get the right messages
|
|
if (this.env.search_request)
|
|
url += '&_search='+this.env.search_request;
|
|
|
|
// add browser capabilities, so we can properly handle attachments
|
|
url += '&_caps='+urlencode(this.browser_capabilities());
|
|
|
|
if (this.env.extwin)
|
|
url += '&_extwin=1';
|
|
|
|
if (preview && String(target.location.href).indexOf(url) >= 0) {
|
|
this.show_contentframe(true);
|
|
}
|
|
else {
|
|
if (!preview && this.env.message_extwin && !this.env.extwin)
|
|
this.open_window(this.env.comm_path+url, true);
|
|
else
|
|
this.location_href(this.env.comm_path+url, target, true);
|
|
|
|
// mark as read and change mbox unread counter
|
|
if (preview && this.message_list && this.message_list.rows[id] && this.message_list.rows[id].unread && this.env.preview_pane_mark_read >= 0) {
|
|
this.preview_read_timer = setTimeout(function() {
|
|
ref.set_message(id, 'unread', false);
|
|
if (ref.env.unread_counts[ref.env.mailbox]) {
|
|
ref.env.unread_counts[ref.env.mailbox] -= 1;
|
|
ref.set_unread_count(ref.env.mailbox, ref.env.unread_counts[ref.env.mailbox], ref.env.mailbox == 'INBOX');
|
|
}
|
|
if (ref.env.preview_pane_mark_read > 0)
|
|
ref.http_post('mark', {_uid: id, _flag: 'read', _quiet: 1});
|
|
}, this.env.preview_pane_mark_read * 1000);
|
|
}
|
|
}
|
|
};
|
|
|
|
this.show_contentframe = function(show)
|
|
{
|
|
var frame, win, name = this.env.contentframe;
|
|
|
|
if (name && (frame = this.get_frame_element(name))) {
|
|
if (!show && (win = this.get_frame_window(name))) {
|
|
if (win.location.href.indexOf(this.env.blankpage) < 0) {
|
|
if (win.stop)
|
|
win.stop();
|
|
else // IE
|
|
win.document.execCommand('Stop');
|
|
|
|
win.location.href = this.env.blankpage;
|
|
}
|
|
}
|
|
else if (!bw.safari && !bw.konq)
|
|
$(frame)[show ? 'show' : 'hide']();
|
|
}
|
|
|
|
if (!show && this.env.frame_lock)
|
|
this.set_busy(false, null, this.env.frame_lock);
|
|
};
|
|
|
|
this.get_frame_element = function(id)
|
|
{
|
|
var frame;
|
|
|
|
if (id && (frame = document.getElementById(id)))
|
|
return frame;
|
|
};
|
|
|
|
this.get_frame_window = function(id)
|
|
{
|
|
var frame = this.get_frame_element(id);
|
|
|
|
if (frame && frame.name && window.frames)
|
|
return window.frames[frame.name];
|
|
};
|
|
|
|
this.lock_frame = function()
|
|
{
|
|
if (!this.env.frame_lock)
|
|
(this.is_framed() ? parent.rcmail : this).env.frame_lock = this.set_busy(true, 'loading');
|
|
};
|
|
|
|
// list a specific page
|
|
this.list_page = function(page)
|
|
{
|
|
if (page == 'next')
|
|
page = this.env.current_page+1;
|
|
else if (page == 'last')
|
|
page = this.env.pagecount;
|
|
else if (page == 'prev' && this.env.current_page > 1)
|
|
page = this.env.current_page-1;
|
|
else if (page == 'first' && this.env.current_page > 1)
|
|
page = 1;
|
|
|
|
if (page > 0 && page <= this.env.pagecount) {
|
|
this.env.current_page = page;
|
|
|
|
if (this.task == 'addressbook' || this.contact_list)
|
|
this.list_contacts(this.env.source, this.env.group, page);
|
|
else if (this.task == 'mail')
|
|
this.list_mailbox(this.env.mailbox, page);
|
|
}
|
|
};
|
|
|
|
// sends request to check for recent messages
|
|
this.checkmail = function()
|
|
{
|
|
var lock = this.set_busy(true, 'checkingmail'),
|
|
params = this.check_recent_params();
|
|
|
|
this.http_post('check-recent', params, lock);
|
|
};
|
|
|
|
// list messages of a specific mailbox using filter
|
|
this.filter_mailbox = function(filter)
|
|
{
|
|
var lock = this.set_busy(true, 'searching');
|
|
|
|
this.clear_message_list();
|
|
|
|
// reset vars
|
|
this.env.current_page = 1;
|
|
this.env.search_filter = filter;
|
|
this.http_request('search', this.search_params(false, filter), lock);
|
|
};
|
|
|
|
// reload the current message listing
|
|
this.refresh_list = function()
|
|
{
|
|
this.list_mailbox(this.env.mailbox, this.env.current_page || 1, null, { _clear:1 }, true);
|
|
if (this.message_list)
|
|
this.message_list.clear_selection();
|
|
};
|
|
|
|
// list messages of a specific mailbox
|
|
this.list_mailbox = function(mbox, page, sort, url, update_only)
|
|
{
|
|
var win, target = window;
|
|
|
|
if (typeof url != 'object')
|
|
url = {};
|
|
|
|
if (!mbox)
|
|
mbox = this.env.mailbox ? this.env.mailbox : 'INBOX';
|
|
|
|
// add sort to url if set
|
|
if (sort)
|
|
url._sort = sort;
|
|
|
|
// also send search request to get the right messages
|
|
if (this.env.search_request)
|
|
url._search = this.env.search_request;
|
|
|
|
// set page=1 if changeing to another mailbox
|
|
if (this.env.mailbox != mbox) {
|
|
page = 1;
|
|
this.env.current_page = page;
|
|
this.select_all_mode = false;
|
|
}
|
|
|
|
if (!update_only) {
|
|
// unselect selected messages and clear the list and message data
|
|
this.clear_message_list();
|
|
|
|
if (mbox != this.env.mailbox || (mbox == this.env.mailbox && !page && !sort))
|
|
url._refresh = 1;
|
|
|
|
this.select_folder(mbox, '', true);
|
|
this.unmark_folder(mbox, 'recent', '', true);
|
|
this.env.mailbox = mbox;
|
|
}
|
|
|
|
// load message list remotely
|
|
if (this.gui_objects.messagelist) {
|
|
this.list_mailbox_remote(mbox, page, url);
|
|
return;
|
|
}
|
|
|
|
if (win = this.get_frame_window(this.env.contentframe)) {
|
|
target = win;
|
|
url._framed = 1;
|
|
}
|
|
|
|
// load message list to target frame/window
|
|
if (mbox) {
|
|
this.set_busy(true, 'loading');
|
|
url._mbox = mbox;
|
|
if (page)
|
|
url._page = page;
|
|
this.location_href(url, target);
|
|
}
|
|
};
|
|
|
|
this.clear_message_list = function()
|
|
{
|
|
this.env.messages = {};
|
|
this.last_selected = 0;
|
|
|
|
this.show_contentframe(false);
|
|
if (this.message_list)
|
|
this.message_list.clear(true);
|
|
};
|
|
|
|
// send remote request to load message list
|
|
this.list_mailbox_remote = function(mbox, page, url)
|
|
{
|
|
var lock = this.set_busy(true, 'loading');
|
|
|
|
if (typeof url != 'object')
|
|
url = {};
|
|
url._mbox = mbox;
|
|
if (page)
|
|
url._page = page;
|
|
|
|
this.http_request('list', url, lock);
|
|
};
|
|
|
|
// removes messages that doesn't exists from list selection array
|
|
this.update_selection = function()
|
|
{
|
|
var selected = this.message_list.selection,
|
|
rows = this.message_list.rows,
|
|
i, selection = [];
|
|
|
|
for (i in selected)
|
|
if (rows[selected[i]])
|
|
selection.push(selected[i]);
|
|
|
|
this.message_list.selection = selection;
|
|
}
|
|
|
|
// expand all threads with unread children
|
|
this.expand_unread = function()
|
|
{
|
|
var r, tbody = this.gui_objects.messagelist.tBodies[0],
|
|
new_row = tbody.firstChild;
|
|
|
|
while (new_row) {
|
|
if (new_row.nodeType == 1 && (r = this.message_list.rows[new_row.uid]) && r.unread_children) {
|
|
this.message_list.expand_all(r);
|
|
this.set_unread_children(r.uid);
|
|
}
|
|
new_row = new_row.nextSibling;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// thread expanding/collapsing handler
|
|
this.expand_message_row = function(e, uid)
|
|
{
|
|
var row = this.message_list.rows[uid];
|
|
|
|
// handle unread_children mark
|
|
row.expanded = !row.expanded;
|
|
this.set_unread_children(uid);
|
|
row.expanded = !row.expanded;
|
|
|
|
this.message_list.expand_row(e, uid);
|
|
};
|
|
|
|
// message list expanding
|
|
this.expand_threads = function()
|
|
{
|
|
if (!this.env.threading || !this.env.autoexpand_threads || !this.message_list)
|
|
return;
|
|
|
|
switch (this.env.autoexpand_threads) {
|
|
case 2: this.expand_unread(); break;
|
|
case 1: this.message_list.expand_all(); break;
|
|
}
|
|
};
|
|
|
|
// Initializes threads indicators/expanders after list update
|
|
this.init_threads = function(roots, mbox)
|
|
{
|
|
// #1487752
|
|
if (mbox && mbox != this.env.mailbox)
|
|
return false;
|
|
|
|
for (var n=0, len=roots.length; n<len; n++)
|
|
this.add_tree_icons(roots[n]);
|
|
this.expand_threads();
|
|
};
|
|
|
|
// adds threads tree icons to the list (or specified thread)
|
|
this.add_tree_icons = function(root)
|
|
{
|
|
var i, l, r, n, len, pos, tmp = [], uid = [],
|
|
row, rows = this.message_list.rows;
|
|
|
|
if (root)
|
|
row = rows[root] ? rows[root].obj : null;
|
|
else
|
|
row = this.message_list.tbody.firstChild;
|
|
|
|
while (row) {
|
|
if (row.nodeType == 1 && (r = rows[row.uid])) {
|
|
if (r.depth) {
|
|
for (i=tmp.length-1; i>=0; i--) {
|
|
len = tmp[i].length;
|
|
if (len > r.depth) {
|
|
pos = len - r.depth;
|
|
if (!(tmp[i][pos] & 2))
|
|
tmp[i][pos] = tmp[i][pos] ? tmp[i][pos]+2 : 2;
|
|
}
|
|
else if (len == r.depth) {
|
|
if (!(tmp[i][0] & 2))
|
|
tmp[i][0] += 2;
|
|
}
|
|
if (r.depth > len)
|
|
break;
|
|
}
|
|
|
|
tmp.push(new Array(r.depth));
|
|
tmp[tmp.length-1][0] = 1;
|
|
uid.push(r.uid);
|
|
}
|
|
else {
|
|
if (tmp.length) {
|
|
for (i in tmp) {
|
|
this.set_tree_icons(uid[i], tmp[i]);
|
|
}
|
|
tmp = [];
|
|
uid = [];
|
|
}
|
|
if (root && row != rows[root].obj)
|
|
break;
|
|
}
|
|
}
|
|
row = row.nextSibling;
|
|
}
|
|
|
|
if (tmp.length) {
|
|
for (i in tmp) {
|
|
this.set_tree_icons(uid[i], tmp[i]);
|
|
}
|
|
}
|
|
};
|
|
|
|
// adds tree icons to specified message row
|
|
this.set_tree_icons = function(uid, tree)
|
|
{
|
|
var i, divs = [], html = '', len = tree.length;
|
|
|
|
for (i=0; i<len; i++) {
|
|
if (tree[i] > 2)
|
|
divs.push({'class': 'l3', width: 15});
|
|
else if (tree[i] > 1)
|
|
divs.push({'class': 'l2', width: 15});
|
|
else if (tree[i] > 0)
|
|
divs.push({'class': 'l1', width: 15});
|
|
// separator div
|
|
else if (divs.length && !divs[divs.length-1]['class'])
|
|
divs[divs.length-1].width += 15;
|
|
else
|
|
divs.push({'class': null, width: 15});
|
|
}
|
|
|
|
for (i=divs.length-1; i>=0; i--) {
|
|
if (divs[i]['class'])
|
|
html += '<div class="tree '+divs[i]['class']+'" />';
|
|
else
|
|
html += '<div style="width:'+divs[i].width+'px" />';
|
|
}
|
|
|
|
if (html)
|
|
$('#rcmtab'+this.html_identifier(uid, true)).html(html);
|
|
};
|
|
|
|
// update parent in a thread
|
|
this.update_thread_root = function(uid, flag)
|
|
{
|
|
if (!this.env.threading)
|
|
return;
|
|
|
|
var root = this.message_list.find_root(uid);
|
|
|
|
if (uid == root)
|
|
return;
|
|
|
|
var p = this.message_list.rows[root];
|
|
|
|
if (flag == 'read' && p.unread_children) {
|
|
p.unread_children--;
|
|
}
|
|
else if (flag == 'unread' && p.has_children) {
|
|
// unread_children may be undefined
|
|
p.unread_children = p.unread_children ? p.unread_children + 1 : 1;
|
|
}
|
|
else {
|
|
return;
|
|
}
|
|
|
|
this.set_message_icon(root);
|
|
this.set_unread_children(root);
|
|
};
|
|
|
|
// update thread indicators for all messages in a thread below the specified message
|
|
// return number of removed/added root level messages
|
|
this.update_thread = function (uid)
|
|
{
|
|
if (!this.env.threading)
|
|
return 0;
|
|
|
|
var r, parent, count = 0,
|
|
rows = this.message_list.rows,
|
|
row = rows[uid],
|
|
depth = rows[uid].depth,
|
|
roots = [];
|
|
|
|
if (!row.depth) // root message: decrease roots count
|
|
count--;
|
|
else if (row.unread) {
|
|
// update unread_children for thread root
|
|
parent = this.message_list.find_root(uid);
|
|
rows[parent].unread_children--;
|
|
this.set_unread_children(parent);
|
|
}
|
|
|
|
parent = row.parent_uid;
|
|
|
|
// childrens
|
|
row = row.obj.nextSibling;
|
|
while (row) {
|
|
if (row.nodeType == 1 && (r = rows[row.uid])) {
|
|
if (!r.depth || r.depth <= depth)
|
|
break;
|
|
|
|
r.depth--; // move left
|
|
// reset width and clear the content of a tab, icons will be added later
|
|
$('#rcmtab'+r.id).width(r.depth * 15).html('');
|
|
if (!r.depth) { // a new root
|
|
count++; // increase roots count
|
|
r.parent_uid = 0;
|
|
if (r.has_children) {
|
|
// replace 'leaf' with 'collapsed'
|
|
$('#'+r.id+' .leaf:first')
|
|
.attr('id', 'rcmexpando' + r.id)
|
|
.attr('class', (r.obj.style.display != 'none' ? 'expanded' : 'collapsed'))
|
|
.bind('mousedown', {uid:r.uid, p:this},
|
|
function(e) { return e.data.p.expand_message_row(e, e.data.uid); });
|
|
|
|
r.unread_children = 0;
|
|
roots.push(r);
|
|
}
|
|
// show if it was hidden
|
|
if (r.obj.style.display == 'none')
|
|
$(r.obj).show();
|
|
}
|
|
else {
|
|
if (r.depth == depth)
|
|
r.parent_uid = parent;
|
|
if (r.unread && roots.length)
|
|
roots[roots.length-1].unread_children++;
|
|
}
|
|
}
|
|
row = row.nextSibling;
|
|
}
|
|
|
|
// update unread_children for roots
|
|
for (var i=0; i<roots.length; i++)
|
|
this.set_unread_children(roots[i].uid);
|
|
|
|
return count;
|
|
};
|
|
|
|
this.delete_excessive_thread_rows = function()
|
|
{
|
|
var rows = this.message_list.rows,
|
|
tbody = this.message_list.tbody,
|
|
row = tbody.firstChild,
|
|
cnt = this.env.pagesize + 1;
|
|
|
|
while (row) {
|
|
if (row.nodeType == 1 && (r = rows[row.uid])) {
|
|
if (!r.depth && cnt)
|
|
cnt--;
|
|
|
|
if (!cnt)
|
|
this.message_list.remove_row(row.uid);
|
|
}
|
|
row = row.nextSibling;
|
|
}
|
|
};
|
|
|
|
// set message icon
|
|
this.set_message_icon = function(uid)
|
|
{
|
|
var css_class,
|
|
row = this.message_list.rows[uid];
|
|
|
|
if (!row)
|
|
return false;
|
|
|
|
if (row.icon) {
|
|
css_class = 'msgicon';
|
|
if (row.deleted)
|
|
css_class += ' deleted';
|
|
else if (row.unread)
|
|
css_class += ' unread';
|
|
else if (row.unread_children)
|
|
css_class += ' unreadchildren';
|
|
if (row.msgicon == row.icon) {
|
|
if (row.replied)
|
|
css_class += ' replied';
|
|
if (row.forwarded)
|
|
css_class += ' forwarded';
|
|
css_class += ' status';
|
|
}
|
|
|
|
row.icon.className = css_class;
|
|
}
|
|
|
|
if (row.msgicon && row.msgicon != row.icon) {
|
|
css_class = 'msgicon';
|
|
if (!row.unread && row.unread_children)
|
|
css_class += ' unreadchildren';
|
|
if (row.replied)
|
|
css_class += ' replied';
|
|
if (row.forwarded)
|
|
css_class += ' forwarded';
|
|
|
|
row.msgicon.className = css_class;
|
|
}
|
|
|
|
if (row.flagicon) {
|
|
css_class = (row.flagged ? 'flagged' : 'unflagged');
|
|
row.flagicon.className = css_class;
|
|
}
|
|
};
|
|
|
|
// set message status
|
|
this.set_message_status = function(uid, flag, status)
|
|
{
|
|
var row = this.message_list.rows[uid];
|
|
|
|
if (!row)
|
|
return false;
|
|
|
|
if (flag == 'unread') {
|
|
if (row.unread != status)
|
|
this.update_thread_root(uid, status ? 'unread' : 'read');
|
|
row.unread = status;
|
|
}
|
|
else if(flag == 'deleted')
|
|
row.deleted = status;
|
|
else if (flag == 'replied')
|
|
row.replied = status;
|
|
else if (flag == 'forwarded')
|
|
row.forwarded = status;
|
|
else if (flag == 'flagged')
|
|
row.flagged = status;
|
|
};
|
|
|
|
// set message row status, class and icon
|
|
this.set_message = function(uid, flag, status)
|
|
{
|
|
var row = this.message_list && this.message_list.rows[uid];
|
|
|
|
if (!row)
|
|
return false;
|
|
|
|
if (flag)
|
|
this.set_message_status(uid, flag, status);
|
|
|
|
var rowobj = $(row.obj);
|
|
|
|
if (row.unread && !rowobj.hasClass('unread'))
|
|
rowobj.addClass('unread');
|
|
else if (!row.unread && rowobj.hasClass('unread'))
|
|
rowobj.removeClass('unread');
|
|
|
|
if (row.deleted && !rowobj.hasClass('deleted'))
|
|
rowobj.addClass('deleted');
|
|
else if (!row.deleted && rowobj.hasClass('deleted'))
|
|
rowobj.removeClass('deleted');
|
|
|
|
if (row.flagged && !rowobj.hasClass('flagged'))
|
|
rowobj.addClass('flagged');
|
|
else if (!row.flagged && rowobj.hasClass('flagged'))
|
|
rowobj.removeClass('flagged');
|
|
|
|
this.set_unread_children(uid);
|
|
this.set_message_icon(uid);
|
|
};
|
|
|
|
// sets unroot (unread_children) class of parent row
|
|
this.set_unread_children = function(uid)
|
|
{
|
|
var row = this.message_list.rows[uid];
|
|
|
|
if (row.parent_uid)
|
|
return;
|
|
|
|
if (!row.unread && row.unread_children && !row.expanded)
|
|
$(row.obj).addClass('unroot');
|
|
else
|
|
$(row.obj).removeClass('unroot');
|
|
};
|
|
|
|
// copy selected messages to the specified mailbox
|
|
this.copy_messages = function(mbox, obj)
|
|
{
|
|
if (mbox && typeof mbox === 'object')
|
|
mbox = mbox.id;
|
|
else if (!mbox)
|
|
return this.folder_selector(obj, function(folder) { ref.command('copy', folder); });
|
|
|
|
// exit if current or no mailbox specified
|
|
if (!mbox || mbox == this.env.mailbox)
|
|
return;
|
|
|
|
var post_data = this.selection_post_data({_target_mbox: mbox});
|
|
|
|
// exit if selection is empty
|
|
if (!post_data._uid)
|
|
return;
|
|
|
|
// send request to server
|
|
this.http_post('copy', post_data, this.display_message(this.get_label('copyingmessage'), 'loading'));
|
|
};
|
|
|
|
// move selected messages to the specified mailbox
|
|
this.move_messages = function(mbox, obj)
|
|
{
|
|
if (mbox && typeof mbox === 'object')
|
|
mbox = mbox.id;
|
|
else if (!mbox)
|
|
return this.folder_selector(obj, function(folder) { ref.command('move', folder); });
|
|
|
|
// exit if current or no mailbox specified
|
|
if (!mbox || (mbox == this.env.mailbox && !this.is_multifolder_listing()))
|
|
return;
|
|
|
|
var lock = false, post_data = this.selection_post_data({_target_mbox: mbox});
|
|
|
|
// exit if selection is empty
|
|
if (!post_data._uid)
|
|
return;
|
|
|
|
// show wait message
|
|
if (this.env.action == 'show')
|
|
lock = this.set_busy(true, 'movingmessage');
|
|
else
|
|
this.show_contentframe(false);
|
|
|
|
// Hide message command buttons until a message is selected
|
|
this.enable_command(this.env.message_commands, false);
|
|
|
|
this._with_selected_messages('move', post_data, lock);
|
|
};
|
|
|
|
// delete selected messages from the current mailbox
|
|
this.delete_messages = function(event)
|
|
{
|
|
var list = this.message_list, trash = this.env.trash_mailbox;
|
|
|
|
// if config is set to flag for deletion
|
|
if (this.env.flag_for_deletion) {
|
|
this.mark_message('delete');
|
|
return false;
|
|
}
|
|
// if there isn't a defined trash mailbox or we are in it
|
|
else if (!trash || this.env.mailbox == trash)
|
|
this.permanently_remove_messages();
|
|
// we're in Junk folder and delete_junk is enabled
|
|
else if (this.env.delete_junk && this.env.junk_mailbox && this.env.mailbox == this.env.junk_mailbox)
|
|
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 ((list && list.modkey == SHIFT_KEY) || (event && rcube_event.get_modifier(event) == SHIFT_KEY)) {
|
|
if (confirm(this.get_label('deletemessagesconfirm')))
|
|
this.permanently_remove_messages();
|
|
}
|
|
else
|
|
this.move_messages(trash);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
// delete the selected messages permanently
|
|
this.permanently_remove_messages = function()
|
|
{
|
|
var post_data = this.selection_post_data();
|
|
|
|
// exit if selection is empty
|
|
if (!post_data._uid)
|
|
return;
|
|
|
|
this.show_contentframe(false);
|
|
this._with_selected_messages('delete', post_data);
|
|
};
|
|
|
|
// Send a specific move/delete request with UIDs of all selected messages
|
|
// @private
|
|
this._with_selected_messages = function(action, post_data, lock)
|
|
{
|
|
var count = 0, msg,
|
|
remove = (action == 'delete' || !this.is_multifolder_listing());
|
|
|
|
// update the list (remove rows, clear selection)
|
|
if (this.message_list) {
|
|
var n, id, root, roots = [],
|
|
selection = this.message_list.get_selection();
|
|
|
|
for (n=0, len=selection.length; n<len; n++) {
|
|
id = selection[n];
|
|
|
|
if (this.env.threading) {
|
|
count += this.update_thread(id);
|
|
root = this.message_list.find_root(id);
|
|
if (root != id && $.inArray(root, roots) < 0) {
|
|
roots.push(root);
|
|
}
|
|
}
|
|
if (remove)
|
|
this.message_list.remove_row(id, (this.env.display_next && n == selection.length-1));
|
|
}
|
|
// make sure there are no selected rows
|
|
if (!this.env.display_next && remove)
|
|
this.message_list.clear_selection();
|
|
// update thread tree icons
|
|
for (n=0, len=roots.length; n<len; n++) {
|
|
this.add_tree_icons(roots[n]);
|
|
}
|
|
}
|
|
|
|
if (count < 0)
|
|
post_data._count = (count*-1);
|
|
// remove threads from the end of the list
|
|
else if (count > 0 && remove)
|
|
this.delete_excessive_thread_rows();
|
|
|
|
if (!remove)
|
|
post_data._refresh = 1;
|
|
|
|
if (!lock) {
|
|
msg = action == 'move' ? 'movingmessage' : 'deletingmessage';
|
|
lock = this.display_message(this.get_label(msg), 'loading');
|
|
}
|
|
|
|
// send request to server
|
|
this.http_post(action, post_data, lock);
|
|
};
|
|
|
|
// build post data for message delete/move/copy/flag requests
|
|
this.selection_post_data = function(data)
|
|
{
|
|
if (typeof(data) != 'object')
|
|
data = {};
|
|
|
|
data._mbox = this.env.mailbox;
|
|
|
|
if (!data._uid) {
|
|
var uids = this.env.uid ? [this.env.uid] : this.message_list.get_selection();
|
|
data._uid = this.uids_to_list(uids);
|
|
}
|
|
|
|
if (this.env.action)
|
|
data._from = this.env.action;
|
|
|
|
// also send search request to get the right messages
|
|
if (this.env.search_request)
|
|
data._search = this.env.search_request;
|
|
|
|
if (this.env.display_next && this.env.next_uid)
|
|
data._next_uid = this.env.next_uid;
|
|
|
|
return data;
|
|
};
|
|
|
|
// set a specific flag to one or more messages
|
|
this.mark_message = function(flag, uid)
|
|
{
|
|
var a_uids = [], r_uids = [], len, n, id,
|
|
list = this.message_list;
|
|
|
|
if (uid)
|
|
a_uids[0] = uid;
|
|
else if (this.env.uid)
|
|
a_uids[0] = this.env.uid;
|
|
else if (list)
|
|
a_uids = list.get_selection();
|
|
|
|
if (!list)
|
|
r_uids = a_uids;
|
|
else {
|
|
list.focus();
|
|
for (n=0, len=a_uids.length; n<len; n++) {
|
|
id = a_uids[n];
|
|
if ((flag == 'read' && list.rows[id].unread)
|
|
|| (flag == 'unread' && !list.rows[id].unread)
|
|
|| (flag == 'delete' && !list.rows[id].deleted)
|
|
|| (flag == 'undelete' && list.rows[id].deleted)
|
|
|| (flag == 'flagged' && !list.rows[id].flagged)
|
|
|| (flag == 'unflagged' && list.rows[id].flagged))
|
|
{
|
|
r_uids.push(id);
|
|
}
|
|
}
|
|
}
|
|
|
|
// nothing to do
|
|
if (!r_uids.length && !this.select_all_mode)
|
|
return;
|
|
|
|
switch (flag) {
|
|
case 'read':
|
|
case 'unread':
|
|
this.toggle_read_status(flag, r_uids);
|
|
break;
|
|
case 'delete':
|
|
case 'undelete':
|
|
this.toggle_delete_status(r_uids);
|
|
break;
|
|
case 'flagged':
|
|
case 'unflagged':
|
|
this.toggle_flagged_status(flag, a_uids);
|
|
break;
|
|
}
|
|
};
|
|
|
|
// set class to read/unread
|
|
this.toggle_read_status = function(flag, a_uids)
|
|
{
|
|
var i, len = a_uids.length,
|
|
post_data = this.selection_post_data({_uid: this.uids_to_list(a_uids), _flag: flag}),
|
|
lock = this.display_message(this.get_label('markingmessage'), 'loading');
|
|
|
|
// mark all message rows as read/unread
|
|
for (i=0; i<len; i++)
|
|
this.set_message(a_uids[i], 'unread', (flag == 'unread' ? true : false));
|
|
|
|
this.http_post('mark', post_data, lock);
|
|
};
|
|
|
|
// set image to flagged or unflagged
|
|
this.toggle_flagged_status = function(flag, a_uids)
|
|
{
|
|
var i, len = a_uids.length,
|
|
post_data = this.selection_post_data({_uid: this.uids_to_list(a_uids), _flag: flag}),
|
|
lock = this.display_message(this.get_label('markingmessage'), 'loading');
|
|
|
|
// mark all message rows as flagged/unflagged
|
|
for (i=0; i<len; i++)
|
|
this.set_message(a_uids[i], 'flagged', (flag == 'flagged' ? true : false));
|
|
|
|
this.http_post('mark', post_data, lock);
|
|
};
|
|
|
|
// mark all message rows as deleted/undeleted
|
|
this.toggle_delete_status = function(a_uids)
|
|
{
|
|
var len = a_uids.length,
|
|
i, uid, all_deleted = true,
|
|
rows = this.message_list ? this.message_list.rows : {};
|
|
|
|
if (len == 1) {
|
|
if (!this.message_list || (rows[a_uids[0]] && !rows[a_uids[0]].deleted))
|
|
this.flag_as_deleted(a_uids);
|
|
else
|
|
this.flag_as_undeleted(a_uids);
|
|
|
|
return true;
|
|
}
|
|
|
|
for (i=0; i<len; i++) {
|
|
uid = a_uids[i];
|
|
if (rows[uid] && !rows[uid].deleted) {
|
|
all_deleted = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (all_deleted)
|
|
this.flag_as_undeleted(a_uids);
|
|
else
|
|
this.flag_as_deleted(a_uids);
|
|
|
|
return true;
|
|
};
|
|
|
|
this.flag_as_undeleted = function(a_uids)
|
|
{
|
|
var i, len = a_uids.length,
|
|
post_data = this.selection_post_data({_uid: this.uids_to_list(a_uids), _flag: 'undelete'}),
|
|
lock = this.display_message(this.get_label('markingmessage'), 'loading');
|
|
|
|
for (i=0; i<len; i++)
|
|
this.set_message(a_uids[i], 'deleted', false);
|
|
|
|
this.http_post('mark', post_data, lock);
|
|
};
|
|
|
|
this.flag_as_deleted = function(a_uids)
|
|
{
|
|
var r_uids = [],
|
|
post_data = this.selection_post_data({_uid: this.uids_to_list(a_uids), _flag: 'delete'}),
|
|
lock = this.display_message(this.get_label('markingmessage'), 'loading'),
|
|
rows = this.message_list ? this.message_list.rows : {},
|
|
count = 0;
|
|
|
|
for (var i=0, len=a_uids.length; i<len; i++) {
|
|
uid = a_uids[i];
|
|
if (rows[uid]) {
|
|
if (rows[uid].unread)
|
|
r_uids[r_uids.length] = uid;
|
|
|
|
if (this.env.skip_deleted) {
|
|
count += this.update_thread(uid);
|
|
this.message_list.remove_row(uid, (this.env.display_next && i == this.message_list.selection.length-1));
|
|
}
|
|
else
|
|
this.set_message(uid, 'deleted', true);
|
|
}
|
|
}
|
|
|
|
// make sure there are no selected rows
|
|
if (this.env.skip_deleted && this.message_list) {
|
|
if (!this.env.display_next)
|
|
this.message_list.clear_selection();
|
|
if (count < 0)
|
|
post_data._count = (count*-1);
|
|
else if (count > 0)
|
|
// remove threads from the end of the list
|
|
this.delete_excessive_thread_rows();
|
|
}
|
|
|
|
// ??
|
|
if (r_uids.length)
|
|
post_data._ruid = this.uids_to_list(r_uids);
|
|
|
|
if (this.env.skip_deleted && this.env.display_next && this.env.next_uid)
|
|
post_data._next_uid = this.env.next_uid;
|
|
|
|
this.http_post('mark', post_data, lock);
|
|
};
|
|
|
|
// flag as read without mark request (called from backend)
|
|
// argument should be a coma-separated list of uids
|
|
this.flag_deleted_as_read = function(uids)
|
|
{
|
|
var icn_src, uid, i, len,
|
|
rows = this.message_list ? this.message_list.rows : {};
|
|
|
|
if (typeof uids == 'string')
|
|
uids = uids.split(',');
|
|
|
|
for (i=0, len=uids.length; i<len; i++) {
|
|
uid = uids[i];
|
|
if (rows[uid])
|
|
this.set_message(uid, 'unread', false);
|
|
}
|
|
};
|
|
|
|
// Converts array of message UIDs to comma-separated list for use in URL
|
|
// with select_all mode checking
|
|
this.uids_to_list = function(uids)
|
|
{
|
|
return this.select_all_mode ? '*' : (uids.length <= 1 ? uids.join(',') : uids);
|
|
};
|
|
|
|
// Sets title of the delete button
|
|
this.set_button_titles = function()
|
|
{
|
|
var label = 'deletemessage';
|
|
|
|
if (!this.env.flag_for_deletion
|
|
&& this.env.trash_mailbox && this.env.mailbox != this.env.trash_mailbox
|
|
&& (!this.env.delete_junk || !this.env.junk_mailbox || this.env.mailbox != this.env.junk_mailbox)
|
|
)
|
|
label = 'movemessagetotrash';
|
|
|
|
this.set_alttext('delete', label);
|
|
};
|
|
|
|
/*********************************************************/
|
|
/********* mailbox folders methods *********/
|
|
/*********************************************************/
|
|
|
|
this.expunge_mailbox = function(mbox)
|
|
{
|
|
var lock, post_data = {_mbox: mbox};
|
|
|
|
// lock interface if it's the active mailbox
|
|
if (mbox == this.env.mailbox) {
|
|
lock = this.set_busy(true, 'loading');
|
|
post_data._reload = 1;
|
|
if (this.env.search_request)
|
|
post_data._search = this.env.search_request;
|
|
}
|
|
|
|
// send request to server
|
|
this.http_post('expunge', post_data, lock);
|
|
};
|
|
|
|
this.purge_mailbox = function(mbox)
|
|
{
|
|
var lock, post_data = {_mbox: mbox};
|
|
|
|
if (!confirm(this.get_label('purgefolderconfirm')))
|
|
return false;
|
|
|
|
// lock interface if it's the active mailbox
|
|
if (mbox == this.env.mailbox) {
|
|
lock = this.set_busy(true, 'loading');
|
|
post_data._reload = 1;
|
|
}
|
|
|
|
// send request to server
|
|
this.http_post('purge', post_data, lock);
|
|
};
|
|
|
|
// test if purge command is allowed
|
|
this.purge_mailbox_test = function()
|
|
{
|
|
return (this.env.exists && (
|
|
this.env.mailbox == this.env.trash_mailbox
|
|
|| this.env.mailbox == this.env.junk_mailbox
|
|
|| this.env.mailbox.startsWith(this.env.trash_mailbox + this.env.delimiter)
|
|
|| this.env.mailbox.startsWith(this.env.junk_mailbox + this.env.delimiter)
|
|
));
|
|
};
|
|
|
|
|
|
/*********************************************************/
|
|
/********* login form methods *********/
|
|
/*********************************************************/
|
|
|
|
// handler for keyboard events on the _user field
|
|
this.login_user_keyup = function(e)
|
|
{
|
|
var key = rcube_event.get_keycode(e),
|
|
passwd = $('#rcmloginpwd');
|
|
|
|
// enter
|
|
if (key == 13 && passwd.length && !passwd.val()) {
|
|
passwd.focus();
|
|
return rcube_event.cancel(e);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
|
|
/*********************************************************/
|
|
/********* message compose methods *********/
|
|
/*********************************************************/
|
|
|
|
this.open_compose_step = function(p)
|
|
{
|
|
var url = this.url('mail/compose', p);
|
|
|
|
// open new compose window
|
|
if (this.env.compose_extwin && !this.env.extwin) {
|
|
this.open_window(url);
|
|
}
|
|
else {
|
|
this.redirect(url);
|
|
if (this.env.extwin)
|
|
window.resizeTo(Math.max(this.env.popup_width, $(window).width()), $(window).height() + 24);
|
|
}
|
|
};
|
|
|
|
// init message compose form: set focus and eventhandlers
|
|
this.init_messageform = function()
|
|
{
|
|
if (!this.gui_objects.messageform)
|
|
return false;
|
|
|
|
var input_from = $("[name='_from']"),
|
|
input_to = $("[name='_to']"),
|
|
input_subject = $("input[name='_subject']"),
|
|
input_message = $("[name='_message']").get(0),
|
|
html_mode = $("input[name='_is_html']").val() == '1',
|
|
ac_fields = ['cc', 'bcc', 'replyto', 'followupto'],
|
|
ac_props, opener_rc = this.opener();
|
|
|
|
// close compose step in opener
|
|
if (opener_rc && opener_rc.env.action == 'compose') {
|
|
setTimeout(function(){
|
|
if (opener.history.length > 1)
|
|
opener.history.back();
|
|
else
|
|
opener_rc.redirect(opener_rc.get_task_url('mail'));
|
|
}, 100);
|
|
this.env.opened_extwin = true;
|
|
}
|
|
|
|
// configure parallel autocompletion
|
|
if (this.env.autocomplete_threads > 0) {
|
|
ac_props = {
|
|
threads: this.env.autocomplete_threads,
|
|
sources: this.env.autocomplete_sources
|
|
};
|
|
}
|
|
|
|
// init live search events
|
|
this.init_address_input_events(input_to, ac_props);
|
|
for (var i in ac_fields) {
|
|
this.init_address_input_events($("[name='_"+ac_fields[i]+"']"), ac_props);
|
|
}
|
|
|
|
if (!html_mode) {
|
|
this.set_caret_pos(input_message, this.env.top_posting ? 0 : $(input_message).val().length);
|
|
// add signature according to selected identity
|
|
// if we have HTML editor, signature is added in callback
|
|
if (input_from.prop('type') == 'select-one') {
|
|
this.change_identity(input_from[0]);
|
|
}
|
|
}
|
|
|
|
// check for locally stored compose data
|
|
if (window.localStorage) {
|
|
var index = this.local_storage_get_item('compose.index', []);
|
|
|
|
for (var key, i = 0; i < index.length; i++) {
|
|
key = index[i], formdata = this.local_storage_get_item('compose.' + key, null, true);
|
|
if (!formdata) {
|
|
continue;
|
|
}
|
|
// restore saved copy of current compose_id
|
|
if (formdata.changed && key == this.env.compose_id) {
|
|
this.restore_compose_form(key, html_mode);
|
|
break;
|
|
}
|
|
// skip records from 'other' drafts
|
|
if (this.env.draft_id && formdata.draft_id && formdata.draft_id != this.env.draft_id) {
|
|
continue;
|
|
}
|
|
// skip records on reply
|
|
if (this.env.reply_msgid && formdata.reply_msgid != this.env.reply_msgid) {
|
|
continue;
|
|
}
|
|
// show dialog asking to restore the message
|
|
if (formdata.changed && formdata.session != this.env.session_id) {
|
|
this.show_popup_dialog(
|
|
this.get_label('restoresavedcomposedata')
|
|
.replace('$date', new Date(formdata.changed).toLocaleString())
|
|
.replace('$subject', formdata._subject)
|
|
.replace(/\n/g, '<br/>'),
|
|
this.get_label('restoremessage'),
|
|
[{
|
|
text: this.get_label('restore'),
|
|
click: function(){
|
|
ref.restore_compose_form(key, html_mode);
|
|
ref.remove_compose_data(key); // remove old copy
|
|
ref.save_compose_form_local(); // save under current compose_id
|
|
$(this).dialog('close');
|
|
}
|
|
},
|
|
{
|
|
text: this.get_label('delete'),
|
|
click: function(){
|
|
ref.remove_compose_data(key);
|
|
$(this).dialog('close');
|
|
}
|
|
},
|
|
{
|
|
text: this.get_label('ignore'),
|
|
click: function(){
|
|
$(this).dialog('close');
|
|
}
|
|
}]
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (input_to.val() == '')
|
|
input_to.focus();
|
|
else if (input_subject.val() == '')
|
|
input_subject.focus();
|
|
else if (input_message)
|
|
input_message.focus();
|
|
|
|
this.env.compose_focus_elem = document.activeElement;
|
|
|
|
// get summary of all field values
|
|
this.compose_field_hash(true);
|
|
|
|
// start the auto-save timer
|
|
this.auto_save_start();
|
|
};
|
|
|
|
this.init_address_input_events = function(obj, props)
|
|
{
|
|
this.env.recipients_delimiter = this.env.recipients_separator + ' ';
|
|
|
|
obj.keydown(function(e) { return ref.ksearch_keydown(e, this, props); })
|
|
.attr('autocomplete', 'off');
|
|
};
|
|
|
|
this.submit_messageform = function(draft)
|
|
{
|
|
var form = this.gui_objects.messageform;
|
|
|
|
if (!form)
|
|
return;
|
|
|
|
// all checks passed, send message
|
|
var msgid = this.set_busy(true, draft ? 'savingmessage' : 'sendingmessage'),
|
|
lang = this.spellcheck_lang(),
|
|
files = [];
|
|
|
|
// send files list
|
|
$('li', this.gui_objects.attachmentlist).each(function() { files.push(this.id.replace(/^rcmfile/, '')); });
|
|
$('input[name="_attachments"]', form).val(files.join());
|
|
|
|
form.target = 'savetarget';
|
|
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();
|
|
};
|
|
|
|
this.compose_recipient_select = function(list)
|
|
{
|
|
var id, n, recipients = 0;
|
|
for (n=0; n < list.selection.length; n++) {
|
|
id = list.selection[n];
|
|
if (this.env.contactdata[id])
|
|
recipients++;
|
|
}
|
|
this.enable_command('add-recipient', recipients);
|
|
};
|
|
|
|
this.compose_add_recipient = function(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++) {
|
|
id = this.contact_list.selection[n];
|
|
if (id && this.env.contactdata[id]) {
|
|
recipients.push(this.env.contactdata[id]);
|
|
|
|
// group is added, expand it
|
|
if (id.charAt(0) == 'E' && this.env.contactdata[id].indexOf('@') < 0 && input.length) {
|
|
var gid = id.substr(1);
|
|
this.group2expand[gid] = { name:this.env.contactdata[id], input:input.get(0) };
|
|
this.http_request('group-expand', {_source: this.env.source, _gid: gid}, false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (recipients.length && input.length) {
|
|
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 });
|
|
}
|
|
};
|
|
|
|
// checks the input fields before sending a message
|
|
this.check_compose_input = function(cmd)
|
|
{
|
|
// check input fields
|
|
var ed, input_to = $("[name='_to']"),
|
|
input_cc = $("[name='_cc']"),
|
|
input_bcc = $("[name='_bcc']"),
|
|
input_from = $("[name='_from']"),
|
|
input_subject = $("[name='_subject']"),
|
|
input_message = $("[name='_message']");
|
|
|
|
// check sender (if have no identities)
|
|
if (input_from.prop('type') == 'text' && !rcube_check_email(input_from.val(), true)) {
|
|
alert(this.get_label('nosenderwarning'));
|
|
input_from.focus();
|
|
return false;
|
|
}
|
|
|
|
// check for empty recipient
|
|
var recipients = input_to.val() ? input_to.val() : (input_cc.val() ? input_cc.val() : input_bcc.val());
|
|
if (!rcube_check_email(recipients.replace(/^\s+/, '').replace(/[\s,;]+$/, ''), true)) {
|
|
alert(this.get_label('norecipientwarning'));
|
|
input_to.focus();
|
|
return false;
|
|
}
|
|
|
|
// 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) {
|
|
alert(this.get_label('notuploadedwarning'));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// display localized warning for missing subject
|
|
if (input_subject.val() == '') {
|
|
var myprompt = $('<div class="prompt">').html('<div class="message">' + this.get_label('nosubjectwarning') + '</div>').appendTo(document.body);
|
|
var prompt_value = $('<input>').attr('type', 'text').attr('size', 30).appendTo(myprompt).val(this.get_label('nosubject'));
|
|
|
|
var buttons = {};
|
|
buttons[this.get_label('cancel')] = function(){
|
|
input_subject.focus();
|
|
$(this).dialog('close');
|
|
};
|
|
buttons[this.get_label('sendmessage')] = function(){
|
|
input_subject.val(prompt_value.val());
|
|
$(this).dialog('close');
|
|
ref.command(cmd, { nocheck:true }); // repeat command which triggered this
|
|
};
|
|
|
|
myprompt.dialog({
|
|
modal: true,
|
|
resizable: false,
|
|
buttons: buttons,
|
|
close: function(event, ui) { $(this).remove() }
|
|
});
|
|
prompt_value.select();
|
|
return false;
|
|
}
|
|
|
|
// Apply spellcheck changes if spell checker is active
|
|
this.stop_spellchecking();
|
|
|
|
if (window.tinyMCE)
|
|
ed = tinyMCE.get(this.env.composebody);
|
|
|
|
// check for empty body
|
|
if (!ed && input_message.val() == '' && !confirm(this.get_label('nobodywarning'))) {
|
|
input_message.focus();
|
|
return false;
|
|
}
|
|
else if (ed) {
|
|
if (!ed.getContent() && !confirm(this.get_label('nobodywarning'))) {
|
|
ed.focus();
|
|
return false;
|
|
}
|
|
// move body from html editor to textarea (just to be sure, #1485860)
|
|
tinyMCE.triggerSave();
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
this.toggle_editor = function(props)
|
|
{
|
|
this.stop_spellchecking();
|
|
|
|
var input = $('#' + props.id);
|
|
|
|
if (props.mode == 'html')
|
|
this.plain2html(input.val(), function(data) {
|
|
input.val(data);
|
|
tinyMCE.execCommand('mceAddControl', false, props.id);
|
|
|
|
if (ref.env.default_font)
|
|
setTimeout(function() {
|
|
$(tinyMCE.get(props.id).getBody()).css('font-family', ref.env.default_font);
|
|
}, 500);
|
|
});
|
|
else
|
|
this.html2plain(tinyMCE.get(props.id).getContent(), function(data) {
|
|
tinyMCE.execCommand('mceRemoveControl', false, props.id);
|
|
input.val(data);
|
|
});
|
|
|
|
return true;
|
|
};
|
|
|
|
this.insert_response = function(key)
|
|
{
|
|
var insert = this.env.textresponses[key] ? this.env.textresponses[key].text : null;
|
|
if (!insert)
|
|
return false;
|
|
|
|
// insert into tinyMCE editor
|
|
if ($("input[name='_is_html']").val() == '1') {
|
|
var editor = tinyMCE.get(this.env.composebody);
|
|
editor.getWin().focus(); // correct focus in IE & Chrome
|
|
editor.selection.setContent(this.quote_html(insert).replace(/\r?\n/g, '<br/>'), { format:'text' });
|
|
}
|
|
// replace selection in compose textarea
|
|
else {
|
|
var textarea = rcube_find_object(this.env.composebody),
|
|
selection = $(textarea).is(':focus') ? this.get_input_selection(textarea) : { start:0, end:0 },
|
|
inp_value = textarea.value;
|
|
pre = inp_value.substring(0, selection.start),
|
|
end = inp_value.substring(selection.end, inp_value.length);
|
|
|
|
// insert response text
|
|
textarea.value = pre + insert + end;
|
|
|
|
// set caret after inserted text
|
|
this.set_caret_pos(textarea, selection.start + insert.length);
|
|
textarea.focus();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Open the dialog to save a new canned response
|
|
*/
|
|
this.save_response = function()
|
|
{
|
|
var sigstart, text = '', strip = false;
|
|
|
|
// get selected text from tinyMCE editor
|
|
if ($("input[name='_is_html']").val() == '1') {
|
|
var editor = tinyMCE.get(this.env.composebody);
|
|
editor.getWin().focus(); // correct focus in IE & Chrome
|
|
text = editor.selection.getContent({ format:'text' });
|
|
|
|
if (!text) {
|
|
text = editor.getContent({ format:'text' });
|
|
strip = true;
|
|
}
|
|
}
|
|
// get selected text from compose textarea
|
|
else {
|
|
var textarea = rcube_find_object(this.env.composebody), sigstart;
|
|
if (textarea && $(textarea).is(':focus')) {
|
|
text = this.get_input_selection(textarea).text;
|
|
}
|
|
|
|
if (!text && textarea) {
|
|
text = textarea.value;
|
|
strip = true;
|
|
}
|
|
}
|
|
|
|
// strip off signature
|
|
if (strip) {
|
|
sigstart = text.indexOf('-- \n');
|
|
if (sigstart > 0) {
|
|
text = text.substring(0, sigstart);
|
|
}
|
|
}
|
|
|
|
// show dialog to enter a name and to modify the text to be saved
|
|
var buttons = {},
|
|
html = '<form class="propform">' +
|
|
'<div class="prop block"><label>' + this.get_label('responsename') + '</label>' +
|
|
'<input type="text" name="name" id="ffresponsename" size="40" /></div>' +
|
|
'<div class="prop block"><label>' + this.get_label('responsetext') + '</label>' +
|
|
'<textarea name="text" id="ffresponsetext" cols="40" rows="8"></textarea></div>' +
|
|
'</form>';
|
|
|
|
buttons[this.gettext('save')] = function(e) {
|
|
var name = $('#ffresponsename').val(),
|
|
text = $('#ffresponsetext').val();
|
|
|
|
if (!text) {
|
|
$('#ffresponsetext').select();
|
|
return false;
|
|
}
|
|
if (!name)
|
|
name = text.substring(0,40);
|
|
|
|
var lock = ref.display_message(ref.get_label('savingresponse'), 'loading');
|
|
ref.http_post('settings/responses', { _insert:1, _name:name, _text:text }, lock);
|
|
$(this).dialog('close');
|
|
};
|
|
|
|
buttons[this.gettext('cancel')] = function() {
|
|
$(this).dialog('close');
|
|
};
|
|
|
|
this.show_popup_dialog(html, this.gettext('savenewresponse'), buttons);
|
|
|
|
$('#ffresponsetext').val(text);
|
|
$('#ffresponsename').select();
|
|
};
|
|
|
|
this.add_response_item = function(response)
|
|
{
|
|
var key = response.key;
|
|
this.env.textresponses[key] = response;
|
|
|
|
// append to responses list
|
|
if (this.gui_objects.responseslist) {
|
|
var li = $('<li>').appendTo(this.gui_objects.responseslist);
|
|
$('<a>').addClass('insertresponse active')
|
|
.attr('href', '#')
|
|
.attr('rel', key)
|
|
.html(this.quote_html(response.name))
|
|
.appendTo(li)
|
|
.mousedown(function(e){
|
|
return rcube_event.cancel(e);
|
|
})
|
|
.mouseup(function(e){
|
|
ref.command('insert-response', key);
|
|
$(document.body).trigger('mouseup'); // hides the menu
|
|
return rcube_event.cancel(e);
|
|
});
|
|
}
|
|
};
|
|
|
|
this.edit_responses = function()
|
|
{
|
|
// TODO: implement inline editing of responses
|
|
};
|
|
|
|
this.delete_response = function(key)
|
|
{
|
|
if (!key && this.responses_list) {
|
|
var selection = this.responses_list.get_selection();
|
|
key = selection[0];
|
|
}
|
|
|
|
// submit delete request
|
|
if (key && confirm(this.get_label('deleteresponseconfirm'))) {
|
|
this.http_post('settings/delete-response', { _key: key }, false);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
this.stop_spellchecking = function()
|
|
{
|
|
var ed;
|
|
|
|
if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody))) {
|
|
if (ed.plugins && ed.plugins.spellchecker && ed.plugins.spellchecker.active)
|
|
ed.execCommand('mceSpellCheck');
|
|
}
|
|
else if (ed = this.env.spellcheck) {
|
|
if (ed.state && ed.state != 'ready' && ed.state != 'no_error_found')
|
|
$(ed.spell_span).trigger('click');
|
|
}
|
|
|
|
this.spellcheck_state();
|
|
};
|
|
|
|
this.spellcheck_state = function()
|
|
{
|
|
var ed, active;
|
|
|
|
if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)) && ed.plugins && ed.plugins.spellchecker)
|
|
active = ed.plugins.spellchecker.active;
|
|
else if ((ed = this.env.spellcheck) && ed.state)
|
|
active = ed.state != 'ready' && ed.state != 'no_error_found';
|
|
|
|
if (rcmail.buttons.spellcheck)
|
|
$('#'+rcmail.buttons.spellcheck[0].id)[active ? 'addClass' : 'removeClass']('selected');
|
|
|
|
return active;
|
|
};
|
|
|
|
// get selected language
|
|
this.spellcheck_lang = function()
|
|
{
|
|
var ed;
|
|
|
|
if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)) && ed.plugins && ed.plugins.spellchecker)
|
|
return ed.plugins.spellchecker.selectedLang;
|
|
else if (this.env.spellcheck)
|
|
return GOOGIE_CUR_LANG;
|
|
};
|
|
|
|
this.spellcheck_lang_set = function(lang)
|
|
{
|
|
var ed;
|
|
|
|
if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)) && ed.plugins)
|
|
ed.plugins.spellchecker.selectedLang = lang;
|
|
else if (this.env.spellcheck)
|
|
this.env.spellcheck.setCurrentLanguage(lang);
|
|
};
|
|
|
|
// resume spellchecking, highlight provided mispellings without new ajax request
|
|
this.spellcheck_resume = function(ishtml, data)
|
|
{
|
|
if (ishtml) {
|
|
var ed = tinyMCE.get(this.env.composebody);
|
|
sp = ed.plugins.spellchecker;
|
|
|
|
sp.active = 1;
|
|
sp._markWords(data);
|
|
ed.nodeChanged();
|
|
}
|
|
else {
|
|
var sp = this.env.spellcheck;
|
|
sp.prepare(false, true);
|
|
sp.processData(data);
|
|
}
|
|
|
|
this.spellcheck_state();
|
|
}
|
|
|
|
this.set_draft_id = function(id)
|
|
{
|
|
var rc;
|
|
|
|
if (id && id != this.env.draft_id) {
|
|
if (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);
|
|
|
|
// reset history of hidden iframe used for saving draft (#1489643)
|
|
// but don't do this on timer-triggered draft-autosaving (#1489789)
|
|
if (window.frames['savetarget'] && window.frames['savetarget'].history && !this.draft_autosave_submit) {
|
|
window.frames['savetarget'].history.back();
|
|
}
|
|
|
|
this.draft_autosave_submit = false;
|
|
}
|
|
|
|
// always remove local copy upon saving as draft
|
|
this.remove_compose_data(this.env.compose_id);
|
|
};
|
|
|
|
this.auto_save_start = function()
|
|
{
|
|
if (this.env.draft_autosave)
|
|
this.draft_autosave_submit = false;
|
|
this.save_timer = setTimeout(function(){
|
|
ref.draft_autosave_submit = true; // set auto-saved flag (#1489789)
|
|
ref.command("savedraft");
|
|
}, this.env.draft_autosave * 1000);
|
|
|
|
// save compose form content to local storage every 5 seconds
|
|
if (!this.local_save_timer && window.localStorage) {
|
|
// track typing activity and only save on changes
|
|
this.compose_type_activity = this.compose_type_activity_last = 0;
|
|
$(document).bind('keypress', function(e){ ref.compose_type_activity++; });
|
|
|
|
this.local_save_timer = setInterval(function(){
|
|
if (ref.compose_type_activity > ref.compose_type_activity_last) {
|
|
ref.save_compose_form_local();
|
|
ref.compose_type_activity_last = ref.compose_type_activity;
|
|
}
|
|
}, 5000);
|
|
}
|
|
|
|
// Unlock interface now that saving is complete
|
|
this.busy = false;
|
|
};
|
|
|
|
this.compose_field_hash = function(save)
|
|
{
|
|
// check input fields
|
|
var ed, i, val, str = '', hash_fields = ['to', 'cc', 'bcc', 'subject'];
|
|
|
|
for (i=0; i<hash_fields.length; i++)
|
|
if (val = $('[name="_' + hash_fields[i] + '"]').val())
|
|
str += val + ':';
|
|
|
|
if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody)))
|
|
str += ed.getContent();
|
|
else
|
|
str += $("[name='_message']").val();
|
|
|
|
if (this.env.attachments)
|
|
for (var upload_id in this.env.attachments)
|
|
str += upload_id;
|
|
|
|
if (save)
|
|
this.cmp_hash = str;
|
|
|
|
return str;
|
|
};
|
|
|
|
// store the contents of the compose form to localstorage
|
|
this.save_compose_form_local = function()
|
|
{
|
|
var formdata = { session:this.env.session_id, changed:new Date().getTime() },
|
|
ed, empty = true;
|
|
|
|
// get fresh content from editor
|
|
if (window.tinyMCE && (ed = tinyMCE.get(this.env.composebody))) {
|
|
tinyMCE.triggerSave();
|
|
}
|
|
|
|
if (this.env.draft_id) {
|
|
formdata.draft_id = this.env.draft_id;
|
|
}
|
|
if (this.env.reply_msgid) {
|
|
formdata.reply_msgid = this.env.reply_msgid;
|
|
}
|
|
|
|
$('input, select, textarea', this.gui_objects.messageform).each(function(i, elem) {
|
|
switch (elem.tagName.toLowerCase()) {
|
|
case 'input':
|
|
if (elem.type == 'button' || elem.type == 'submit' || (elem.type == 'hidden' && elem.name != '_is_html')) {
|
|
break;
|
|
}
|
|
formdata[elem.name] = elem.type != 'checkbox' || elem.checked ? $(elem).val() : '';
|
|
|
|
if (formdata[elem.name] != '' && elem.type != 'hidden')
|
|
empty = false;
|
|
break;
|
|
|
|
case 'select':
|
|
formdata[elem.name] = $('option:checked', elem).val();
|
|
break;
|
|
|
|
default:
|
|
formdata[elem.name] = $(elem).val();
|
|
if (formdata[elem.name] != '')
|
|
empty = false;
|
|
}
|
|
});
|
|
|
|
if (window.localStorage && !empty) {
|
|
var index = this.local_storage_get_item('compose.index', []),
|
|
key = this.env.compose_id;
|
|
|
|
if ($.inArray(key, index) < 0) {
|
|
index.push(key);
|
|
}
|
|
this.local_storage_set_item('compose.' + key, formdata, true);
|
|
this.local_storage_set_item('compose.index', index);
|
|
}
|
|
};
|
|
|
|
// write stored compose data back to form
|
|
this.restore_compose_form = function(key, html_mode)
|
|
{
|
|
var ed, formdata = this.local_storage_get_item('compose.' + key, true);
|
|
|
|
if (formdata && typeof formdata == 'object') {
|
|
$.each(formdata, function(k, value) {
|
|
if (k[0] == '_') {
|
|
var elem = $("*[name='"+k+"']");
|
|
if (elem[0] && elem[0].type == 'checkbox') {
|
|
elem.prop('checked', value != '');
|
|
}
|
|
else {
|
|
elem.val(value);
|
|
}
|
|
}
|
|
});
|
|
|
|
// initialize HTML editor
|
|
if (formdata._is_html == '1') {
|
|
if (!html_mode) {
|
|
tinyMCE.execCommand('mceAddControl', false, this.env.composebody);
|
|
this.triggerEvent('aftertoggle-editor', { mode:'html' });
|
|
}
|
|
}
|
|
else if (html_mode) {
|
|
tinyMCE.execCommand('mceRemoveControl', false, this.env.composebody);
|
|
this.triggerEvent('aftertoggle-editor', { mode:'plain' });
|
|
}
|
|
}
|
|
};
|
|
|
|
// remove stored compose data from localStorage
|
|
this.remove_compose_data = function(key)
|
|
{
|
|
if (window.localStorage) {
|
|
var index = this.local_storage_get_item('compose.index', []);
|
|
|
|
if ($.inArray(key, index) >= 0) {
|
|
this.local_storage_remove_item('compose.' + key);
|
|
this.local_storage_set_item('compose.index', $.grep(index, function(val,i) { return val != key; }));
|
|
}
|
|
}
|
|
};
|
|
|
|
// clear all stored compose data of this user
|
|
this.clear_compose_data = function()
|
|
{
|
|
if (window.localStorage) {
|
|
var i, index = this.local_storage_get_item('compose.index', []);
|
|
|
|
for (i=0; i < index.length; i++) {
|
|
this.local_storage_remove_item('compose.' + index[i]);
|
|
}
|
|
this.local_storage_remove_item('compose.index');
|
|
}
|
|
}
|
|
|
|
|
|
this.change_identity = function(obj, show_sig)
|
|
{
|
|
if (!obj || !obj.options)
|
|
return false;
|
|
|
|
if (!show_sig)
|
|
show_sig = this.env.show_sig;
|
|
|
|
// first function execution
|
|
if (!this.env.identities_initialized) {
|
|
this.env.identities_initialized = true;
|
|
if (this.env.show_sig_later)
|
|
this.env.show_sig = true;
|
|
if (this.env.opened_extwin)
|
|
return;
|
|
}
|
|
|
|
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,
|
|
delim = this.env.recipients_separator,
|
|
rx_delim = RegExp.escape(delim),
|
|
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(rx_delim + '\\s*' + rx_delim, 'g');
|
|
input_val = input_val.replace(rx, delim);
|
|
rx = new RegExp('^[\\s' + rx_delim + ']+');
|
|
input_val = input_val.replace(rx, '');
|
|
|
|
// add new address(es)
|
|
if (new_val && input_val.indexOf(new_val) == -1 && input_val.indexOf(new_val.replace(/"/g, '')) == -1) {
|
|
if (input_val) {
|
|
rx = new RegExp('[' + rx_delim + '\\s]+$')
|
|
input_val = input_val.replace(rx, '') + 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]) {
|
|
this.enable_command('insert-sig', true);
|
|
this.env.compose_commands.push('insert-sig');
|
|
}
|
|
else
|
|
this.enable_command('insert-sig', false);
|
|
|
|
if (!is_html) {
|
|
// remove the 'old' signature
|
|
if (show_sig && sig && this.env.signatures && this.env.signatures[sig]) {
|
|
sig = this.env.signatures[sig].text;
|
|
sig = sig.replace(/\r\n/g, '\n');
|
|
|
|
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);
|
|
}
|
|
// add the new signature string
|
|
if (show_sig && this.env.signatures && this.env.signatures[id]) {
|
|
sig = this.env.signatures[id].text;
|
|
sig = sig.replace(/\r\n/g, '\n');
|
|
|
|
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;
|
|
}
|
|
else if (!message) { // empty message
|
|
cursor_pos = 0;
|
|
message = '\n\n' + sig;
|
|
}
|
|
else if (pos = this.get_caret_pos(input_message.get(0))) { // at cursor position
|
|
message = message.substring(0, pos) + '\n' + sig + '\n\n' + message.substring(pos, message.length);
|
|
cursor_pos = pos;
|
|
}
|
|
else { // on top
|
|
cursor_pos = 0;
|
|
message = '\n\n' + sig + '\n\n' + message.replace(/^[\r\n]+/, '');
|
|
}
|
|
}
|
|
else {
|
|
message = message.replace(/[\r\n]+$/, '');
|
|
cursor_pos = !this.env.top_posting && message.length ? message.length+1 : 0;
|
|
message += '\n\n' + sig;
|
|
}
|
|
}
|
|
else
|
|
cursor_pos = this.env.top_posting ? 0 : message.length;
|
|
|
|
input_message.val(message);
|
|
|
|
// move cursor before the signature
|
|
this.set_caret_pos(input_message.get(0), cursor_pos);
|
|
}
|
|
else if (show_sig && this.env.signatures) { // html
|
|
var editor = tinyMCE.get(this.env.composebody),
|
|
sigElem = editor.dom.get('_rc_sig');
|
|
|
|
// Append the signature as a div within the body
|
|
if (!sigElem) {
|
|
var body = editor.getBody(),
|
|
doc = editor.getDoc();
|
|
|
|
sigElem = doc.createElement('div');
|
|
sigElem.setAttribute('id', '_rc_sig');
|
|
|
|
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
|
|
|
|
var node = editor.selection.getNode();
|
|
if (node.nodeName == 'BODY') {
|
|
// no real focus, insert at start
|
|
body.insertBefore(sigElem, body.firstChild);
|
|
body.insertBefore(doc.createElement('br'), body.firstChild);
|
|
}
|
|
else {
|
|
body.insertBefore(sigElem, node.nextSibling);
|
|
body.insertBefore(doc.createElement('br'), node.nextSibling);
|
|
}
|
|
}
|
|
else {
|
|
if (bw.ie) // add empty line before signature on IE
|
|
body.appendChild(doc.createElement('br'));
|
|
|
|
body.appendChild(sigElem);
|
|
}
|
|
}
|
|
|
|
if (this.env.signatures[id])
|
|
sigElem.innerHTML = this.env.signatures[id].html;
|
|
}
|
|
|
|
this.env.identity = id;
|
|
this.triggerEvent('change_identity');
|
|
return true;
|
|
};
|
|
|
|
// upload (attachment) file
|
|
this.upload_file = function(form, action)
|
|
{
|
|
if (!form)
|
|
return;
|
|
|
|
// count files and size on capable browser
|
|
var size = 0, numfiles = 0;
|
|
|
|
$('input[type=file]', form).each(function(i, field) {
|
|
var files = field.files ? field.files.length : (field.value ? 1 : 0);
|
|
|
|
// check file size
|
|
if (field.files) {
|
|
for (var i=0; i < files; i++)
|
|
size += field.files[i].size;
|
|
}
|
|
|
|
numfiles += files;
|
|
});
|
|
|
|
// create hidden iframe and post upload form
|
|
if (numfiles) {
|
|
if (this.env.max_filesize && this.env.filesizeerror && size > this.env.max_filesize) {
|
|
this.display_message(this.env.filesizeerror, 'error');
|
|
return false;
|
|
}
|
|
|
|
var frame_name = this.async_upload_form(form, action || 'upload', function(e) {
|
|
var d, content = '';
|
|
try {
|
|
if (this.contentDocument) {
|
|
d = this.contentDocument;
|
|
} else if (this.contentWindow) {
|
|
d = this.contentWindow.document;
|
|
}
|
|
content = d.childNodes[0].innerHTML;
|
|
} catch (err) {}
|
|
|
|
if (!content.match(/add2attachment/) && (!bw.opera || (rcmail.env.uploadframe && rcmail.env.uploadframe == e.data.ts))) {
|
|
if (!content.match(/display_message/))
|
|
rcmail.display_message(rcmail.get_label('fileuploaderror'), 'error');
|
|
rcmail.remove_from_attachment_list(e.data.ts);
|
|
}
|
|
// Opera hack: handle double onload
|
|
if (bw.opera)
|
|
rcmail.env.uploadframe = e.data.ts;
|
|
});
|
|
|
|
// display upload indicator and cancel button
|
|
var content = '<span>' + this.get_label('uploading' + (numfiles > 1 ? 'many' : '')) + '</span>',
|
|
ts = frame_name.replace(/^rcmupload/, '');
|
|
|
|
this.add2attachment_list(ts, { name:'', html:content, classname:'uploading', frame:frame_name, complete:false });
|
|
|
|
// upload progress support
|
|
if (this.env.upload_progress_time) {
|
|
this.upload_progress_start('upload', ts);
|
|
}
|
|
|
|
// set reference to the form object
|
|
this.gui_objects.attachmentform = form;
|
|
return true;
|
|
}
|
|
};
|
|
|
|
// add file name to attachment list
|
|
// called from upload page
|
|
this.add2attachment_list = function(name, att, upload_id)
|
|
{
|
|
if (!this.gui_objects.attachmentlist)
|
|
return false;
|
|
|
|
if (!att.complete && ref.env.loadingicon)
|
|
att.html = '<img src="'+ref.env.loadingicon+'" alt="" class="uploading" />' + att.html;
|
|
|
|
if (!att.complete && att.frame)
|
|
att.html = '<a title="'+this.get_label('cancel')+'" onclick="return rcmail.cancel_attachment_upload(\''+name+'\', \''+att.frame+'\');" href="#cancelupload" class="cancelupload">'
|
|
+ (this.env.cancelicon ? '<img src="'+this.env.cancelicon+'" alt="" />' : this.get_label('cancel')) + '</a>' + att.html;
|
|
|
|
var indicator, li = $('<li>');
|
|
|
|
li.attr('id', name)
|
|
.addClass(att.classname)
|
|
.html(att.html)
|
|
.on('mouseover', function() { rcube_webmail.long_subject_title_ex(this); });
|
|
|
|
// replace indicator's li
|
|
if (upload_id && (indicator = document.getElementById(upload_id))) {
|
|
li.replaceAll(indicator);
|
|
}
|
|
else { // add new li
|
|
li.appendTo(this.gui_objects.attachmentlist);
|
|
}
|
|
|
|
if (upload_id && this.env.attachments[upload_id])
|
|
delete this.env.attachments[upload_id];
|
|
|
|
this.env.attachments[name] = att;
|
|
|
|
return true;
|
|
};
|
|
|
|
this.remove_from_attachment_list = function(name)
|
|
{
|
|
if (this.env.attachments) {
|
|
delete this.env.attachments[name];
|
|
$('#'+name).remove();
|
|
}
|
|
};
|
|
|
|
this.remove_attachment = function(name)
|
|
{
|
|
if (name && this.env.attachments[name])
|
|
this.http_post('remove-attachment', { _id:this.env.compose_id, _file:name });
|
|
|
|
return true;
|
|
};
|
|
|
|
this.cancel_attachment_upload = function(name, frame_name)
|
|
{
|
|
if (!name || !frame_name)
|
|
return false;
|
|
|
|
this.remove_from_attachment_list(name);
|
|
$("iframe[name='"+frame_name+"']").remove();
|
|
return false;
|
|
};
|
|
|
|
this.upload_progress_start = function(action, name)
|
|
{
|
|
setTimeout(function() { rcmail.http_request(action, {_progress: name}); },
|
|
this.env.upload_progress_time * 1000);
|
|
};
|
|
|
|
this.upload_progress_update = function(param)
|
|
{
|
|
var elem = $('#'+param.name + '> span');
|
|
|
|
if (!elem.length || !param.text)
|
|
return;
|
|
|
|
elem.text(param.text);
|
|
|
|
if (!param.done)
|
|
this.upload_progress_start(param.action, param.name);
|
|
};
|
|
|
|
// send remote request to add a new contact
|
|
this.add_contact = function(value)
|
|
{
|
|
if (value)
|
|
this.http_post('addcontact', {_address: value});
|
|
|
|
return true;
|
|
};
|
|
|
|
// send remote request to search mail or contacts
|
|
this.qsearch = function(value)
|
|
{
|
|
if (value != '') {
|
|
var r, lock = this.set_busy(true, 'searching'),
|
|
url = this.search_params(value);
|
|
|
|
if (this.message_list)
|
|
this.clear_message_list();
|
|
else if (this.contact_list)
|
|
this.list_contacts_clear();
|
|
|
|
if (this.env.source)
|
|
url._source = this.env.source;
|
|
if (this.env.group)
|
|
url._gid = this.env.group;
|
|
|
|
// reset vars
|
|
this.env.current_page = 1;
|
|
|
|
var action = this.env.action == 'compose' && this.contact_list ? 'search-contacts' : 'search';
|
|
r = this.http_request(action, url, lock);
|
|
|
|
this.env.qsearch = {lock: lock, request: r};
|
|
this.enable_command('set-listmode', this.env.threads && (this.env.search_scope || 'base') == 'base');
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
this.continue_search = function(request_id)
|
|
{
|
|
var lock = ref.set_busy(true, 'stillsearching');
|
|
|
|
setTimeout(function(){
|
|
var url = ref.search_params();
|
|
url._continue = request_id;
|
|
ref.env.qsearch = { lock: lock, request: ref.http_request('search', url, lock) };
|
|
}, 100);
|
|
};
|
|
|
|
// build URL params for search
|
|
this.search_params = function(search, filter, smods)
|
|
{
|
|
var n, url = {}, mods_arr = [],
|
|
mods = this.env.search_mods,
|
|
scope = this.env.search_scope || 'base',
|
|
mbox = scope == 'all' ? '*' : this.env.mailbox;
|
|
|
|
if (!filter && this.gui_objects.search_filter)
|
|
filter = this.gui_objects.search_filter.value;
|
|
|
|
if (!search && this.gui_objects.qsearchbox)
|
|
search = this.gui_objects.qsearchbox.value;
|
|
|
|
if (filter)
|
|
url._filter = filter;
|
|
|
|
if (search) {
|
|
url._q = search;
|
|
|
|
if (!smods && mods && this.message_list)
|
|
mods = mods[mbox] || mods['*'];
|
|
|
|
if (mods) {
|
|
for (n in mods)
|
|
mods_arr.push(n);
|
|
url._headers = mods_arr.join(',');
|
|
}
|
|
}
|
|
|
|
if (scope)
|
|
url._scope = scope;
|
|
if (mbox && scope != 'all')
|
|
url._mbox = mbox;
|
|
|
|
return url;
|
|
};
|
|
|
|
// reset quick-search form
|
|
this.reset_qsearch = function()
|
|
{
|
|
if (this.gui_objects.qsearchbox)
|
|
this.gui_objects.qsearchbox.value = '';
|
|
|
|
if (this.env.qsearch)
|
|
this.abort_request(this.env.qsearch);
|
|
|
|
this.env.qsearch = null;
|
|
this.env.search_request = null;
|
|
this.env.search_id = null;
|
|
|
|
this.enable_command('set-listmode', this.env.threads);
|
|
};
|
|
|
|
this.set_searchscope = function(scope)
|
|
{
|
|
var old = this.env.search_scope;
|
|
this.env.search_scope = scope;
|
|
|
|
// re-send search query with new scope
|
|
if (scope != old && this.env.search_request) {
|
|
if (!this.qsearch(this.gui_objects.qsearchbox.value) && this.env.search_filter && this.env.search_filter != 'ALL')
|
|
this.filter_mailbox(this.env.search_filter);
|
|
if (scope != 'all')
|
|
this.select_folder(this.env.mailbox, '', true);
|
|
}
|
|
};
|
|
|
|
this.set_searchmods = function(mods)
|
|
{
|
|
var mbox = rcmail.env.mailbox,
|
|
scope = this.env.search_scope || 'base';
|
|
|
|
if (scope == 'all')
|
|
mbox = '*';
|
|
|
|
if (!this.env.search_mods)
|
|
this.env.search_mods = {};
|
|
|
|
if (mbox)
|
|
this.env.search_mods[mbox] = mods;
|
|
};
|
|
|
|
this.is_multifolder_listing = function()
|
|
{
|
|
return typeof this.env.multifolder_listing != 'undefined' ? this.env.multifolder_listing :
|
|
(this.env.search_request && (this.env.search_scope || 'base') != 'base');
|
|
}
|
|
|
|
this.sent_successfully = function(type, msg, folders)
|
|
{
|
|
this.display_message(msg, type);
|
|
|
|
if (this.env.extwin) {
|
|
var rc = this.opener();
|
|
this.lock_form(this.gui_objects.messageform);
|
|
if (rc) {
|
|
rc.display_message(msg, type);
|
|
// refresh the folder where sent message was saved or replied message comes from
|
|
if (folders && rc.env.task == 'mail' && rc.env.action == '' && $.inArray(rc.env.mailbox, folders) >= 0) {
|
|
// @TODO: try with 'checkmail' here when #1485186 is fixed. See also #1489249.
|
|
rc.command('list');
|
|
}
|
|
}
|
|
setTimeout(function(){ window.close() }, 1000);
|
|
}
|
|
else {
|
|
// before redirect we need to wait some time for Chrome (#1486177)
|
|
setTimeout(function(){ ref.list_mailbox(); }, 500);
|
|
}
|
|
};
|
|
|
|
|
|
/*********************************************************/
|
|
/********* keyboard live-search methods *********/
|
|
/*********************************************************/
|
|
|
|
// handler for keyboard events on address-fields
|
|
this.ksearch_keydown = function(e, obj, props)
|
|
{
|
|
if (this.ksearch_timer)
|
|
clearTimeout(this.ksearch_timer);
|
|
|
|
var highlight,
|
|
key = rcube_event.get_keycode(e),
|
|
mod = rcube_event.get_modifier(e);
|
|
|
|
switch (key) {
|
|
case 38: // arrow up
|
|
case 40: // arrow down
|
|
if (!this.ksearch_visible())
|
|
return;
|
|
|
|
var dir = key==38 ? 1 : 0;
|
|
|
|
highlight = document.getElementById('rcmksearchSelected');
|
|
if (!highlight)
|
|
highlight = this.ksearch_pane.__ul.firstChild;
|
|
|
|
if (highlight)
|
|
this.ksearch_select(dir ? highlight.previousSibling : highlight.nextSibling);
|
|
|
|
return rcube_event.cancel(e);
|
|
|
|
case 9: // tab
|
|
if (mod == SHIFT_KEY || !this.ksearch_visible()) {
|
|
this.ksearch_hide();
|
|
return;
|
|
}
|
|
|
|
case 13: // enter
|
|
if (!this.ksearch_visible())
|
|
return false;
|
|
|
|
// insert selected address and hide ksearch pane
|
|
this.insert_recipient(this.ksearch_selected);
|
|
this.ksearch_hide();
|
|
|
|
return rcube_event.cancel(e);
|
|
|
|
case 27: // escape
|
|
this.ksearch_hide();
|
|
return;
|
|
|
|
case 37: // left
|
|
case 39: // right
|
|
return;
|
|
}
|
|
|
|
// start timer
|
|
this.ksearch_timer = setTimeout(function(){ ref.ksearch_get_results(props); }, 200);
|
|
this.ksearch_input = obj;
|
|
|
|
return true;
|
|
};
|
|
|
|
this.ksearch_visible = function()
|
|
{
|
|
return (this.ksearch_selected !== null && this.ksearch_selected !== undefined && this.ksearch_value);
|
|
};
|
|
|
|
this.ksearch_select = function(node)
|
|
{
|
|
var current = $('#rcmksearchSelected');
|
|
if (current[0] && node) {
|
|
current.removeAttr('id').removeClass('selected');
|
|
}
|
|
|
|
if (node) {
|
|
$(node).attr('id', 'rcmksearchSelected').addClass('selected');
|
|
this.ksearch_selected = node._rcm_id;
|
|
}
|
|
};
|
|
|
|
this.insert_recipient = function(id)
|
|
{
|
|
if (id === null || !this.env.contacts[id] || !this.ksearch_input)
|
|
return;
|
|
|
|
// get cursor pos
|
|
var inp_value = this.ksearch_input.value,
|
|
cpos = this.get_caret_pos(this.ksearch_input),
|
|
p = inp_value.lastIndexOf(this.ksearch_value, cpos),
|
|
trigger = false,
|
|
insert = '',
|
|
// replace search string with full address
|
|
pre = inp_value.substring(0, p),
|
|
end = inp_value.substring(p+this.ksearch_value.length, inp_value.length);
|
|
|
|
this.ksearch_destroy();
|
|
|
|
// insert all members of a group
|
|
if (typeof this.env.contacts[id] === 'object' && this.env.contacts[id].type == 'group') {
|
|
insert += this.env.contacts[id].name + this.env.recipients_delimiter;
|
|
this.group2expand[this.env.contacts[id].id] = $.extend({ input: this.ksearch_input }, this.env.contacts[id]);
|
|
this.http_request('mail/group-expand', {_source: this.env.contacts[id].source, _gid: this.env.contacts[id].id}, false);
|
|
}
|
|
else if (typeof this.env.contacts[id] === 'object' && this.env.contacts[id].name) {
|
|
insert = this.env.contacts[id].name + this.env.recipients_delimiter;
|
|
trigger = true;
|
|
}
|
|
else if (typeof this.env.contacts[id] === 'string') {
|
|
insert = this.env.contacts[id] + this.env.recipients_delimiter;
|
|
trigger = true;
|
|
}
|
|
|
|
this.ksearch_input.value = pre + insert + end;
|
|
|
|
// set caret to insert pos
|
|
this.set_caret_pos(this.ksearch_input, p + insert.length);
|
|
|
|
if (trigger) {
|
|
this.triggerEvent('autocomplete_insert', { field:this.ksearch_input, insert:insert, data:this.env.contacts[id] });
|
|
this.compose_type_activity++;
|
|
}
|
|
};
|
|
|
|
this.replace_group_recipients = function(id, recipients)
|
|
{
|
|
if (this.group2expand[id]) {
|
|
this.group2expand[id].input.value = this.group2expand[id].input.value.replace(this.group2expand[id].name, recipients);
|
|
this.triggerEvent('autocomplete_insert', { field:this.group2expand[id].input, insert:recipients });
|
|
this.group2expand[id] = null;
|
|
this.compose_type_activity++;
|
|
}
|
|
};
|
|
|
|
// address search processor
|
|
this.ksearch_get_results = function(props)
|
|
{
|
|
var inp_value = this.ksearch_input ? this.ksearch_input.value : null;
|
|
|
|
if (inp_value === null)
|
|
return;
|
|
|
|
if (this.ksearch_pane && this.ksearch_pane.is(":visible"))
|
|
this.ksearch_pane.hide();
|
|
|
|
// get string from current cursor pos to last comma
|
|
var cpos = this.get_caret_pos(this.ksearch_input),
|
|
p = inp_value.lastIndexOf(this.env.recipients_separator, cpos-1),
|
|
q = inp_value.substring(p+1, cpos),
|
|
min = this.env.autocomplete_min_length,
|
|
data = this.ksearch_data;
|
|
|
|
// trim query string
|
|
q = $.trim(q);
|
|
|
|
// Don't (re-)search if the last results are still active
|
|
if (q == this.ksearch_value)
|
|
return;
|
|
|
|
this.ksearch_destroy();
|
|
|
|
if (q.length && q.length < min) {
|
|
if (!this.ksearch_info) {
|
|
this.ksearch_info = this.display_message(
|
|
this.get_label('autocompletechars').replace('$min', min));
|
|
}
|
|
return;
|
|
}
|
|
|
|
var old_value = this.ksearch_value;
|
|
this.ksearch_value = q;
|
|
|
|
// ...string is empty
|
|
if (!q.length)
|
|
return;
|
|
|
|
// ...new search value contains old one and previous search was not finished or its result was empty
|
|
if (old_value && old_value.length && q.startsWith(old_value) && (!data || data.num <= 0) && this.env.contacts && !this.env.contacts.length)
|
|
return;
|
|
|
|
var sources = props && props.sources ? props.sources : [''];
|
|
var reqid = this.multi_thread_http_request({
|
|
items: sources,
|
|
threads: props && props.threads ? props.threads : 1,
|
|
action: props && props.action ? props.action : 'mail/autocomplete',
|
|
postdata: { _search:q, _source:'%s' },
|
|
lock: this.display_message(this.get_label('searching'), 'loading')
|
|
});
|
|
|
|
this.ksearch_data = { id:reqid, sources:sources.slice(), num:sources.length };
|
|
};
|
|
|
|
this.ksearch_query_results = function(results, search, reqid)
|
|
{
|
|
// trigger multi-thread http response callback
|
|
this.multi_thread_http_response(results, reqid);
|
|
|
|
// search stopped in meantime?
|
|
if (!this.ksearch_value)
|
|
return;
|
|
|
|
// ignore this outdated search response
|
|
if (this.ksearch_input && search != this.ksearch_value)
|
|
return;
|
|
|
|
// display search results
|
|
var i, len, ul, li, text, type, init,
|
|
value = this.ksearch_value,
|
|
maxlen = this.env.autocomplete_max ? this.env.autocomplete_max : 15;
|
|
|
|
// create results pane if not present
|
|
if (!this.ksearch_pane) {
|
|
ul = $('<ul>');
|
|
this.ksearch_pane = $('<div>').attr('id', 'rcmKSearchpane')
|
|
.css({ position:'absolute', 'z-index':30000 }).append(ul).appendTo(document.body);
|
|
this.ksearch_pane.__ul = ul[0];
|
|
}
|
|
|
|
ul = this.ksearch_pane.__ul;
|
|
|
|
// remove all search results or add to existing list if parallel search
|
|
if (reqid && this.ksearch_pane.data('reqid') == reqid) {
|
|
maxlen -= ul.childNodes.length;
|
|
}
|
|
else {
|
|
this.ksearch_pane.data('reqid', reqid);
|
|
init = 1;
|
|
// reset content
|
|
ul.innerHTML = '';
|
|
this.env.contacts = [];
|
|
// move the results pane right under the input box
|
|
var pos = $(this.ksearch_input).offset();
|
|
this.ksearch_pane.css({ left:pos.left+'px', top:(pos.top + this.ksearch_input.offsetHeight)+'px', display: 'none'});
|
|
}
|
|
|
|
// add each result line to list
|
|
if (results && (len = results.length)) {
|
|
for (i=0; i < len && maxlen > 0; i++) {
|
|
text = typeof results[i] === 'object' ? results[i].name : results[i];
|
|
type = typeof results[i] === 'object' ? results[i].type : '';
|
|
li = document.createElement('LI');
|
|
li.innerHTML = text.replace(new RegExp('('+RegExp.escape(value)+')', 'ig'), '##$1%%').replace(/</g, '<').replace(/>/g, '>').replace(/##([^%]+)%%/g, '<b>$1</b>');
|
|
li.onmouseover = function(){ ref.ksearch_select(this); };
|
|
li.onmouseup = function(){ ref.ksearch_click(this) };
|
|
li._rcm_id = this.env.contacts.length + i;
|
|
if (type) li.className = type;
|
|
ul.appendChild(li);
|
|
maxlen -= 1;
|
|
}
|
|
}
|
|
|
|
if (ul.childNodes.length) {
|
|
this.ksearch_pane.show();
|
|
// select the first
|
|
if (!this.env.contacts.length) {
|
|
$('li:first', ul).attr('id', 'rcmksearchSelected').addClass('selected');
|
|
this.ksearch_selected = 0;
|
|
}
|
|
}
|
|
|
|
if (len)
|
|
this.env.contacts = this.env.contacts.concat(results);
|
|
|
|
if (this.ksearch_data.id == reqid)
|
|
this.ksearch_data.num--;
|
|
};
|
|
|
|
this.ksearch_click = function(node)
|
|
{
|
|
if (this.ksearch_input)
|
|
this.ksearch_input.focus();
|
|
|
|
this.insert_recipient(node._rcm_id);
|
|
this.ksearch_hide();
|
|
};
|
|
|
|
this.ksearch_blur = function()
|
|
{
|
|
if (this.ksearch_timer)
|
|
clearTimeout(this.ksearch_timer);
|
|
|
|
this.ksearch_input = null;
|
|
this.ksearch_hide();
|
|
};
|
|
|
|
this.ksearch_hide = function()
|
|
{
|
|
this.ksearch_selected = null;
|
|
this.ksearch_value = '';
|
|
|
|
if (this.ksearch_pane)
|
|
this.ksearch_pane.hide();
|
|
|
|
this.ksearch_destroy();
|
|
};
|
|
|
|
// Clears autocomplete data/requests
|
|
this.ksearch_destroy = function()
|
|
{
|
|
if (this.ksearch_data)
|
|
this.multi_thread_request_abort(this.ksearch_data.id);
|
|
|
|
if (this.ksearch_info)
|
|
this.hide_message(this.ksearch_info);
|
|
|
|
if (this.ksearch_msg)
|
|
this.hide_message(this.ksearch_msg);
|
|
|
|
this.ksearch_data = null;
|
|
this.ksearch_info = null;
|
|
this.ksearch_msg = null;
|
|
};
|
|
|
|
|
|
/*********************************************************/
|
|
/********* address book methods *********/
|
|
/*********************************************************/
|
|
|
|
this.contactlist_keypress = function(list)
|
|
{
|
|
if (list.key_pressed == list.DELETE_KEY)
|
|
this.command('delete');
|
|
};
|
|
|
|
this.contactlist_select = function(list)
|
|
{
|
|
if (this.preview_timer)
|
|
clearTimeout(this.preview_timer);
|
|
|
|
var n, id, sid, contact, 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)
|
|
this.show_contentframe(false);
|
|
|
|
if (list.selection.length) {
|
|
list.draggable = false;
|
|
|
|
// no source = search result, we'll need to detect if any of
|
|
// selected contacts are in writable addressbook to enable edit/delete
|
|
// we'll also need to know sources used in selection for copy
|
|
// and group-addmember operations (drag&drop)
|
|
this.env.selection_sources = [];
|
|
|
|
if (source) {
|
|
this.env.selection_sources.push(this.env.source);
|
|
}
|
|
|
|
for (n in list.selection) {
|
|
contact = list.data[list.selection[n]];
|
|
if (!source) {
|
|
sid = String(list.selection[n]).replace(/^[^-]+-/, '');
|
|
if (sid && this.env.address_sources[sid]) {
|
|
writable = writable || (!this.env.address_sources[sid].readonly && !contact.readonly);
|
|
this.env.selection_sources.push(sid);
|
|
}
|
|
}
|
|
else {
|
|
writable = writable || (!source.readonly && !contact.readonly);
|
|
}
|
|
|
|
if (contact._type != 'group')
|
|
list.draggable = true;
|
|
}
|
|
|
|
this.env.selection_sources = $.unique(this.env.selection_sources);
|
|
}
|
|
|
|
// if a group is currently selected, and there is at least one contact selected
|
|
// thend we can enable the group-remove-selected command
|
|
this.enable_command('group-remove-selected', this.env.group && list.selection.length > 0 && writable);
|
|
this.enable_command('compose', this.env.group || list.selection.length > 0);
|
|
this.enable_command('export-selected', 'copy', list.selection.length > 0);
|
|
this.enable_command('edit', id && writable);
|
|
this.enable_command('delete', 'move', list.selection.length > 0 && writable);
|
|
|
|
return false;
|
|
};
|
|
|
|
this.list_contacts = function(src, group, page)
|
|
{
|
|
var win, folder, url = {},
|
|
target = window;
|
|
|
|
if (!src)
|
|
src = this.env.source;
|
|
|
|
if (page && this.current_page == page && src == this.env.source && group == this.env.group)
|
|
return false;
|
|
|
|
if (src != this.env.source) {
|
|
page = this.env.current_page = 1;
|
|
this.reset_qsearch();
|
|
}
|
|
else if (group != this.env.group)
|
|
page = this.env.current_page = 1;
|
|
|
|
if (this.env.search_id)
|
|
folder = 'S'+this.env.search_id;
|
|
else if (!this.env.search_request)
|
|
folder = group ? 'G'+src+group : src;
|
|
|
|
this.env.source = src;
|
|
this.env.group = group;
|
|
|
|
// truncate groups listing stack
|
|
var index = $.inArray(this.env.group, this.env.address_group_stack);
|
|
if (index < 0)
|
|
this.env.address_group_stack = [];
|
|
else
|
|
this.env.address_group_stack = this.env.address_group_stack.slice(0,index);
|
|
|
|
// make sure the current group is on top of the stack
|
|
if (this.env.group) {
|
|
this.env.address_group_stack.push(this.env.group);
|
|
|
|
// mark the first group on the stack as selected in the directory list
|
|
folder = 'G'+src+this.env.address_group_stack[0];
|
|
}
|
|
else if (this.gui_objects.addresslist_title) {
|
|
$(this.gui_objects.addresslist_title).html(this.get_label('contacts'));
|
|
}
|
|
|
|
this.select_folder(folder, '', true);
|
|
|
|
// load contacts remotely
|
|
if (this.gui_objects.contactslist) {
|
|
this.list_contacts_remote(src, group, page);
|
|
return;
|
|
}
|
|
|
|
if (win = this.get_frame_window(this.env.contentframe)) {
|
|
target = win;
|
|
url._framed = 1;
|
|
}
|
|
|
|
if (group)
|
|
url._gid = group;
|
|
if (page)
|
|
url._page = page;
|
|
if (src)
|
|
url._source = src;
|
|
|
|
// also send search request to get the correct listing
|
|
if (this.env.search_request)
|
|
url._search = this.env.search_request;
|
|
|
|
this.set_busy(true, 'loading');
|
|
this.location_href(url, target);
|
|
};
|
|
|
|
// send remote request to load contacts list
|
|
this.list_contacts_remote = function(src, group, page)
|
|
{
|
|
// clear message list first
|
|
this.list_contacts_clear();
|
|
|
|
// send request to server
|
|
var url = {}, lock = this.set_busy(true, 'loading');
|
|
|
|
if (src)
|
|
url._source = src;
|
|
if (page)
|
|
url._page = page;
|
|
if (group)
|
|
url._gid = group;
|
|
|
|
this.env.source = src;
|
|
this.env.group = group;
|
|
|
|
// also send search request to get the right records
|
|
if (this.env.search_request)
|
|
url._search = this.env.search_request;
|
|
|
|
this.http_request(this.env.task == 'mail' ? 'list-contacts' : 'list', url, lock);
|
|
};
|
|
|
|
this.list_contacts_clear = function()
|
|
{
|
|
this.contact_list.data = {};
|
|
this.contact_list.clear(true);
|
|
this.show_contentframe(false);
|
|
this.enable_command('delete', 'move', 'copy', false);
|
|
this.enable_command('compose', this.env.group ? true : false);
|
|
};
|
|
|
|
this.set_group_prop = function(prop)
|
|
{
|
|
if (this.gui_objects.addresslist_title) {
|
|
var boxtitle = $(this.gui_objects.addresslist_title).html(''); // clear contents
|
|
|
|
// add link to pop back to parent group
|
|
if (this.env.address_group_stack.length > 1) {
|
|
$('<a href="#list">...</a>')
|
|
.addClass('poplink')
|
|
.appendTo(boxtitle)
|
|
.click(function(e){ return ref.command('popgroup','',this); });
|
|
boxtitle.append(' » ');
|
|
}
|
|
|
|
boxtitle.append($('<span>').text(prop.name));
|
|
}
|
|
|
|
this.triggerEvent('groupupdate', prop);
|
|
};
|
|
|
|
// load contact record
|
|
this.load_contact = function(cid, action, framed)
|
|
{
|
|
var win, url = {}, target = window,
|
|
rec = this.contact_list ? this.contact_list.data[cid] : null;
|
|
|
|
if (win = this.get_frame_window(this.env.contentframe)) {
|
|
url._framed = 1;
|
|
target = win;
|
|
this.show_contentframe(true);
|
|
|
|
// load dummy content, unselect selected row(s)
|
|
if (!cid)
|
|
this.contact_list.clear_selection();
|
|
|
|
this.enable_command('compose', rec && rec.email);
|
|
this.enable_command('export-selected', rec && rec._type != 'group');
|
|
}
|
|
else if (framed)
|
|
return false;
|
|
|
|
if (action && (cid || action=='add') && !this.drag_active) {
|
|
if (this.env.group)
|
|
url._gid = this.env.group;
|
|
|
|
url._action = action;
|
|
url._source = this.env.source;
|
|
url._cid = cid;
|
|
|
|
this.location_href(url, target, true);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
// add/delete member to/from the group
|
|
this.group_member_change = function(what, cid, source, gid)
|
|
{
|
|
what = what == 'add' ? 'add' : 'del';
|
|
var label = this.get_label(what == 'add' ? 'addingmember' : 'removingmember'),
|
|
lock = this.display_message(label, 'loading'),
|
|
post_data = {_cid: cid, _source: source, _gid: gid};
|
|
|
|
this.http_post('group-'+what+'members', post_data, lock);
|
|
};
|
|
|
|
this.contacts_drag_menu = function(e, to)
|
|
{
|
|
var dest = to.type == 'group' ? to.source : to.id,
|
|
source = this.env.source;
|
|
|
|
if (!this.env.address_sources[dest] || this.env.address_sources[dest].readonly)
|
|
return true;
|
|
|
|
// search result may contain contacts from many sources, but if there is only one...
|
|
if (source == '' && this.env.selection_sources.length == 1)
|
|
source = this.env.selection_sources[0];
|
|
|
|
if (to.type == 'group' && dest == source) {
|
|
var cid = this.contact_list.get_selection().join(',');
|
|
this.group_member_change('add', cid, dest, to.id);
|
|
return true;
|
|
}
|
|
// move action is not possible, "redirect" to copy if menu wasn't requested
|
|
else if (!this.commands.move && rcube_event.get_modifier(e) != SHIFT_KEY) {
|
|
this.copy_contacts(to);
|
|
return true;
|
|
}
|
|
|
|
return this.drag_menu(e, to);
|
|
};
|
|
|
|
// copy contact(s) to the specified target (group or directory)
|
|
this.copy_contacts = function(to)
|
|
{
|
|
var n, dest = to.type == 'group' ? to.source : to.id,
|
|
source = this.env.source,
|
|
group = this.env.group ? this.env.group : '',
|
|
cid = this.contact_list.get_selection().join(',');
|
|
|
|
if (!cid || !this.env.address_sources[dest] || this.env.address_sources[dest].readonly)
|
|
return;
|
|
|
|
// search result may contain contacts from many sources, but if there is only one...
|
|
if (source == '' && this.env.selection_sources.length == 1)
|
|
source = this.env.selection_sources[0];
|
|
|
|
// tagret is a group
|
|
if (to.type == 'group') {
|
|
if (dest == source)
|
|
return;
|
|
|
|
var lock = this.display_message(this.get_label('copyingcontact'), 'loading'),
|
|
post_data = {_cid: cid, _source: this.env.source, _to: dest, _togid: to.id, _gid: group};
|
|
|
|
this.http_post('copy', post_data, lock);
|
|
}
|
|
// target is an addressbook
|
|
else if (to.id != source) {
|
|
var lock = this.display_message(this.get_label('copyingcontact'), 'loading'),
|
|
post_data = {_cid: cid, _source: this.env.source, _to: to.id, _gid: group};
|
|
|
|
this.http_post('copy', post_data, lock);
|
|
}
|
|
};
|
|
|
|
// move contact(s) to the specified target (group or directory)
|
|
this.move_contacts = function(to)
|
|
{
|
|
var dest = to.type == 'group' ? to.source : to.id,
|
|
source = this.env.source,
|
|
group = this.env.group ? this.env.group : '';
|
|
|
|
if (!this.env.address_sources[dest] || this.env.address_sources[dest].readonly)
|
|
return;
|
|
|
|
// search result may contain contacts from many sources, but if there is only one...
|
|
if (source == '' && this.env.selection_sources.length == 1)
|
|
source = this.env.selection_sources[0];
|
|
|
|
if (to.type == 'group') {
|
|
if (dest == source)
|
|
return;
|
|
|
|
this._with_selected_contacts('move', {_to: dest, _togid: to.id});
|
|
}
|
|
// target is an addressbook
|
|
else if (to.id != source)
|
|
this._with_selected_contacts('move', {_to: to.id});
|
|
};
|
|
|
|
// delete contact(s)
|
|
this.delete_contacts = function()
|
|
{
|
|
var undelete = this.env.source && this.env.address_sources[this.env.source].undelete;
|
|
|
|
if (!undelete && !confirm(this.get_label('deletecontactconfirm')))
|
|
return;
|
|
|
|
return this._with_selected_contacts('delete');
|
|
};
|
|
|
|
this._with_selected_contacts = function(action, post_data)
|
|
{
|
|
var selection = this.contact_list ? this.contact_list.get_selection() : [];
|
|
|
|
// exit if no mailbox specified or if selection is empty
|
|
if (!selection.length && !this.env.cid)
|
|
return;
|
|
|
|
var n, a_cids = [],
|
|
label = action == 'delete' ? 'contactdeleting' : 'movingcontact',
|
|
lock = this.display_message(this.get_label(label), 'loading');
|
|
if (this.env.cid)
|
|
a_cids.push(this.env.cid);
|
|
else {
|
|
for (n=0; n<selection.length; n++) {
|
|
id = selection[n];
|
|
a_cids.push(id);
|
|
this.contact_list.remove_row(id, (n == selection.length-1));
|
|
}
|
|
|
|
// hide content frame if we delete the currently displayed contact
|
|
if (selection.length == 1)
|
|
this.show_contentframe(false);
|
|
}
|
|
|
|
if (!post_data)
|
|
post_data = {};
|
|
|
|
post_data._source = this.env.source;
|
|
post_data._from = this.env.action;
|
|
post_data._cid = a_cids.join(',');
|
|
|
|
if (this.env.group)
|
|
post_data._gid = this.env.group;
|
|
|
|
// also send search request to get the right records from the next page
|
|
if (this.env.search_request)
|
|
post_data._search = this.env.search_request;
|
|
|
|
// send request to server
|
|
this.http_post(action, post_data, lock)
|
|
|
|
return true;
|
|
};
|
|
|
|
// update a contact record in the list
|
|
this.update_contact_row = function(cid, cols_arr, newcid, source, data)
|
|
{
|
|
var c, row, list = this.contact_list;
|
|
|
|
cid = this.html_identifier(cid);
|
|
|
|
// when in searching mode, concat cid with the source name
|
|
if (!list.rows[cid]) {
|
|
cid = cid+'-'+source;
|
|
if (newcid)
|
|
newcid = newcid+'-'+source;
|
|
}
|
|
|
|
list.update_row(cid, cols_arr, newcid, true);
|
|
list.data[cid] = data;
|
|
};
|
|
|
|
// add row to contacts list
|
|
this.add_contact_row = function(cid, cols, classes, data)
|
|
{
|
|
if (!this.gui_objects.contactslist)
|
|
return false;
|
|
|
|
var c, col, list = this.contact_list,
|
|
row = { cols:[] };
|
|
|
|
row.id = 'rcmrow'+this.html_identifier(cid);
|
|
row.className = 'contact ' + (classes || '');
|
|
|
|
if (list.in_selection(cid))
|
|
row.className += ' selected';
|
|
|
|
// add each submitted col
|
|
for (c in cols) {
|
|
col = {};
|
|
col.className = String(c).toLowerCase();
|
|
col.innerHTML = cols[c];
|
|
row.cols.push(col);
|
|
}
|
|
|
|
// store data in list member
|
|
list.data[cid] = data;
|
|
list.insert_row(row);
|
|
|
|
this.enable_command('export', list.rowcount > 0);
|
|
};
|
|
|
|
this.init_contact_form = function()
|
|
{
|
|
var col;
|
|
|
|
if (this.env.coltypes) {
|
|
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;
|
|
});
|
|
|
|
// enable date pickers on date fields
|
|
if ($.datepicker && this.env.date_format) {
|
|
$.datepicker.setDefaults({
|
|
dateFormat: this.env.date_format,
|
|
changeMonth: true,
|
|
changeYear: true,
|
|
yearRange: '-100:+10',
|
|
showOtherMonths: true,
|
|
selectOtherMonths: true,
|
|
onSelect: function(dateText) { $(this).focus().val(dateText) }
|
|
});
|
|
$('input.datepicker').datepicker();
|
|
}
|
|
|
|
// Submit search form on Enter
|
|
if (this.env.action == 'search')
|
|
$(this.gui_objects.editform).append($('<input type="submit">').hide())
|
|
.submit(function() { $('input.mainaction').click(); return false; });
|
|
};
|
|
|
|
this.group_create = function()
|
|
{
|
|
this.add_input_row('contactgroup');
|
|
};
|
|
|
|
this.group_rename = function()
|
|
{
|
|
if (!this.env.group || !this.gui_objects.folderlist)
|
|
return;
|
|
|
|
if (!this.name_input) {
|
|
this.enable_command('list', 'listgroup', false);
|
|
this.name_input = $('<input>').attr('type', 'text').val(this.env.contactgroups['G'+this.env.source+this.env.group].name);
|
|
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('G'+this.env.source+this.env.group,'',true);
|
|
if (li && (link = li.firstChild)) {
|
|
$(link).hide().before(this.name_input);
|
|
}
|
|
}
|
|
|
|
this.name_input.select().focus();
|
|
};
|
|
|
|
this.group_delete = function()
|
|
{
|
|
if (this.env.group && confirm(this.get_label('deletegroupconfirm'))) {
|
|
var lock = this.set_busy(true, 'groupdeleting');
|
|
this.http_post('group-delete', {_source: this.env.source, _gid: this.env.group}, lock);
|
|
}
|
|
};
|
|
|
|
// callback from server upon group-delete command
|
|
this.remove_group_item = function(prop)
|
|
{
|
|
var key = 'G'+prop.source+prop.id;
|
|
if (this.treelist.remove(key)) {
|
|
this.triggerEvent('group_delete', { source:prop.source, id:prop.id });
|
|
delete this.env.contactfolders[key];
|
|
delete this.env.contactgroups[key];
|
|
}
|
|
|
|
this.list_contacts(prop.source, 0);
|
|
};
|
|
|
|
// @TODO: maybe it would be better to use popup instead of inserting input to the list?
|
|
this.add_input_row = function(type)
|
|
{
|
|
if (!this.gui_objects.folderlist)
|
|
return;
|
|
|
|
if (!this.name_input) {
|
|
this.name_input = $('<input>').attr('type', 'text').data('tt', type);
|
|
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 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();
|
|
};
|
|
|
|
//remove selected contacts from current active group
|
|
this.group_remove_selected = function()
|
|
{
|
|
ref.http_post('group-delmembers', {_cid: this.contact_list.selection,
|
|
_source: this.env.source, _gid: this.env.group});
|
|
};
|
|
|
|
//callback after deleting contact(s) from current group
|
|
this.remove_group_contacts = function(props)
|
|
{
|
|
if('undefined' != typeof this.env.group && (this.env.group === props.gid)){
|
|
var n, selection = this.contact_list.get_selection();
|
|
for (n=0; n<selection.length; n++) {
|
|
id = selection[n];
|
|
this.contact_list.remove_row(id, (n == selection.length-1));
|
|
}
|
|
}
|
|
}
|
|
|
|
// handler for keyboard events on the input field
|
|
this.add_input_keydown = function(e)
|
|
{
|
|
var key = rcube_event.get_keycode(e),
|
|
input = $(e.target), itype = input.data('tt');
|
|
|
|
// enter
|
|
if (key == 13) {
|
|
var newname = input.val();
|
|
|
|
if (newname) {
|
|
var lock = this.set_busy(true, 'loading');
|
|
|
|
if (itype == 'contactsearch')
|
|
this.http_post('search-create', {_search: this.env.search_request, _name: newname}, lock);
|
|
else if (this.env.group_renaming)
|
|
this.http_post('group-rename', {_source: this.env.source, _gid: this.env.group, _name: newname}, lock);
|
|
else
|
|
this.http_post('group-create', {_source: this.env.source, _name: newname}, lock);
|
|
}
|
|
return false;
|
|
}
|
|
// escape
|
|
else if (key == 27)
|
|
this.reset_add_input();
|
|
|
|
return true;
|
|
};
|
|
|
|
this.reset_add_input = function()
|
|
{
|
|
if (this.name_input) {
|
|
var li = this.name_input.parent();
|
|
if (this.env.group_renaming) {
|
|
li.children().last().show();
|
|
this.env.group_renaming = false;
|
|
}
|
|
else if ($('li', li.parent()).length == 1)
|
|
li.parent().hide();
|
|
|
|
this.name_input.remove();
|
|
|
|
if (this.name_input_li)
|
|
this.name_input_li.remove();
|
|
|
|
this.name_input = this.name_input_li = null;
|
|
}
|
|
|
|
this.enable_command('list', 'listgroup', true);
|
|
};
|
|
|
|
// callback for creating a new contact group
|
|
this.insert_contact_group = function(prop)
|
|
{
|
|
this.reset_add_input();
|
|
|
|
prop.type = 'group';
|
|
var key = 'G'+prop.source+prop.id,
|
|
link = $('<a>').attr('href', '#')
|
|
.attr('rel', prop.source+':'+prop.id)
|
|
.click(function() { return rcmail.command('listgroup', prop, this); })
|
|
.html(prop.name);
|
|
|
|
this.env.contactfolders[key] = this.env.contactgroups[key] = prop;
|
|
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:this.treelist.get_item(key) });
|
|
};
|
|
|
|
// callback for renaming a contact group
|
|
this.update_contact_group = function(prop)
|
|
{
|
|
this.reset_add_input();
|
|
|
|
var key = 'G'+prop.source+prop.id,
|
|
newnode = {};
|
|
|
|
// group ID has changed, replace link node and identifiers
|
|
if (prop.newid) {
|
|
var newkey = 'G'+prop.source+prop.newid,
|
|
newprop = $.extend({}, prop);
|
|
|
|
this.env.contactfolders[newkey] = this.env.contactfolders[key];
|
|
this.env.contactfolders[newkey].id = prop.newid;
|
|
this.env.group = prop.newid;
|
|
|
|
delete this.env.contactfolders[key];
|
|
delete this.env.contactgroups[key];
|
|
|
|
newprop.id = prop.newid;
|
|
newprop.type = 'group';
|
|
|
|
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);
|
|
}
|
|
// update displayed group name
|
|
else {
|
|
$(this.treelist.get_item(key)).children().first().html(prop.name);
|
|
this.env.contactfolders[key].name = this.env.contactgroups[key].name = prop.name;
|
|
}
|
|
|
|
// update list node and re-sort it
|
|
this.treelist.update(key, newnode, true);
|
|
|
|
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()
|
|
{
|
|
var source = this.env.source != '' ? this.env.address_sources[this.env.source] : null;
|
|
this.enable_command('group-create', (source && source.groups && !source.readonly));
|
|
this.enable_command('group-rename', 'group-delete', (source && source.groups && this.env.group && !source.readonly));
|
|
};
|
|
|
|
this.init_edit_field = function(col, elem)
|
|
{
|
|
var label = this.env.coltypes[col].label;
|
|
|
|
if (!elem)
|
|
elem = $('.ff_' + col);
|
|
|
|
if (label)
|
|
elem.placeholder(label);
|
|
};
|
|
|
|
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+'"]').prop('disabled', true);
|
|
}
|
|
else {
|
|
var lastelem = $('.ff_'+col),
|
|
appendcontainer = $('#contactsection'+section+' .contactcontroller'+col);
|
|
|
|
if (!appendcontainer.length) {
|
|
var sect = $('#contactsection'+section),
|
|
lastgroup = $('.contactfieldgroup', sect).last();
|
|
appendcontainer = $('<fieldset>').addClass('contactfieldgroup contactcontroller'+col);
|
|
if (lastgroup.length)
|
|
appendcontainer.insertAfter(lastgroup);
|
|
else
|
|
sect.prepend(appendcontainer);
|
|
}
|
|
|
|
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', name: '_'+col+name_suffix, size: colprop.size})
|
|
.appendTo(cell);
|
|
|
|
this.init_edit_field(col, input);
|
|
|
|
if (colprop.type == 'date' && $.datepicker)
|
|
input.datepicker();
|
|
}
|
|
else if (colprop.type == 'textarea') {
|
|
input = $('<textarea>')
|
|
.addClass('ff_'+col)
|
|
.attr({ name: '_'+col+name_suffix, cols:colprop.size, rows:colprop.rows })
|
|
.appendTo(cell);
|
|
|
|
this.init_edit_field(col, input);
|
|
}
|
|
else if (colprop.type == 'composite') {
|
|
var childcol, cp, first, templ, cols = [], suffices = [];
|
|
// read template for composite field order
|
|
if ((templ = this.env[col+'_template'])) {
|
|
for (var j=0; j < templ.length; j++) {
|
|
cols.push(templ[j][1]);
|
|
suffices.push(templ[j][2]);
|
|
}
|
|
}
|
|
else { // list fields according to appearance in colprop
|
|
for (childcol in colprop.childs)
|
|
cols.push(childcol);
|
|
}
|
|
|
|
for (var i=0; i < cols.length; i++) {
|
|
childcol = cols[i];
|
|
cp = colprop.childs[childcol];
|
|
input = $('<input>')
|
|
.addClass('ff_'+childcol)
|
|
.attr({ type: 'text', name: '_'+childcol+name_suffix, size: cp.size })
|
|
.appendTo(cell);
|
|
cell.append(suffices[i] || " ");
|
|
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'), 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+'"]').prop('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.prop('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.file_upload_id);
|
|
});
|
|
|
|
// display upload indicator
|
|
this.file_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 || 0) + '&_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.file_upload_id);
|
|
delete this.file_upload_id;
|
|
};
|
|
|
|
this.set_photo_actions = function(id)
|
|
{
|
|
var n, buttons = this.buttons['upload-photo'];
|
|
for (n=0; buttons && n < buttons.length; n++)
|
|
$('a#'+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-');
|
|
};
|
|
|
|
// load advanced search page
|
|
this.advanced_search = function()
|
|
{
|
|
var win, url = {_form: 1, _action: 'search'}, target = window;
|
|
|
|
if (win = this.get_frame_window(this.env.contentframe)) {
|
|
url._framed = 1;
|
|
target = win;
|
|
this.contact_list.clear_selection();
|
|
}
|
|
|
|
this.location_href(url, target, true);
|
|
|
|
return true;
|
|
};
|
|
|
|
// unselect directory/group
|
|
this.unselect_directory = function()
|
|
{
|
|
this.select_folder('');
|
|
this.enable_command('search-delete', false);
|
|
};
|
|
|
|
// callback for creating a new saved search record
|
|
this.insert_saved_search = function(name, id)
|
|
{
|
|
this.reset_add_input();
|
|
|
|
var key = 'S'+id,
|
|
link = $('<a>').attr('href', '#')
|
|
.attr('rel', id)
|
|
.click(function() { return rcmail.command('listsearch', id, this); })
|
|
.html(name),
|
|
prop = { name:name, id: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);
|
|
};
|
|
|
|
// creates an input for saved search name
|
|
this.search_create = function()
|
|
{
|
|
this.add_input_row('contactsearch');
|
|
};
|
|
|
|
this.search_delete = function()
|
|
{
|
|
if (this.env.search_request) {
|
|
var lock = this.set_busy(true, 'savedsearchdeleting');
|
|
this.http_post('search-delete', {_sid: this.env.search_id}, lock);
|
|
}
|
|
};
|
|
|
|
// callback from server upon search-delete command
|
|
this.remove_search_item = function(id)
|
|
{
|
|
var li, key = 'S'+id;
|
|
if (this.treelist.remove(key)) {
|
|
this.triggerEvent('search_delete', { id:id, li:li });
|
|
}
|
|
|
|
this.env.search_id = null;
|
|
this.env.search_request = null;
|
|
this.list_contacts_clear();
|
|
this.reset_qsearch();
|
|
this.enable_command('search-delete', 'search-create', false);
|
|
};
|
|
|
|
this.listsearch = function(id)
|
|
{
|
|
var folder, lock = this.set_busy(true, 'searching');
|
|
|
|
if (this.contact_list) {
|
|
this.list_contacts_clear();
|
|
}
|
|
|
|
this.reset_qsearch();
|
|
this.select_folder('S'+id, '', true);
|
|
|
|
// reset vars
|
|
this.env.current_page = 1;
|
|
this.http_request('search', {_sid: id}, lock);
|
|
};
|
|
|
|
|
|
/*********************************************************/
|
|
/********* user settings methods *********/
|
|
/*********************************************************/
|
|
|
|
// preferences section select and load options frame
|
|
this.section_select = function(list)
|
|
{
|
|
var win, id = list.get_single_selection(), target = window,
|
|
url = {_action: 'edit-prefs', _section: id};
|
|
|
|
if (id) {
|
|
if (win = this.get_frame_window(this.env.contentframe)) {
|
|
url._framed = 1;
|
|
target = win;
|
|
}
|
|
this.location_href(url, target, true);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
this.identity_select = function(list)
|
|
{
|
|
var id;
|
|
if (id = list.get_single_selection()) {
|
|
this.enable_command('delete', list.rowcount > 1 && this.env.identities_level < 2);
|
|
this.load_identity(id, 'edit-identity');
|
|
}
|
|
};
|
|
|
|
// load identity record
|
|
this.load_identity = function(id, action)
|
|
{
|
|
if (action == 'edit-identity' && (!id || id == this.env.iid))
|
|
return false;
|
|
|
|
var win, target = window,
|
|
url = {_action: action, _iid: id};
|
|
|
|
if (win = this.get_frame_window(this.env.contentframe)) {
|
|
url._framed = 1;
|
|
target = win;
|
|
}
|
|
|
|
if (id || action == 'add-identity') {
|
|
this.location_href(url, target, true);
|
|
}
|
|
|
|
return true;
|
|
};
|
|
|
|
this.delete_identity = function(id)
|
|
{
|
|
// exit if no identity is specified or if selection is empty
|
|
var selection = this.identity_list.get_selection();
|
|
if (!(selection.length || this.env.iid))
|
|
return;
|
|
|
|
if (!id)
|
|
id = this.env.iid ? this.env.iid : selection[0];
|
|
|
|
// submit request with appended token
|
|
if (confirm(this.get_label('deleteidentityconfirm')))
|
|
this.goto_url('delete-identity', { _iid: id, _token: this.env.request_token }, true);
|
|
|
|
return true;
|
|
};
|
|
|
|
this.update_identity_row = function(id, name, add)
|
|
{
|
|
var list = this.identity_list,
|
|
rid = this.html_identifier(id);
|
|
|
|
if (add) {
|
|
list.insert_row({ id:'rcmrow'+rid, cols:[ { className:'mail', innerHTML:name } ] });
|
|
list.select(rid);
|
|
}
|
|
else {
|
|
list.update_row(rid, [ name ]);
|
|
}
|
|
};
|
|
|
|
this.update_response_row = function(response, oldkey)
|
|
{
|
|
var list = this.responses_list;
|
|
|
|
if (list && oldkey) {
|
|
list.update_row(oldkey, [ response.name ], response.key, true);
|
|
}
|
|
else if (list) {
|
|
list.insert_row({ id:'rcmrow'+response.key, cols:[ { className:'name', innerHTML:response.name } ] });
|
|
list.select(response.key);
|
|
}
|
|
};
|
|
|
|
this.remove_response = function(key)
|
|
{
|
|
var frame;
|
|
|
|
if (this.env.textresponses) {
|
|
delete this.env.textresponses[key];
|
|
}
|
|
|
|
if (this.responses_list) {
|
|
this.responses_list.remove_row(key);
|
|
if (this.env.contentframe && (frame = this.get_frame_window(this.env.contentframe))) {
|
|
frame.location.href = this.env.blankpage;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/*********************************************************/
|
|
/********* folder manager methods *********/
|
|
/*********************************************************/
|
|
|
|
this.init_subscription_list = function()
|
|
{
|
|
var delim = RegExp.escape(this.env.delimiter);
|
|
|
|
this.last_sub_rx = RegExp('['+delim+']?[^'+delim+']+$');
|
|
|
|
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){ ref.subscription_select(o); })
|
|
.addEventListener('dragstart', function(o){ ref.drag_active = true; })
|
|
.addEventListener('dragend', function(o){ ref.subscription_move_folder(o); })
|
|
.addEventListener('initrow', function (row) {
|
|
row.obj.onmouseover = function() { ref.focus_subscription(row.id); };
|
|
row.obj.onmouseout = function() { ref.unfocus_subscription(row.id); };
|
|
})
|
|
.init();
|
|
|
|
$('#mailboxroot')
|
|
.mouseover(function(){ ref.focus_subscription(this.id); })
|
|
.mouseout(function(){ ref.unfocus_subscription(this.id); })
|
|
};
|
|
|
|
this.focus_subscription = function(id)
|
|
{
|
|
var row, folder;
|
|
|
|
if (this.drag_active && this.env.mailbox && (row = document.getElementById(id)))
|
|
if (this.env.subscriptionrows[id] &&
|
|
(folder = this.env.subscriptionrows[id][0]) !== null
|
|
) {
|
|
if (this.check_droptarget(folder) &&
|
|
!this.env.subscriptionrows[this.get_folder_row_id(this.env.mailbox)][2] &&
|
|
folder != this.env.mailbox.replace(this.last_sub_rx, '') &&
|
|
!folder.startsWith(this.env.mailbox + this.env.delimiter)
|
|
) {
|
|
this.env.dstfolder = folder;
|
|
$(row).addClass('droptarget');
|
|
}
|
|
}
|
|
};
|
|
|
|
this.unfocus_subscription = function(id)
|
|
{
|
|
var row = $('#'+id);
|
|
|
|
this.env.dstfolder = null;
|
|
|
|
if (this.env.subscriptionrows[id] && row.length)
|
|
row.removeClass('droptarget');
|
|
else
|
|
$(this.subscription_list.frame).removeClass('droptarget');
|
|
};
|
|
|
|
this.subscription_select = function(list)
|
|
{
|
|
var id, folder;
|
|
|
|
if (list && (id = list.get_single_selection()) &&
|
|
(folder = this.env.subscriptionrows['rcmrow'+id])
|
|
) {
|
|
this.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)
|
|
{
|
|
if (this.env.mailbox && this.env.dstfolder !== null &&
|
|
this.env.dstfolder != this.env.mailbox &&
|
|
this.env.dstfolder != this.env.mailbox.replace(this.last_sub_rx, '')
|
|
) {
|
|
var path = this.env.mailbox.split(this.env.delimiter),
|
|
basename = path.pop(),
|
|
newname = this.env.dstfolder === '' ? basename : this.env.dstfolder + this.env.delimiter + basename;
|
|
|
|
if (newname != this.env.mailbox) {
|
|
this.http_post('rename-folder', {_folder_oldname: this.env.mailbox, _folder_newname: newname}, this.set_busy(true, 'foldermoving'));
|
|
this.subscription_list.draglayer.hide();
|
|
}
|
|
}
|
|
|
|
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()
|
|
{
|
|
this.show_folder('', this.env.mailbox);
|
|
};
|
|
|
|
// delete a specific mailbox with all its messages
|
|
this.delete_folder = function(name)
|
|
{
|
|
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', {_mbox: folder}, lock);
|
|
}
|
|
};
|
|
|
|
// Add folder row to the table and initialize it
|
|
this.add_folder_row = function (name, display_name, is_protected, subscribed, skip_init, class_name)
|
|
{
|
|
if (!this.gui_objects.subscriptionlist)
|
|
return false;
|
|
|
|
var row, n, i, tmp, tmp_name, rowid, folders = [], list = [], slist = [],
|
|
tbody = this.gui_objects.subscriptionlist.tBodies[0],
|
|
refrow = $('tr', tbody).get(1),
|
|
id = 'rcmrow'+((new Date).getTime());
|
|
|
|
if (!refrow) {
|
|
// Refresh page if we don't have a table row to clone
|
|
this.goto_url('folders');
|
|
return false;
|
|
}
|
|
|
|
// clone a table row if there are existing rows
|
|
row = $(refrow).clone(true);
|
|
|
|
// set ID, reset css class
|
|
row.attr({id: id, 'class': class_name});
|
|
|
|
// set folder name
|
|
row.find('td:first').html(display_name);
|
|
|
|
// update subscription checkbox
|
|
$('input[name="_subscribed[]"]', row).val(name)
|
|
.prop({checked: subscribed ? true : false, disabled: is_protected ? true : false});
|
|
|
|
// add to folder/row-ID map
|
|
this.env.subscriptionrows[id] = [name, display_name, false];
|
|
|
|
// sort folders (to find a place where to insert the row)
|
|
// replace delimiter with \0 character to fix sorting
|
|
// issue where 'Abc Abc' would be placed before 'Abc/def'
|
|
var replace_from = RegExp(RegExp.escape(this.env.delimiter), 'g'),
|
|
replace_to = String.fromCharCode(0);
|
|
|
|
$.each(this.env.subscriptionrows, function(k,v) {
|
|
if (v.length < 4) {
|
|
var n = v[0];
|
|
n = n.replace(replace_from, replace_to);
|
|
v.push(n);
|
|
}
|
|
folders.push(v);
|
|
});
|
|
|
|
folders.sort(function(a, b) {
|
|
var len = a.length - 1; n1 = a[len], n2 = b[len];
|
|
return n1 < n2 ? -1 : 1;
|
|
});
|
|
|
|
for (n in folders) {
|
|
// protected folder
|
|
if (folders[n][2]) {
|
|
tmp_name = folders[n][0] + this.env.delimiter;
|
|
// prefix namespace cannot have subfolders (#1488349)
|
|
if (tmp_name == this.env.prefix_ns)
|
|
continue;
|
|
slist.push(folders[n][0]);
|
|
tmp = tmp_name;
|
|
}
|
|
// protected folder's child
|
|
else if (tmp && folders[n][0].startsWith(tmp))
|
|
slist.push(folders[n][0]);
|
|
// other
|
|
else {
|
|
list.push(folders[n][0]);
|
|
tmp = null;
|
|
}
|
|
}
|
|
|
|
// check if subfolder of a protected folder
|
|
for (n=0; n<slist.length; n++) {
|
|
if (name.startsWith(slist[n] + this.env.delimiter))
|
|
rowid = this.get_folder_row_id(slist[n]);
|
|
}
|
|
|
|
// find folder position after sorting
|
|
for (n=0; !rowid && n<list.length; n++) {
|
|
if (n && list[n] == name)
|
|
rowid = this.get_folder_row_id(list[n-1]);
|
|
}
|
|
|
|
// add row to the table
|
|
if (rowid)
|
|
$('#'+rowid).after(row);
|
|
else
|
|
row.appendTo(tbody);
|
|
|
|
// update list widget
|
|
this.subscription_list.clear_selection();
|
|
if (!skip_init)
|
|
this.init_subscription_list();
|
|
|
|
row = row.get(0);
|
|
if (row.scrollIntoView)
|
|
row.scrollIntoView();
|
|
|
|
return row;
|
|
};
|
|
|
|
// replace an existing table row with a new folder line (with subfolders)
|
|
this.replace_folder_row = function(oldfolder, newfolder, display_name, is_protected, class_name)
|
|
{
|
|
if (!this.gui_objects.subscriptionlist) {
|
|
if (this.is_framed)
|
|
return parent.rcmail.replace_folder_row(oldfolder, newfolder, display_name, is_protected, class_name);
|
|
return false;
|
|
}
|
|
|
|
var i, n, len, name, dispname, oldrow, tmprow, row, level,
|
|
tbody = this.gui_objects.subscriptionlist.tBodies[0],
|
|
folders = this.env.subscriptionrows,
|
|
id = this.get_folder_row_id(oldfolder),
|
|
prefix_len = oldfolder.length,
|
|
subscribed = $('input[name="_subscribed[]"]', $('#'+id)).prop('checked'),
|
|
// find subfolders of renamed folder
|
|
list = this.get_subfolders(oldfolder);
|
|
|
|
// no renaming, only update class_name
|
|
if (oldfolder == newfolder) {
|
|
$('#'+id).attr('class', class_name || '');
|
|
this.subscription_list.focus();
|
|
return;
|
|
}
|
|
|
|
// replace an existing table row
|
|
this._remove_folder_row(id);
|
|
row = $(this.add_folder_row(newfolder, display_name, is_protected, subscribed, true, class_name));
|
|
|
|
// detect tree depth change
|
|
if (len = list.length) {
|
|
level = (oldfolder.split(this.env.delimiter)).length - (newfolder.split(this.env.delimiter)).length;
|
|
}
|
|
|
|
// move subfolders to the new branch
|
|
for (n=0; n<len; n++) {
|
|
id = list[n];
|
|
name = this.env.subscriptionrows[id][0];
|
|
dispname = this.env.subscriptionrows[id][1];
|
|
oldrow = $('#'+id);
|
|
tmprow = oldrow.clone(true);
|
|
oldrow.remove();
|
|
row.after(tmprow);
|
|
row = tmprow;
|
|
// update folder index
|
|
name = newfolder + name.slice(prefix_len);
|
|
$('input[name="_subscribed[]"]', row).val(name);
|
|
this.env.subscriptionrows[id][0] = name;
|
|
// update the name if level is changed
|
|
if (level != 0) {
|
|
if (level > 0) {
|
|
for (i=level; i>0; i--)
|
|
dispname = dispname.replace(/^ /, '');
|
|
}
|
|
else {
|
|
for (i=level; i<0; i++)
|
|
dispname = ' ' + dispname;
|
|
}
|
|
row.find('td:first').html(dispname);
|
|
this.env.subscriptionrows[id][1] = dispname;
|
|
}
|
|
}
|
|
|
|
// update list widget
|
|
this.init_subscription_list();
|
|
};
|
|
|
|
// remove the table row of a specific mailbox from the table
|
|
this.remove_folder_row = function(folder, subs)
|
|
{
|
|
var n, len, list = [], id = this.get_folder_row_id(folder);
|
|
|
|
// get subfolders if any
|
|
if (subs)
|
|
list = this.get_subfolders(folder);
|
|
|
|
// remove old row
|
|
this._remove_folder_row(id);
|
|
|
|
// remove subfolders
|
|
for (n=0, len=list.length; n<len; n++)
|
|
this._remove_folder_row(list[n]);
|
|
};
|
|
|
|
this._remove_folder_row = function(id)
|
|
{
|
|
this.subscription_list.remove_row(id.replace(/^rcmrow/, ''));
|
|
$('#'+id).remove();
|
|
delete this.env.subscriptionrows[id];
|
|
}
|
|
|
|
this.get_subfolders = function(folder)
|
|
{
|
|
var name, list = [],
|
|
prefix = folder + this.env.delimiter,
|
|
row = $('#'+this.get_folder_row_id(folder)).get(0);
|
|
|
|
while (row = row.nextSibling) {
|
|
if (row.id) {
|
|
name = this.env.subscriptionrows[row.id][0];
|
|
if (name && name.startsWith(prefix)) {
|
|
list.push(row.id);
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
this.subscribe = function(folder)
|
|
{
|
|
if (folder) {
|
|
var lock = this.display_message(this.get_label('foldersubscribing'), 'loading');
|
|
this.http_post('subscribe', {_mbox: folder}, lock);
|
|
}
|
|
};
|
|
|
|
this.unsubscribe = function(folder)
|
|
{
|
|
if (folder) {
|
|
var lock = this.display_message(this.get_label('folderunsubscribing'), 'loading');
|
|
this.http_post('unsubscribe', {_mbox: folder}, lock);
|
|
}
|
|
};
|
|
|
|
// helper method to find a specific mailbox row ID
|
|
this.get_folder_row_id = function(folder)
|
|
{
|
|
var id, folders = this.env.subscriptionrows;
|
|
for (id in folders)
|
|
if (folders[id] && folders[id][0] == folder)
|
|
break;
|
|
|
|
return id;
|
|
};
|
|
|
|
// when user select a folder in manager
|
|
this.show_folder = function(folder, path, force)
|
|
{
|
|
var win, target = window,
|
|
url = '&_action=edit-folder&_mbox='+urlencode(folder);
|
|
|
|
if (path)
|
|
url += '&_path='+urlencode(path);
|
|
|
|
if (win = this.get_frame_window(this.env.contentframe)) {
|
|
target = win;
|
|
url += '&_framed=1';
|
|
}
|
|
|
|
if (String(target.location.href).indexOf(url) >= 0 && !force)
|
|
this.show_contentframe(true);
|
|
else
|
|
this.location_href(this.env.comm_path+url, target, true);
|
|
};
|
|
|
|
// 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)).prop('disabled', true);
|
|
};
|
|
|
|
this.folder_size = function(folder)
|
|
{
|
|
var lock = this.set_busy(true, 'loading');
|
|
this.http_post('folder-size', {_mbox: folder}, lock);
|
|
};
|
|
|
|
this.folder_size_update = function(size)
|
|
{
|
|
$('#folder-size').replaceWith(size);
|
|
};
|
|
|
|
|
|
/*********************************************************/
|
|
/********* GUI functionality *********/
|
|
/*********************************************************/
|
|
|
|
var init_button = function(cmd, prop)
|
|
{
|
|
var elm = document.getElementById(prop.id);
|
|
if (!elm)
|
|
return;
|
|
|
|
var preload = false;
|
|
if (prop.type == 'image') {
|
|
elm = elm.parentNode;
|
|
preload = true;
|
|
}
|
|
|
|
elm._command = cmd;
|
|
elm._id = prop.id;
|
|
if (prop.sel) {
|
|
elm.onmousedown = function(e){ return rcmail.button_sel(this._command, this._id); };
|
|
elm.onmouseup = function(e){ return rcmail.button_out(this._command, this._id); };
|
|
if (preload)
|
|
new Image().src = prop.sel;
|
|
}
|
|
if (prop.over) {
|
|
elm.onmouseover = function(e){ return rcmail.button_over(this._command, this._id); };
|
|
elm.onmouseout = function(e){ return rcmail.button_out(this._command, this._id); };
|
|
if (preload)
|
|
new Image().src = prop.over;
|
|
}
|
|
};
|
|
|
|
// set event handlers on registered buttons
|
|
this.init_buttons = function()
|
|
{
|
|
for (var cmd in this.buttons) {
|
|
if (typeof cmd !== 'string')
|
|
continue;
|
|
|
|
for (var i=0; i<this.buttons[cmd].length; i++) {
|
|
init_button(cmd, this.buttons[cmd][i]);
|
|
}
|
|
}
|
|
|
|
// set active task button
|
|
this.set_button(this.task, 'sel');
|
|
};
|
|
|
|
// set button to a specific state
|
|
this.set_button = function(command, state)
|
|
{
|
|
var n, button, obj, a_buttons = this.buttons[command],
|
|
len = a_buttons ? a_buttons.length : 0;
|
|
|
|
for (n=0; n<len; n++) {
|
|
button = a_buttons[n];
|
|
obj = document.getElementById(button.id);
|
|
|
|
if (!obj)
|
|
continue;
|
|
|
|
// get default/passive setting of the button
|
|
if (button.type == 'image' && !button.status) {
|
|
button.pas = obj._original_src ? obj._original_src : obj.src;
|
|
// respect PNG fix on IE browsers
|
|
if (obj.runtimeStyle && obj.runtimeStyle.filter && obj.runtimeStyle.filter.match(/src=['"]([^'"]+)['"]/))
|
|
button.pas = RegExp.$1;
|
|
}
|
|
else if (!button.status)
|
|
button.pas = String(obj.className);
|
|
|
|
// set image according to button state
|
|
if (button.type == 'image' && button[state]) {
|
|
button.status = state;
|
|
obj.src = button[state];
|
|
}
|
|
// set class name according to button state
|
|
else if (button[state] !== undefined) {
|
|
button.status = state;
|
|
obj.className = button[state];
|
|
}
|
|
// disable/enable input buttons
|
|
if (button.type == 'input') {
|
|
button.status = state;
|
|
obj.disabled = state == 'pas';
|
|
}
|
|
else if (button.type == 'uibutton') {
|
|
$(obj).button('option', 'disabled', state == 'pas');
|
|
}
|
|
}
|
|
};
|
|
|
|
// display a specific alttext
|
|
this.set_alttext = function(command, label)
|
|
{
|
|
var n, button, obj, link, a_buttons = this.buttons[command],
|
|
len = a_buttons ? a_buttons.length : 0;
|
|
|
|
for (n=0; n<len; n++) {
|
|
button = a_buttons[n];
|
|
obj = document.getElementById(button.id);
|
|
|
|
if (button.type == 'image' && obj) {
|
|
obj.setAttribute('alt', this.get_label(label));
|
|
if ((link = obj.parentNode) && link.tagName.toLowerCase() == 'a')
|
|
link.setAttribute('title', this.get_label(label));
|
|
}
|
|
else if (obj)
|
|
obj.setAttribute('title', this.get_label(label));
|
|
}
|
|
};
|
|
|
|
// mouse over button
|
|
this.button_over = function(command, id)
|
|
{
|
|
this.button_event(command, id, 'over');
|
|
};
|
|
|
|
// mouse down on button
|
|
this.button_sel = function(command, id)
|
|
{
|
|
this.button_event(command, id, 'sel');
|
|
};
|
|
|
|
// mouse out of button
|
|
this.button_out = function(command, id)
|
|
{
|
|
this.button_event(command, id, 'act');
|
|
};
|
|
|
|
// event of button
|
|
this.button_event = function(command, id, event)
|
|
{
|
|
var n, button, obj, a_buttons = this.buttons[command],
|
|
len = a_buttons ? a_buttons.length : 0;
|
|
|
|
for (n=0; n<len; n++) {
|
|
button = a_buttons[n];
|
|
if (button.id == id && button.status == 'act') {
|
|
if (button[event] && (obj = document.getElementById(button.id))) {
|
|
obj[button.type == 'image' ? 'src' : 'className'] = button[event];
|
|
}
|
|
|
|
if (event == 'sel') {
|
|
this.buttons_sel[id] = command;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// write to the document/window title
|
|
this.set_pagetitle = function(title)
|
|
{
|
|
if (title && document.title)
|
|
document.title = title;
|
|
};
|
|
|
|
// display a system message, list of types in common.css (below #message definition)
|
|
this.display_message = function(msg, type, timeout)
|
|
{
|
|
// pass command to parent window
|
|
if (this.is_framed())
|
|
return parent.rcmail.display_message(msg, type, timeout);
|
|
|
|
if (!this.gui_objects.message) {
|
|
// save message in order to display after page loaded
|
|
if (type != 'loading')
|
|
this.pending_message = [msg, type, timeout];
|
|
return 1;
|
|
}
|
|
|
|
type = type ? type : 'notice';
|
|
|
|
var key = this.html_identifier(msg),
|
|
date = new Date(),
|
|
id = type + date.getTime();
|
|
|
|
if (!timeout)
|
|
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 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);
|
|
setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout);
|
|
return id;
|
|
}
|
|
|
|
// 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); });
|
|
}
|
|
|
|
this.triggerEvent('message', { message:msg, type:type, timeout:timeout, object:obj });
|
|
|
|
if (timeout > 0)
|
|
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.is_framed())
|
|
return parent.rcmail.hide_message(obj, fade);
|
|
|
|
if (!this.gui_objects.message)
|
|
return;
|
|
|
|
var k, n, i, o, m = this.messages;
|
|
|
|
// Hide message by object, don't use for 'loading'!
|
|
if (typeof obj === 'object') {
|
|
o = $(obj);
|
|
k = o.data('key');
|
|
this.hide_message_object(o, fade);
|
|
if (m[k])
|
|
delete m[k];
|
|
}
|
|
// Hide message by id
|
|
else {
|
|
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) {
|
|
this.hide_message_object(m[k].obj, fade);
|
|
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 {
|
|
o = m[k].labels[i].msg;
|
|
m[k].obj.html(o);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// 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()
|
|
{
|
|
// pass command to parent window
|
|
if (this.is_framed())
|
|
return parent.rcmail.clear_messages();
|
|
|
|
var k, n, m = this.messages;
|
|
|
|
for (k in m)
|
|
for (n in m[k].elements)
|
|
if (m[k].obj)
|
|
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, buttons, options)
|
|
{
|
|
// forward call to parent window
|
|
if (this.is_framed()) {
|
|
return parent.rcmail.show_popup_dialog(html, title, buttons, options);
|
|
}
|
|
|
|
var popup = $('<div class="popup">')
|
|
.html(html)
|
|
.dialog($.extend({
|
|
title: title,
|
|
buttons: buttons,
|
|
modal: true,
|
|
resizable: true,
|
|
width: 500,
|
|
close: function(event, ui) { $(this).remove() }
|
|
}, options || {}));
|
|
|
|
// 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 + 36)
|
|
});
|
|
|
|
return popup;
|
|
};
|
|
|
|
// enable/disable buttons for page shifting
|
|
this.set_page_buttons = function()
|
|
{
|
|
this.enable_command('nextpage', 'lastpage', this.env.pagecount > this.env.current_page);
|
|
this.enable_command('previouspage', 'firstpage', this.env.current_page > 1);
|
|
};
|
|
|
|
// mark a mailbox as selected and set environment variable
|
|
this.select_folder = function(name, prefix, encode)
|
|
{
|
|
if (this.treelist) {
|
|
this.treelist.select(name);
|
|
}
|
|
else if (this.gui_objects.folderlist) {
|
|
$('li.selected', this.gui_objects.folderlist)
|
|
.removeClass('selected').addClass('unfocused');
|
|
$(this.get_folder_li(name, prefix, encode))
|
|
.removeClass('unfocused').addClass('selected');
|
|
|
|
// trigger event hook
|
|
this.triggerEvent('selectfolder', { folder:name, prefix:prefix });
|
|
}
|
|
};
|
|
|
|
// adds a class to selected folder
|
|
this.mark_folder = function(name, class_name, prefix, encode)
|
|
{
|
|
$(this.get_folder_li(name, prefix, encode)).addClass(class_name);
|
|
this.triggerEvent('markfolder', {folder: name, mark: class_name, status: true});
|
|
};
|
|
|
|
// adds a class to selected folder
|
|
this.unmark_folder = function(name, class_name, prefix, encode)
|
|
{
|
|
$(this.get_folder_li(name, prefix, encode)).removeClass(class_name);
|
|
this.triggerEvent('markfolder', {folder: name, mark: class_name, status: false});
|
|
};
|
|
|
|
// helper method to find a folder list item
|
|
this.get_folder_li = function(name, prefix, encode)
|
|
{
|
|
if (!prefix)
|
|
prefix = 'rcmli';
|
|
|
|
if (this.gui_objects.folderlist) {
|
|
name = this.html_identifier(name, encode);
|
|
return document.getElementById(prefix+name);
|
|
}
|
|
};
|
|
|
|
// for reordering column array (Konqueror workaround)
|
|
// and for setting some message list global variables
|
|
this.set_message_coltypes = function(listcols, repl, smart_col)
|
|
{
|
|
var list = this.message_list,
|
|
thead = list ? list.thead : null,
|
|
repl, cell, col, n, len, tr;
|
|
|
|
this.env.listcols = listcols;
|
|
|
|
// replace old column headers
|
|
if (thead) {
|
|
if (repl) {
|
|
thead.innerHTML = '';
|
|
tr = document.createElement('tr');
|
|
|
|
for (c=0, len=repl.length; c < len; c++) {
|
|
cell = document.createElement('td');
|
|
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);
|
|
}
|
|
thead.appendChild(tr);
|
|
}
|
|
|
|
for (n=0, len=this.env.listcols.length; n<len; n++) {
|
|
col = this.env.listcols[n];
|
|
if ((cell = thead.rows[0].cells[n]) && (col == 'from' || col == 'to' || col == 'fromto')) {
|
|
$(cell).attr('rel', col).find('span,a').text(this.get_label(col == 'fromto' ? smart_col : col));
|
|
}
|
|
}
|
|
}
|
|
|
|
this.env.subject_col = null;
|
|
this.env.flagged_col = null;
|
|
this.env.status_col = null;
|
|
|
|
if (this.env.coltypes.folder)
|
|
this.env.coltypes.folder.hidden = !(this.env.search_request || this.env.search_id) || this.env.search_scope == 'base';
|
|
|
|
if ((n = $.inArray('subject', this.env.listcols)) >= 0) {
|
|
this.env.subject_col = n;
|
|
if (list)
|
|
list.subject_col = n;
|
|
}
|
|
if ((n = $.inArray('flag', this.env.listcols)) >= 0)
|
|
this.env.flagged_col = n;
|
|
if ((n = $.inArray('status', this.env.listcols)) >= 0)
|
|
this.env.status_col = n;
|
|
|
|
if (list) {
|
|
list.hide_column('folder', (this.env.coltypes.folder && this.env.coltypes.folder.hidden) || $.inArray('folder', this.env.listcols) < 0);
|
|
list.init_header();
|
|
}
|
|
};
|
|
|
|
// replace content of row count display
|
|
this.set_rowcount = function(text, mbox)
|
|
{
|
|
// #1487752
|
|
if (mbox && mbox != this.env.mailbox)
|
|
return false;
|
|
|
|
$(this.gui_objects.countdisplay).html(text);
|
|
|
|
// update page navigation buttons
|
|
this.set_page_buttons();
|
|
};
|
|
|
|
// replace content of mailboxname display
|
|
this.set_mailboxname = function(content)
|
|
{
|
|
if (this.gui_objects.mailboxname && content)
|
|
this.gui_objects.mailboxname.innerHTML = content;
|
|
};
|
|
|
|
// replace content of quota display
|
|
this.set_quota = function(content)
|
|
{
|
|
if (this.gui_objects.quotadisplay && content && content.type == 'text')
|
|
$(this.gui_objects.quotadisplay).html(content.percent+'%').attr('title', content.title);
|
|
|
|
this.triggerEvent('setquota', content);
|
|
this.env.quota_content = content;
|
|
};
|
|
|
|
// update trash folder state
|
|
this.set_trash_count = function(count)
|
|
{
|
|
this[(count ? 'un' : '') + 'mark_folder'](this.env.trash_mailbox, 'empty', '', true);
|
|
};
|
|
|
|
// update the mailboxlist
|
|
this.set_unread_count = function(mbox, count, set_title, mark)
|
|
{
|
|
if (!this.gui_objects.mailboxlist)
|
|
return false;
|
|
|
|
this.env.unread_counts[mbox] = count;
|
|
this.set_unread_count_display(mbox, set_title);
|
|
|
|
if (mark)
|
|
this.mark_folder(mbox, mark, '', true);
|
|
else if (!count)
|
|
this.unmark_folder(mbox, 'recent', '', true);
|
|
};
|
|
|
|
// update the mailbox count display
|
|
this.set_unread_count_display = function(mbox, set_title)
|
|
{
|
|
var reg, link, text_obj, item, mycount, childcount, div;
|
|
|
|
if (item = this.get_folder_li(mbox, '', true)) {
|
|
mycount = this.env.unread_counts[mbox] ? this.env.unread_counts[mbox] : 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;
|
|
if ((div = item.getElementsByTagName('div')[0]) &&
|
|
div.className.match(/collapsed/)) {
|
|
// add children's counters
|
|
for (var k in this.env.unread_counts)
|
|
if (k.startsWith(mbox + this.env.delimiter))
|
|
childcount += this.env.unread_counts[k];
|
|
}
|
|
|
|
if (mycount && text_obj.length)
|
|
text_obj.html(this.env.unreadwrap.replace(/%[sd]/, 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) + ']+$');
|
|
if (mbox.match(reg))
|
|
this.set_unread_count_display(mbox.replace(reg, ''), false);
|
|
|
|
// set the right classes
|
|
if ((mycount+childcount)>0)
|
|
$(item).addClass('unread');
|
|
else
|
|
$(item).removeClass('unread');
|
|
}
|
|
|
|
// set unread count to window title
|
|
reg = /^\([0-9]+\)\s+/i;
|
|
if (set_title && document.title) {
|
|
var new_title = '',
|
|
doc_title = String(document.title);
|
|
|
|
if (mycount && doc_title.match(reg))
|
|
new_title = doc_title.replace(reg, '('+mycount+') ');
|
|
else if (mycount)
|
|
new_title = '('+mycount+') '+doc_title;
|
|
else
|
|
new_title = doc_title.replace(reg, '');
|
|
|
|
this.set_pagetitle(new_title);
|
|
}
|
|
};
|
|
|
|
// display fetched raw headers
|
|
this.set_headers = function(content)
|
|
{
|
|
if (this.gui_objects.all_headers_row && this.gui_objects.all_headers_box && content)
|
|
$(this.gui_objects.all_headers_box).html(content).show();
|
|
};
|
|
|
|
// display all-headers row and fetch raw message headers
|
|
this.show_headers = function(props, elem)
|
|
{
|
|
if (!this.gui_objects.all_headers_row || !this.gui_objects.all_headers_box || !this.env.uid)
|
|
return;
|
|
|
|
$(elem).removeClass('show-headers').addClass('hide-headers');
|
|
$(this.gui_objects.all_headers_row).show();
|
|
elem.onclick = function() { rcmail.command('hide-headers', '', elem); };
|
|
|
|
// fetch headers only once
|
|
if (!this.gui_objects.all_headers_box.innerHTML) {
|
|
this.http_post('headers', {_uid: this.env.uid, _mbox: this.env.mailbox},
|
|
this.display_message(this.get_label('loading'), 'loading')
|
|
);
|
|
}
|
|
};
|
|
|
|
// hide all-headers row
|
|
this.hide_headers = function(props, elem)
|
|
{
|
|
if (!this.gui_objects.all_headers_row || !this.gui_objects.all_headers_box)
|
|
return;
|
|
|
|
$(elem).removeClass('hide-headers').addClass('show-headers');
|
|
$(this.gui_objects.all_headers_row).hide();
|
|
elem.onclick = function() { rcmail.command('show-headers', '', elem); };
|
|
};
|
|
|
|
// create folder selector popup, position and display it
|
|
this.folder_selector = function(obj, callback)
|
|
{
|
|
var container = this.folder_selector_element;
|
|
|
|
if (!container) {
|
|
var rows = [],
|
|
delim = this.env.delimiter,
|
|
ul = $('<ul class="toolbarmenu iconized">'),
|
|
li = document.createElement('li'),
|
|
link = document.createElement('a'),
|
|
span = document.createElement('span');
|
|
|
|
container = $('<div id="folder-selector" class="popupmenu"></div>');
|
|
link.href = '#';
|
|
link.className = 'icon';
|
|
|
|
// loop over sorted folders list
|
|
$.each(this.env.mailboxes_list, function() {
|
|
var tmp, n = 0, s = 0,
|
|
folder = ref.env.mailboxes[this],
|
|
id = folder.id,
|
|
a = link.cloneNode(false), row = li.cloneNode(false);
|
|
|
|
if (folder.virtual)
|
|
a.className += ' virtual';
|
|
else {
|
|
a.className += ' active';
|
|
a.onclick = function() { container.hide().data('callback')(folder.id); };
|
|
}
|
|
|
|
if (folder['class'])
|
|
a.className += ' ' + folder['class'];
|
|
|
|
// calculate/set indentation level
|
|
while ((s = id.indexOf(delim, s)) >= 0) {
|
|
n++; s++;
|
|
}
|
|
a.style.paddingLeft = n ? (n * 16) + 'px' : 0;
|
|
|
|
// add folder name element
|
|
tmp = span.cloneNode(false);
|
|
$(tmp).text(folder.name);
|
|
a.appendChild(tmp);
|
|
|
|
row.appendChild(a);
|
|
rows.push(row);
|
|
});
|
|
|
|
ul.append(rows).appendTo(container);
|
|
|
|
// temporarily show element to calculate its size
|
|
container.css({left: '-1000px', top: '-1000px'})
|
|
.appendTo($('body')).show();
|
|
|
|
// set max-height if the list is long
|
|
if (rows.length > 10)
|
|
container.css('max-height', $('li', container)[0].offsetHeight * 10 + 9)
|
|
|
|
// hide selector on click out of selector element
|
|
var fn = function(e) { if (e.target != container.get(0)) container.hide(); };
|
|
$(document.body).on('mouseup', fn);
|
|
$('iframe').contents().on('mouseup', fn)
|
|
.load(function(e) { try { $(this).contents().on('mouseup', fn); } catch(e) {}; });
|
|
|
|
this.folder_selector_element = container;
|
|
}
|
|
|
|
// position menu on the screen
|
|
this.element_position(container, obj);
|
|
|
|
container.show().data('callback', callback);
|
|
};
|
|
|
|
// position a menu element on the screen in relation to other object
|
|
this.element_position = function(element, obj)
|
|
{
|
|
var obj = $(obj), win = $(window),
|
|
width = obj.outerWidth(),
|
|
height = obj.outerHeight(),
|
|
menu_pos = obj.data('menu-pos'),
|
|
win_height = win.height(),
|
|
elem_height = $(element).height(),
|
|
elem_width = $(element).width(),
|
|
pos = obj.offset(),
|
|
top = pos.top,
|
|
left = pos.left + width;
|
|
|
|
if (menu_pos == 'bottom') {
|
|
top += height;
|
|
left -= width;
|
|
}
|
|
else
|
|
left -= 5;
|
|
|
|
if (top + elem_height > win_height) {
|
|
top -= elem_height - height;
|
|
if (top < 0)
|
|
top = Math.max(0, (win_height - elem_height) / 2);
|
|
}
|
|
|
|
if (left + elem_width > win.width())
|
|
left -= elem_width + width;
|
|
|
|
element.css({left: left + 'px', top: top + 'px'});
|
|
};
|
|
|
|
|
|
/********************************************************/
|
|
/********* html to text conversion functions *********/
|
|
/********************************************************/
|
|
|
|
this.html2plain = function(html, func)
|
|
{
|
|
return this.format_converter(html, 'html', func);
|
|
};
|
|
|
|
this.plain2html = function(plain, func)
|
|
{
|
|
return this.format_converter(plain, 'plain', func);
|
|
};
|
|
|
|
this.format_converter = function(text, format, func)
|
|
{
|
|
// warn the user (if converted content is not empty)
|
|
if (!text
|
|
|| (format == 'html' && !(text.replace(/<[^>]+>| |\xC2\xA0|\s/g, '')).length)
|
|
|| (format != 'html' && !(text.replace(/\xC2\xA0|\s/g, '')).length)
|
|
) {
|
|
// without setTimeout() here, textarea is filled with initial (onload) content
|
|
setTimeout(function() { if (func) func(''); }, 50);
|
|
return true;
|
|
}
|
|
|
|
var confirmed = this.env.editor_warned || confirm(this.get_label('editorwarning'));
|
|
|
|
this.env.editor_warned = true;
|
|
|
|
if (!confirmed)
|
|
return false;
|
|
|
|
var url = '?_task=utils&_action=' + (format == 'html' ? 'html2text' : 'text2html'),
|
|
lock = this.set_busy(true, 'converting');
|
|
|
|
this.log('HTTP POST: ' + url);
|
|
|
|
$.ajax({ type: 'POST', url: url, data: text, contentType: 'application/octet-stream',
|
|
error: function(o, status, err) { ref.http_error(o, status, err, lock); },
|
|
success: function(data) {
|
|
ref.set_busy(false, null, lock);
|
|
if (func) func(data);
|
|
}
|
|
});
|
|
|
|
return true;
|
|
};
|
|
|
|
|
|
/********************************************************/
|
|
/********* 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 if (this.env.action)
|
|
query._action = this.env.action;
|
|
|
|
var base = this.env.comm_path, k, param = {};
|
|
|
|
// overwrite task name
|
|
if (action && action.match(/([a-z0-9_-]+)\/([a-z0-9-_.]+)/)) {
|
|
query._action = RegExp.$2;
|
|
base = base.replace(/\_task=[a-z0-9_-]+/, '_task='+RegExp.$1);
|
|
}
|
|
|
|
// remove undefined values
|
|
for (k in query) {
|
|
if (query[k] !== undefined && query[k] !== null)
|
|
param[k] = query[k];
|
|
}
|
|
|
|
return base + (base.indexOf('?') > -1 ? '&' : '?') + $.param(param) + querystring;
|
|
};
|
|
|
|
this.redirect = function(url, lock)
|
|
{
|
|
if (lock || lock === null)
|
|
this.set_busy(true);
|
|
|
|
if (this.is_framed()) {
|
|
parent.rcmail.redirect(url, lock);
|
|
}
|
|
else {
|
|
if (this.env.extwin) {
|
|
if (typeof url == 'string')
|
|
url += (url.indexOf('?') < 0 ? '?' : '&') + '_extwin=1';
|
|
else
|
|
url._extwin = 1;
|
|
}
|
|
this.location_href(url, window);
|
|
}
|
|
};
|
|
|
|
this.goto_url = function(action, query, lock)
|
|
{
|
|
this.redirect(this.url(action, query), lock);
|
|
};
|
|
|
|
this.location_href = function(url, target, frame)
|
|
{
|
|
if (frame)
|
|
this.lock_frame();
|
|
|
|
if (typeof url == 'object')
|
|
url = this.env.comm_path + '&' + $.param(url);
|
|
|
|
// 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;
|
|
|
|
// reset keep-alive interval
|
|
this.start_keepalive();
|
|
};
|
|
|
|
// send a http request to the server
|
|
this.http_request = function(action, query, lock)
|
|
{
|
|
var url = this.url(action, query);
|
|
|
|
// trigger plugin hook
|
|
var result = this.triggerEvent('request'+action, query);
|
|
|
|
if (result !== undefined) {
|
|
// abort if one the handlers returned false
|
|
if (result === false)
|
|
return false;
|
|
else
|
|
url = this.url(action, result);
|
|
}
|
|
|
|
url += '&_remote=1';
|
|
|
|
// send request
|
|
this.log('HTTP GET: ' + url);
|
|
|
|
// reset keep-alive interval
|
|
this.start_keepalive();
|
|
|
|
return $.ajax({
|
|
type: 'GET', url: url, data: { _unlock:(lock?lock:0) }, dataType: 'json',
|
|
success: function(data){ ref.http_response(data); },
|
|
error: function(o, status, err) { ref.http_error(o, status, err, lock, action); }
|
|
});
|
|
};
|
|
|
|
// send a http POST request to the server
|
|
this.http_post = function(action, postdata, lock)
|
|
{
|
|
var url = this.url(action);
|
|
|
|
if (postdata && typeof postdata === 'object') {
|
|
postdata._remote = 1;
|
|
postdata._unlock = (lock ? lock : 0);
|
|
}
|
|
else
|
|
postdata += (postdata ? '&' : '') + '_remote=1' + (lock ? '&_unlock='+lock : '');
|
|
|
|
// trigger plugin hook
|
|
var result = this.triggerEvent('request'+action, postdata);
|
|
if (result !== undefined) {
|
|
// abort if one of the handlers returned false
|
|
if (result === false)
|
|
return false;
|
|
else
|
|
postdata = result;
|
|
}
|
|
|
|
// send request
|
|
this.log('HTTP POST: ' + url);
|
|
|
|
// reset keep-alive interval
|
|
this.start_keepalive();
|
|
|
|
return $.ajax({
|
|
type: 'POST', url: url, data: postdata, dataType: 'json',
|
|
success: function(data){ ref.http_response(data); },
|
|
error: function(o, status, err) { ref.http_error(o, status, err, lock, action); }
|
|
});
|
|
};
|
|
|
|
// aborts ajax request
|
|
this.abort_request = function(r)
|
|
{
|
|
if (r.request)
|
|
r.request.abort();
|
|
if (r.lock)
|
|
this.set_busy(false, null, r.lock);
|
|
};
|
|
|
|
// handle HTTP response
|
|
this.http_response = function(response)
|
|
{
|
|
if (!response)
|
|
return;
|
|
|
|
if (response.unlock)
|
|
this.set_busy(false);
|
|
|
|
this.triggerEvent('responsebefore', {response: response});
|
|
this.triggerEvent('responsebefore'+response.action, {response: response});
|
|
|
|
// set env vars
|
|
if (response.env)
|
|
this.set_env(response.env);
|
|
|
|
// we have labels to add
|
|
if (typeof response.texts === 'object') {
|
|
for (var name in response.texts)
|
|
if (typeof response.texts[name] === 'string')
|
|
this.add_label(name, response.texts[name]);
|
|
}
|
|
|
|
// if we get javascript code from server -> execute it
|
|
if (response.exec) {
|
|
this.log(response.exec);
|
|
eval(response.exec);
|
|
}
|
|
|
|
// execute callback functions of plugins
|
|
if (response.callbacks && response.callbacks.length) {
|
|
for (var i=0; i < response.callbacks.length; i++)
|
|
this.triggerEvent(response.callbacks[i][0], response.callbacks[i][1]);
|
|
}
|
|
|
|
// process the response data according to the sent action
|
|
switch (response.action) {
|
|
case 'delete':
|
|
if (this.task == 'addressbook') {
|
|
var sid, uid = this.contact_list.get_selection(), writable = false;
|
|
|
|
if (uid && this.contact_list.rows[uid]) {
|
|
// search results, get source ID from record ID
|
|
if (this.env.source == '') {
|
|
sid = String(uid).replace(/^[^-]+-/, '');
|
|
writable = sid && this.env.address_sources[sid] && !this.env.address_sources[sid].readonly;
|
|
}
|
|
else {
|
|
writable = !this.env.address_sources[this.env.source].readonly;
|
|
}
|
|
}
|
|
this.enable_command('compose', (uid && this.contact_list.rows[uid]));
|
|
this.enable_command('delete', 'edit', writable);
|
|
this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
|
|
this.enable_command('export-selected', false);
|
|
}
|
|
|
|
case 'move':
|
|
if (this.env.action == 'show') {
|
|
// re-enable commands on move/delete error
|
|
this.enable_command(this.env.message_commands, true);
|
|
if (!this.env.list_post)
|
|
this.enable_command('reply-list', false);
|
|
}
|
|
else if (this.task == 'addressbook') {
|
|
this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
|
|
}
|
|
|
|
case 'purge':
|
|
case 'expunge':
|
|
if (this.task == 'mail') {
|
|
if (!this.env.exists) {
|
|
// clear preview pane content
|
|
if (this.env.contentframe)
|
|
this.show_contentframe(false);
|
|
// disable commands useless when mailbox is empty
|
|
this.enable_command(this.env.message_commands, 'purge', 'expunge',
|
|
'select-all', 'select-none', 'expand-all', 'expand-unread', 'collapse-all', false);
|
|
}
|
|
if (this.message_list)
|
|
this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount });
|
|
}
|
|
break;
|
|
|
|
case 'refresh':
|
|
case 'check-recent':
|
|
// update message flags
|
|
$.each(this.env.recent_flags || {}, function(uid, flags) {
|
|
ref.set_message(uid, 'deleted', flags.deleted);
|
|
ref.set_message(uid, 'replied', flags.answered);
|
|
ref.set_message(uid, 'unread', !flags.seen);
|
|
ref.set_message(uid, 'forwarded', flags.forwarded);
|
|
ref.set_message(uid, 'flagged', flags.flagged);
|
|
});
|
|
delete this.env.recent_flags;
|
|
|
|
case 'getunread':
|
|
case 'search':
|
|
this.env.qsearch = null;
|
|
case 'list':
|
|
if (this.task == 'mail') {
|
|
var is_multifolder = this.is_multifolder_listing();
|
|
this.enable_command('show', 'select-all', 'select-none', this.env.messagecount > 0);
|
|
this.enable_command('expunge', this.env.exists && !is_multifolder);
|
|
this.enable_command('purge', this.purge_mailbox_test() && !is_multifolder);
|
|
this.enable_command('import-messages', !is_multifolder);
|
|
this.enable_command('expand-all', 'expand-unread', 'collapse-all', this.env.threading && this.env.messagecount && !is_multifolder);
|
|
this.enable_command('set-listmode', this.env.threads && !is_multifolder);
|
|
|
|
if ((response.action == 'list' || response.action == 'search') && this.message_list) {
|
|
this.msglist_select(this.message_list);
|
|
this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount });
|
|
}
|
|
}
|
|
else if (this.task == 'addressbook') {
|
|
this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
|
|
|
|
if (response.action == 'list' || response.action == 'search') {
|
|
this.enable_command('search-create', this.env.source == '');
|
|
this.enable_command('search-delete', this.env.search_id);
|
|
this.update_group_commands();
|
|
this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (response.unlock)
|
|
this.hide_message(response.unlock);
|
|
|
|
this.triggerEvent('responseafter', {response: response});
|
|
this.triggerEvent('responseafter'+response.action, {response: response});
|
|
|
|
// reset keep-alive interval
|
|
this.start_keepalive();
|
|
};
|
|
|
|
// handle HTTP request errors
|
|
this.http_error = function(request, status, err, lock, action)
|
|
{
|
|
var errmsg = request.statusText;
|
|
|
|
this.set_busy(false, null, lock);
|
|
request.abort();
|
|
|
|
// don't display error message on page unload (#1488547)
|
|
if (this.unload)
|
|
return;
|
|
|
|
if (request.status && errmsg)
|
|
this.display_message(this.get_label('servererror') + ' (' + errmsg + ')', 'error');
|
|
else if (status == 'timeout')
|
|
this.display_message(this.get_label('requesttimedout'), 'error');
|
|
else if (request.status == 0 && status != 'abort')
|
|
this.display_message(this.get_label('connerror'), 'error');
|
|
|
|
// redirect to url specified in location header if not empty
|
|
var location_url = request.getResponseHeader("Location");
|
|
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);
|
|
};
|
|
|
|
// handler for session errors detected on the server
|
|
this.session_error = function(redirect_url)
|
|
{
|
|
this.env.server_error = 401;
|
|
|
|
// save message in local storage and do not redirect
|
|
if (this.env.action == 'compose') {
|
|
this.save_compose_form_local();
|
|
}
|
|
else if (redirect_url) {
|
|
window.setTimeout(function(){ ref.redirect(redirect_url, true); }, 2000);
|
|
}
|
|
};
|
|
|
|
// 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);
|
|
};
|
|
|
|
/**
|
|
Send multi-threaded parallel HTTP requests to the server for a list if items.
|
|
The string '%' in either a GET query or POST parameters will be replaced with the respective item value.
|
|
This is the argument object expected: {
|
|
items: ['foo','bar','gna'], // list of items to send requests for
|
|
action: 'task/some-action', // Roudncube action to call
|
|
query: { q:'%s' }, // GET query parameters
|
|
postdata: { source:'%s' }, // POST data (sends a POST request if present)
|
|
threads: 3, // max. number of concurrent requests
|
|
onresponse: function(data){ }, // Callback function called for every response received from server
|
|
whendone: function(alldata){ } // Callback function called when all requests have been sent
|
|
}
|
|
*/
|
|
this.multi_thread_http_request = function(prop)
|
|
{
|
|
var i, item, reqid = new Date().getTime(),
|
|
threads = prop.threads || 1;
|
|
|
|
prop.reqid = reqid;
|
|
prop.running = 0;
|
|
prop.requests = [];
|
|
prop.result = [];
|
|
prop._items = $.extend([], prop.items); // copy items
|
|
|
|
if (!prop.lock)
|
|
prop.lock = this.display_message(this.get_label('loading'), 'loading');
|
|
|
|
// add the request arguments to the jobs pool
|
|
this.http_request_jobs[reqid] = prop;
|
|
|
|
// start n threads
|
|
for (i=0; i < threads; i++) {
|
|
item = prop._items.shift();
|
|
if (item === undefined)
|
|
break;
|
|
|
|
prop.running++;
|
|
prop.requests.push(this.multi_thread_send_request(prop, item));
|
|
}
|
|
|
|
return reqid;
|
|
};
|
|
|
|
// helper method to send an HTTP request with the given iterator value
|
|
this.multi_thread_send_request = function(prop, item)
|
|
{
|
|
var k, postdata, query;
|
|
|
|
// replace %s in post data
|
|
if (prop.postdata) {
|
|
postdata = {};
|
|
for (k in prop.postdata) {
|
|
postdata[k] = String(prop.postdata[k]).replace('%s', item);
|
|
}
|
|
postdata._reqid = prop.reqid;
|
|
}
|
|
// replace %s in query
|
|
else if (typeof prop.query == 'string') {
|
|
query = prop.query.replace('%s', item);
|
|
query += '&_reqid=' + prop.reqid;
|
|
}
|
|
else if (typeof prop.query == 'object' && prop.query) {
|
|
query = {};
|
|
for (k in prop.query) {
|
|
query[k] = String(prop.query[k]).replace('%s', item);
|
|
}
|
|
query._reqid = prop.reqid;
|
|
}
|
|
|
|
// send HTTP GET or POST request
|
|
return postdata ? this.http_post(prop.action, postdata) : this.http_request(prop.action, query);
|
|
};
|
|
|
|
// callback function for multi-threaded http responses
|
|
this.multi_thread_http_response = function(data, reqid)
|
|
{
|
|
var prop = this.http_request_jobs[reqid];
|
|
if (!prop || prop.running <= 0 || prop.cancelled)
|
|
return;
|
|
|
|
prop.running--;
|
|
|
|
// trigger response callback
|
|
if (prop.onresponse && typeof prop.onresponse == 'function') {
|
|
prop.onresponse(data);
|
|
}
|
|
|
|
prop.result = $.extend(prop.result, data);
|
|
|
|
// send next request if prop.items is not yet empty
|
|
var item = prop._items.shift();
|
|
if (item !== undefined) {
|
|
prop.running++;
|
|
prop.requests.push(this.multi_thread_send_request(prop, item));
|
|
}
|
|
// trigger whendone callback and mark this request as done
|
|
else if (prop.running == 0) {
|
|
if (prop.whendone && typeof prop.whendone == 'function') {
|
|
prop.whendone(prop.result);
|
|
}
|
|
|
|
this.set_busy(false, '', prop.lock);
|
|
|
|
// remove from this.http_request_jobs pool
|
|
delete this.http_request_jobs[reqid];
|
|
}
|
|
};
|
|
|
|
// abort a running multi-thread request with the given identifier
|
|
this.multi_thread_request_abort = function(reqid)
|
|
{
|
|
var prop = this.http_request_jobs[reqid];
|
|
if (prop) {
|
|
for (var i=0; prop.running > 0 && i < prop.requests.length; i++) {
|
|
if (prop.requests[i].abort)
|
|
prop.requests[i].abort();
|
|
}
|
|
|
|
prop.running = 0;
|
|
prop.cancelled = true;
|
|
this.set_busy(false, '', prop.lock);
|
|
}
|
|
};
|
|
|
|
// post the given form to a hidden iframe
|
|
this.async_upload_form = function(form, action, onload)
|
|
{
|
|
var frame, ts = new Date().getTime(),
|
|
frame_name = 'rcmupload'+ts;
|
|
|
|
// upload progress support
|
|
if (this.env.upload_progress_name) {
|
|
var fname = this.env.upload_progress_name,
|
|
field = $('input[name='+fname+']', form);
|
|
|
|
if (!field.length) {
|
|
field = $('<input>').attr({type: 'hidden', name: fname});
|
|
field.prependTo(form);
|
|
}
|
|
|
|
field.val(ts);
|
|
}
|
|
|
|
// have to do it this way for IE
|
|
// otherwise the form will be posted to a new window
|
|
if (document.all) {
|
|
document.body.insertAdjacentHTML('BeforeEnd', '<iframe name="'+frame_name+'"'
|
|
+ ' src="program/resources/blank.gif" style="width:0;height:0;visibility:hidden;"></iframe>');
|
|
frame = $('iframe[name="'+frame_name+'"]');
|
|
}
|
|
// for standards-compliant browsers
|
|
else {
|
|
frame = $('<iframe>').attr('name', frame_name)
|
|
.css({border: 'none', width: 0, height: 0, visibility: 'hidden'})
|
|
.appendTo(document.body);
|
|
}
|
|
|
|
// handle upload errors, parsing iframe content in onload
|
|
frame.bind('load', {ts:ts}, onload);
|
|
|
|
$(form).attr({
|
|
target: frame_name,
|
|
action: this.url(action, { _id:this.env.compose_id||'', _uploadid:ts }),
|
|
method: 'POST'})
|
|
.attr(form.encoding ? 'encoding' : 'enctype', 'multipart/form-data')
|
|
.submit();
|
|
|
|
return frame_name;
|
|
};
|
|
|
|
// html5 file-drop API
|
|
this.document_drag_hover = function(e, over)
|
|
{
|
|
e.preventDefault();
|
|
$(ref.gui_objects.filedrop)[(over?'addClass':'removeClass')]('active');
|
|
};
|
|
|
|
this.file_drag_hover = function(e, over)
|
|
{
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
$(ref.gui_objects.filedrop)[(over?'addClass':'removeClass')]('hover');
|
|
};
|
|
|
|
// handler when files are dropped to a designated area.
|
|
// compose a multipart form data and submit it to the server
|
|
this.file_dropped = function(e)
|
|
{
|
|
// abort event and reset UI
|
|
this.file_drag_hover(e, false);
|
|
|
|
// prepare multipart form data composition
|
|
var files = e.target.files || e.dataTransfer.files,
|
|
formdata = window.FormData ? new FormData() : null,
|
|
fieldname = (this.env.filedrop.fieldname || '_file') + (this.env.filedrop.single ? '' : '[]'),
|
|
boundary = '------multipartformboundary' + (new Date).getTime(),
|
|
dashdash = '--', crlf = '\r\n',
|
|
multipart = dashdash + boundary + crlf;
|
|
|
|
if (!files || !files.length)
|
|
return;
|
|
|
|
// inline function to submit the files to the server
|
|
var submit_data = function() {
|
|
var multiple = files.length > 1,
|
|
ts = new Date().getTime(),
|
|
content = '<span>' + (multiple ? ref.get_label('uploadingmany') : files[0].name) + '</span>';
|
|
|
|
// add to attachments list
|
|
if (!ref.add2attachment_list(ts, { name:'', html:content, classname:'uploading', complete:false }))
|
|
ref.file_upload_id = ref.set_busy(true, 'uploading');
|
|
|
|
// complete multipart content and post request
|
|
multipart += dashdash + boundary + dashdash + crlf;
|
|
|
|
$.ajax({
|
|
type: 'POST',
|
|
dataType: 'json',
|
|
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},
|
|
xhr: function() { var xhr = jQuery.ajaxSettings.xhr(); if (!formdata && xhr.sendAsBinary) xhr.send = xhr.sendAsBinary; return xhr; },
|
|
success: function(data){ ref.http_response(data); },
|
|
error: function(o, status, err) { ref.http_error(o, status, err, null, 'attachment'); }
|
|
});
|
|
};
|
|
|
|
// get contents of all dropped files
|
|
var last = this.env.filedrop.single ? 0 : files.length - 1;
|
|
for (var j=0, i=0, f; j <= last && (f = files[i]); i++) {
|
|
if (!f.name) f.name = f.fileName;
|
|
if (!f.size) f.size = f.fileSize;
|
|
if (!f.type) f.type = 'application/octet-stream';
|
|
|
|
// file name contains non-ASCII characters, do UTF8-binary string conversion.
|
|
if (!formdata && /[^\x20-\x7E]/.test(f.name))
|
|
f.name_bin = unescape(encodeURIComponent(f.name));
|
|
|
|
// filter by file type if requested
|
|
if (this.env.filedrop.filter && !f.type.match(new RegExp(this.env.filedrop.filter))) {
|
|
// TODO: show message to user
|
|
continue;
|
|
}
|
|
|
|
// do it the easy way with FormData (FF 4+, Chrome 5+, Safari 5+)
|
|
if (formdata) {
|
|
formdata.append(fieldname, f);
|
|
if (j == last)
|
|
return submit_data();
|
|
}
|
|
// use FileReader supporetd by Firefox 3.6
|
|
else if (window.FileReader) {
|
|
var reader = new FileReader();
|
|
|
|
// closure to pass file properties to async callback function
|
|
reader.onload = (function(file, j) {
|
|
return function(e) {
|
|
multipart += 'Content-Disposition: form-data; name="' + fieldname + '"';
|
|
multipart += '; filename="' + (f.name_bin || file.name) + '"' + crlf;
|
|
multipart += 'Content-Length: ' + file.size + crlf;
|
|
multipart += 'Content-Type: ' + file.type + crlf + crlf;
|
|
multipart += reader.result + crlf;
|
|
multipart += dashdash + boundary + crlf;
|
|
|
|
if (j == last) // we're done, submit the data
|
|
return submit_data();
|
|
}
|
|
})(f,j);
|
|
reader.readAsBinaryString(f);
|
|
}
|
|
// Firefox 3
|
|
else if (f.getAsBinary) {
|
|
multipart += 'Content-Disposition: form-data; name="' + fieldname + '"';
|
|
multipart += '; filename="' + (f.name_bin || f.name) + '"' + crlf;
|
|
multipart += 'Content-Length: ' + f.size + crlf;
|
|
multipart += 'Content-Type: ' + f.type + crlf + crlf;
|
|
multipart += f.getAsBinary() + crlf;
|
|
multipart += dashdash + boundary +crlf;
|
|
|
|
if (j == last)
|
|
return submit_data();
|
|
}
|
|
|
|
j++;
|
|
}
|
|
};
|
|
|
|
// starts interval for keep-alive signal
|
|
this.start_keepalive = function()
|
|
{
|
|
if (!this.env.session_lifetime || this.env.framed || this.env.extwin || this.task == 'login' || this.env.action == 'print')
|
|
return;
|
|
|
|
if (this._keepalive)
|
|
clearInterval(this._keepalive);
|
|
|
|
this._keepalive = setInterval(function(){ ref.keep_alive(); }, this.env.session_lifetime * 0.5 * 1000);
|
|
};
|
|
|
|
// starts interval for refresh signal
|
|
this.start_refresh = function()
|
|
{
|
|
if (!this.env.refresh_interval || this.env.framed || this.env.extwin || this.task == 'login' || this.env.action == 'print')
|
|
return;
|
|
|
|
if (this._refresh)
|
|
clearInterval(this._refresh);
|
|
|
|
this._refresh = setInterval(function(){ ref.refresh(); }, this.env.refresh_interval * 1000);
|
|
};
|
|
|
|
// sends keep-alive signal
|
|
this.keep_alive = function()
|
|
{
|
|
if (!this.busy)
|
|
this.http_request('keep-alive');
|
|
};
|
|
|
|
// sends refresh signal
|
|
this.refresh = function()
|
|
{
|
|
if (this.busy) {
|
|
// try again after 10 seconds
|
|
setTimeout(function(){ ref.refresh(); ref.start_refresh(); }, 10000);
|
|
return;
|
|
}
|
|
|
|
var params = {}, lock = this.set_busy(true, 'refreshing');
|
|
|
|
if (this.task == 'mail' && this.gui_objects.mailboxlist)
|
|
params = this.check_recent_params();
|
|
|
|
params._last = Math.floor(this.env.lastrefresh.getTime() / 1000);
|
|
this.env.lastrefresh = new Date();
|
|
|
|
// plugins should bind to 'requestrefresh' event to add own params
|
|
this.http_post('refresh', params, lock);
|
|
};
|
|
|
|
// returns check-recent request parameters
|
|
this.check_recent_params = function()
|
|
{
|
|
var params = {_mbox: this.env.mailbox};
|
|
|
|
if (this.gui_objects.mailboxlist)
|
|
params._folderlist = 1;
|
|
if (this.gui_objects.quotadisplay)
|
|
params._quota = 1;
|
|
if (this.env.search_request)
|
|
params._search = this.env.search_request;
|
|
|
|
if (this.gui_objects.messagelist) {
|
|
params._list = 1;
|
|
|
|
// message uids for flag updates check
|
|
params._uids = $.map(this.message_list.rows, function(row, uid) { return uid; }).join(',');
|
|
}
|
|
|
|
return params;
|
|
};
|
|
|
|
|
|
/********************************************************/
|
|
/********* helper methods *********/
|
|
/********************************************************/
|
|
|
|
/**
|
|
* Quote html entities
|
|
*/
|
|
this.quote_html = function(str)
|
|
{
|
|
return String(str).replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
};
|
|
|
|
// 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()
|
|
{
|
|
return this.env.uid ? this.env.uid : (this.message_list ? this.message_list.get_single_selection() : null);
|
|
};
|
|
|
|
// same as above but for contacts
|
|
this.get_single_cid = function()
|
|
{
|
|
return this.env.cid ? this.env.cid : (this.contact_list ? this.contact_list.get_single_selection() : null);
|
|
};
|
|
|
|
// get the IMP mailbox of the message with the given UID
|
|
this.get_message_mailbox = function(uid)
|
|
{
|
|
var msg = this.env.messages ? this.env.messages[uid] : {};
|
|
return msg.mbox || this.env.mailbox;
|
|
};
|
|
|
|
// gets cursor position
|
|
this.get_caret_pos = function(obj)
|
|
{
|
|
if (obj.selectionEnd !== undefined)
|
|
return obj.selectionEnd;
|
|
|
|
return obj.value.length;
|
|
};
|
|
|
|
// moves cursor to specified position
|
|
this.set_caret_pos = function(obj, pos)
|
|
{
|
|
try {
|
|
if (obj.setSelectionRange)
|
|
obj.setSelectionRange(pos, pos);
|
|
}
|
|
catch(e) {}; // catch Firefox exception if obj is hidden
|
|
};
|
|
|
|
// get selected text from an input field
|
|
this.get_input_selection = function(obj)
|
|
{
|
|
var start = 0, end = 0, normalizedValue = '';
|
|
|
|
if (typeof obj.selectionStart == "number" && typeof obj.selectionEnd == "number") {
|
|
normalizedValue = obj.value;
|
|
start = obj.selectionStart;
|
|
end = obj.selectionEnd;
|
|
}
|
|
|
|
return {start: start, end: end, text: normalizedValue.substr(start, end-start)};
|
|
};
|
|
|
|
// disable/enable all fields of a form
|
|
this.lock_form = function(form, lock)
|
|
{
|
|
if (!form || !form.elements)
|
|
return;
|
|
|
|
var n, len, elm;
|
|
|
|
if (lock)
|
|
this.disabled_form_elements = [];
|
|
|
|
for (n=0, len=form.elements.length; n<len; n++) {
|
|
elm = form.elements[n];
|
|
|
|
if (elm.type == 'hidden')
|
|
continue;
|
|
// remember which elem was disabled before lock
|
|
if (lock && elm.disabled)
|
|
this.disabled_form_elements.push(elm);
|
|
else if (lock || $.inArray(elm, this.disabled_form_elements) < 0)
|
|
elm.disabled = lock;
|
|
}
|
|
};
|
|
|
|
this.mailto_handler_uri = function()
|
|
{
|
|
return location.href.split('?')[0] + '?_task=mail&_action=compose&_to=%s';
|
|
};
|
|
|
|
this.register_protocol_handler = function(name)
|
|
{
|
|
try {
|
|
window.navigator.registerProtocolHandler('mailto', this.mailto_handler_uri(), name);
|
|
}
|
|
catch(e) {
|
|
this.display_message(String(e), 'error');
|
|
};
|
|
};
|
|
|
|
this.check_protocol_handler = function(name, elem)
|
|
{
|
|
var nav = window.navigator;
|
|
if (!nav || (typeof nav.registerProtocolHandler != 'function')) {
|
|
$(elem).addClass('disabled').click(function(){ return false; });
|
|
}
|
|
else {
|
|
var status = null;
|
|
if (typeof nav.isProtocolHandlerRegistered == 'function') {
|
|
status = nav.isProtocolHandlerRegistered('mailto', this.mailto_handler_uri());
|
|
if (status)
|
|
$(elem).parent().find('.mailtoprotohandler-status').html(status);
|
|
}
|
|
else {
|
|
$(elem).click(function() { rcmail.register_protocol_handler(name); return false; });
|
|
}
|
|
}
|
|
};
|
|
|
|
// Checks browser capabilities eg. PDF support, TIF support
|
|
this.browser_capabilities_check = function()
|
|
{
|
|
if (!this.env.browser_capabilities)
|
|
this.env.browser_capabilities = {};
|
|
|
|
if (this.env.browser_capabilities.pdf === undefined)
|
|
this.env.browser_capabilities.pdf = this.pdf_support_check();
|
|
|
|
if (this.env.browser_capabilities.flash === undefined)
|
|
this.env.browser_capabilities.flash = this.flash_support_check();
|
|
|
|
if (this.env.browser_capabilities.tif === undefined)
|
|
this.tif_support_check();
|
|
};
|
|
|
|
// Returns browser capabilities string
|
|
this.browser_capabilities = function()
|
|
{
|
|
if (!this.env.browser_capabilities)
|
|
return '';
|
|
|
|
var n, ret = [];
|
|
|
|
for (n in this.env.browser_capabilities)
|
|
ret.push(n + '=' + this.env.browser_capabilities[n]);
|
|
|
|
return ret.join();
|
|
};
|
|
|
|
this.tif_support_check = function()
|
|
{
|
|
var img = new Image();
|
|
|
|
img.onload = function() { rcmail.env.browser_capabilities.tif = 1; };
|
|
img.onerror = function() { rcmail.env.browser_capabilities.tif = 0; };
|
|
img.src = 'program/resources/blank.tif';
|
|
};
|
|
|
|
this.pdf_support_check = function()
|
|
{
|
|
var plugin = navigator.mimeTypes ? navigator.mimeTypes["application/pdf"] : {},
|
|
plugins = navigator.plugins,
|
|
len = plugins.length,
|
|
regex = /Adobe Reader|PDF|Acrobat/i;
|
|
|
|
if (plugin && plugin.enabledPlugin)
|
|
return 1;
|
|
|
|
if (window.ActiveXObject) {
|
|
try {
|
|
if (axObj = new ActiveXObject("AcroPDF.PDF"))
|
|
return 1;
|
|
}
|
|
catch (e) {}
|
|
try {
|
|
if (axObj = new ActiveXObject("PDF.PdfCtrl"))
|
|
return 1;
|
|
}
|
|
catch (e) {}
|
|
}
|
|
|
|
for (i=0; i<len; i++) {
|
|
plugin = plugins[i];
|
|
if (typeof plugin === 'String') {
|
|
if (regex.test(plugin))
|
|
return 1;
|
|
}
|
|
else if (plugin.name && regex.test(plugin.name))
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
};
|
|
|
|
this.flash_support_check = function()
|
|
{
|
|
var plugin = navigator.mimeTypes ? navigator.mimeTypes["application/x-shockwave-flash"] : {};
|
|
|
|
if (plugin && plugin.enabledPlugin)
|
|
return 1;
|
|
|
|
if (window.ActiveXObject) {
|
|
try {
|
|
if (axObj = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"))
|
|
return 1;
|
|
}
|
|
catch (e) {}
|
|
}
|
|
|
|
return 0;
|
|
};
|
|
|
|
// Cookie setter
|
|
this.set_cookie = function(name, value, expires)
|
|
{
|
|
setCookie(name, value, expires, this.env.cookie_path, this.env.cookie_domain, this.env.cookie_secure);
|
|
};
|
|
|
|
this.get_local_storage_prefix = function()
|
|
{
|
|
if (!this.local_storage_prefix)
|
|
this.local_storage_prefix = 'roundcube.' + (this.env.user_id || 'anonymous') + '.';
|
|
|
|
return this.local_storage_prefix;
|
|
};
|
|
|
|
// wrapper for localStorage.getItem(key)
|
|
this.local_storage_get_item = function(key, deflt, encrypted)
|
|
{
|
|
// TODO: add encryption
|
|
var item = localStorage.getItem(this.get_local_storage_prefix() + key);
|
|
return item !== null ? JSON.parse(item) : (deflt || null);
|
|
};
|
|
|
|
// wrapper for localStorage.setItem(key, data)
|
|
this.local_storage_set_item = function(key, data, encrypted)
|
|
{
|
|
// TODO: add encryption
|
|
return localStorage.setItem(this.get_local_storage_prefix() + key, JSON.stringify(data));
|
|
};
|
|
|
|
// wrapper for localStorage.removeItem(key)
|
|
this.local_storage_remove_item = function(key)
|
|
{
|
|
return localStorage.removeItem(this.get_local_storage_prefix() + key);
|
|
};
|
|
|
|
} // 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 || 0) * 15 > $elem.parent().width())
|
|
elem.title = $elem.text();
|
|
}
|
|
};
|
|
|
|
rcube_webmail.long_subject_title_ex = function(elem)
|
|
{
|
|
if (!elem.title) {
|
|
var $elem = $(elem),
|
|
txt = $.trim($elem.text()),
|
|
tmp = $('<span>').text(txt)
|
|
.css({'position': 'absolute', 'float': 'left', 'visibility': 'hidden',
|
|
'font-size': $elem.css('font-size'), 'font-weight': $elem.css('font-weight')})
|
|
.appendTo($('body')),
|
|
w = tmp.width();
|
|
|
|
tmp.remove();
|
|
if (w + $('span.branch', $elem).width() * 15 > $elem.width())
|
|
elem.title = txt;
|
|
}
|
|
};
|
|
|
|
rcube_webmail.prototype.get_cookie = getCookie;
|
|
|
|
// copy event engine prototype
|
|
rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener;
|
|
rcube_webmail.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener;
|
|
rcube_webmail.prototype.triggerEvent = rcube_event_engine.prototype.triggerEvent;
|