0
|
1 /**
|
|
2 * Roundcube Webmail Client Script
|
|
3 *
|
|
4 * This file is part of the Roundcube Webmail client
|
|
5 *
|
|
6 * @licstart The following is the entire license notice for the
|
|
7 * JavaScript code in this file.
|
|
8 *
|
|
9 * Copyright (C) 2005-2015, The Roundcube Dev Team
|
|
10 * Copyright (C) 2011-2015, Kolab Systems AG
|
|
11 *
|
|
12 * The JavaScript code in this page is free software: you can
|
|
13 * redistribute it and/or modify it under the terms of the GNU
|
|
14 * General Public License (GNU GPL) as published by the Free Software
|
|
15 * Foundation, either version 3 of the License, or (at your option)
|
|
16 * any later version. The code is distributed WITHOUT ANY WARRANTY;
|
|
17 * without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
18 * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
|
|
19 *
|
|
20 * As additional permission under GNU GPL version 3 section 7, you
|
|
21 * may distribute non-source (e.g., minimized or compacted) forms of
|
|
22 * that code without the copy of the GNU GPL normally required by
|
|
23 * section 4, provided you include this license notice and a URL
|
|
24 * through which recipients can access the Corresponding Source.
|
|
25 *
|
|
26 * @licend The above is the entire license notice
|
|
27 * for the JavaScript code in this file.
|
|
28 *
|
|
29 * @author Thomas Bruederli <roundcube@gmail.com>
|
|
30 * @author Aleksander 'A.L.E.C' Machniak <alec@alec.pl>
|
|
31 * @author Charles McNulty <charles@charlesmcnulty.com>
|
|
32 *
|
|
33 * @requires jquery.js, common.js, list.js
|
|
34 */
|
|
35
|
|
36 function rcube_webmail()
|
|
37 {
|
|
38 this.labels = {};
|
|
39 this.buttons = {};
|
|
40 this.buttons_sel = {};
|
|
41 this.gui_objects = {};
|
|
42 this.gui_containers = {};
|
|
43 this.commands = {};
|
|
44 this.command_handlers = {};
|
|
45 this.onloads = [];
|
|
46 this.messages = {};
|
|
47 this.group2expand = {};
|
|
48 this.http_request_jobs = {};
|
|
49 this.menu_stack = [];
|
|
50
|
|
51 // webmail client settings
|
|
52 this.dblclick_time = 500;
|
|
53 this.message_time = 5000;
|
|
54 this.preview_delay_select = 400;
|
|
55 this.preview_delay_click = 60;
|
|
56 this.identifier_expr = /[^0-9a-z_-]/gi;
|
|
57
|
|
58 // environment defaults
|
|
59 this.env = {
|
|
60 request_timeout: 180, // seconds
|
|
61 draft_autosave: 0, // seconds
|
|
62 comm_path: './',
|
|
63 recipients_separator: ',',
|
|
64 recipients_delimiter: ', ',
|
|
65 popup_width: 1150,
|
|
66 popup_width_small: 900
|
|
67 };
|
|
68
|
|
69 // create protected reference to myself
|
|
70 this.ref = 'rcmail';
|
|
71 var ref = this;
|
|
72
|
|
73 // set jQuery ajax options
|
|
74 $.ajaxSetup({
|
|
75 cache: false,
|
|
76 timeout: this.env.request_timeout * 1000,
|
|
77 error: function(request, status, err){ ref.http_error(request, status, err); },
|
|
78 beforeSend: function(xmlhttp){ xmlhttp.setRequestHeader('X-Roundcube-Request', ref.env.request_token); }
|
|
79 });
|
|
80
|
|
81 // unload fix
|
|
82 $(window).on('beforeunload', function() { ref.unload = true; });
|
|
83
|
|
84 // set environment variable(s)
|
|
85 this.set_env = function(p, value)
|
|
86 {
|
|
87 if (p != null && typeof p === 'object' && !value)
|
|
88 for (var n in p)
|
|
89 this.env[n] = p[n];
|
|
90 else
|
|
91 this.env[p] = value;
|
|
92 };
|
|
93
|
|
94 // add a localized label to the client environment
|
|
95 this.add_label = function(p, value)
|
|
96 {
|
|
97 if (typeof p == 'string')
|
|
98 this.labels[p] = value;
|
|
99 else if (typeof p == 'object')
|
|
100 $.extend(this.labels, p);
|
|
101 };
|
|
102
|
|
103 // add a button to the button list
|
|
104 this.register_button = function(command, id, type, act, sel, over)
|
|
105 {
|
|
106 var button_prop = {id:id, type:type};
|
|
107
|
|
108 if (act) button_prop.act = act;
|
|
109 if (sel) button_prop.sel = sel;
|
|
110 if (over) button_prop.over = over;
|
|
111
|
|
112 if (!this.buttons[command])
|
|
113 this.buttons[command] = [];
|
|
114
|
|
115 this.buttons[command].push(button_prop);
|
|
116
|
|
117 if (this.loaded)
|
|
118 init_button(command, button_prop);
|
|
119 };
|
|
120
|
|
121 // register a specific gui object
|
|
122 this.gui_object = function(name, id)
|
|
123 {
|
|
124 this.gui_objects[name] = this.loaded ? rcube_find_object(id) : id;
|
|
125 };
|
|
126
|
|
127 // register a container object
|
|
128 this.gui_container = function(name, id)
|
|
129 {
|
|
130 this.gui_containers[name] = id;
|
|
131 };
|
|
132
|
|
133 // add a GUI element (html node) to a specified container
|
|
134 this.add_element = function(elm, container)
|
|
135 {
|
|
136 if (this.gui_containers[container] && this.gui_containers[container].jquery)
|
|
137 this.gui_containers[container].append(elm);
|
|
138 };
|
|
139
|
|
140 // register an external handler for a certain command
|
|
141 this.register_command = function(command, callback, enable)
|
|
142 {
|
|
143 this.command_handlers[command] = callback;
|
|
144
|
|
145 if (enable)
|
|
146 this.enable_command(command, true);
|
|
147 };
|
|
148
|
|
149 // execute the given script on load
|
|
150 this.add_onload = function(f)
|
|
151 {
|
|
152 this.onloads.push(f);
|
|
153 };
|
|
154
|
|
155 // initialize webmail client
|
|
156 this.init = function()
|
|
157 {
|
|
158 var n;
|
|
159 this.task = this.env.task;
|
|
160
|
|
161 // check browser capabilities (never use version checks here)
|
|
162 if (this.env.server_error != 409 && (!bw.dom || !bw.xmlhttp_test())) {
|
|
163 this.goto_url('error', '_code=0x199');
|
|
164 return;
|
|
165 }
|
|
166
|
|
167 if (!this.env.blankpage)
|
|
168 this.env.blankpage = this.assets_path('program/resources/blank.gif');
|
|
169
|
|
170 // find all registered gui containers
|
|
171 for (n in this.gui_containers)
|
|
172 this.gui_containers[n] = $('#'+this.gui_containers[n]);
|
|
173
|
|
174 // find all registered gui objects
|
|
175 for (n in this.gui_objects)
|
|
176 this.gui_objects[n] = rcube_find_object(this.gui_objects[n]);
|
|
177
|
|
178 // clickjacking protection
|
|
179 if (n = this.env.x_frame_options) {
|
|
180 try {
|
|
181 // bust frame if not allowed
|
|
182 if (n.toLowerCase() == 'deny' && top.location.href != self.location.href)
|
|
183 top.location.href = self.location.href;
|
|
184 else if (/^allow-from[\s\t]+(.+)$/i.test(n) && RegExp.$1.indexOf(top.location.origin) != 0)
|
|
185 throw 1;
|
|
186 else if (top.location.hostname != self.location.hostname)
|
|
187 throw 1;
|
|
188 } catch (e) {
|
|
189 // possible clickjacking attack: disable all form elements
|
|
190 $('form').each(function(){ ref.lock_form(this, true); });
|
|
191 this.display_message("Blocked: possible clickjacking attack!", 'error');
|
|
192 return;
|
|
193 }
|
|
194 }
|
|
195
|
|
196 // init registered buttons
|
|
197 this.init_buttons();
|
|
198
|
|
199 // tell parent window that this frame is loaded
|
|
200 if (this.is_framed()) {
|
|
201 parent.rcmail.set_busy(false, null, parent.rcmail.env.frame_lock);
|
|
202 parent.rcmail.env.frame_lock = null;
|
|
203 }
|
|
204
|
|
205 // enable general commands
|
|
206 this.enable_command('close', 'logout', 'mail', 'addressbook', 'settings', 'save-pref',
|
|
207 'compose', 'undo', 'about', 'switch-task', 'menu-open', 'menu-close', 'menu-save', true);
|
|
208
|
|
209 // set active task button
|
|
210 this.set_button(this.task, 'sel');
|
|
211
|
|
212 if (this.env.permaurl)
|
|
213 this.enable_command('permaurl', 'extwin', true);
|
|
214
|
|
215 switch (this.task) {
|
|
216
|
|
217 case 'mail':
|
|
218 // enable mail commands
|
|
219 this.enable_command('list', 'checkmail', 'add-contact', 'search', 'reset-search', 'collapse-folder', 'import-messages', true);
|
|
220
|
|
221 if (this.gui_objects.messagelist) {
|
|
222 this.env.widescreen_list_template = [
|
|
223 {className: 'threads', cells: ['threads']},
|
2
|
224 {className: 'subject', cells: ['fromto', 'date', 'status', 'subject','dates']},
|
0
|
225 {className: 'flags', cells: ['flag', 'attachment']}
|
|
226 ];
|
|
227
|
|
228 this.message_list = new rcube_list_widget(this.gui_objects.messagelist, {
|
|
229 multiselect:true, multiexpand:true, draggable:true, keyboard:true,
|
|
230 column_movable:this.env.col_movable, dblclick_time:this.dblclick_time
|
|
231 });
|
|
232 this.message_list
|
|
233 .addEventListener('initrow', function(o) { ref.init_message_row(o); })
|
|
234 .addEventListener('dblclick', function(o) { ref.msglist_dbl_click(o); })
|
|
235 .addEventListener('keypress', function(o) { ref.msglist_keypress(o); })
|
|
236 .addEventListener('select', function(o) { ref.msglist_select(o); })
|
|
237 .addEventListener('dragstart', function(o) { ref.drag_start(o); })
|
|
238 .addEventListener('dragmove', function(e) { ref.drag_move(e); })
|
|
239 .addEventListener('dragend', function(e) { ref.drag_end(e); })
|
|
240 .addEventListener('expandcollapse', function(o) { ref.msglist_expand(o); })
|
|
241 .addEventListener('column_replace', function(o) { ref.msglist_set_coltypes(o); })
|
|
242 .addEventListener('listupdate', function(o) { ref.triggerEvent('listupdate', o); })
|
|
243 .init();
|
|
244
|
|
245 // TODO: this should go into the list-widget code
|
|
246 $(this.message_list.thead).on('click', 'a.sortcol', function(e){
|
|
247 return ref.command('sort', $(this).attr('rel'), this);
|
|
248 });
|
|
249
|
|
250 this.enable_command('toggle_status', 'toggle_flag', 'sort', true);
|
|
251 this.enable_command('set-listmode', this.env.threads && !this.is_multifolder_listing());
|
|
252
|
|
253 // load messages
|
|
254 this.command('list');
|
|
255
|
|
256 $(this.gui_objects.qsearchbox).val(this.env.search_text).focusin(function() { ref.message_list.blur(); });
|
|
257 }
|
|
258
|
|
259 this.set_button_titles();
|
|
260
|
|
261 this.env.message_commands = ['show', 'reply', 'reply-all', 'reply-list',
|
|
262 'move', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource',
|
|
263 'print', 'load-attachment', 'download-attachment', 'show-headers', 'hide-headers', 'download',
|
|
264 'forward', 'forward-inline', 'forward-attachment', 'change-format'];
|
|
265
|
|
266 if (this.env.action == 'show' || this.env.action == 'preview') {
|
|
267 this.enable_command(this.env.message_commands, this.env.uid);
|
|
268 this.enable_command('reply-list', this.env.list_post);
|
|
269
|
|
270 if (this.env.action == 'show') {
|
|
271 this.http_request('pagenav', {_uid: this.env.uid, _mbox: this.env.mailbox, _search: this.env.search_request},
|
|
272 this.display_message('', 'loading'));
|
|
273 }
|
|
274
|
|
275 if (this.env.mail_read_time > 0)
|
|
276 setTimeout(function() {
|
|
277 ref.http_post('mark', {_uid: ref.env.uid, _flag: 'read', _mbox: ref.env.mailbox, _quiet: 1});
|
|
278 }, this.env.mail_read_time * 1000);
|
|
279
|
|
280 if (this.env.blockedobjects) {
|
|
281 if (this.gui_objects.remoteobjectsmsg)
|
|
282 this.gui_objects.remoteobjectsmsg.style.display = 'block';
|
|
283 this.enable_command('load-images', 'always-load', true);
|
|
284 }
|
|
285
|
|
286 // make preview/message frame visible
|
|
287 if (this.env.action == 'preview' && this.is_framed()) {
|
|
288 this.enable_command('compose', 'add-contact', false);
|
|
289 parent.rcmail.show_contentframe(true);
|
|
290 }
|
|
291
|
|
292 // initialize drag-n-drop on attachments, so they can e.g.
|
|
293 // be dropped into mail compose attachments in another window
|
|
294 if (this.gui_objects.attachments)
|
|
295 $('li > a', this.gui_objects.attachments).not('.drop').on('dragstart', function(e) {
|
|
296 var n, href = this.href, dt = e.originalEvent.dataTransfer;
|
|
297 if (dt) {
|
|
298 // inject username to the uri
|
|
299 href = href.replace(/^https?:\/\//, function(m) { return m + urlencode(ref.env.username) + '@'});
|
|
300 // cleanup the node to get filename without the size test
|
|
301 n = $(this).clone();
|
|
302 n.children().remove();
|
|
303
|
|
304 dt.setData('roundcube-uri', href);
|
|
305 dt.setData('roundcube-name', $.trim(n.text()));
|
|
306 }
|
|
307 });
|
|
308 }
|
|
309 else if (this.env.action == 'compose') {
|
|
310 this.env.address_group_stack = [];
|
|
311 this.env.compose_commands = ['send-attachment', 'remove-attachment', 'send', 'cancel',
|
|
312 'toggle-editor', 'list-addresses', 'pushgroup', 'search', 'reset-search', 'extwin',
|
|
313 'insert-response', 'save-response', 'menu-open', 'menu-close', 'load-attachment',
|
|
314 'download-attachment', 'open-attachment', 'rename-attachment'];
|
|
315
|
|
316 if (this.env.drafts_mailbox)
|
|
317 this.env.compose_commands.push('savedraft')
|
|
318
|
|
319 this.enable_command(this.env.compose_commands, 'identities', 'responses', true);
|
|
320
|
|
321 // add more commands (not enabled)
|
|
322 $.merge(this.env.compose_commands, ['add-recipient', 'firstpage', 'previouspage', 'nextpage', 'lastpage']);
|
|
323
|
|
324 if (window.googie) {
|
|
325 this.env.editor_config.spellchecker = googie;
|
|
326 this.env.editor_config.spellcheck_observer = function(s) { ref.spellcheck_state(); };
|
|
327
|
|
328 this.env.compose_commands.push('spellcheck')
|
|
329 this.enable_command('spellcheck', true);
|
|
330 }
|
|
331
|
|
332 // initialize HTML editor
|
|
333 this.editor_init(this.env.editor_config, this.env.composebody);
|
|
334
|
|
335 // init canned response functions
|
|
336 if (this.gui_objects.responseslist) {
|
|
337 $('a.insertresponse', this.gui_objects.responseslist)
|
|
338 .attr('unselectable', 'on')
|
|
339 .mousedown(function(e) { return rcube_event.cancel(e); })
|
|
340 .on('mouseup keypress', function(e) {
|
|
341 if (e.type == 'mouseup' || rcube_event.get_keycode(e) == 13) {
|
|
342 ref.command('insert-response', $(this).attr('rel'));
|
|
343 $(document.body).trigger('mouseup'); // hides the menu
|
|
344 return rcube_event.cancel(e);
|
|
345 }
|
|
346 });
|
|
347
|
|
348 // avoid textarea loosing focus when hitting the save-response button/link
|
|
349 $.each(this.buttons['save-response'] || [], function (i, v) {
|
|
350 $('#' + v.id).mousedown(function(e){ return rcube_event.cancel(e); })
|
|
351 });
|
|
352 }
|
|
353
|
|
354 // init message compose form
|
|
355 this.init_messageform();
|
|
356 }
|
|
357 else if (this.env.action == 'get') {
|
|
358 this.enable_command('download', true);
|
|
359
|
|
360 // Mozilla's PDF.js viewer does not allow printing from host page (#5125)
|
|
361 // to minimize user confusion we disable the Print button
|
|
362 if (bw.mz && this.env.mimetype == 'application/pdf') {
|
|
363 n = 0; // there will be two onload events, first for the preload page
|
|
364 $(this.gui_objects.messagepartframe).on('load', function() {
|
|
365 if (n++) try { if (this.contentWindow.document) ref.enable_command('print', true); }
|
|
366 catch (e) {/* ignore */}
|
|
367 });
|
|
368 }
|
|
369 else
|
|
370 this.enable_command('print', true);
|
|
371
|
|
372 if (this.env.is_message) {
|
|
373 this.enable_command('reply', 'reply-all', 'edit', 'viewsource',
|
|
374 'forward', 'forward-inline', 'forward-attachment', true);
|
|
375 if (this.env.list_post)
|
|
376 this.enable_command('reply-list', true);
|
|
377 }
|
|
378
|
|
379 // center and scale the image in preview frame
|
|
380 if (this.env.mimetype.startsWith('image/'))
|
|
381 $(this.gui_objects.messagepartframe).on('load', function() {
|
|
382 var css = 'img { max-width:100%; max-height:100%; } ' // scale
|
|
383 + 'body { display:flex; align-items:center; justify-content:center; height:100%; margin:0; }'; // align
|
|
384
|
|
385 $(this).contents().find('head').append('<style type="text/css">'+ css + '</style>');
|
|
386 });
|
|
387 }
|
|
388 // show printing dialog
|
|
389 else if (this.env.action == 'print' && this.env.uid
|
|
390 && !this.env.is_pgp_content && !this.env.pgp_mime_part
|
|
391 ) {
|
|
392 this.print_dialog();
|
|
393 }
|
|
394
|
|
395 // get unread count for each mailbox
|
|
396 if (this.gui_objects.mailboxlist) {
|
|
397 this.env.unread_counts = {};
|
|
398 this.gui_objects.folderlist = this.gui_objects.mailboxlist;
|
|
399 this.http_request('getunread', {_page: this.env.current_page});
|
|
400 }
|
|
401
|
|
402 // init address book widget
|
|
403 if (this.gui_objects.contactslist) {
|
|
404 this.contact_list = new rcube_list_widget(this.gui_objects.contactslist,
|
|
405 { multiselect:true, draggable:false, keyboard:true });
|
|
406 this.contact_list
|
|
407 .addEventListener('initrow', function(o) { ref.triggerEvent('insertrow', { cid:o.uid, row:o }); })
|
|
408 .addEventListener('select', function(o) { ref.compose_recipient_select(o); })
|
|
409 .addEventListener('dblclick', function(o) { ref.compose_add_recipient(); })
|
|
410 .addEventListener('keypress', function(o) {
|
|
411 if (o.key_pressed == o.ENTER_KEY) {
|
|
412 if (!ref.compose_add_recipient()) {
|
|
413 // execute link action on <enter> if not a recipient entry
|
|
414 if (o.last_selected && String(o.last_selected).charAt(0) == 'G') {
|
|
415 $(o.rows[o.last_selected].obj).find('a').first().click();
|
|
416 }
|
|
417 }
|
|
418 }
|
|
419 })
|
|
420 .init();
|
|
421
|
|
422 // remember last focused address field
|
|
423 $('#_to,#_cc,#_bcc').focus(function() { ref.env.focused_field = this; });
|
|
424 }
|
|
425
|
|
426 if (this.gui_objects.addressbookslist) {
|
|
427 this.gui_objects.folderlist = this.gui_objects.addressbookslist;
|
|
428 this.enable_command('list-addresses', true);
|
|
429 }
|
|
430
|
|
431 // ask user to send MDN
|
|
432 if (this.env.mdn_request && this.env.uid) {
|
|
433 var postact = 'sendmdn',
|
|
434 postdata = {_uid: this.env.uid, _mbox: this.env.mailbox};
|
|
435 if (!confirm(this.get_label('mdnrequest'))) {
|
|
436 postdata._flag = 'mdnsent';
|
|
437 postact = 'mark';
|
|
438 }
|
|
439 this.http_post(postact, postdata);
|
|
440 }
|
|
441
|
|
442 this.check_mailvelope(this.env.action);
|
|
443
|
|
444 // detect browser capabilities
|
|
445 if (!this.is_framed() && !this.env.extwin)
|
|
446 this.browser_capabilities_check();
|
|
447
|
|
448 break;
|
|
449
|
|
450 case 'addressbook':
|
|
451 this.env.address_group_stack = [];
|
|
452
|
|
453 if (this.gui_objects.folderlist)
|
|
454 this.env.contactfolders = $.extend($.extend({}, this.env.address_sources), this.env.contactgroups);
|
|
455
|
|
456 this.enable_command('add', 'import', this.env.writable_source);
|
|
457 this.enable_command('list', 'listgroup', 'pushgroup', 'popgroup', 'listsearch', 'search', 'reset-search', 'advanced-search', true);
|
|
458
|
|
459 if (this.gui_objects.contactslist) {
|
|
460 this.contact_list = new rcube_list_widget(this.gui_objects.contactslist,
|
|
461 {multiselect:true, draggable:this.gui_objects.folderlist?true:false, keyboard:true});
|
|
462 this.contact_list
|
|
463 .addEventListener('initrow', function(o) { ref.triggerEvent('insertrow', { cid:o.uid, row:o }); })
|
|
464 .addEventListener('keypress', function(o) { ref.contactlist_keypress(o); })
|
|
465 .addEventListener('select', function(o) { ref.contactlist_select(o); })
|
|
466 .addEventListener('dragstart', function(o) { ref.drag_start(o); })
|
|
467 .addEventListener('dragmove', function(e) { ref.drag_move(e); })
|
|
468 .addEventListener('dragend', function(e) { ref.drag_end(e); })
|
|
469 .init();
|
|
470
|
|
471 $(this.gui_objects.qsearchbox).focusin(function() { ref.contact_list.blur(); });
|
|
472
|
|
473 this.update_group_commands();
|
|
474 this.command('list');
|
|
475 }
|
|
476
|
|
477 if (this.gui_objects.savedsearchlist) {
|
|
478 this.savedsearchlist = new rcube_treelist_widget(this.gui_objects.savedsearchlist, {
|
|
479 id_prefix: 'rcmli',
|
|
480 id_encode: this.html_identifier_encode,
|
|
481 id_decode: this.html_identifier_decode
|
|
482 });
|
|
483
|
|
484 this.savedsearchlist.addEventListener('select', function(node) {
|
|
485 ref.triggerEvent('selectfolder', { folder:node.id, prefix:'rcmli' }); });
|
|
486 }
|
|
487
|
|
488 this.set_page_buttons();
|
|
489
|
|
490 if (this.env.cid) {
|
|
491 this.enable_command('show', 'edit', 'qrcode', true);
|
|
492 // register handlers for group assignment via checkboxes
|
|
493 if (this.gui_objects.editform) {
|
|
494 $('input.groupmember').change(function() {
|
|
495 ref.group_member_change(this.checked ? 'add' : 'del', ref.env.cid, ref.env.source, this.value);
|
|
496 });
|
|
497 }
|
|
498 }
|
|
499
|
|
500 if (this.gui_objects.editform) {
|
|
501 this.enable_command('save', true);
|
|
502 if (this.env.action == 'add' || this.env.action == 'edit' || this.env.action == 'search')
|
|
503 this.init_contact_form();
|
|
504 }
|
|
505 else if (this.env.action == 'print') {
|
|
506 this.print_dialog();
|
|
507 }
|
|
508
|
|
509 break;
|
|
510
|
|
511 case 'settings':
|
|
512 this.enable_command('preferences', 'identities', 'responses', 'save', 'folders', true);
|
|
513
|
|
514 if (this.env.action == 'identities') {
|
|
515 this.enable_command('add', this.env.identities_level < 2);
|
|
516 }
|
|
517 else if (this.env.action == 'edit-identity' || this.env.action == 'add-identity') {
|
|
518 this.enable_command('save', 'edit', 'toggle-editor', true);
|
|
519 this.enable_command('delete', this.env.identities_level < 2);
|
|
520
|
|
521 // initialize HTML editor
|
|
522 this.editor_init(this.env.editor_config, 'rcmfd_signature');
|
|
523 }
|
|
524 else if (this.env.action == 'folders') {
|
|
525 this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', true);
|
|
526 }
|
|
527 else if (this.env.action == 'edit-folder' && this.gui_objects.editform) {
|
|
528 this.enable_command('save', 'folder-size', true);
|
|
529 parent.rcmail.env.exists = this.env.messagecount;
|
|
530 parent.rcmail.enable_command('purge', this.env.messagecount);
|
|
531 }
|
|
532 else if (this.env.action == 'responses') {
|
|
533 this.enable_command('add', true);
|
|
534 }
|
|
535
|
|
536 if (this.gui_objects.identitieslist) {
|
|
537 this.identity_list = new rcube_list_widget(this.gui_objects.identitieslist,
|
|
538 {multiselect:false, draggable:false, keyboard:true});
|
|
539 this.identity_list
|
|
540 .addEventListener('select', function(o) { ref.identity_select(o); })
|
|
541 .addEventListener('keypress', function(o) {
|
|
542 if (o.key_pressed == o.ENTER_KEY) {
|
|
543 ref.identity_select(o);
|
|
544 }
|
|
545 })
|
|
546 .init()
|
|
547 .focus();
|
|
548 }
|
|
549 else if (this.gui_objects.sectionslist) {
|
|
550 this.sections_list = new rcube_list_widget(this.gui_objects.sectionslist, {multiselect:false, draggable:false, keyboard:true});
|
|
551 this.sections_list
|
|
552 .addEventListener('select', function(o) { ref.section_select(o); })
|
|
553 .addEventListener('keypress', function(o) { if (o.key_pressed == o.ENTER_KEY) ref.section_select(o); })
|
|
554 .init()
|
|
555 .focus();
|
|
556 }
|
|
557 else if (this.gui_objects.subscriptionlist) {
|
|
558 this.init_subscription_list();
|
|
559 }
|
|
560 else if (this.gui_objects.responseslist) {
|
|
561 this.responses_list = new rcube_list_widget(this.gui_objects.responseslist, {multiselect:false, draggable:false, keyboard:true});
|
|
562 this.responses_list
|
|
563 .addEventListener('select', function(list) {
|
|
564 var win, id = list.get_single_selection();
|
|
565 ref.enable_command('delete', !!id && $.inArray(id, ref.env.readonly_responses) < 0);
|
|
566 if (id && (win = ref.get_frame_window(ref.env.contentframe))) {
|
|
567 ref.set_busy(true);
|
|
568 ref.location_href({ _action:'edit-response', _key:id, _framed:1 }, win);
|
|
569 }
|
|
570 })
|
|
571 .init()
|
|
572 .focus();
|
|
573 }
|
|
574
|
|
575 break;
|
|
576
|
|
577 case 'login':
|
|
578 var tz, tz_name, jstz = window.jstz,
|
|
579 input_user = $('#rcmloginuser'),
|
|
580 input_tz = $('#rcmlogintz');
|
|
581
|
|
582 input_user.keyup(function(e) { return ref.login_user_keyup(e); });
|
|
583
|
|
584 if (input_user.val() == '')
|
|
585 input_user.focus();
|
|
586 else
|
|
587 $('#rcmloginpwd').focus();
|
|
588
|
|
589 // detect client timezone
|
|
590 if (jstz && (tz = jstz.determine()))
|
|
591 tz_name = tz.name();
|
|
592
|
|
593 input_tz.val(tz_name ? tz_name : (new Date().getStdTimezoneOffset() / -60));
|
|
594
|
|
595 // display 'loading' message on form submit, lock submit button
|
|
596 $('form').submit(function () {
|
|
597 $('input[type=submit]', this).prop('disabled', true);
|
|
598 ref.clear_messages();
|
|
599 ref.display_message('', 'loading');
|
|
600 });
|
|
601
|
|
602 this.enable_command('login', true);
|
|
603 break;
|
|
604 }
|
|
605
|
|
606 // select first input field in an edit form
|
|
607 if (this.gui_objects.editform)
|
|
608 $("input,select,textarea", this.gui_objects.editform)
|
|
609 .not(':hidden').not(':disabled').first().select().focus();
|
|
610
|
|
611 // prevent from form submit with Enter key in file input fields
|
|
612 if (bw.ie)
|
|
613 $('input[type=file]').keydown(function(e) { if (e.keyCode == '13') e.preventDefault(); });
|
|
614
|
|
615 // flag object as complete
|
|
616 this.loaded = true;
|
|
617 this.env.lastrefresh = new Date();
|
|
618
|
|
619 // show message
|
|
620 if (this.pending_message)
|
|
621 this.display_message.apply(this, this.pending_message);
|
|
622
|
|
623 // init treelist widget
|
|
624 if (this.gui_objects.folderlist && window.rcube_treelist_widget
|
|
625 // some plugins may load rcube_treelist_widget and there's one case
|
|
626 // when this will cause problems - addressbook widget in compose,
|
|
627 // which already has been initialized using rcube_list_widget
|
|
628 && this.gui_objects.folderlist != this.gui_objects.addressbookslist
|
|
629 ) {
|
|
630 this.treelist = new rcube_treelist_widget(this.gui_objects.folderlist, {
|
|
631 selectable: true,
|
|
632 id_prefix: 'rcmli',
|
|
633 parent_focus: true,
|
|
634 id_encode: this.html_identifier_encode,
|
|
635 id_decode: this.html_identifier_decode,
|
|
636 check_droptarget: function(node) { return !node.virtual && ref.check_droptarget(node.id) }
|
|
637 });
|
|
638
|
|
639 this.treelist
|
|
640 .addEventListener('collapse', function(node) { ref.folder_collapsed(node) })
|
|
641 .addEventListener('expand', function(node) { ref.folder_collapsed(node) })
|
|
642 .addEventListener('beforeselect', function(node) { return !ref.busy; })
|
|
643 .addEventListener('select', function(node) {
|
|
644 ref.triggerEvent('selectfolder', { folder:node.id, prefix:'rcmli' });
|
|
645 ref.mark_all_read_state();
|
|
646 });
|
|
647 }
|
|
648
|
|
649 // activate html5 file drop feature (if browser supports it and if configured)
|
|
650 if (this.gui_objects.filedrop && this.env.filedrop && ((window.XMLHttpRequest && XMLHttpRequest.prototype && XMLHttpRequest.prototype.sendAsBinary) || window.FormData)) {
|
|
651 $(document.body).on('dragover dragleave drop', function(e) { return ref.document_drag_hover(e, e.type == 'dragover'); });
|
|
652 $(this.gui_objects.filedrop).addClass('droptarget')
|
|
653 .on('dragover dragleave', function(e) { return ref.file_drag_hover(e, e.type == 'dragover'); })
|
|
654 .get(0).addEventListener('drop', function(e) { return ref.file_dropped(e); }, false);
|
|
655 }
|
|
656
|
|
657 // catch document (and iframe) mouse clicks
|
|
658 var body_mouseup = function(e) { return ref.doc_mouse_up(e); };
|
|
659 $(document.body)
|
|
660 .mouseup(body_mouseup)
|
|
661 .keydown(function(e) { return ref.doc_keypress(e); });
|
|
662
|
|
663 rcube_webmail.set_iframe_events({mouseup: body_mouseup});
|
|
664
|
|
665 // trigger init event hook
|
|
666 this.triggerEvent('init', { task:this.task, action:this.env.action });
|
|
667
|
|
668 // execute all foreign onload scripts
|
|
669 // @deprecated
|
|
670 for (n in this.onloads) {
|
|
671 if (typeof this.onloads[n] === 'string')
|
|
672 eval(this.onloads[n]);
|
|
673 else if (typeof this.onloads[n] === 'function')
|
|
674 this.onloads[n]();
|
|
675 }
|
|
676
|
|
677 // start keep-alive and refresh intervals
|
|
678 this.start_refresh();
|
|
679 this.start_keepalive();
|
|
680 };
|
|
681
|
|
682 this.log = function(msg)
|
|
683 {
|
|
684 if (this.env.devel_mode && window.console && console.log)
|
|
685 console.log(msg);
|
|
686 };
|
|
687
|
|
688 /*********************************************************/
|
|
689 /********* client command interface *********/
|
|
690 /*********************************************************/
|
|
691
|
|
692 // execute a specific command on the web client
|
|
693 this.command = function(command, props, obj, event)
|
|
694 {
|
|
695 var ret, uid, cid, url, flag, aborted = false;
|
|
696
|
|
697 if (obj && obj.blur && !(event && rcube_event.is_keyboard(event)))
|
|
698 obj.blur();
|
|
699
|
|
700 // do nothing if interface is locked by another command
|
|
701 // with exception for searching reset and menu
|
|
702 if (this.busy && !(command == 'reset-search' && this.last_command == 'search') && !command.match(/^menu-/))
|
|
703 return false;
|
|
704
|
|
705 // let the browser handle this click (shift/ctrl usually opens the link in a new window/tab)
|
|
706 if ((obj && obj.href && String(obj.href).indexOf('#') < 0) && rcube_event.get_modifier(event)) {
|
|
707 return true;
|
|
708 }
|
|
709
|
|
710 // command not supported or allowed
|
|
711 if (!this.commands[command]) {
|
|
712 // pass command to parent window
|
|
713 if (this.is_framed())
|
|
714 parent.rcmail.command(command, props);
|
|
715
|
|
716 return false;
|
|
717 }
|
|
718
|
|
719 // check input before leaving compose step
|
|
720 if (this.task == 'mail' && this.env.action == 'compose' && !this.env.server_error && command != 'save-pref'
|
|
721 && $.inArray(command, this.env.compose_commands) < 0
|
|
722 ) {
|
|
723 if (!this.env.is_sent && this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning')))
|
|
724 return false;
|
|
725
|
|
726 // remove copy from local storage if compose screen is left intentionally
|
|
727 this.remove_compose_data(this.env.compose_id);
|
|
728 this.compose_skip_unsavedcheck = true;
|
|
729 }
|
|
730
|
|
731 this.last_command = command;
|
|
732
|
|
733 // process external commands
|
|
734 if (typeof this.command_handlers[command] === 'function') {
|
|
735 ret = this.command_handlers[command](props, obj, event);
|
|
736 return ret !== undefined ? ret : (obj ? false : true);
|
|
737 }
|
|
738 else if (typeof this.command_handlers[command] === 'string') {
|
|
739 ret = window[this.command_handlers[command]](props, obj, event);
|
|
740 return ret !== undefined ? ret : (obj ? false : true);
|
|
741 }
|
|
742
|
|
743 // trigger plugin hooks
|
|
744 this.triggerEvent('actionbefore', {props:props, action:command, originalEvent:event});
|
|
745 ret = this.triggerEvent('before'+command, props || event);
|
|
746 if (ret !== undefined) {
|
|
747 // abort if one of the handlers returned false
|
|
748 if (ret === false)
|
|
749 return false;
|
|
750 else
|
|
751 props = ret;
|
|
752 }
|
|
753
|
|
754 ret = undefined;
|
|
755
|
|
756 // process internal command
|
|
757 switch (command) {
|
|
758
|
|
759 case 'login':
|
|
760 if (this.gui_objects.loginform)
|
|
761 this.gui_objects.loginform.submit();
|
|
762 break;
|
|
763
|
|
764 // commands to switch task
|
|
765 case 'logout':
|
|
766 case 'mail':
|
|
767 case 'addressbook':
|
|
768 case 'settings':
|
|
769 this.switch_task(command);
|
|
770 break;
|
|
771
|
|
772 case 'about':
|
|
773 this.redirect('?_task=settings&_action=about', false);
|
|
774 break;
|
|
775
|
|
776 case 'permaurl':
|
|
777 if (obj && obj.href && obj.target)
|
|
778 return true;
|
|
779 else if (this.env.permaurl)
|
|
780 parent.location.href = this.env.permaurl;
|
|
781 break;
|
|
782
|
|
783 case 'extwin':
|
|
784 if (this.env.action == 'compose') {
|
|
785 var form = this.gui_objects.messageform,
|
|
786 win = this.open_window('');
|
|
787
|
|
788 if (win) {
|
|
789 this.save_compose_form_local();
|
|
790 this.compose_skip_unsavedcheck = true;
|
|
791 $("input[name='_action']", form).val('compose');
|
|
792 form.action = this.url('mail/compose', { _id: this.env.compose_id, _extwin: 1 });
|
|
793 form.target = win.name;
|
|
794 form.submit();
|
|
795 }
|
|
796 }
|
|
797 else {
|
|
798 this.open_window(this.env.permaurl, true);
|
|
799 }
|
|
800 break;
|
|
801
|
|
802 case 'change-format':
|
|
803 url = this.env.permaurl + '&_format=' + props;
|
|
804
|
|
805 if (this.env.action == 'preview')
|
|
806 url = url.replace(/_action=show/, '_action=preview') + '&_framed=1';
|
|
807 if (this.env.extwin)
|
|
808 url += '&_extwin=1';
|
|
809
|
|
810 location.href = url;
|
|
811 break;
|
|
812
|
|
813 case 'menu-open':
|
|
814 if (props && props.menu == 'attachmentmenu') {
|
|
815 var mimetype = this.env.attachments[props.id];
|
|
816 if (mimetype && mimetype.mimetype) // in compose format is different
|
|
817 mimetype = mimetype.mimetype;
|
|
818 this.enable_command('open-attachment', mimetype && this.env.mimetypes && $.inArray(mimetype, this.env.mimetypes) >= 0);
|
|
819 }
|
|
820 this.show_menu(props, props.show || undefined, event);
|
|
821 break;
|
|
822
|
|
823 case 'menu-close':
|
|
824 this.hide_menu(props, event);
|
|
825 break;
|
|
826
|
|
827 case 'menu-save':
|
|
828 this.triggerEvent(command, {props:props, originalEvent:event});
|
|
829 return false;
|
|
830
|
|
831 case 'open':
|
|
832 if (uid = this.get_single_uid()) {
|
|
833 obj.href = this.url('show', this.params_from_uid(uid));
|
|
834 return true;
|
|
835 }
|
|
836 break;
|
|
837
|
|
838 case 'close':
|
|
839 if (this.env.extwin)
|
|
840 window.close();
|
|
841 break;
|
|
842
|
|
843 case 'list':
|
|
844 if (props && props != '') {
|
|
845 this.reset_qsearch(true);
|
|
846 }
|
|
847 if (this.env.action == 'compose' && this.env.extwin) {
|
|
848 window.close();
|
|
849 }
|
|
850 else if (this.task == 'mail') {
|
|
851 this.list_mailbox(props);
|
|
852 this.set_button_titles();
|
|
853 }
|
|
854 else if (this.task == 'addressbook')
|
|
855 this.list_contacts(props);
|
|
856 break;
|
|
857
|
|
858 case 'set-listmode':
|
|
859 this.set_list_options(null, undefined, undefined, props == 'threads' ? 1 : 0);
|
|
860 break;
|
|
861
|
|
862 case 'sort':
|
|
863 var sort_order = this.env.sort_order,
|
|
864 sort_col = !this.env.disabled_sort_col ? props : this.env.sort_col;
|
|
865
|
|
866 if (!this.env.disabled_sort_order)
|
|
867 sort_order = this.env.sort_col == sort_col && sort_order == 'ASC' ? 'DESC' : 'ASC';
|
|
868
|
|
869 // set table header and update env
|
|
870 this.set_list_sorting(sort_col, sort_order);
|
|
871
|
|
872 // reload message list
|
|
873 this.list_mailbox('', '', sort_col+'_'+sort_order);
|
|
874 break;
|
|
875
|
|
876 case 'nextpage':
|
|
877 this.list_page('next');
|
|
878 break;
|
|
879
|
|
880 case 'lastpage':
|
|
881 this.list_page('last');
|
|
882 break;
|
|
883
|
|
884 case 'previouspage':
|
|
885 this.list_page('prev');
|
|
886 break;
|
|
887
|
|
888 case 'firstpage':
|
|
889 this.list_page('first');
|
|
890 break;
|
|
891
|
|
892 case 'expunge':
|
|
893 if (this.env.exists)
|
|
894 this.expunge_mailbox(this.env.mailbox);
|
|
895 break;
|
|
896
|
|
897 case 'purge':
|
|
898 case 'empty-mailbox':
|
|
899 if (this.env.exists)
|
|
900 this.purge_mailbox(this.env.mailbox);
|
|
901 break;
|
|
902
|
|
903 // common commands used in multiple tasks
|
|
904 case 'show':
|
|
905 if (this.task == 'mail') {
|
|
906 uid = this.get_single_uid();
|
|
907 if (uid && (!this.env.uid || uid != this.env.uid)) {
|
|
908 if (this.env.mailbox == this.env.drafts_mailbox)
|
|
909 this.open_compose_step({ _draft_uid: uid, _mbox: this.env.mailbox });
|
|
910 else
|
|
911 this.show_message(uid);
|
|
912 }
|
|
913 }
|
|
914 else if (this.task == 'addressbook') {
|
|
915 cid = props ? props : this.get_single_cid();
|
|
916 if (cid && !(this.env.action == 'show' && cid == this.env.cid))
|
|
917 this.load_contact(cid, 'show');
|
|
918 }
|
|
919 break;
|
|
920
|
|
921 case 'add':
|
|
922 if (this.task == 'addressbook')
|
|
923 this.load_contact(0, 'add');
|
|
924 else if (this.task == 'settings' && this.env.action == 'responses') {
|
|
925 var frame;
|
|
926 if ((frame = this.get_frame_window(this.env.contentframe))) {
|
|
927 this.set_busy(true);
|
|
928 this.location_href({ _action:'add-response', _framed:1 }, frame);
|
|
929 }
|
|
930 }
|
|
931 else if (this.task == 'settings') {
|
|
932 this.identity_list.clear_selection();
|
|
933 this.load_identity(0, 'add-identity');
|
|
934 }
|
|
935 break;
|
|
936
|
|
937 case 'edit':
|
|
938 if (this.task == 'addressbook' && (cid = this.get_single_cid()))
|
|
939 this.load_contact(cid, 'edit');
|
|
940 else if (this.task == 'settings' && props)
|
|
941 this.load_identity(props, 'edit-identity');
|
|
942 else if (this.task == 'mail' && (uid = this.get_single_uid())) {
|
|
943 url = { _mbox: this.get_message_mailbox(uid) };
|
|
944 url[this.env.mailbox == this.env.drafts_mailbox && props != 'new' ? '_draft_uid' : '_uid'] = uid;
|
|
945 this.open_compose_step(url);
|
|
946 }
|
|
947 break;
|
|
948
|
|
949 case 'save':
|
|
950 var input, form = this.gui_objects.editform;
|
|
951 if (form) {
|
|
952 // adv. search
|
|
953 if (this.env.action == 'search') {
|
|
954 }
|
|
955 // user prefs
|
|
956 else if ((input = $("input[name='_pagesize']", form)) && input.length && isNaN(parseInt(input.val()))) {
|
|
957 alert(this.get_label('nopagesizewarning'));
|
|
958 input.focus();
|
|
959 break;
|
|
960 }
|
|
961 // contacts/identities
|
|
962 else {
|
|
963 // reload form
|
|
964 if (props == 'reload') {
|
|
965 form.action += '&_reload=1';
|
|
966 }
|
|
967 else if (this.task == 'settings' && (this.env.identities_level % 2) == 0 &&
|
|
968 (input = $("input[name='_email']", form)) && input.length && !rcube_check_email(input.val())
|
|
969 ) {
|
|
970 alert(this.get_label('noemailwarning'));
|
|
971 input.focus();
|
|
972 break;
|
|
973 }
|
|
974 }
|
|
975
|
|
976 // add selected source (on the list)
|
|
977 if (parent.rcmail && parent.rcmail.env.source)
|
|
978 form.action = this.add_url(form.action, '_orig_source', parent.rcmail.env.source);
|
|
979
|
|
980 form.submit();
|
|
981 }
|
|
982 break;
|
|
983
|
|
984 case 'delete':
|
|
985 // mail task
|
|
986 if (this.task == 'mail')
|
|
987 this.delete_messages(event);
|
|
988 // addressbook task
|
|
989 else if (this.task == 'addressbook')
|
|
990 this.delete_contacts();
|
|
991 // settings: canned response
|
|
992 else if (this.task == 'settings' && this.env.action == 'responses')
|
|
993 this.delete_response();
|
|
994 // settings: user identities
|
|
995 else if (this.task == 'settings')
|
|
996 this.delete_identity();
|
|
997 break;
|
|
998
|
|
999 // mail task commands
|
|
1000 case 'move':
|
|
1001 case 'moveto': // deprecated
|
|
1002 if (this.task == 'mail')
|
|
1003 this.move_messages(props, event);
|
|
1004 else if (this.task == 'addressbook')
|
|
1005 this.move_contacts(props);
|
|
1006 break;
|
|
1007
|
|
1008 case 'copy':
|
|
1009 if (this.task == 'mail')
|
|
1010 this.copy_messages(props, event);
|
|
1011 else if (this.task == 'addressbook')
|
|
1012 this.copy_contacts(props);
|
|
1013 break;
|
|
1014
|
|
1015 case 'mark':
|
|
1016 if (props)
|
|
1017 this.mark_message(props);
|
|
1018 break;
|
|
1019
|
|
1020 case 'toggle_status':
|
|
1021 case 'toggle_flag':
|
|
1022 flag = command == 'toggle_flag' ? 'flagged' : 'read';
|
|
1023
|
|
1024 if (uid = props) {
|
|
1025 // toggle flagged/unflagged
|
|
1026 if (flag == 'flagged') {
|
|
1027 if (this.message_list.rows[uid].flagged)
|
|
1028 flag = 'unflagged';
|
|
1029 }
|
|
1030 // toggle read/unread
|
|
1031 else if (this.message_list.rows[uid].deleted)
|
|
1032 flag = 'undelete';
|
|
1033 else if (!this.message_list.rows[uid].unread)
|
|
1034 flag = 'unread';
|
|
1035
|
|
1036 this.mark_message(flag, uid);
|
|
1037 }
|
|
1038
|
|
1039 break;
|
|
1040
|
|
1041 case 'always-load':
|
|
1042 if (this.env.uid && this.env.sender) {
|
|
1043 this.add_contact(this.env.sender);
|
|
1044 setTimeout(function(){ ref.command('load-images'); }, 300);
|
|
1045 break;
|
|
1046 }
|
|
1047
|
|
1048 case 'load-images':
|
|
1049 if (this.env.uid)
|
|
1050 this.show_message(this.env.uid, true, this.env.action=='preview');
|
|
1051 break;
|
|
1052
|
|
1053 case 'load-attachment':
|
|
1054 case 'open-attachment':
|
|
1055 case 'download-attachment':
|
|
1056 var params, mimetype = this.env.attachments[props];
|
|
1057
|
|
1058 if (this.env.action == 'compose') {
|
|
1059 params = {_file: props, _id: this.env.compose_id};
|
|
1060 mimetype = mimetype ? mimetype.mimetype : '';
|
|
1061 }
|
|
1062 else {
|
|
1063 params = {_mbox: this.env.mailbox, _uid: this.env.uid, _part: props};
|
|
1064 }
|
|
1065
|
|
1066 // open attachment in frame if it's of a supported mimetype
|
|
1067 if (command != 'download-attachment' && mimetype && this.env.mimetypes && $.inArray(mimetype, this.env.mimetypes) >= 0) {
|
|
1068 if (this.open_window(this.url('get', $.extend({_frame: 1}, params))))
|
|
1069 break;
|
|
1070 }
|
|
1071
|
|
1072 params._download = 1;
|
|
1073
|
|
1074 // prevent from page unload warning in compose
|
|
1075 this.compose_skip_unsavedcheck = 1;
|
|
1076 this.goto_url('get', params, false, true);
|
|
1077 this.compose_skip_unsavedcheck = 0;
|
|
1078
|
|
1079 break;
|
|
1080
|
|
1081 case 'select-all':
|
|
1082 this.select_all_mode = props ? false : true;
|
|
1083 this.dummy_select = true; // prevent msg opening if there's only one msg on the list
|
|
1084 if (props == 'invert')
|
|
1085 this.message_list.invert_selection();
|
|
1086 else
|
|
1087 this.message_list.select_all(props == 'page' ? '' : props);
|
|
1088 this.dummy_select = null;
|
|
1089 break;
|
|
1090
|
|
1091 case 'select-none':
|
|
1092 this.select_all_mode = false;
|
|
1093 this.message_list.clear_selection();
|
|
1094 break;
|
|
1095
|
|
1096 case 'expand-all':
|
|
1097 this.env.autoexpand_threads = 1;
|
|
1098 this.message_list.expand_all();
|
|
1099 break;
|
|
1100
|
|
1101 case 'expand-unread':
|
|
1102 this.env.autoexpand_threads = 2;
|
|
1103 this.message_list.collapse_all();
|
|
1104 this.expand_unread();
|
|
1105 break;
|
|
1106
|
|
1107 case 'collapse-all':
|
|
1108 this.env.autoexpand_threads = 0;
|
|
1109 this.message_list.collapse_all();
|
|
1110 break;
|
|
1111
|
|
1112 case 'nextmessage':
|
|
1113 if (this.env.next_uid)
|
|
1114 this.show_message(this.env.next_uid, false, this.env.action == 'preview');
|
|
1115 break;
|
|
1116
|
|
1117 case 'lastmessage':
|
|
1118 if (this.env.last_uid)
|
|
1119 this.show_message(this.env.last_uid);
|
|
1120 break;
|
|
1121
|
|
1122 case 'previousmessage':
|
|
1123 if (this.env.prev_uid)
|
|
1124 this.show_message(this.env.prev_uid, false, this.env.action == 'preview');
|
|
1125 break;
|
|
1126
|
|
1127 case 'firstmessage':
|
|
1128 if (this.env.first_uid)
|
|
1129 this.show_message(this.env.first_uid);
|
|
1130 break;
|
|
1131
|
|
1132 case 'compose':
|
|
1133 url = {};
|
|
1134
|
|
1135 if (this.task == 'mail') {
|
|
1136 url = {_mbox: this.env.mailbox, _search: this.env.search_request};
|
|
1137 if (props)
|
|
1138 url._to = props;
|
|
1139 }
|
|
1140 // modify url if we're in addressbook
|
|
1141 else if (this.task == 'addressbook') {
|
|
1142 // switch to mail compose step directly
|
|
1143 if (props && props.indexOf('@') > 0) {
|
|
1144 url._to = props;
|
|
1145 }
|
|
1146 else {
|
|
1147 var a_cids = [];
|
|
1148 // use contact id passed as command parameter
|
|
1149 if (props)
|
|
1150 a_cids.push(props);
|
|
1151 // get selected contacts
|
|
1152 else if (this.contact_list)
|
|
1153 a_cids = this.contact_list.get_selection();
|
|
1154
|
|
1155 if (a_cids.length)
|
|
1156 this.http_post('mailto', { _cid: a_cids.join(','), _source: this.env.source }, true);
|
|
1157 else if (this.env.group)
|
|
1158 this.http_post('mailto', { _gid: this.env.group, _source: this.env.source }, true);
|
|
1159
|
|
1160 break;
|
|
1161 }
|
|
1162 }
|
|
1163 else if (props && typeof props == 'string') {
|
|
1164 url._to = props;
|
|
1165 }
|
|
1166 else if (props && typeof props == 'object') {
|
|
1167 $.extend(url, props);
|
|
1168 }
|
|
1169
|
|
1170 this.open_compose_step(url);
|
|
1171 break;
|
|
1172
|
|
1173 case 'spellcheck':
|
|
1174 if (this.spellcheck_state()) {
|
|
1175 this.editor.spellcheck_stop();
|
|
1176 }
|
|
1177 else {
|
|
1178 this.editor.spellcheck_start();
|
|
1179 }
|
|
1180 break;
|
|
1181
|
|
1182 case 'savedraft':
|
|
1183 // Reset the auto-save timer
|
|
1184 clearTimeout(this.save_timer);
|
|
1185
|
|
1186 // compose form did not change (and draft wasn't saved already)
|
|
1187 if (this.env.draft_id && this.cmp_hash == this.compose_field_hash()) {
|
|
1188 this.auto_save_start();
|
|
1189 break;
|
|
1190 }
|
|
1191
|
|
1192 this.submit_messageform(true);
|
|
1193 break;
|
|
1194
|
|
1195 case 'send':
|
|
1196 if (!props.nocheck && !this.env.is_sent && !this.check_compose_input(command))
|
|
1197 break;
|
|
1198
|
|
1199 // Reset the auto-save timer
|
|
1200 clearTimeout(this.save_timer);
|
|
1201
|
|
1202 this.submit_messageform();
|
|
1203 break;
|
|
1204
|
|
1205 case 'send-attachment':
|
|
1206 // Reset the auto-save timer
|
|
1207 clearTimeout(this.save_timer);
|
|
1208
|
|
1209 if (!(flag = this.upload_file(props || this.gui_objects.uploadform, 'upload'))) {
|
|
1210 if (flag !== false)
|
|
1211 alert(this.get_label('selectimportfile'));
|
|
1212 aborted = true;
|
|
1213 }
|
|
1214 break;
|
|
1215
|
|
1216 case 'insert-sig':
|
|
1217 this.change_identity($("[name='_from']")[0], true);
|
|
1218 break;
|
|
1219
|
|
1220 case 'list-addresses':
|
|
1221 this.list_contacts(props);
|
|
1222 this.enable_command('add-recipient', false);
|
|
1223 break;
|
|
1224
|
|
1225 case 'add-recipient':
|
|
1226 this.compose_add_recipient(props);
|
|
1227 break;
|
|
1228
|
|
1229 case 'reply-all':
|
|
1230 case 'reply-list':
|
|
1231 case 'reply':
|
|
1232 if (uid = this.get_single_uid()) {
|
|
1233 url = {_reply_uid: uid, _mbox: this.get_message_mailbox(uid), _search: this.env.search_request};
|
|
1234 if (command == 'reply-all')
|
|
1235 // do reply-list, when list is detected and popup menu wasn't used
|
|
1236 url._all = (!props && this.env.reply_all_mode == 1 && this.commands['reply-list'] ? 'list' : 'all');
|
|
1237 else if (command == 'reply-list')
|
|
1238 url._all = 'list';
|
|
1239
|
|
1240 this.open_compose_step(url);
|
|
1241 }
|
|
1242 break;
|
|
1243
|
|
1244 case 'forward-attachment':
|
|
1245 case 'forward-inline':
|
|
1246 case 'forward':
|
|
1247 var uids = this.env.uid ? [this.env.uid] : (this.message_list ? this.message_list.get_selection() : []);
|
|
1248 if (uids.length) {
|
|
1249 url = { _forward_uid: this.uids_to_list(uids), _mbox: this.env.mailbox, _search: this.env.search_request };
|
|
1250 if (command == 'forward-attachment' || (!props && this.env.forward_attachment) || uids.length > 1)
|
|
1251 url._attachment = 1;
|
|
1252 this.open_compose_step(url);
|
|
1253 }
|
|
1254 break;
|
|
1255
|
|
1256 case 'print':
|
|
1257 if (this.task == 'addressbook') {
|
|
1258 if (uid = this.contact_list.get_single_selection()) {
|
|
1259 url = '&_action=print&_cid=' + uid;
|
|
1260 if (this.env.source)
|
|
1261 url += '&_source=' + urlencode(this.env.source);
|
|
1262 this.open_window(this.env.comm_path + url, true, true);
|
|
1263 }
|
|
1264 }
|
|
1265 else if (this.env.action == 'get' && !this.env.is_message) {
|
|
1266 this.gui_objects.messagepartframe.contentWindow.print();
|
|
1267 }
|
|
1268 else if (uid = this.get_single_uid()) {
|
|
1269 url = this.url('print', this.params_from_uid(uid, {_safe: this.env.safemode ? 1 : 0}));
|
|
1270 if (this.open_window(url, true, true)) {
|
|
1271 if (this.env.action != 'show' && this.env.action != 'get')
|
|
1272 this.mark_message('read', uid);
|
|
1273 }
|
|
1274 }
|
|
1275 break;
|
|
1276
|
|
1277 case 'viewsource':
|
|
1278 if (uid = this.get_single_uid())
|
|
1279 this.open_window(this.url('viewsource', this.params_from_uid(uid)), true, true);
|
|
1280 break;
|
|
1281
|
|
1282 case 'download':
|
|
1283 if (this.env.action == 'get') {
|
|
1284 location.href = this.secure_url(location.href.replace(/_frame=/, '_download='));
|
|
1285 }
|
|
1286 else if (uid = this.get_single_uid()) {
|
|
1287 this.goto_url('viewsource', this.params_from_uid(uid, {_save: 1}), false, true);
|
|
1288 }
|
|
1289 break;
|
|
1290
|
|
1291 // quicksearch
|
|
1292 case 'search':
|
|
1293 ret = this.qsearch(props);
|
|
1294 break;
|
|
1295
|
|
1296 // reset quicksearch
|
|
1297 case 'reset-search':
|
|
1298 var n, s = this.env.search_request || this.env.qsearch;
|
|
1299
|
|
1300 this.reset_qsearch(true);
|
|
1301
|
|
1302 if (s && this.env.action == 'compose') {
|
|
1303 if (this.contact_list)
|
|
1304 this.list_contacts_clear();
|
|
1305 }
|
|
1306 else if (s && this.env.mailbox) {
|
|
1307 this.list_mailbox(this.env.mailbox, 1);
|
|
1308 }
|
|
1309 else if (s && this.task == 'addressbook') {
|
|
1310 if (this.env.source == '') {
|
|
1311 for (n in this.env.address_sources) break;
|
|
1312 this.env.source = n;
|
|
1313 this.env.group = '';
|
|
1314 }
|
|
1315 this.list_contacts(this.env.source, this.env.group, 1);
|
|
1316 }
|
|
1317 break;
|
|
1318
|
|
1319 case 'pushgroup':
|
|
1320 // add group ID and current search to stack
|
|
1321 var group = {
|
|
1322 id: props.id,
|
|
1323 search_request: this.env.search_request,
|
|
1324 page: this.env.current_page,
|
|
1325 search: this.env.search_request && this.gui_objects.qsearchbox ? this.gui_objects.qsearchbox.value : null
|
|
1326 };
|
|
1327
|
|
1328 this.env.address_group_stack.push(group);
|
|
1329 if (obj && event)
|
|
1330 rcube_event.cancel(event);
|
|
1331
|
|
1332 case 'listgroup':
|
|
1333 this.reset_qsearch();
|
|
1334 this.list_contacts(props.source, props.id, 1, group);
|
|
1335 break;
|
|
1336
|
|
1337 case 'popgroup':
|
|
1338 if (this.env.address_group_stack.length) {
|
|
1339 var old = this.env.address_group_stack.pop();
|
|
1340 this.reset_qsearch();
|
|
1341
|
|
1342 if (old.search_request) {
|
|
1343 // this code is executed when going back to the search result
|
|
1344 if (old.search && this.gui_objects.qsearchbox)
|
|
1345 $(this.gui_objects.qsearchbox).val(old.search);
|
|
1346 this.env.search_request = old.search_request;
|
|
1347 this.list_contacts_remote(null, null, this.env.current_page = old.page);
|
|
1348 }
|
|
1349 else
|
|
1350 this.list_contacts(props.source, this.env.address_group_stack[this.env.address_group_stack.length-1].id);
|
|
1351 }
|
|
1352 break;
|
|
1353
|
|
1354 case 'import-messages':
|
|
1355 var form = props || this.gui_objects.importform,
|
|
1356 importlock = this.set_busy(true, 'importwait');
|
|
1357
|
|
1358 $('input[name="_unlock"]', form).val(importlock);
|
|
1359
|
|
1360 if (!(flag = this.upload_file(form, 'import', importlock))) {
|
|
1361 this.set_busy(false, null, importlock);
|
|
1362 if (flag !== false)
|
|
1363 alert(this.get_label('selectimportfile'));
|
|
1364 aborted = true;
|
|
1365 }
|
|
1366 break;
|
|
1367
|
|
1368 case 'import':
|
|
1369 if (this.env.action == 'import' && this.gui_objects.importform) {
|
|
1370 var file = document.getElementById('rcmimportfile');
|
|
1371 if (file && !file.value) {
|
|
1372 alert(this.get_label('selectimportfile'));
|
|
1373 aborted = true;
|
|
1374 break;
|
|
1375 }
|
|
1376 this.gui_objects.importform.submit();
|
|
1377 this.set_busy(true, 'importwait');
|
|
1378 this.lock_form(this.gui_objects.importform, true);
|
|
1379 }
|
|
1380 else
|
|
1381 this.goto_url('import', (this.env.source ? '_target='+urlencode(this.env.source)+'&' : ''));
|
|
1382 break;
|
|
1383
|
|
1384 case 'export':
|
|
1385 if (this.contact_list.rowcount > 0) {
|
|
1386 this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _search: this.env.search_request }, false, true);
|
|
1387 }
|
|
1388 break;
|
|
1389
|
|
1390 case 'export-selected':
|
|
1391 if (this.contact_list.rowcount > 0) {
|
|
1392 this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _cid: this.contact_list.get_selection().join(',') }, false, true);
|
|
1393 }
|
|
1394 break;
|
|
1395
|
|
1396 case 'upload-photo':
|
|
1397 this.upload_contact_photo(props || this.gui_objects.uploadform);
|
|
1398 break;
|
|
1399
|
|
1400 case 'delete-photo':
|
|
1401 this.replace_contact_photo('-del-');
|
|
1402 break;
|
|
1403
|
|
1404 // user settings commands
|
|
1405 case 'preferences':
|
|
1406 case 'identities':
|
|
1407 case 'responses':
|
|
1408 case 'folders':
|
|
1409 this.goto_url('settings/' + command);
|
|
1410 break;
|
|
1411
|
|
1412 case 'undo':
|
|
1413 this.http_request('undo', '', this.display_message('', 'loading'));
|
|
1414 break;
|
|
1415
|
|
1416 // unified command call (command name == function name)
|
|
1417 default:
|
|
1418 var func = command.replace(/-/g, '_');
|
|
1419 if (this[func] && typeof this[func] === 'function') {
|
|
1420 ret = this[func](props, obj, event);
|
|
1421 }
|
|
1422 break;
|
|
1423 }
|
|
1424
|
|
1425 if (!aborted && this.triggerEvent('after'+command, props) === false)
|
|
1426 ret = false;
|
|
1427 this.triggerEvent('actionafter', { props:props, action:command, aborted:aborted, ret:ret });
|
|
1428
|
|
1429 return ret === false ? false : obj ? false : true;
|
|
1430 };
|
|
1431
|
|
1432 // set command(s) enabled or disabled
|
|
1433 this.enable_command = function()
|
|
1434 {
|
|
1435 var i, n, args = Array.prototype.slice.call(arguments),
|
|
1436 enable = args.pop(), cmd;
|
|
1437
|
|
1438 for (n=0; n<args.length; n++) {
|
|
1439 cmd = args[n];
|
|
1440 // argument of type array
|
|
1441 if (typeof cmd === 'string') {
|
|
1442 this.commands[cmd] = enable;
|
|
1443 this.set_button(cmd, (enable ? 'act' : 'pas'));
|
|
1444 this.triggerEvent('enable-command', {command: cmd, status: enable});
|
|
1445 }
|
|
1446 // push array elements into commands array
|
|
1447 else {
|
|
1448 for (i in cmd)
|
|
1449 args.push(cmd[i]);
|
|
1450 }
|
|
1451 }
|
|
1452 };
|
|
1453
|
|
1454 this.command_enabled = function(cmd)
|
|
1455 {
|
|
1456 return this.commands[cmd];
|
|
1457 };
|
|
1458
|
|
1459 // lock/unlock interface
|
|
1460 this.set_busy = function(a, message, id)
|
|
1461 {
|
|
1462 if (a && message) {
|
|
1463 var msg = this.get_label(message);
|
|
1464 if (msg == message)
|
|
1465 msg = 'Loading...';
|
|
1466
|
|
1467 id = this.display_message(msg, 'loading');
|
|
1468 }
|
|
1469 else if (!a && id) {
|
|
1470 this.hide_message(id);
|
|
1471 }
|
|
1472
|
|
1473 this.busy = a;
|
|
1474 //document.body.style.cursor = a ? 'wait' : 'default';
|
|
1475
|
|
1476 if (this.gui_objects.editform)
|
|
1477 this.lock_form(this.gui_objects.editform, a);
|
|
1478
|
|
1479 return id;
|
|
1480 };
|
|
1481
|
|
1482 // return a localized string
|
|
1483 this.get_label = function(name, domain)
|
|
1484 {
|
|
1485 if (domain && this.labels[domain+'.'+name])
|
|
1486 return this.labels[domain+'.'+name];
|
|
1487 else if (this.labels[name])
|
|
1488 return this.labels[name];
|
|
1489 else
|
|
1490 return name;
|
|
1491 };
|
|
1492
|
|
1493 // alias for convenience reasons
|
|
1494 this.gettext = this.get_label;
|
|
1495
|
|
1496 // switch to another application task
|
|
1497 this.switch_task = function(task)
|
|
1498 {
|
|
1499 if (this.task === task && task != 'mail')
|
|
1500 return;
|
|
1501
|
|
1502 var url = this.get_task_url(task);
|
|
1503
|
|
1504 if (task == 'mail')
|
|
1505 url += '&_mbox=INBOX';
|
|
1506 else if (task == 'logout' && !this.env.server_error) {
|
|
1507 url = this.secure_url(url);
|
|
1508 this.clear_compose_data();
|
|
1509 }
|
|
1510
|
|
1511 this.redirect(url);
|
|
1512 };
|
|
1513
|
|
1514 this.get_task_url = function(task, url)
|
|
1515 {
|
|
1516 if (!url)
|
|
1517 url = this.env.comm_path;
|
|
1518
|
|
1519 if (url.match(/[?&]_task=[a-zA-Z0-9_-]+/))
|
|
1520 return url.replace(/_task=[a-zA-Z0-9_-]+/, '_task=' + task);
|
|
1521 else
|
|
1522 return url.replace(/\?.*$/, '') + '?_task=' + task;
|
|
1523 };
|
|
1524
|
|
1525 this.reload = function(delay)
|
|
1526 {
|
|
1527 if (this.is_framed())
|
|
1528 parent.rcmail.reload(delay);
|
|
1529 else if (delay)
|
|
1530 setTimeout(function() { ref.reload(); }, delay);
|
|
1531 else if (window.location)
|
|
1532 location.href = this.url('', {_extwin: this.env.extwin});
|
|
1533 };
|
|
1534
|
|
1535 // Add variable to GET string, replace old value if exists
|
|
1536 this.add_url = function(url, name, value)
|
|
1537 {
|
|
1538 value = urlencode(value);
|
|
1539
|
|
1540 if (/(\?.*)$/.test(url)) {
|
|
1541 var urldata = RegExp.$1,
|
|
1542 datax = RegExp('((\\?|&)'+RegExp.escape(name)+'=[^&]*)');
|
|
1543
|
|
1544 if (datax.test(urldata)) {
|
|
1545 urldata = urldata.replace(datax, RegExp.$2 + name + '=' + value);
|
|
1546 }
|
|
1547 else
|
|
1548 urldata += '&' + name + '=' + value
|
|
1549
|
|
1550 return url.replace(/(\?.*)$/, urldata);
|
|
1551 }
|
|
1552
|
|
1553 return url + '?' + name + '=' + value;
|
|
1554 };
|
|
1555
|
|
1556 // append CSRF protection token to the given url
|
|
1557 this.secure_url = function(url)
|
|
1558 {
|
|
1559 return this.add_url(url, '_token', this.env.request_token);
|
|
1560 },
|
|
1561
|
|
1562 this.is_framed = function()
|
|
1563 {
|
|
1564 return this.env.framed && parent.rcmail && parent.rcmail != this && typeof parent.rcmail.command == 'function';
|
|
1565 };
|
|
1566
|
|
1567 this.save_pref = function(prop)
|
|
1568 {
|
|
1569 var request = {_name: prop.name, _value: prop.value};
|
|
1570
|
|
1571 if (prop.session)
|
|
1572 request._session = prop.session;
|
|
1573 if (prop.env)
|
|
1574 this.env[prop.env] = prop.value;
|
|
1575
|
|
1576 this.http_post('save-pref', request);
|
|
1577 };
|
|
1578
|
|
1579 this.html_identifier = function(str, encode)
|
|
1580 {
|
|
1581 return encode ? this.html_identifier_encode(str) : String(str).replace(this.identifier_expr, '_');
|
|
1582 };
|
|
1583
|
|
1584 this.html_identifier_encode = function(str)
|
|
1585 {
|
|
1586 return Base64.encode(String(str)).replace(/=+$/, '').replace(/\+/g, '-').replace(/\//g, '_');
|
|
1587 };
|
|
1588
|
|
1589 this.html_identifier_decode = function(str)
|
|
1590 {
|
|
1591 str = String(str).replace(/-/g, '+').replace(/_/g, '/');
|
|
1592
|
|
1593 while (str.length % 4) str += '=';
|
|
1594
|
|
1595 return Base64.decode(str);
|
|
1596 };
|
|
1597
|
|
1598
|
|
1599 /*********************************************************/
|
|
1600 /********* event handling methods *********/
|
|
1601 /*********************************************************/
|
|
1602
|
|
1603 this.drag_menu = function(e, target)
|
|
1604 {
|
|
1605 var modkey = rcube_event.get_modifier(e),
|
|
1606 menu = this.gui_objects.dragmenu;
|
|
1607
|
|
1608 if (menu && modkey == SHIFT_KEY && this.commands['copy']) {
|
|
1609 var pos = rcube_event.get_mouse_pos(e);
|
|
1610 this.env.drag_target = target;
|
|
1611 this.show_menu(this.gui_objects.dragmenu.id, true, e);
|
|
1612 $(menu).css({top: (pos.y-10)+'px', left: (pos.x-10)+'px'});
|
|
1613 return true;
|
|
1614 }
|
|
1615
|
|
1616 return false;
|
|
1617 };
|
|
1618
|
|
1619 this.drag_menu_action = function(action)
|
|
1620 {
|
|
1621 var menu = this.gui_objects.dragmenu;
|
|
1622 if (menu) {
|
|
1623 $(menu).hide();
|
|
1624 }
|
|
1625 this.command(action, this.env.drag_target);
|
|
1626 this.env.drag_target = null;
|
|
1627 };
|
|
1628
|
|
1629 this.drag_start = function(list)
|
|
1630 {
|
|
1631 this.drag_active = true;
|
|
1632
|
|
1633 if (this.preview_timer)
|
|
1634 clearTimeout(this.preview_timer);
|
|
1635
|
|
1636 // prepare treelist widget for dragging interactions
|
|
1637 if (this.treelist)
|
|
1638 this.treelist.drag_start();
|
|
1639 };
|
|
1640
|
|
1641 this.drag_end = function(e)
|
|
1642 {
|
|
1643 var list, model;
|
|
1644
|
|
1645 if (this.treelist)
|
|
1646 this.treelist.drag_end();
|
|
1647
|
|
1648 // execute drag & drop action when mouse was released
|
|
1649 if (list = this.message_list)
|
|
1650 model = this.env.mailboxes;
|
|
1651 else if (list = this.contact_list)
|
|
1652 model = this.env.contactfolders;
|
|
1653
|
|
1654 if (this.drag_active && model && this.env.last_folder_target) {
|
|
1655 var target = model[this.env.last_folder_target];
|
|
1656 list.draglayer.hide();
|
|
1657
|
|
1658 if (this.contact_list) {
|
|
1659 if (!this.contacts_drag_menu(e, target))
|
|
1660 this.command('move', target);
|
|
1661 }
|
|
1662 else if (!this.drag_menu(e, target))
|
|
1663 this.command('move', target);
|
|
1664 }
|
|
1665
|
|
1666 this.drag_active = false;
|
|
1667 this.env.last_folder_target = null;
|
|
1668 };
|
|
1669
|
|
1670 this.drag_move = function(e)
|
|
1671 {
|
|
1672 if (this.gui_objects.folderlist) {
|
|
1673 var drag_target, oldclass,
|
|
1674 layerclass = 'draglayernormal',
|
|
1675 mouse = rcube_event.get_mouse_pos(e);
|
|
1676
|
|
1677 if (this.contact_list && this.contact_list.draglayer)
|
|
1678 oldclass = this.contact_list.draglayer.attr('class');
|
|
1679
|
|
1680 // mouse intersects a valid drop target on the treelist
|
|
1681 if (this.treelist && (drag_target = this.treelist.intersects(mouse, true))) {
|
|
1682 this.env.last_folder_target = drag_target;
|
|
1683 layerclass = 'draglayer' + (this.check_droptarget(drag_target) > 1 ? 'copy' : 'normal');
|
|
1684 }
|
|
1685 else {
|
|
1686 // Clear target, otherwise drag end will trigger move into last valid droptarget
|
|
1687 this.env.last_folder_target = null;
|
|
1688 }
|
|
1689
|
|
1690 if (layerclass != oldclass && this.contact_list && this.contact_list.draglayer)
|
|
1691 this.contact_list.draglayer.attr('class', layerclass);
|
|
1692 }
|
|
1693 };
|
|
1694
|
|
1695 this.collapse_folder = function(name)
|
|
1696 {
|
|
1697 if (this.treelist)
|
|
1698 this.treelist.toggle(name);
|
|
1699 };
|
|
1700
|
|
1701 this.folder_collapsed = function(node)
|
|
1702 {
|
|
1703 var prefname = this.env.task == 'addressbook' ? 'collapsed_abooks' : 'collapsed_folders',
|
|
1704 old = this.env[prefname];
|
|
1705
|
|
1706 if (node.collapsed) {
|
|
1707 this.env[prefname] = this.env[prefname] + '&'+urlencode(node.id)+'&';
|
|
1708
|
|
1709 // select the folder if one of its childs is currently selected
|
|
1710 // don't select if it's virtual (#1488346)
|
|
1711 if (!node.virtual && this.env.mailbox && this.env.mailbox.startsWith(node.id + this.env.delimiter))
|
|
1712 this.command('list', node.id);
|
|
1713 }
|
|
1714 else {
|
|
1715 var reg = new RegExp('&'+urlencode(node.id)+'&');
|
|
1716 this.env[prefname] = this.env[prefname].replace(reg, '');
|
|
1717 }
|
|
1718
|
|
1719 if (!this.drag_active) {
|
|
1720 if (old !== this.env[prefname])
|
|
1721 this.command('save-pref', { name: prefname, value: this.env[prefname] });
|
|
1722
|
|
1723 if (this.env.unread_counts)
|
|
1724 this.set_unread_count_display(node.id, false);
|
|
1725 }
|
|
1726 };
|
|
1727
|
|
1728 // global mouse-click handler to cleanup some UI elements
|
|
1729 this.doc_mouse_up = function(e)
|
|
1730 {
|
|
1731 var list, id, target = rcube_event.get_target(e);
|
|
1732
|
|
1733 // ignore event if jquery UI dialog is open
|
|
1734 if ($(target).closest('.ui-dialog, .ui-widget-overlay').length)
|
|
1735 return;
|
|
1736
|
|
1737 // remove focus from list widgets
|
|
1738 if (window.rcube_list_widget && rcube_list_widget._instances.length) {
|
|
1739 $.each(rcube_list_widget._instances, function(i,list){
|
|
1740 if (list && !rcube_mouse_is_over(e, list.list.parentNode))
|
|
1741 list.blur();
|
|
1742 });
|
|
1743 }
|
|
1744
|
|
1745 // reset 'pressed' buttons
|
|
1746 if (this.buttons_sel) {
|
|
1747 for (id in this.buttons_sel)
|
|
1748 if (typeof id !== 'function')
|
|
1749 this.button_out(this.buttons_sel[id], id);
|
|
1750 this.buttons_sel = {};
|
|
1751 }
|
|
1752
|
|
1753 // reset popup menus; delayed to have updated menu_stack data
|
|
1754 setTimeout(function(e){
|
|
1755 var obj, skip, config, id, i, parents = $(target).parents();
|
|
1756 for (i = ref.menu_stack.length - 1; i >= 0; i--) {
|
|
1757 id = ref.menu_stack[i];
|
|
1758 obj = $('#' + id);
|
|
1759
|
|
1760 if (obj.is(':visible')
|
|
1761 && target != obj.data('opener')
|
|
1762 && target != obj.get(0) // check if scroll bar was clicked (#1489832)
|
|
1763 && !parents.is(obj.data('opener'))
|
|
1764 && id != skip
|
|
1765 && (obj.attr('data-editable') != 'true' || !$(target).parents('#' + id).length)
|
|
1766 && (obj.attr('data-sticky') != 'true' || !rcube_mouse_is_over(e, obj.get(0)))
|
|
1767 ) {
|
|
1768 ref.hide_menu(id, e);
|
|
1769 }
|
|
1770 skip = obj.data('parent');
|
|
1771 }
|
|
1772 }, 10, e);
|
|
1773 };
|
|
1774
|
|
1775 // global keypress event handler
|
|
1776 this.doc_keypress = function(e)
|
|
1777 {
|
|
1778 // Helper method to move focus to the next/prev active menu item
|
|
1779 var focus_menu_item = function(dir) {
|
|
1780 var obj, item, mod = dir < 0 ? 'prevAll' : 'nextAll', limit = dir < 0 ? 'last' : 'first';
|
|
1781 if (ref.focused_menu && (obj = $('#'+ref.focused_menu))) {
|
|
1782 item = obj.find(':focus').closest('li')[mod](':has(:not([aria-disabled=true]))').find('a,input')[limit]();
|
|
1783 if (!item.length)
|
|
1784 item = obj.find(':focus').closest('ul')[mod](':has(:not([aria-disabled=true]))').find('a,input')[limit]();
|
|
1785 return item.focus().length;
|
|
1786 }
|
|
1787
|
|
1788 return 0;
|
|
1789 };
|
|
1790
|
|
1791 var target = e.target || {},
|
|
1792 keyCode = rcube_event.get_keycode(e);
|
|
1793
|
|
1794 if (e.keyCode != 27 && (!this.menu_keyboard_active || target.nodeName == 'TEXTAREA' || target.nodeName == 'SELECT')) {
|
|
1795 return true;
|
|
1796 }
|
|
1797
|
|
1798 switch (keyCode) {
|
|
1799 case 38:
|
|
1800 case 40:
|
|
1801 case 63232: // "up", in safari keypress
|
|
1802 case 63233: // "down", in safari keypress
|
|
1803 focus_menu_item(keyCode == 38 || keyCode == 63232 ? -1 : 1);
|
|
1804 return rcube_event.cancel(e);
|
|
1805
|
|
1806 case 9: // tab
|
|
1807 if (this.focused_menu) {
|
|
1808 var mod = rcube_event.get_modifier(e);
|
|
1809 if (!focus_menu_item(mod == SHIFT_KEY ? -1 : 1)) {
|
|
1810 this.hide_menu(this.focused_menu, e);
|
|
1811 }
|
|
1812 }
|
|
1813 return rcube_event.cancel(e);
|
|
1814
|
|
1815 case 27: // esc
|
|
1816 if (this.menu_stack.length)
|
|
1817 this.hide_menu(this.menu_stack[this.menu_stack.length-1], e);
|
|
1818 break;
|
|
1819 }
|
|
1820
|
|
1821 return true;
|
|
1822 }
|
|
1823
|
|
1824 this.msglist_select = function(list)
|
|
1825 {
|
|
1826 if (this.preview_timer)
|
|
1827 clearTimeout(this.preview_timer);
|
|
1828
|
|
1829 var selected = list.get_single_selection();
|
|
1830
|
|
1831 this.enable_command(this.env.message_commands, selected != null);
|
|
1832 if (selected) {
|
|
1833 // Hide certain command buttons when Drafts folder is selected
|
|
1834 if (this.env.mailbox == this.env.drafts_mailbox)
|
|
1835 this.enable_command('reply', 'reply-all', 'reply-list', 'forward', 'forward-attachment', 'forward-inline', false);
|
|
1836 // Disable reply-list when List-Post header is not set
|
|
1837 else {
|
|
1838 var msg = this.env.messages[selected];
|
|
1839 if (!msg.ml)
|
|
1840 this.enable_command('reply-list', false);
|
|
1841 }
|
|
1842 }
|
|
1843 // Multi-message commands
|
|
1844 this.enable_command('delete', 'move', 'copy', 'mark', 'forward', 'forward-attachment', list.selection.length > 0);
|
|
1845
|
|
1846 // reset all-pages-selection
|
|
1847 if (selected || (list.selection.length && list.selection.length != list.rowcount))
|
|
1848 this.select_all_mode = false;
|
|
1849
|
|
1850 // start timer for message preview (wait for double click)
|
|
1851 if (selected && this.env.contentframe && !list.multi_selecting && !this.dummy_select) {
|
|
1852 // try to be responsive and try not to overload the server when user is pressing up/down key repeatedly
|
|
1853 var now = new Date().getTime();
|
|
1854 var time_diff = now - (this._last_msglist_select_time || 0);
|
|
1855 var preview_pane_delay = this.preview_delay_click;
|
|
1856
|
|
1857 // user is selecting messages repeatedly, wait until this ends (use larger delay)
|
|
1858 if (time_diff < this.preview_delay_select) {
|
|
1859 preview_pane_delay = this.preview_delay_select;
|
|
1860 if (this.preview_timer) {
|
|
1861 clearTimeout(this.preview_timer);
|
|
1862 }
|
|
1863 if (this.env.contentframe) {
|
|
1864 this.show_contentframe(false);
|
|
1865 }
|
|
1866 }
|
|
1867
|
|
1868 this._last_msglist_select_time = now;
|
|
1869 this.preview_timer = setTimeout(function() { ref.msglist_get_preview(); }, preview_pane_delay);
|
|
1870 }
|
|
1871 else if (this.env.contentframe) {
|
|
1872 this.show_contentframe(false);
|
|
1873 }
|
|
1874 };
|
|
1875
|
|
1876 this.msglist_dbl_click = function(list)
|
|
1877 {
|
|
1878 if (this.preview_timer)
|
|
1879 clearTimeout(this.preview_timer);
|
|
1880
|
|
1881 var uid = list.get_single_selection();
|
|
1882
|
|
1883 if (uid && (this.env.messages[uid].mbox || this.env.mailbox) == this.env.drafts_mailbox)
|
|
1884 this.open_compose_step({ _draft_uid: uid, _mbox: this.env.mailbox });
|
|
1885 else if (uid)
|
|
1886 this.show_message(uid, false, false);
|
|
1887 };
|
|
1888
|
|
1889 this.msglist_keypress = function(list)
|
|
1890 {
|
|
1891 if (list.modkey == CONTROL_KEY)
|
|
1892 return;
|
|
1893
|
|
1894 if (list.key_pressed == list.ENTER_KEY)
|
|
1895 this.command('show');
|
|
1896 else if (list.key_pressed == list.DELETE_KEY || list.key_pressed == list.BACKSPACE_KEY)
|
|
1897 this.command('delete');
|
|
1898 else if (list.key_pressed == 33)
|
|
1899 this.command('previouspage');
|
|
1900 else if (list.key_pressed == 34)
|
|
1901 this.command('nextpage');
|
|
1902 };
|
|
1903
|
|
1904 this.msglist_get_preview = function()
|
|
1905 {
|
|
1906 var uid = this.get_single_uid();
|
|
1907 if (uid && this.env.contentframe && !this.drag_active)
|
|
1908 this.show_message(uid, false, true);
|
|
1909 else if (this.env.contentframe)
|
|
1910 this.show_contentframe(false);
|
|
1911 };
|
|
1912
|
|
1913 this.msglist_expand = function(row)
|
|
1914 {
|
|
1915 if (this.env.messages[row.uid])
|
|
1916 this.env.messages[row.uid].expanded = row.expanded;
|
|
1917 $(row.obj)[row.expanded?'addClass':'removeClass']('expanded');
|
|
1918 };
|
|
1919
|
|
1920 this.msglist_set_coltypes = function(list)
|
|
1921 {
|
|
1922 var i, found, name, cols = list.thead.rows[0].cells;
|
|
1923
|
|
1924 this.env.listcols = [];
|
|
1925
|
|
1926 for (i=0; i<cols.length; i++)
|
|
1927 if (cols[i].id && cols[i].id.startsWith('rcm')) {
|
|
1928 name = cols[i].id.slice(3);
|
|
1929 this.env.listcols.push(name);
|
|
1930 }
|
|
1931
|
|
1932 if ((found = $.inArray('flag', this.env.listcols)) >= 0)
|
|
1933 this.env.flagged_col = found;
|
|
1934
|
|
1935 if ((found = $.inArray('subject', this.env.listcols)) >= 0)
|
|
1936 this.env.subject_col = found;
|
|
1937
|
|
1938 this.command('save-pref', { name: 'list_cols', value: this.env.listcols, session: 'list_attrib/columns' });
|
|
1939 };
|
|
1940
|
|
1941 this.check_droptarget = function(id)
|
|
1942 {
|
|
1943 switch (this.task) {
|
|
1944 case 'mail':
|
|
1945 return (this.env.mailboxes[id]
|
|
1946 && !this.env.mailboxes[id].virtual
|
|
1947 && (this.env.mailboxes[id].id != this.env.mailbox || this.is_multifolder_listing())) ? 1 : 0;
|
|
1948
|
|
1949 case 'addressbook':
|
|
1950 var target;
|
|
1951 if (id != this.env.source && (target = this.env.contactfolders[id])) {
|
|
1952 // droptarget is a group
|
|
1953 if (target.type == 'group') {
|
|
1954 if (target.id != this.env.group && !this.env.contactfolders[target.source].readonly) {
|
|
1955 var is_other = this.env.selection_sources.length > 1 || $.inArray(target.source, this.env.selection_sources) == -1;
|
|
1956 return !is_other || this.commands.move ? 1 : 2;
|
|
1957 }
|
|
1958 }
|
|
1959 // droptarget is a (writable) addressbook and it's not the source
|
|
1960 else if (!target.readonly && (this.env.selection_sources.length > 1 || $.inArray(id, this.env.selection_sources) == -1)) {
|
|
1961 return this.commands.move ? 1 : 2;
|
|
1962 }
|
|
1963 }
|
|
1964 }
|
|
1965
|
|
1966 return 0;
|
|
1967 };
|
|
1968
|
|
1969 // open popup window
|
|
1970 this.open_window = function(url, small, toolbar)
|
|
1971 {
|
|
1972 var wname = 'rcmextwin' + new Date().getTime();
|
|
1973
|
|
1974 url += (url.match(/\?/) ? '&' : '?') + '_extwin=1';
|
|
1975
|
|
1976 if (this.env.standard_windows)
|
|
1977 var extwin = window.open(url, wname);
|
|
1978 else {
|
|
1979 var win = this.is_framed() ? parent.window : window,
|
|
1980 page = $(win),
|
|
1981 page_width = page.width(),
|
|
1982 page_height = bw.mz ? $('body', win).height() : page.height(),
|
|
1983 w = Math.min(small ? this.env.popup_width_small : this.env.popup_width, page_width),
|
|
1984 h = page_height, // always use same height
|
|
1985 l = (win.screenLeft || win.screenX) + 20,
|
|
1986 t = (win.screenTop || win.screenY) + 20,
|
|
1987 extwin = window.open(url, wname,
|
|
1988 'width='+w+',height='+h+',top='+t+',left='+l+',resizable=yes,location=no,scrollbars=yes'
|
|
1989 +(toolbar ? ',toolbar=yes,menubar=yes,status=yes' : ',toolbar=no,menubar=no,status=no'));
|
|
1990 }
|
|
1991
|
|
1992 // detect popup blocker (#1489618)
|
|
1993 // don't care this might not work with all browsers
|
|
1994 if (!extwin || extwin.closed) {
|
|
1995 this.display_message(this.get_label('windowopenerror'), 'warning');
|
|
1996 return;
|
|
1997 }
|
|
1998
|
|
1999 // write loading... message to empty windows
|
|
2000 if (!url && extwin.document) {
|
|
2001 extwin.document.write('<html><body>' + this.get_label('loading') + '</body></html>');
|
|
2002 }
|
|
2003
|
|
2004 // allow plugins to grab the window reference (#1489413)
|
|
2005 this.triggerEvent('openwindow', { url:url, handle:extwin });
|
|
2006
|
|
2007 // focus window, delayed to bring to front
|
|
2008 setTimeout(function() { extwin && extwin.focus(); }, 10);
|
|
2009
|
|
2010 return extwin;
|
|
2011 };
|
|
2012
|
|
2013
|
|
2014 /*********************************************************/
|
|
2015 /********* (message) list functionality *********/
|
|
2016 /*********************************************************/
|
|
2017
|
|
2018 this.init_message_row = function(row)
|
|
2019 {
|
|
2020 var i, fn = {}, uid = row.uid,
|
|
2021 status_icon = (this.env.status_col != null ? 'status' : 'msg') + 'icn' + row.id;
|
|
2022
|
|
2023 if (uid && this.env.messages[uid])
|
|
2024 $.extend(row, this.env.messages[uid]);
|
|
2025
|
|
2026 // set eventhandler to status icon
|
|
2027 if (row.icon = document.getElementById(status_icon)) {
|
|
2028 fn.icon = function(e) { ref.command('toggle_status', uid); };
|
|
2029 }
|
|
2030
|
|
2031 // save message icon position too
|
|
2032 if (this.env.status_col != null)
|
|
2033 row.msgicon = document.getElementById('msgicn'+row.id);
|
|
2034 else
|
|
2035 row.msgicon = row.icon;
|
|
2036
|
|
2037 // set eventhandler to flag icon
|
|
2038 if (this.env.flagged_col != null && (row.flagicon = document.getElementById('flagicn'+row.id))) {
|
|
2039 fn.flagicon = function(e) { ref.command('toggle_flag', uid); };
|
|
2040 }
|
|
2041
|
|
2042 // set event handler to thread expand/collapse icon
|
|
2043 if (!row.depth && row.has_children && (row.expando = document.getElementById('rcmexpando'+row.id))) {
|
|
2044 fn.expando = function(e) { ref.expand_message_row(e, uid); };
|
|
2045 }
|
|
2046
|
|
2047 // attach events
|
|
2048 $.each(fn, function(i, f) {
|
|
2049 row[i].onclick = function(e) { f(e); return rcube_event.cancel(e); };
|
|
2050 if (bw.touch && row[i].addEventListener) {
|
|
2051 row[i].addEventListener('touchend', function(e) {
|
|
2052 if (e.changedTouches.length == 1) {
|
|
2053 f(e);
|
|
2054 return rcube_event.cancel(e);
|
|
2055 }
|
|
2056 }, false);
|
|
2057 }
|
|
2058 });
|
|
2059
|
|
2060 this.triggerEvent('insertrow', { uid:uid, row:row });
|
|
2061 };
|
|
2062
|
|
2063 // create a table row in the message list
|
|
2064 this.add_message_row = function(uid, cols, flags, attop)
|
|
2065 {
|
|
2066 if (!this.gui_objects.messagelist || !this.message_list)
|
|
2067 return false;
|
|
2068
|
|
2069 // Prevent from adding messages from different folder (#1487752)
|
|
2070 if (flags.mbox != this.env.mailbox && !flags.skip_mbox_check)
|
|
2071 return false;
|
|
2072
|
|
2073 // When deleting messages fast it may happen that the same message
|
|
2074 // from the next page could be added many times, we prevent this here
|
|
2075 if (this.message_list.rows[uid])
|
|
2076 return false;
|
|
2077
|
|
2078 if (!this.env.messages[uid])
|
|
2079 this.env.messages[uid] = {};
|
|
2080
|
|
2081 // merge flags over local message object
|
|
2082 $.extend(this.env.messages[uid], {
|
|
2083 deleted: flags.deleted?1:0,
|
|
2084 replied: flags.answered?1:0,
|
|
2085 unread: !flags.seen?1:0,
|
|
2086 forwarded: flags.forwarded?1:0,
|
|
2087 flagged: flags.flagged?1:0,
|
|
2088 has_children: flags.has_children?1:0,
|
|
2089 depth: flags.depth?flags.depth:0,
|
|
2090 unread_children: flags.unread_children || 0,
|
|
2091 flagged_children: flags.flagged_children || 0,
|
|
2092 parent_uid: flags.parent_uid || 0,
|
|
2093 selected: this.select_all_mode || this.message_list.in_selection(uid),
|
|
2094 ml: flags.ml?1:0,
|
|
2095 ctype: flags.ctype,
|
|
2096 mbox: flags.mbox,
|
|
2097 // flags from plugins
|
|
2098 flags: flags.extra_flags
|
|
2099 });
|
|
2100
|
|
2101 var c, n, col, html, css_class, label, status_class = '', status_label = '',
|
|
2102 tree = '', expando = '',
|
|
2103 list = this.message_list,
|
|
2104 rows = list.rows,
|
|
2105 message = this.env.messages[uid],
|
|
2106 msg_id = this.html_identifier(uid,true),
|
|
2107 row_class = 'message'
|
|
2108 + (!flags.seen ? ' unread' : '')
|
|
2109 + (flags.deleted ? ' deleted' : '')
|
|
2110 + (flags.flagged ? ' flagged' : '')
|
|
2111 + (message.selected ? ' selected' : ''),
|
|
2112 row = { cols:[], style:{}, id:'rcmrow'+msg_id, uid:uid };
|
|
2113
|
|
2114 // message status icons
|
|
2115 css_class = 'msgicon';
|
|
2116 if (this.env.status_col === null) {
|
|
2117 css_class += ' status';
|
|
2118 if (flags.deleted) {
|
|
2119 status_class += ' deleted';
|
|
2120 status_label += this.get_label('deleted') + ' ';
|
|
2121 }
|
|
2122 else if (!flags.seen) {
|
|
2123 status_class += ' unread';
|
|
2124 status_label += this.get_label('unread') + ' ';
|
|
2125 }
|
|
2126 else if (flags.unread_children > 0) {
|
|
2127 status_class += ' unreadchildren';
|
|
2128 }
|
|
2129 }
|
|
2130 if (flags.answered) {
|
|
2131 status_class += ' replied';
|
|
2132 status_label += this.get_label('replied') + ' ';
|
|
2133 }
|
|
2134 if (flags.forwarded) {
|
|
2135 status_class += ' forwarded';
|
|
2136 status_label += this.get_label('forwarded') + ' ';
|
|
2137 }
|
|
2138
|
|
2139 // update selection
|
|
2140 if (message.selected && !list.in_selection(uid))
|
|
2141 list.selection.push(uid);
|
|
2142
|
|
2143 // threads
|
|
2144 if (this.env.threading) {
|
|
2145 if (message.depth) {
|
|
2146 // This assumes that div width is hardcoded to 15px,
|
|
2147 tree += '<span id="rcmtab' + msg_id + '" class="branch" style="width:' + (message.depth * 15) + 'px;"> </span>';
|
|
2148
|
|
2149 if ((rows[message.parent_uid] && rows[message.parent_uid].expanded === false)
|
|
2150 || ((this.env.autoexpand_threads == 0 || this.env.autoexpand_threads == 2) &&
|
|
2151 (!rows[message.parent_uid] || !rows[message.parent_uid].expanded))
|
|
2152 ) {
|
|
2153 row.style.display = 'none';
|
|
2154 message.expanded = false;
|
|
2155 }
|
|
2156 else
|
|
2157 message.expanded = true;
|
|
2158
|
|
2159 row_class += ' thread expanded';
|
|
2160 }
|
|
2161 else if (message.has_children) {
|
|
2162 if (message.expanded === undefined && (this.env.autoexpand_threads == 1 || (this.env.autoexpand_threads == 2 && message.unread_children))) {
|
|
2163 message.expanded = true;
|
|
2164 }
|
|
2165
|
|
2166 expando = '<div id="rcmexpando' + row.id + '" class="' + (message.expanded ? 'expanded' : 'collapsed') + '"> </div>';
|
|
2167 row_class += ' thread' + (message.expanded ? ' expanded' : '');
|
|
2168 }
|
|
2169
|
|
2170 if (flags.unread_children && flags.seen && !message.expanded)
|
|
2171 row_class += ' unroot';
|
|
2172
|
|
2173 if (flags.flagged_children && !message.expanded)
|
|
2174 row_class += ' flaggedroot';
|
|
2175 }
|
|
2176
|
|
2177 tree += '<span id="msgicn'+row.id+'" class="'+css_class+status_class+'" title="'+status_label+'"></span>';
|
|
2178 row.className = row_class;
|
|
2179
|
|
2180 // build subject link
|
|
2181 if (cols.subject) {
|
|
2182 var action = flags.mbox == this.env.drafts_mailbox ? 'compose' : 'show',
|
|
2183 uid_param = flags.mbox == this.env.drafts_mailbox ? '_draft_uid' : '_uid',
|
|
2184 query = { _mbox: flags.mbox };
|
|
2185 query[uid_param] = uid;
|
|
2186 cols.subject = '<a href="' + this.url(action, query) + '" onclick="return rcube_event.keyboard_only(event)"' +
|
|
2187 ' onmouseover="rcube_webmail.long_subject_title(this,'+(message.depth+1)+')" tabindex="-1"><span>'+cols.subject+'</span></a>';
|
|
2188 }
|
|
2189
|
|
2190 // add each submitted col
|
|
2191 for (n in this.env.listcols) {
|
|
2192 c = this.env.listcols[n];
|
|
2193 col = {className: String(c).toLowerCase(), events:{}};
|
|
2194
|
|
2195 if (this.env.coltypes[c] && this.env.coltypes[c].hidden) {
|
|
2196 col.className += ' hidden';
|
|
2197 }
|
|
2198
|
|
2199 if (c == 'flag') {
|
|
2200 css_class = (flags.flagged ? 'flagged' : 'unflagged');
|
|
2201 label = this.get_label(css_class);
|
|
2202 html = '<span id="flagicn'+row.id+'" class="'+css_class+'" title="'+label+'"></span>';
|
|
2203 }
|
|
2204 else if (c == 'attachment') {
|
|
2205 label = this.get_label('withattachment');
|
|
2206 if (flags.attachmentClass)
|
|
2207 html = '<span class="'+flags.attachmentClass+'" title="'+label+'"></span>';
|
|
2208 else if (/application\/|multipart\/(m|signed)/.test(flags.ctype))
|
|
2209 html = '<span class="attachment" title="'+label+'"></span>';
|
|
2210 else if (/multipart\/report/.test(flags.ctype))
|
|
2211 html = '<span class="report"></span>';
|
|
2212 else if (flags.ctype == 'multipart/encrypted' || flags.ctype == 'application/pkcs7-mime')
|
|
2213 html = '<span class="encrypted"></span>';
|
|
2214 else
|
|
2215 html = ' ';
|
|
2216 }
|
|
2217 else if (c == 'status') {
|
|
2218 label = '';
|
|
2219 if (flags.deleted) {
|
|
2220 css_class = 'deleted';
|
|
2221 label = this.get_label('deleted');
|
|
2222 }
|
|
2223 else if (!flags.seen) {
|
|
2224 css_class = 'unread';
|
|
2225 label = this.get_label('unread');
|
|
2226 }
|
|
2227 else if (flags.unread_children > 0) {
|
|
2228 css_class = 'unreadchildren';
|
|
2229 }
|
|
2230 else
|
|
2231 css_class = 'msgicon';
|
|
2232 html = '<span id="statusicn'+row.id+'" class="'+css_class+status_class+'" title="'+label+'"></span>';
|
|
2233 }
|
|
2234 else if (c == 'threads')
|
|
2235 html = expando;
|
|
2236 else if (c == 'subject') {
|
|
2237 html = tree + cols[c];
|
|
2238 }
|
|
2239 else if (c == 'priority') {
|
|
2240 if (flags.prio > 0 && flags.prio < 6) {
|
|
2241 label = this.get_label('priority') + ' ' + flags.prio;
|
|
2242 html = '<span class="prio'+flags.prio+'" title="'+label+'"></span>';
|
|
2243 }
|
|
2244 else
|
|
2245 html = ' ';
|
|
2246 }
|
|
2247 else if (c == 'folder') {
|
|
2248 html = '<span onmouseover="rcube_webmail.long_subject_title(this)">' + cols[c] + '<span>';
|
|
2249 }
|
|
2250 else
|
|
2251 html = cols[c];
|
|
2252
|
|
2253 col.innerHTML = html;
|
|
2254 row.cols.push(col);
|
|
2255 }
|
|
2256
|
|
2257 if (this.env.layout == 'widescreen')
|
|
2258 row = this.widescreen_message_row(row, uid, message);
|
|
2259
|
|
2260 list.insert_row(row, attop);
|
|
2261
|
|
2262 // remove 'old' row
|
|
2263 if (attop && this.env.pagesize && list.rowcount > this.env.pagesize) {
|
|
2264 var uid = list.get_last_row();
|
|
2265 list.remove_row(uid);
|
|
2266 list.clear_selection(uid);
|
|
2267 }
|
|
2268 };
|
|
2269
|
|
2270 // Converts standard message list record into "widescreen" (3-column) layout
|
|
2271 this.widescreen_message_row = function(row, uid, message)
|
|
2272 {
|
|
2273 var domrow = document.createElement('tr');
|
|
2274
|
|
2275 domrow.id = row.id;
|
|
2276 domrow.uid = row.uid;
|
|
2277 domrow.className = row.className;
|
|
2278 if (row.style) $.extend(domrow.style, row.style);
|
|
2279
|
|
2280 $.each(this.env.widescreen_list_template, function() {
|
|
2281 if (!ref.env.threading && this.className == 'threads')
|
|
2282 return;
|
|
2283
|
|
2284 var i, n, e, col, domcol,
|
|
2285 domcell = document.createElement('td');
|
|
2286
|
|
2287 if (this.className) domcell.className = this.className;
|
|
2288
|
|
2289 for (i=0; this.cells && i < this.cells.length; i++) {
|
|
2290 for (n=0; row.cols && n < row.cols.length; n++) {
|
|
2291 if (this.cells[i] == row.cols[n].className) {
|
|
2292 col = row.cols[n];
|
|
2293 domcol = document.createElement('span');
|
|
2294 domcol.className = this.cells[i];
|
|
2295 if (this.className == 'subject' && domcol.className != 'subject')
|
|
2296 domcol.className += ' skip-on-drag';
|
|
2297 if (col.innerHTML)
|
|
2298 domcol.innerHTML = col.innerHTML;
|
|
2299 domcell.appendChild(domcol);
|
|
2300 break;
|
|
2301 }
|
|
2302 }
|
|
2303 }
|
|
2304
|
|
2305 domrow.appendChild(domcell);
|
|
2306 });
|
|
2307
|
|
2308 if (this.env.threading && message.depth) {
|
|
2309 $('td.subject', domrow).attr('style', 'padding-left:' + Math.min(90, message.depth * 15) + 'px !important');
|
|
2310 $('span.branch', domrow).remove();
|
|
2311 }
|
|
2312
|
|
2313 return domrow;
|
|
2314 };
|
|
2315
|
|
2316 this.set_list_sorting = function(sort_col, sort_order)
|
|
2317 {
|
|
2318 var sort_old = this.env.sort_col == 'arrival' ? 'date' : this.env.sort_col,
|
|
2319 sort_new = sort_col == 'arrival' ? 'date' : sort_col;
|
|
2320
|
|
2321 // set table header class
|
|
2322 $('#rcm' + sort_old).removeClass('sorted' + this.env.sort_order.toUpperCase());
|
|
2323 if (sort_new)
|
|
2324 $('#rcm' + sort_new).addClass('sorted' + sort_order);
|
|
2325
|
|
2326 // if sorting by 'arrival' is selected, click on date column should not switch to 'date'
|
|
2327 $('#rcmdate > a').prop('rel', sort_col == 'arrival' ? 'arrival' : 'date');
|
|
2328
|
|
2329 this.env.sort_col = sort_col;
|
|
2330 this.env.sort_order = sort_order;
|
|
2331 };
|
|
2332
|
|
2333 this.set_list_options = function(cols, sort_col, sort_order, threads, layout)
|
|
2334 {
|
|
2335 var update, post_data = {};
|
|
2336
|
|
2337 if (sort_col === undefined)
|
|
2338 sort_col = this.env.sort_col;
|
|
2339 if (!sort_order)
|
|
2340 sort_order = this.env.sort_order;
|
|
2341
|
|
2342 if (this.env.sort_col != sort_col || this.env.sort_order != sort_order) {
|
|
2343 update = 1;
|
|
2344 this.set_list_sorting(sort_col, sort_order);
|
|
2345 }
|
|
2346
|
|
2347 if (this.env.threading != threads) {
|
|
2348 update = 1;
|
|
2349 post_data._threads = threads;
|
|
2350 }
|
|
2351
|
|
2352 if (layout && this.env.layout != layout) {
|
|
2353 this.triggerEvent('layout-change', {old_layout: this.env.layout, new_layout: layout});
|
|
2354 update = 1;
|
|
2355 this.env.layout = post_data._layout = layout;
|
|
2356 }
|
|
2357
|
|
2358 if (cols && cols.length) {
|
|
2359 // make sure new columns are added at the end of the list
|
|
2360 var i, idx, name, newcols = [], oldcols = this.env.listcols;
|
|
2361 for (i=0; i<oldcols.length; i++) {
|
|
2362 name = oldcols[i];
|
|
2363 idx = $.inArray(name, cols);
|
|
2364 if (idx != -1) {
|
|
2365 newcols.push(name);
|
|
2366 delete cols[idx];
|
|
2367 }
|
|
2368 }
|
|
2369 for (i=0; i<cols.length; i++)
|
|
2370 if (cols[i])
|
|
2371 newcols.push(cols[i]);
|
|
2372
|
|
2373 if (newcols.join() != oldcols.join()) {
|
|
2374 update = 1;
|
|
2375 post_data._cols = newcols.join(',');
|
|
2376 }
|
|
2377 }
|
|
2378
|
|
2379 if (update)
|
|
2380 this.list_mailbox('', '', sort_col+'_'+sort_order, post_data);
|
|
2381 };
|
|
2382
|
|
2383 // when user double-clicks on a row
|
|
2384 this.show_message = function(id, safe, preview)
|
|
2385 {
|
|
2386 if (!id)
|
|
2387 return;
|
|
2388
|
|
2389 var win, target = window,
|
|
2390 url = this.params_from_uid(id, {_caps: this.browser_capabilities()});
|
|
2391
|
|
2392 if (preview && (win = this.get_frame_window(this.env.contentframe))) {
|
|
2393 target = win;
|
|
2394 url._framed = 1;
|
|
2395 }
|
|
2396
|
|
2397 if (safe)
|
|
2398 url._safe = 1;
|
|
2399
|
|
2400 // also send search request to get the right messages
|
|
2401 if (this.env.search_request)
|
|
2402 url._search = this.env.search_request;
|
|
2403
|
|
2404 if (this.env.extwin)
|
|
2405 url._extwin = 1;
|
|
2406
|
|
2407 url = this.url(preview ? 'preview': 'show', url);
|
|
2408
|
|
2409 if (preview && String(target.location.href).indexOf(url) >= 0) {
|
|
2410 this.show_contentframe(true);
|
|
2411 }
|
|
2412 else {
|
|
2413 if (!preview && this.env.message_extwin && !this.env.extwin)
|
|
2414 this.open_window(url, true);
|
|
2415 else
|
|
2416 this.location_href(url, target, true);
|
|
2417 }
|
|
2418 };
|
|
2419
|
|
2420 // update message status and unread counter after marking a message as read
|
|
2421 this.set_unread_message = function(id, folder)
|
|
2422 {
|
|
2423 var self = this;
|
|
2424
|
|
2425 // find window with messages list
|
|
2426 if (!self.message_list)
|
|
2427 self = self.opener();
|
|
2428
|
|
2429 if (!self && window.parent)
|
|
2430 self = parent.rcmail;
|
|
2431
|
|
2432 if (!self || !self.message_list)
|
|
2433 return;
|
|
2434
|
|
2435 // this may fail in multifolder mode
|
|
2436 if (self.set_message(id, 'unread', false) === false)
|
|
2437 self.set_message(id + '-' + folder, 'unread', false);
|
|
2438
|
|
2439 if (self.env.unread_counts[folder] > 0) {
|
|
2440 self.env.unread_counts[folder] -= 1;
|
|
2441 self.set_unread_count(folder, self.env.unread_counts[folder], folder == 'INBOX' && !self.is_multifolder_listing());
|
|
2442 }
|
|
2443 };
|
|
2444
|
|
2445 this.show_contentframe = function(show)
|
|
2446 {
|
|
2447 var frame, win, name = this.env.contentframe;
|
|
2448
|
|
2449 if (frame = this.get_frame_element(name)) {
|
|
2450 if (!show && (win = this.get_frame_window(name))) {
|
|
2451 if (win.location.href.indexOf(this.env.blankpage) < 0) {
|
|
2452 if (win.stop)
|
|
2453 win.stop();
|
|
2454 else // IE
|
|
2455 win.document.execCommand('Stop');
|
|
2456
|
|
2457 win.location.href = this.env.blankpage;
|
|
2458 }
|
|
2459 }
|
|
2460 else if (!bw.safari && !bw.konq)
|
|
2461 $(frame)[show ? 'show' : 'hide']();
|
|
2462 }
|
|
2463
|
|
2464 if (!show && this.env.frame_lock)
|
|
2465 this.set_busy(false, null, this.env.frame_lock);
|
|
2466 };
|
|
2467
|
|
2468 this.get_frame_element = function(id)
|
|
2469 {
|
|
2470 var frame;
|
|
2471
|
|
2472 if (id && (frame = document.getElementById(id)))
|
|
2473 return frame;
|
|
2474 };
|
|
2475
|
|
2476 this.get_frame_window = function(id)
|
|
2477 {
|
|
2478 var frame = this.get_frame_element(id);
|
|
2479
|
|
2480 if (frame && frame.name && window.frames)
|
|
2481 return window.frames[frame.name];
|
|
2482 };
|
|
2483
|
|
2484 this.lock_frame = function()
|
|
2485 {
|
|
2486 if (!this.env.frame_lock)
|
|
2487 (this.is_framed() ? parent.rcmail : this).env.frame_lock = this.set_busy(true, 'loading');
|
|
2488 };
|
|
2489
|
|
2490 // list a specific page
|
|
2491 this.list_page = function(page)
|
|
2492 {
|
|
2493 if (page == 'next')
|
|
2494 page = this.env.current_page+1;
|
|
2495 else if (page == 'last')
|
|
2496 page = this.env.pagecount;
|
|
2497 else if (page == 'prev' && this.env.current_page > 1)
|
|
2498 page = this.env.current_page-1;
|
|
2499 else if (page == 'first' && this.env.current_page > 1)
|
|
2500 page = 1;
|
|
2501
|
|
2502 if (page > 0 && page <= this.env.pagecount) {
|
|
2503 this.env.current_page = page;
|
|
2504
|
|
2505 if (this.task == 'addressbook' || this.contact_list)
|
|
2506 this.list_contacts(this.env.source, this.env.group, page);
|
|
2507 else if (this.task == 'mail')
|
|
2508 this.list_mailbox(this.env.mailbox, page);
|
|
2509 }
|
|
2510 };
|
|
2511
|
|
2512 // sends request to check for recent messages
|
|
2513 this.checkmail = function()
|
|
2514 {
|
|
2515 var lock = this.set_busy(true, 'checkingmail'),
|
|
2516 params = this.check_recent_params();
|
|
2517
|
|
2518 this.http_post('check-recent', params, lock);
|
|
2519 };
|
|
2520
|
|
2521 // list messages of a specific mailbox using filter
|
|
2522 this.filter_mailbox = function(filter)
|
|
2523 {
|
|
2524 if (this.filter_disabled)
|
|
2525 return;
|
|
2526
|
|
2527 var lock = this.set_busy(true, 'searching');
|
|
2528
|
|
2529 this.clear_message_list();
|
|
2530
|
|
2531 // reset vars
|
|
2532 this.env.current_page = 1;
|
|
2533 this.env.search_filter = filter;
|
|
2534 this.http_request('search', this.search_params(false, filter), lock);
|
|
2535 };
|
|
2536
|
|
2537 // reload the current message listing
|
|
2538 this.refresh_list = function()
|
|
2539 {
|
|
2540 this.list_mailbox(this.env.mailbox, this.env.current_page || 1, null, { _clear:1 }, true);
|
|
2541 if (this.message_list)
|
|
2542 this.message_list.clear_selection();
|
|
2543 };
|
|
2544
|
|
2545 // list messages of a specific mailbox
|
|
2546 this.list_mailbox = function(mbox, page, sort, url, update_only)
|
|
2547 {
|
|
2548 var win, target = window;
|
|
2549
|
|
2550 if (typeof url != 'object')
|
|
2551 url = {};
|
|
2552
|
|
2553 if (!mbox)
|
|
2554 mbox = this.env.mailbox ? this.env.mailbox : 'INBOX';
|
|
2555
|
|
2556 // add sort to url if set
|
|
2557 if (sort)
|
|
2558 url._sort = sort;
|
|
2559
|
|
2560 // folder change, reset page, search scope, etc.
|
|
2561 if (this.env.mailbox != mbox) {
|
|
2562 page = 1;
|
|
2563 this.env.current_page = page;
|
|
2564 this.env.search_scope = 'base';
|
|
2565 this.select_all_mode = false;
|
|
2566 this.reset_search_filter();
|
|
2567 }
|
|
2568 // also send search request to get the right messages
|
|
2569 else if (this.env.search_request)
|
|
2570 url._search = this.env.search_request;
|
|
2571
|
|
2572 if (!update_only) {
|
|
2573 // unselect selected messages and clear the list and message data
|
|
2574 this.clear_message_list();
|
|
2575
|
|
2576 if (mbox != this.env.mailbox || (mbox == this.env.mailbox && !page && !sort))
|
|
2577 url._refresh = 1;
|
|
2578
|
|
2579 this.select_folder(mbox, '', true);
|
|
2580 this.unmark_folder(mbox, 'recent', '', true);
|
|
2581 this.env.mailbox = mbox;
|
|
2582 }
|
|
2583
|
|
2584 // load message list remotely
|
|
2585 if (this.gui_objects.messagelist) {
|
|
2586 this.list_mailbox_remote(mbox, page, url);
|
|
2587 return;
|
|
2588 }
|
|
2589
|
|
2590 if (win = this.get_frame_window(this.env.contentframe)) {
|
|
2591 target = win;
|
|
2592 url._framed = 1;
|
|
2593 }
|
|
2594
|
|
2595 if (this.env.uid)
|
|
2596 url._uid = this.env.uid;
|
|
2597
|
|
2598 // load message list to target frame/window
|
|
2599 if (mbox) {
|
|
2600 this.set_busy(true, 'loading');
|
|
2601 url._mbox = mbox;
|
|
2602 if (page)
|
|
2603 url._page = page;
|
|
2604 this.location_href(url, target);
|
|
2605 }
|
|
2606 };
|
|
2607
|
|
2608 this.clear_message_list = function()
|
|
2609 {
|
|
2610 this.env.messages = {};
|
|
2611
|
|
2612 this.show_contentframe(false);
|
|
2613 if (this.message_list)
|
|
2614 this.message_list.clear(true);
|
|
2615 };
|
|
2616
|
|
2617 // send remote request to load message list
|
|
2618 this.list_mailbox_remote = function(mbox, page, url)
|
|
2619 {
|
|
2620 var lock = this.set_busy(true, 'loading');
|
|
2621
|
|
2622 if (typeof url != 'object')
|
|
2623 url = {};
|
|
2624 url._mbox = mbox;
|
|
2625 if (page)
|
|
2626 url._page = page;
|
|
2627
|
|
2628 this.http_request('list', url, lock);
|
|
2629 this.update_state({ _mbox: mbox, _page: (page && page > 1 ? page : null) });
|
|
2630 };
|
|
2631
|
|
2632 // removes messages that doesn't exists from list selection array
|
|
2633 this.update_selection = function()
|
|
2634 {
|
|
2635 var list = this.message_list,
|
|
2636 selected = list.selection,
|
|
2637 rows = list.rows,
|
|
2638 i, selection = [];
|
|
2639
|
|
2640 for (i in selected)
|
|
2641 if (rows[selected[i]])
|
|
2642 selection.push(selected[i]);
|
|
2643
|
|
2644 list.selection = selection;
|
|
2645
|
|
2646 // reset preview frame, if currently previewed message is not selected (has been removed)
|
|
2647 try {
|
|
2648 var win = this.get_frame_window(this.env.contentframe),
|
|
2649 id = win.rcmail.env.uid;
|
|
2650
|
|
2651 if (id && !list.in_selection(id))
|
|
2652 this.show_contentframe(false);
|
|
2653 }
|
|
2654 catch (e) {};
|
|
2655 };
|
|
2656
|
|
2657 // expand all threads with unread children
|
|
2658 this.expand_unread = function()
|
|
2659 {
|
|
2660 var r, tbody = this.message_list.tbody,
|
|
2661 new_row = tbody.firstChild;
|
|
2662
|
|
2663 while (new_row) {
|
|
2664 if (new_row.nodeType == 1 && (r = this.message_list.rows[new_row.uid]) && r.unread_children) {
|
|
2665 this.message_list.expand_all(r);
|
|
2666 this.set_unread_children(r.uid);
|
|
2667 }
|
|
2668
|
|
2669 new_row = new_row.nextSibling;
|
|
2670 }
|
|
2671
|
|
2672 return false;
|
|
2673 };
|
|
2674
|
|
2675 // thread expanding/collapsing handler
|
|
2676 this.expand_message_row = function(e, uid)
|
|
2677 {
|
|
2678 var row = this.message_list.rows[uid];
|
|
2679
|
|
2680 // handle unread_children/flagged_children mark
|
|
2681 row.expanded = !row.expanded;
|
|
2682 this.set_unread_children(uid);
|
|
2683 this.set_flagged_children(uid);
|
|
2684 row.expanded = !row.expanded;
|
|
2685
|
|
2686 this.message_list.expand_row(e, uid);
|
|
2687 };
|
|
2688
|
|
2689 // message list expanding
|
|
2690 this.expand_threads = function()
|
|
2691 {
|
|
2692 if (!this.env.threading || !this.env.autoexpand_threads || !this.message_list)
|
|
2693 return;
|
|
2694
|
|
2695 switch (this.env.autoexpand_threads) {
|
|
2696 case 2: this.expand_unread(); break;
|
|
2697 case 1: this.message_list.expand_all(); break;
|
|
2698 }
|
|
2699 };
|
|
2700
|
|
2701 // Initializes threads indicators/expanders after list update
|
|
2702 this.init_threads = function(roots, mbox)
|
|
2703 {
|
|
2704 // #1487752
|
|
2705 if (mbox && mbox != this.env.mailbox)
|
|
2706 return false;
|
|
2707
|
|
2708 for (var n=0, len=roots.length; n<len; n++)
|
|
2709 this.add_tree_icons(roots[n]);
|
|
2710 this.expand_threads();
|
|
2711 };
|
|
2712
|
|
2713 // adds threads tree icons to the list (or specified thread)
|
|
2714 this.add_tree_icons = function(root)
|
|
2715 {
|
|
2716 var i, l, r, n, len, pos, tmp = [], uid = [],
|
|
2717 row, rows = this.message_list.rows;
|
|
2718
|
|
2719 if (root)
|
|
2720 row = rows[root] ? rows[root].obj : null;
|
|
2721 else
|
|
2722 row = this.message_list.tbody.firstChild;
|
|
2723
|
|
2724 while (row) {
|
|
2725 if (row.nodeType == 1 && (r = rows[row.uid])) {
|
|
2726 if (r.depth) {
|
|
2727 for (i=tmp.length-1; i>=0; i--) {
|
|
2728 len = tmp[i].length;
|
|
2729 if (len > r.depth) {
|
|
2730 pos = len - r.depth;
|
|
2731 if (!(tmp[i][pos] & 2))
|
|
2732 tmp[i][pos] = tmp[i][pos] ? tmp[i][pos]+2 : 2;
|
|
2733 }
|
|
2734 else if (len == r.depth) {
|
|
2735 if (!(tmp[i][0] & 2))
|
|
2736 tmp[i][0] += 2;
|
|
2737 }
|
|
2738 if (r.depth > len)
|
|
2739 break;
|
|
2740 }
|
|
2741
|
|
2742 tmp.push(new Array(r.depth));
|
|
2743 tmp[tmp.length-1][0] = 1;
|
|
2744 uid.push(r.uid);
|
|
2745 }
|
|
2746 else {
|
|
2747 if (tmp.length) {
|
|
2748 for (i in tmp) {
|
|
2749 this.set_tree_icons(uid[i], tmp[i]);
|
|
2750 }
|
|
2751 tmp = [];
|
|
2752 uid = [];
|
|
2753 }
|
|
2754 if (root && row != rows[root].obj)
|
|
2755 break;
|
|
2756 }
|
|
2757 }
|
|
2758 row = row.nextSibling;
|
|
2759 }
|
|
2760
|
|
2761 if (tmp.length) {
|
|
2762 for (i in tmp) {
|
|
2763 this.set_tree_icons(uid[i], tmp[i]);
|
|
2764 }
|
|
2765 }
|
|
2766 };
|
|
2767
|
|
2768 // adds tree icons to specified message row
|
|
2769 this.set_tree_icons = function(uid, tree)
|
|
2770 {
|
|
2771 var i, divs = [], html = '', len = tree.length;
|
|
2772
|
|
2773 for (i=0; i<len; i++) {
|
|
2774 if (tree[i] > 2)
|
|
2775 divs.push({'class': 'l3', width: 15});
|
|
2776 else if (tree[i] > 1)
|
|
2777 divs.push({'class': 'l2', width: 15});
|
|
2778 else if (tree[i] > 0)
|
|
2779 divs.push({'class': 'l1', width: 15});
|
|
2780 // separator div
|
|
2781 else if (divs.length && !divs[divs.length-1]['class'])
|
|
2782 divs[divs.length-1].width += 15;
|
|
2783 else
|
|
2784 divs.push({'class': null, width: 15});
|
|
2785 }
|
|
2786
|
|
2787 for (i=divs.length-1; i>=0; i--) {
|
|
2788 if (divs[i]['class'])
|
|
2789 html += '<div class="tree '+divs[i]['class']+'" />';
|
|
2790 else
|
|
2791 html += '<div style="width:'+divs[i].width+'px" />';
|
|
2792 }
|
|
2793
|
|
2794 if (html)
|
|
2795 $('#rcmtab'+this.html_identifier(uid, true)).html(html);
|
|
2796 };
|
|
2797
|
|
2798 // update parent in a thread
|
|
2799 this.update_thread_root = function(uid, flag)
|
|
2800 {
|
|
2801 if (!this.env.threading)
|
|
2802 return;
|
|
2803
|
|
2804 var root = this.message_list.find_root(uid);
|
|
2805
|
|
2806 if (uid == root)
|
|
2807 return;
|
|
2808
|
|
2809 var p = this.message_list.rows[root];
|
|
2810
|
|
2811 if (flag == 'read' && p.unread_children) {
|
|
2812 p.unread_children--;
|
|
2813 }
|
|
2814 else if (flag == 'unread' && p.has_children) {
|
|
2815 // unread_children may be undefined
|
|
2816 p.unread_children = (p.unread_children || 0) + 1;
|
|
2817 }
|
|
2818 else if (flag == 'unflagged' && p.flagged_children) {
|
|
2819 p.flagged_children--;
|
|
2820 }
|
|
2821 else if (flag == 'flagged' && p.has_children) {
|
|
2822 p.flagged_children = (p.flagged_children || 0) + 1;
|
|
2823 }
|
|
2824 else {
|
|
2825 return;
|
|
2826 }
|
|
2827
|
|
2828 this.set_message_icon(root);
|
|
2829 this.set_unread_children(root);
|
|
2830 this.set_flagged_children(root);
|
|
2831 };
|
|
2832
|
|
2833 // update thread indicators for all messages in a thread below the specified message
|
|
2834 // return number of removed/added root level messages
|
|
2835 this.update_thread = function(uid)
|
|
2836 {
|
|
2837 if (!this.env.threading || !this.message_list.rows[uid])
|
|
2838 return 0;
|
|
2839
|
|
2840 var r, parent, count = 0,
|
|
2841 list = this.message_list,
|
|
2842 rows = list.rows,
|
|
2843 row = rows[uid],
|
|
2844 depth = rows[uid].depth,
|
|
2845 roots = [];
|
|
2846
|
|
2847 if (!row.depth) // root message: decrease roots count
|
|
2848 count--;
|
|
2849
|
|
2850 // update unread_children for thread root
|
|
2851 if (row.depth && row.unread) {
|
|
2852 parent = list.find_root(uid);
|
|
2853 rows[parent].unread_children--;
|
|
2854 this.set_unread_children(parent);
|
|
2855 }
|
|
2856
|
|
2857 // update unread_children for thread root
|
|
2858 if (row.depth && row.flagged) {
|
|
2859 parent = list.find_root(uid);
|
|
2860 rows[parent].flagged_children--;
|
|
2861 this.set_flagged_children(parent);
|
|
2862 }
|
|
2863
|
|
2864 parent = row.parent_uid;
|
|
2865
|
|
2866 // childrens
|
|
2867 row = row.obj.nextSibling;
|
|
2868 while (row) {
|
|
2869 if (row.nodeType == 1 && (r = rows[row.uid])) {
|
|
2870 if (!r.depth || r.depth <= depth)
|
|
2871 break;
|
|
2872
|
|
2873 r.depth--; // move left
|
|
2874 // reset width and clear the content of a tab, icons will be added later
|
|
2875 $('#rcmtab'+r.id).width(r.depth * 15).html('');
|
|
2876 if (!r.depth) { // a new root
|
|
2877 count++; // increase roots count
|
|
2878 r.parent_uid = 0;
|
|
2879 if (r.has_children) {
|
|
2880 // replace 'leaf' with 'collapsed'
|
|
2881 $('#'+r.id+' .leaf:first')
|
|
2882 .attr('id', 'rcmexpando' + r.id)
|
|
2883 .attr('class', (r.obj.style.display != 'none' ? 'expanded' : 'collapsed'))
|
|
2884 .mousedown({uid: r.uid}, function(e) {
|
|
2885 return ref.expand_message_row(e, e.data.uid);
|
|
2886 });
|
|
2887
|
|
2888 r.unread_children = 0;
|
|
2889 roots.push(r);
|
|
2890 }
|
|
2891 // show if it was hidden
|
|
2892 if (r.obj.style.display == 'none')
|
|
2893 $(r.obj).show();
|
|
2894 }
|
|
2895 else {
|
|
2896 if (r.depth == depth)
|
|
2897 r.parent_uid = parent;
|
|
2898 if (r.unread && roots.length)
|
|
2899 roots[roots.length-1].unread_children++;
|
|
2900 }
|
|
2901 }
|
|
2902 row = row.nextSibling;
|
|
2903 }
|
|
2904
|
|
2905 // update unread_children/flagged_children for roots
|
|
2906 for (r=0; r<roots.length; r++) {
|
|
2907 this.set_unread_children(roots[r].uid);
|
|
2908 this.set_flagged_children(roots[r].uid);
|
|
2909 }
|
|
2910
|
|
2911 return count;
|
|
2912 };
|
|
2913
|
|
2914 this.delete_excessive_thread_rows = function()
|
|
2915 {
|
|
2916 var rows = this.message_list.rows,
|
|
2917 tbody = this.message_list.tbody,
|
|
2918 row = tbody.firstChild,
|
|
2919 cnt = this.env.pagesize + 1;
|
|
2920
|
|
2921 while (row) {
|
|
2922 if (row.nodeType == 1 && (r = rows[row.uid])) {
|
|
2923 if (!r.depth && cnt)
|
|
2924 cnt--;
|
|
2925
|
|
2926 if (!cnt)
|
|
2927 this.message_list.remove_row(row.uid);
|
|
2928 }
|
|
2929 row = row.nextSibling;
|
|
2930 }
|
|
2931 };
|
|
2932
|
|
2933 // set message icon
|
|
2934 this.set_message_icon = function(uid)
|
|
2935 {
|
|
2936 var css_class, label = '',
|
|
2937 row = this.message_list.rows[uid];
|
|
2938
|
|
2939 if (!row)
|
|
2940 return false;
|
|
2941
|
|
2942 if (row.icon) {
|
|
2943 css_class = 'msgicon';
|
|
2944 if (row.deleted) {
|
|
2945 css_class += ' deleted';
|
|
2946 label += this.get_label('deleted') + ' ';
|
|
2947 }
|
|
2948 else if (row.unread) {
|
|
2949 css_class += ' unread';
|
|
2950 label += this.get_label('unread') + ' ';
|
|
2951 }
|
|
2952 else if (row.unread_children)
|
|
2953 css_class += ' unreadchildren';
|
|
2954 if (row.msgicon == row.icon) {
|
|
2955 if (row.replied) {
|
|
2956 css_class += ' replied';
|
|
2957 label += this.get_label('replied') + ' ';
|
|
2958 }
|
|
2959 if (row.forwarded) {
|
|
2960 css_class += ' forwarded';
|
|
2961 label += this.get_label('forwarded') + ' ';
|
|
2962 }
|
|
2963 css_class += ' status';
|
|
2964 }
|
|
2965
|
|
2966 $(row.icon).attr('class', css_class).attr('title', label);
|
|
2967 }
|
|
2968
|
|
2969 if (row.msgicon && row.msgicon != row.icon) {
|
|
2970 label = '';
|
|
2971 css_class = 'msgicon';
|
|
2972 if (!row.unread && row.unread_children) {
|
|
2973 css_class += ' unreadchildren';
|
|
2974 }
|
|
2975 if (row.replied) {
|
|
2976 css_class += ' replied';
|
|
2977 label += this.get_label('replied') + ' ';
|
|
2978 }
|
|
2979 if (row.forwarded) {
|
|
2980 css_class += ' forwarded';
|
|
2981 label += this.get_label('forwarded') + ' ';
|
|
2982 }
|
|
2983
|
|
2984 $(row.msgicon).attr('class', css_class).attr('title', label);
|
|
2985 }
|
|
2986
|
|
2987 if (row.flagicon) {
|
|
2988 css_class = (row.flagged ? 'flagged' : 'unflagged');
|
|
2989 label = this.get_label(css_class);
|
|
2990 $(row.flagicon).attr('class', css_class)
|
|
2991 .attr('aria-label', label)
|
|
2992 .attr('title', label);
|
|
2993 }
|
|
2994 };
|
|
2995
|
|
2996 // set message status
|
|
2997 this.set_message_status = function(uid, flag, status)
|
|
2998 {
|
|
2999 var row = this.message_list.rows[uid];
|
|
3000
|
|
3001 if (!row)
|
|
3002 return false;
|
|
3003
|
|
3004 if (flag == 'unread') {
|
|
3005 if (row.unread != status)
|
|
3006 this.update_thread_root(uid, status ? 'unread' : 'read');
|
|
3007 }
|
|
3008 else if (flag == 'flagged') {
|
|
3009 this.update_thread_root(uid, status ? 'flagged' : 'unflagged');
|
|
3010 }
|
|
3011
|
|
3012 if ($.inArray(flag, ['unread', 'deleted', 'replied', 'forwarded', 'flagged']) > -1)
|
|
3013 row[flag] = status;
|
|
3014 };
|
|
3015
|
|
3016 // set message row status, class and icon
|
|
3017 this.set_message = function(uid, flag, status)
|
|
3018 {
|
|
3019 var row = this.message_list && this.message_list.rows[uid];
|
|
3020
|
|
3021 if (!row)
|
|
3022 return false;
|
|
3023
|
|
3024 if (flag)
|
|
3025 this.set_message_status(uid, flag, status);
|
|
3026
|
|
3027 if ($.inArray(flag, ['unread', 'deleted', 'flagged']) > -1)
|
|
3028 $(row.obj)[row[flag] ? 'addClass' : 'removeClass'](flag);
|
|
3029
|
|
3030 this.set_unread_children(uid);
|
|
3031 this.set_message_icon(uid);
|
|
3032 };
|
|
3033
|
|
3034 // sets unroot (unread_children) class of parent row
|
|
3035 this.set_unread_children = function(uid)
|
|
3036 {
|
|
3037 var row = this.message_list.rows[uid];
|
|
3038
|
|
3039 if (row.parent_uid)
|
|
3040 return;
|
|
3041
|
|
3042 var enable = !row.unread && row.unread_children && !row.expanded;
|
|
3043 $(row.obj)[enable ? 'addClass' : 'removeClass']('unroot');
|
|
3044 };
|
|
3045
|
|
3046 // sets flaggedroot (flagged_children) class of parent row
|
|
3047 this.set_flagged_children = function(uid)
|
|
3048 {
|
|
3049 var row = this.message_list.rows[uid];
|
|
3050
|
|
3051 if (row.parent_uid)
|
|
3052 return;
|
|
3053
|
|
3054 var enable = row.flagged_children && !row.expanded;
|
|
3055 $(row.obj)[enable ? 'addClass' : 'removeClass']('flaggedroot');
|
|
3056 };
|
|
3057
|
|
3058 // copy selected messages to the specified mailbox
|
|
3059 this.copy_messages = function(mbox, event)
|
|
3060 {
|
|
3061 if (mbox && typeof mbox === 'object')
|
|
3062 mbox = mbox.id;
|
|
3063 else if (!mbox)
|
|
3064 return this.folder_selector(event, function(folder) { ref.command('copy', folder); });
|
|
3065
|
|
3066 // exit if current or no mailbox specified
|
|
3067 if (!mbox || mbox == this.env.mailbox)
|
|
3068 return;
|
|
3069
|
|
3070 var post_data = this.selection_post_data({_target_mbox: mbox});
|
|
3071
|
|
3072 // exit if selection is empty
|
|
3073 if (!post_data._uid)
|
|
3074 return;
|
|
3075
|
|
3076 // send request to server
|
|
3077 this.http_post('copy', post_data, this.display_message(this.get_label('copyingmessage'), 'loading'));
|
|
3078 };
|
|
3079
|
|
3080 // move selected messages to the specified mailbox
|
|
3081 this.move_messages = function(mbox, event)
|
|
3082 {
|
|
3083 if (mbox && typeof mbox === 'object')
|
|
3084 mbox = mbox.id;
|
|
3085 else if (!mbox)
|
|
3086 return this.folder_selector(event, function(folder) { ref.command('move', folder); });
|
|
3087
|
|
3088 // exit if current or no mailbox specified
|
|
3089 if (!mbox || (mbox == this.env.mailbox && !this.is_multifolder_listing()))
|
|
3090 return;
|
|
3091
|
|
3092 var lock = false, post_data = this.selection_post_data({_target_mbox: mbox});
|
|
3093
|
|
3094 // exit if selection is empty
|
|
3095 if (!post_data._uid)
|
|
3096 return;
|
|
3097
|
|
3098 // show wait message
|
|
3099 if (this.env.action == 'show')
|
|
3100 lock = this.set_busy(true, 'movingmessage');
|
|
3101 else
|
|
3102 this.show_contentframe(false);
|
|
3103
|
|
3104 // Hide message command buttons until a message is selected
|
|
3105 this.enable_command(this.env.message_commands, false);
|
|
3106
|
|
3107 this.with_selected_messages('move', post_data, lock);
|
|
3108 };
|
|
3109
|
|
3110 // delete selected messages from the current mailbox
|
|
3111 this.delete_messages = function(event)
|
|
3112 {
|
|
3113 var list = this.message_list, trash = this.env.trash_mailbox;
|
|
3114
|
|
3115 // if config is set to flag for deletion
|
|
3116 if (this.env.flag_for_deletion) {
|
|
3117 this.mark_message('delete');
|
|
3118 return false;
|
|
3119 }
|
|
3120 // if there isn't a defined trash mailbox or we are in it
|
|
3121 else if (!trash || this.env.mailbox == trash)
|
|
3122 this.permanently_remove_messages();
|
|
3123 // we're in Junk folder and delete_junk is enabled
|
|
3124 else if (this.env.delete_junk && this.env.junk_mailbox && this.env.mailbox == this.env.junk_mailbox)
|
|
3125 this.permanently_remove_messages();
|
|
3126 // if there is a trash mailbox defined and we're not currently in it
|
|
3127 else {
|
|
3128 // if shift was pressed delete it immediately
|
|
3129 if ((list && list.modkey == SHIFT_KEY) || (event && rcube_event.get_modifier(event) == SHIFT_KEY)) {
|
|
3130 if (confirm(this.get_label('deletemessagesconfirm')))
|
|
3131 this.permanently_remove_messages();
|
|
3132 }
|
|
3133 else
|
|
3134 this.move_messages(trash);
|
|
3135 }
|
|
3136
|
|
3137 return true;
|
|
3138 };
|
|
3139
|
|
3140 // delete the selected messages permanently
|
|
3141 this.permanently_remove_messages = function()
|
|
3142 {
|
|
3143 var post_data = this.selection_post_data();
|
|
3144
|
|
3145 // exit if selection is empty
|
|
3146 if (!post_data._uid)
|
|
3147 return;
|
|
3148
|
|
3149 this.show_contentframe(false);
|
|
3150 this.with_selected_messages('delete', post_data);
|
|
3151 };
|
|
3152
|
|
3153 // Send a specific move/delete request with UIDs of all selected messages
|
|
3154 this.with_selected_messages = function(action, post_data, lock, http_action)
|
|
3155 {
|
|
3156 var count = 0, msg,
|
|
3157 remove = (action == 'delete' || !this.is_multifolder_listing());
|
|
3158
|
|
3159 // update the list (remove rows, clear selection)
|
|
3160 if (this.message_list) {
|
|
3161 var n, id, root, roots = [],
|
|
3162 selection = this.message_list.get_selection();
|
|
3163
|
|
3164 for (n=0, len=selection.length; n<len; n++) {
|
|
3165 id = selection[n];
|
|
3166
|
|
3167 if (this.env.threading) {
|
|
3168 count += this.update_thread(id);
|
|
3169 root = this.message_list.find_root(id);
|
|
3170 if (root != id && $.inArray(root, roots) < 0) {
|
|
3171 roots.push(root);
|
|
3172 }
|
|
3173 }
|
|
3174 if (remove)
|
|
3175 this.message_list.remove_row(id, (this.env.display_next && n == selection.length-1));
|
|
3176 }
|
|
3177 // make sure there are no selected rows
|
|
3178 if (!this.env.display_next && remove)
|
|
3179 this.message_list.clear_selection();
|
|
3180 // update thread tree icons
|
|
3181 for (n=0, len=roots.length; n<len; n++) {
|
|
3182 this.add_tree_icons(roots[n]);
|
|
3183 }
|
|
3184 }
|
|
3185
|
|
3186 if (count < 0)
|
|
3187 post_data._count = (count*-1);
|
|
3188 // remove threads from the end of the list
|
|
3189 else if (count > 0 && remove)
|
|
3190 this.delete_excessive_thread_rows();
|
|
3191
|
|
3192 if (!remove)
|
|
3193 post_data._refresh = 1;
|
|
3194
|
|
3195 if (!lock) {
|
|
3196 msg = action == 'move' ? 'movingmessage' : 'deletingmessage';
|
|
3197 lock = this.display_message(this.get_label(msg), 'loading');
|
|
3198 }
|
|
3199
|
|
3200 // send request to server
|
|
3201 this.http_post(http_action || action, post_data, lock);
|
|
3202 };
|
|
3203
|
|
3204 // build post data for message delete/move/copy/flag requests
|
|
3205 this.selection_post_data = function(data)
|
|
3206 {
|
|
3207 if (typeof(data) != 'object')
|
|
3208 data = {};
|
|
3209
|
|
3210 data._mbox = this.env.mailbox;
|
|
3211
|
|
3212 if (!data._uid) {
|
|
3213 var uids = this.env.uid ? [this.env.uid] : this.message_list.get_selection();
|
|
3214 data._uid = this.uids_to_list(uids);
|
|
3215 }
|
|
3216
|
|
3217 if (this.env.action)
|
|
3218 data._from = this.env.action;
|
|
3219
|
|
3220 // also send search request to get the right messages
|
|
3221 if (this.env.search_request)
|
|
3222 data._search = this.env.search_request;
|
|
3223
|
|
3224 if (this.env.display_next && this.env.next_uid)
|
|
3225 data._next_uid = this.env.next_uid;
|
|
3226
|
|
3227 return data;
|
|
3228 };
|
|
3229
|
|
3230 // set a specific flag to one or more messages
|
|
3231 this.mark_message = function(flag, uid)
|
|
3232 {
|
|
3233 var a_uids = [], r_uids = [], len, n, id,
|
|
3234 list = this.message_list;
|
|
3235
|
|
3236 if (uid)
|
|
3237 a_uids[0] = uid;
|
|
3238 else if (this.env.uid)
|
|
3239 a_uids[0] = this.env.uid;
|
|
3240 else if (list)
|
|
3241 a_uids = list.get_selection();
|
|
3242
|
|
3243 if (!list)
|
|
3244 r_uids = a_uids;
|
|
3245 else {
|
|
3246 list.focus();
|
|
3247 for (n=0, len=a_uids.length; n<len; n++) {
|
|
3248 id = a_uids[n];
|
|
3249 if ((flag == 'read' && list.rows[id].unread)
|
|
3250 || (flag == 'unread' && !list.rows[id].unread)
|
|
3251 || (flag == 'delete' && !list.rows[id].deleted)
|
|
3252 || (flag == 'undelete' && list.rows[id].deleted)
|
|
3253 || (flag == 'flagged' && !list.rows[id].flagged)
|
|
3254 || (flag == 'unflagged' && list.rows[id].flagged))
|
|
3255 {
|
|
3256 r_uids.push(id);
|
|
3257 }
|
|
3258 }
|
|
3259 }
|
|
3260
|
|
3261 // nothing to do
|
|
3262 if (!r_uids.length && !this.select_all_mode)
|
|
3263 return;
|
|
3264
|
|
3265 switch (flag) {
|
|
3266 case 'read':
|
|
3267 case 'unread':
|
|
3268 this.toggle_read_status(flag, r_uids);
|
|
3269 break;
|
|
3270 case 'delete':
|
|
3271 case 'undelete':
|
|
3272 this.toggle_delete_status(r_uids);
|
|
3273 break;
|
|
3274 case 'flagged':
|
|
3275 case 'unflagged':
|
|
3276 this.toggle_flagged_status(flag, a_uids);
|
|
3277 break;
|
|
3278 }
|
|
3279 };
|
|
3280
|
|
3281 // set class to read/unread
|
|
3282 this.toggle_read_status = function(flag, a_uids)
|
|
3283 {
|
|
3284 var i, len = a_uids.length,
|
|
3285 post_data = this.selection_post_data({_uid: this.uids_to_list(a_uids), _flag: flag}),
|
|
3286 lock = this.display_message(this.get_label('markingmessage'), 'loading');
|
|
3287
|
|
3288 // mark all message rows as read/unread
|
|
3289 for (i=0; i<len; i++)
|
|
3290 this.set_message(a_uids[i], 'unread', (flag == 'unread' ? true : false));
|
|
3291
|
|
3292 this.http_post('mark', post_data, lock);
|
|
3293 };
|
|
3294
|
|
3295 // set image to flagged or unflagged
|
|
3296 this.toggle_flagged_status = function(flag, a_uids)
|
|
3297 {
|
|
3298 var i, len = a_uids.length,
|
|
3299 post_data = this.selection_post_data({_uid: this.uids_to_list(a_uids), _flag: flag}),
|
|
3300 lock = this.display_message(this.get_label('markingmessage'), 'loading');
|
|
3301
|
|
3302 // mark all message rows as flagged/unflagged
|
|
3303 for (i=0; i<len; i++)
|
|
3304 this.set_message(a_uids[i], 'flagged', (flag == 'flagged' ? true : false));
|
|
3305
|
|
3306 this.http_post('mark', post_data, lock);
|
|
3307 };
|
|
3308
|
|
3309 // mark all message rows as deleted/undeleted
|
|
3310 this.toggle_delete_status = function(a_uids)
|
|
3311 {
|
|
3312 var len = a_uids.length,
|
|
3313 i, uid, all_deleted = true,
|
|
3314 rows = this.message_list ? this.message_list.rows : {};
|
|
3315
|
|
3316 if (len == 1) {
|
|
3317 if (!this.message_list || (rows[a_uids[0]] && !rows[a_uids[0]].deleted))
|
|
3318 this.flag_as_deleted(a_uids);
|
|
3319 else
|
|
3320 this.flag_as_undeleted(a_uids);
|
|
3321
|
|
3322 return true;
|
|
3323 }
|
|
3324
|
|
3325 for (i=0; i<len; i++) {
|
|
3326 uid = a_uids[i];
|
|
3327 if (rows[uid] && !rows[uid].deleted) {
|
|
3328 all_deleted = false;
|
|
3329 break;
|
|
3330 }
|
|
3331 }
|
|
3332
|
|
3333 if (all_deleted)
|
|
3334 this.flag_as_undeleted(a_uids);
|
|
3335 else
|
|
3336 this.flag_as_deleted(a_uids);
|
|
3337
|
|
3338 return true;
|
|
3339 };
|
|
3340
|
|
3341 this.flag_as_undeleted = function(a_uids)
|
|
3342 {
|
|
3343 var i, len = a_uids.length,
|
|
3344 post_data = this.selection_post_data({_uid: this.uids_to_list(a_uids), _flag: 'undelete'}),
|
|
3345 lock = this.display_message(this.get_label('markingmessage'), 'loading');
|
|
3346
|
|
3347 for (i=0; i<len; i++)
|
|
3348 this.set_message(a_uids[i], 'deleted', false);
|
|
3349
|
|
3350 this.http_post('mark', post_data, lock);
|
|
3351 };
|
|
3352
|
|
3353 this.flag_as_deleted = function(a_uids)
|
|
3354 {
|
|
3355 var r_uids = [],
|
|
3356 post_data = this.selection_post_data({_uid: this.uids_to_list(a_uids), _flag: 'delete'}),
|
|
3357 lock = this.display_message(this.get_label('markingmessage'), 'loading'),
|
|
3358 list = this.message_list,
|
|
3359 rows = list ? list.rows : {},
|
|
3360 count = 0;
|
|
3361
|
|
3362 for (var i=0, len=a_uids.length; i<len; i++) {
|
|
3363 uid = a_uids[i];
|
|
3364 if (rows[uid]) {
|
|
3365 if (rows[uid].unread)
|
|
3366 r_uids[r_uids.length] = uid;
|
|
3367
|
|
3368 if (this.env.skip_deleted) {
|
|
3369 count += this.update_thread(uid);
|
|
3370 list.remove_row(uid, (this.env.display_next && i == list.selection.length-1));
|
|
3371 }
|
|
3372 else
|
|
3373 this.set_message(uid, 'deleted', true);
|
|
3374 }
|
|
3375 }
|
|
3376
|
|
3377 // make sure there are no selected rows
|
|
3378 if (this.env.skip_deleted && list) {
|
|
3379 if (!this.env.display_next || !list.rowcount)
|
|
3380 list.clear_selection();
|
|
3381 if (count < 0)
|
|
3382 post_data._count = (count*-1);
|
|
3383 else if (count > 0)
|
|
3384 // remove threads from the end of the list
|
|
3385 this.delete_excessive_thread_rows();
|
|
3386 }
|
|
3387
|
|
3388 // set of messages to mark as seen
|
|
3389 if (r_uids.length)
|
|
3390 post_data._ruid = this.uids_to_list(r_uids);
|
|
3391
|
|
3392 if (this.env.skip_deleted && this.env.display_next && this.env.next_uid)
|
|
3393 post_data._next_uid = this.env.next_uid;
|
|
3394
|
|
3395 this.http_post('mark', post_data, lock);
|
|
3396 };
|
|
3397
|
|
3398 // flag as read without mark request (called from backend)
|
|
3399 // argument should be a coma-separated list of uids
|
|
3400 this.flag_deleted_as_read = function(uids)
|
|
3401 {
|
|
3402 var uid, i, len,
|
|
3403 rows = this.message_list ? this.message_list.rows : {};
|
|
3404
|
|
3405 if (typeof uids == 'string')
|
|
3406 uids = uids.split(',');
|
|
3407
|
|
3408 for (i=0, len=uids.length; i<len; i++) {
|
|
3409 uid = uids[i];
|
|
3410 if (rows[uid])
|
|
3411 this.set_message(uid, 'unread', false);
|
|
3412 }
|
|
3413 };
|
|
3414
|
|
3415 // Converts array of message UIDs to comma-separated list for use in URL
|
|
3416 // with select_all mode checking
|
|
3417 this.uids_to_list = function(uids)
|
|
3418 {
|
|
3419 return this.select_all_mode ? '*' : (uids.length <= 1 ? uids.join(',') : uids);
|
|
3420 };
|
|
3421
|
|
3422 // Sets title of the delete button
|
|
3423 this.set_button_titles = function()
|
|
3424 {
|
|
3425 var label = 'deletemessage';
|
|
3426
|
|
3427 if (!this.env.flag_for_deletion
|
|
3428 && this.env.trash_mailbox && this.env.mailbox != this.env.trash_mailbox
|
|
3429 && (!this.env.delete_junk || !this.env.junk_mailbox || this.env.mailbox != this.env.junk_mailbox)
|
|
3430 )
|
|
3431 label = 'movemessagetotrash';
|
|
3432
|
|
3433 this.set_alttext('delete', label);
|
|
3434 };
|
|
3435
|
|
3436 // Initialize input element for list page jump
|
|
3437 this.init_pagejumper = function(element)
|
|
3438 {
|
|
3439 $(element).addClass('rcpagejumper')
|
|
3440 .on('focus', function(e) {
|
|
3441 // create and display popup with page selection
|
|
3442 var i, html = '';
|
|
3443
|
|
3444 for (i = 1; i <= ref.env.pagecount; i++)
|
|
3445 html += '<li>' + i + '</li>';
|
|
3446
|
|
3447 html = '<ul class="toolbarmenu">' + html + '</ul>';
|
|
3448
|
|
3449 if (!ref.pagejump) {
|
|
3450 ref.pagejump = $('<div id="pagejump-selector" class="popupmenu"></div>')
|
|
3451 .appendTo(document.body)
|
|
3452 .on('click', 'li', function() {
|
|
3453 if (!ref.busy)
|
|
3454 $(element).val($(this).text()).change();
|
|
3455 });
|
|
3456 }
|
|
3457
|
|
3458 if (ref.pagejump.data('count') != i)
|
|
3459 ref.pagejump.html(html);
|
|
3460
|
|
3461 ref.pagejump.attr('rel', '#' + this.id).data('count', i);
|
|
3462
|
|
3463 // display page selector
|
|
3464 ref.show_menu('pagejump-selector', true, e);
|
|
3465 $(this).keydown();
|
|
3466 })
|
|
3467 // keyboard navigation
|
|
3468 .on('keydown keyup click', function(e) {
|
|
3469 var current, selector = $('#pagejump-selector'),
|
|
3470 ul = $('ul', selector),
|
|
3471 list = $('li', ul),
|
|
3472 height = ul.height(),
|
|
3473 p = parseInt(this.value);
|
|
3474
|
|
3475 if (e.which != 27 && e.which != 9 && e.which != 13 && !selector.is(':visible'))
|
|
3476 return ref.show_menu('pagejump-selector', true, e);
|
|
3477
|
|
3478 if (e.type == 'keydown') {
|
|
3479 // arrow-down
|
|
3480 if (e.which == 40) {
|
|
3481 if (list.length > p)
|
|
3482 this.value = (p += 1);
|
|
3483 }
|
|
3484 // arrow-up
|
|
3485 else if (e.which == 38) {
|
|
3486 if (p > 1 && list.length > p - 1)
|
|
3487 this.value = (p -= 1);
|
|
3488 }
|
|
3489 // enter
|
|
3490 else if (e.which == 13) {
|
|
3491 return $(this).change();
|
|
3492 }
|
|
3493 // esc, tab
|
|
3494 else if (e.which == 27 || e.which == 9) {
|
|
3495 return $(element).val(ref.env.current_page);
|
|
3496 }
|
|
3497 }
|
|
3498
|
|
3499 $('li.selected', ul).removeClass('selected');
|
|
3500
|
|
3501 if ((current = $(list[p - 1])).length) {
|
|
3502 current.addClass('selected');
|
|
3503 $('#pagejump-selector').scrollTop(((ul.height() / list.length) * (p - 1)) - selector.height() / 2);
|
|
3504 }
|
|
3505 })
|
|
3506 .on('change', function(e) {
|
|
3507 // go to specified page
|
|
3508 var p = parseInt(this.value);
|
|
3509 if (p && p != ref.env.current_page && !ref.busy) {
|
|
3510 ref.hide_menu('pagejump-selector');
|
|
3511 ref.list_page(p);
|
|
3512 }
|
|
3513 });
|
|
3514 };
|
|
3515
|
|
3516 // Update page-jumper state on list updates
|
|
3517 this.update_pagejumper = function()
|
|
3518 {
|
|
3519 $('input.rcpagejumper').val(this.env.current_page).prop('disabled', this.env.pagecount < 2);
|
|
3520 };
|
|
3521
|
|
3522 // check for mailvelope API
|
|
3523 this.check_mailvelope = function(action)
|
|
3524 {
|
|
3525 if (typeof window.mailvelope !== 'undefined') {
|
|
3526 this.mailvelope_load(action);
|
|
3527 }
|
|
3528 else {
|
|
3529 $(window).on('mailvelope', function() {
|
|
3530 ref.mailvelope_load(action);
|
|
3531 });
|
|
3532 }
|
|
3533 };
|
|
3534
|
|
3535 // Load Mailvelope functionality (and initialize keyring if needed)
|
|
3536 this.mailvelope_load = function(action)
|
|
3537 {
|
|
3538 if (this.env.browser_capabilities)
|
|
3539 this.env.browser_capabilities['pgpmime'] = 1;
|
|
3540
|
|
3541 var keyring = this.env.user_id;
|
|
3542
|
|
3543 mailvelope.getKeyring(keyring).then(function(kr) {
|
|
3544 ref.mailvelope_keyring = kr;
|
|
3545 ref.mailvelope_init(action, kr);
|
|
3546 }, function(err) {
|
|
3547 // attempt to create a new keyring for this app/user
|
|
3548 mailvelope.createKeyring(keyring).then(function(kr) {
|
|
3549 ref.mailvelope_keyring = kr;
|
|
3550 ref.mailvelope_init(action, kr);
|
|
3551 }, function(err) {
|
|
3552 console.error(err);
|
|
3553 });
|
|
3554 });
|
|
3555 };
|
|
3556
|
|
3557 // Initializes Mailvelope editor or display container
|
|
3558 this.mailvelope_init = function(action, keyring)
|
|
3559 {
|
|
3560 if (!window.mailvelope)
|
|
3561 return;
|
|
3562
|
|
3563 if (action == 'show' || action == 'preview' || action == 'print') {
|
|
3564 // decrypt text body
|
|
3565 if (this.env.is_pgp_content) {
|
|
3566 var data = $(this.env.is_pgp_content).text();
|
|
3567 ref.mailvelope_display_container(this.env.is_pgp_content, data, keyring);
|
|
3568 }
|
|
3569 // load pgp/mime message and pass it to the mailvelope display container
|
|
3570 else if (this.env.pgp_mime_part) {
|
|
3571 var msgid = this.display_message(this.get_label('loadingdata'), 'loading'),
|
|
3572 selector = this.env.pgp_mime_container;
|
|
3573
|
|
3574 $.ajax({
|
|
3575 type: 'GET',
|
|
3576 url: this.url('get', { '_mbox': this.env.mailbox, '_uid': this.env.uid, '_part': this.env.pgp_mime_part }),
|
|
3577 error: function(o, status, err) {
|
|
3578 ref.http_error(o, status, err, msgid);
|
|
3579 },
|
|
3580 success: function(data) {
|
|
3581 ref.mailvelope_display_container(selector, data, keyring, msgid);
|
|
3582 }
|
|
3583 });
|
|
3584 }
|
|
3585 }
|
|
3586 else if (action == 'compose') {
|
|
3587 this.env.compose_commands.push('compose-encrypted');
|
|
3588
|
|
3589 var is_html = $('input[name="_is_html"]').val() > 0;
|
|
3590
|
|
3591 if (this.env.pgp_mime_message) {
|
|
3592 // fetch PGP/Mime part and open load into Mailvelope editor
|
|
3593 var lock = this.set_busy(true, this.get_label('loadingdata'));
|
|
3594
|
|
3595 $.ajax({
|
|
3596 type: 'GET',
|
|
3597 url: this.url('get', this.env.pgp_mime_message),
|
|
3598 error: function(o, status, err) {
|
|
3599 ref.http_error(o, status, err, lock);
|
|
3600 ref.enable_command('compose-encrypted', !is_html);
|
|
3601 },
|
|
3602 success: function(data) {
|
|
3603 ref.set_busy(false, null, lock);
|
|
3604
|
|
3605 if (is_html) {
|
|
3606 ref.command('toggle-editor', {html: false, noconvert: true});
|
|
3607 $('#' + ref.env.composebody).val('');
|
|
3608 }
|
|
3609
|
|
3610 ref.compose_encrypted({ quotedMail: data });
|
|
3611 ref.enable_command('compose-encrypted', true);
|
|
3612 }
|
|
3613 });
|
|
3614 }
|
|
3615 else {
|
|
3616 // enable encrypted compose toggle
|
|
3617 this.enable_command('compose-encrypted', !is_html);
|
|
3618 }
|
|
3619
|
|
3620 // make sure to disable encryption button after toggling editor into HTML mode
|
|
3621 this.addEventListener('actionafter', function(args) {
|
|
3622 if (args.ret && args.action == 'toggle-editor')
|
|
3623 ref.enable_command('compose-encrypted', !args.props.html);
|
|
3624 });
|
|
3625 }
|
|
3626 };
|
|
3627
|
|
3628 // handler for the 'compose-encrypted' command
|
|
3629 this.compose_encrypted = function(props)
|
|
3630 {
|
|
3631 var options, container = $('#' + this.env.composebody).parent();
|
|
3632
|
|
3633 // remove Mailvelope editor if active
|
|
3634 if (ref.mailvelope_editor) {
|
|
3635 ref.mailvelope_editor = null;
|
|
3636 ref.compose_skip_unsavedcheck = false;
|
|
3637 ref.set_button('compose-encrypted', 'act');
|
|
3638
|
|
3639 container.removeClass('mailvelope')
|
|
3640 .find('iframe:not([aria-hidden=true])').remove();
|
|
3641 $('#' + ref.env.composebody).show();
|
|
3642 $("[name='_pgpmime']").remove();
|
|
3643
|
|
3644 // disable commands that operate on the compose body
|
|
3645 ref.enable_command('spellcheck', 'insert-sig', 'toggle-editor', 'insert-response', 'save-response', true);
|
|
3646 ref.triggerEvent('compose-encrypted', { active:false });
|
|
3647 }
|
|
3648 // embed Mailvelope editor container
|
|
3649 else {
|
|
3650 if (this.spellcheck_state())
|
|
3651 this.editor.spellcheck_stop();
|
|
3652
|
|
3653 if (props.quotedMail) {
|
|
3654 options = { quotedMail: props.quotedMail, quotedMailIndent: false };
|
|
3655 }
|
|
3656 else {
|
|
3657 options = { predefinedText: $('#' + this.env.composebody).val() };
|
|
3658 }
|
|
3659
|
|
3660 if (this.env.compose_mode == 'reply') {
|
|
3661 options.quotedMailIndent = true;
|
|
3662 options.quotedMailHeader = this.env.compose_reply_header;
|
|
3663 }
|
|
3664
|
|
3665 mailvelope.createEditorContainer('#' + container.attr('id'), ref.mailvelope_keyring, options).then(function(editor) {
|
|
3666 ref.mailvelope_editor = editor;
|
|
3667 ref.compose_skip_unsavedcheck = true;
|
|
3668 ref.set_button('compose-encrypted', 'sel');
|
|
3669
|
|
3670 container.addClass('mailvelope');
|
|
3671 $('#' + ref.env.composebody).hide();
|
|
3672
|
|
3673 // disable commands that operate on the compose body
|
|
3674 ref.enable_command('spellcheck', 'insert-sig', 'toggle-editor', 'insert-response', 'save-response', false);
|
|
3675 ref.triggerEvent('compose-encrypted', { active:true });
|
|
3676
|
|
3677 // notify user about loosing attachments
|
|
3678 if (ref.env.attachments && !$.isEmptyObject(ref.env.attachments)) {
|
|
3679 alert(ref.get_label('encryptnoattachments'));
|
|
3680
|
|
3681 $.each(ref.env.attachments, function(name, attach) {
|
|
3682 ref.remove_from_attachment_list(name);
|
|
3683 });
|
|
3684 }
|
|
3685 }, function(err) {
|
|
3686 console.error(err);
|
|
3687 console.log(options);
|
|
3688 });
|
|
3689 }
|
|
3690 };
|
|
3691
|
|
3692 // callback to replace the message body with the full armored
|
|
3693 this.mailvelope_submit_messageform = function(draft, saveonly)
|
|
3694 {
|
|
3695 // get recipients
|
|
3696 var recipients = [];
|
|
3697 $.each(['to', 'cc', 'bcc'], function(i,field) {
|
|
3698 var pos, rcpt, val = $.trim($('[name="_' + field + '"]').val());
|
|
3699 while (val.length && rcube_check_email(val, true)) {
|
|
3700 rcpt = RegExp.$2;
|
|
3701 recipients.push(rcpt);
|
|
3702 val = val.substr(val.indexOf(rcpt) + rcpt.length + 1).replace(/^\s*,\s*/, '');
|
|
3703 }
|
|
3704 });
|
|
3705
|
|
3706 // check if we have keys for all recipients
|
|
3707 var isvalid = recipients.length > 0;
|
|
3708 ref.mailvelope_keyring.validKeyForAddress(recipients).then(function(status) {
|
|
3709 var missing_keys = [];
|
|
3710 $.each(status, function(k,v) {
|
|
3711 if (v === false) {
|
|
3712 isvalid = false;
|
|
3713 missing_keys.push(k);
|
|
3714 }
|
|
3715 });
|
|
3716
|
|
3717 // list recipients with missing keys
|
|
3718 if (!isvalid && missing_keys.length) {
|
|
3719 // display dialog with missing keys
|
|
3720 ref.simple_dialog(
|
|
3721 ref.get_label('nopubkeyfor').replace('$email', missing_keys.join(', ')) +
|
|
3722 '<p>' + ref.get_label('searchpubkeyservers') + '</p>',
|
|
3723 'encryptedsendialog',
|
|
3724 function() {
|
|
3725 ref.mailvelope_search_pubkeys(missing_keys, function() {
|
|
3726 return true; // close dialog
|
|
3727 });
|
|
3728 },
|
|
3729 {button: 'search'}
|
|
3730 );
|
|
3731 return false;
|
|
3732 }
|
|
3733
|
|
3734 if (!isvalid) {
|
|
3735 if (!recipients.length) {
|
|
3736 alert(ref.get_label('norecipientwarning'));
|
|
3737 $("[name='_to']").focus();
|
|
3738 }
|
|
3739 return false;
|
|
3740 }
|
|
3741
|
|
3742 // add sender identity to recipients to be able to decrypt our very own message
|
|
3743 var senders = [], selected_sender = ref.env.identities[$("[name='_from'] option:selected").val()];
|
|
3744 $.each(ref.env.identities, function(k, sender) {
|
|
3745 senders.push(sender.email);
|
|
3746 });
|
|
3747
|
|
3748 ref.mailvelope_keyring.validKeyForAddress(senders).then(function(status) {
|
|
3749 valid_sender = null;
|
|
3750 $.each(status, function(k,v) {
|
|
3751 if (v !== false) {
|
|
3752 valid_sender = k;
|
|
3753 if (valid_sender == selected_sender) {
|
|
3754 return false; // break
|
|
3755 }
|
|
3756 }
|
|
3757 });
|
|
3758
|
|
3759 if (!valid_sender) {
|
|
3760 if (!confirm(ref.get_label('nopubkeyforsender'))) {
|
|
3761 return false;
|
|
3762 }
|
|
3763 }
|
|
3764
|
|
3765 recipients.push(valid_sender);
|
|
3766
|
|
3767 ref.mailvelope_editor.encrypt(recipients).then(function(armored) {
|
|
3768 // all checks passed, send message
|
|
3769 var form = ref.gui_objects.messageform,
|
|
3770 hidden = $("[name='_pgpmime']", form),
|
|
3771 msgid = ref.set_busy(true, draft || saveonly ? 'savingmessage' : 'sendingmessage')
|
|
3772
|
|
3773 form.target = 'savetarget';
|
|
3774 form._draft.value = draft ? '1' : '';
|
|
3775 form.action = ref.add_url(form.action, '_unlock', msgid);
|
|
3776 form.action = ref.add_url(form.action, '_framed', 1);
|
|
3777
|
|
3778 if (saveonly) {
|
|
3779 form.action = ref.add_url(form.action, '_saveonly', 1);
|
|
3780 }
|
|
3781
|
|
3782 // send pgp conent via hidden field
|
|
3783 if (!hidden.length) {
|
|
3784 hidden = $('<input type="hidden" name="_pgpmime">').appendTo(form);
|
|
3785 }
|
|
3786 hidden.val(armored);
|
|
3787
|
|
3788 form.submit();
|
|
3789
|
|
3790 }, function(err) {
|
|
3791 console.log(err);
|
|
3792 }); // mailvelope_editor.encrypt()
|
|
3793
|
|
3794 }, function(err) {
|
|
3795 console.error(err);
|
|
3796 }); // mailvelope_keyring.validKeyForAddress(senders)
|
|
3797
|
|
3798 }, function(err) {
|
|
3799 console.error(err);
|
|
3800 }); // mailvelope_keyring.validKeyForAddress(recipients)
|
|
3801
|
|
3802 return false;
|
|
3803 };
|
|
3804
|
|
3805 // wrapper for the mailvelope.createDisplayContainer API call
|
|
3806 this.mailvelope_display_container = function(selector, data, keyring, msgid)
|
|
3807 {
|
|
3808 var error_handler = function(error) {
|
|
3809 // remove mailvelope frame with the error message
|
|
3810 $(selector + ' > iframe').remove();
|
|
3811 ref.hide_message(msgid);
|
|
3812 ref.display_message(error.message, 'error');
|
|
3813 };
|
|
3814
|
|
3815 mailvelope.createDisplayContainer(selector, data, keyring, { showExternalContent: this.env.safemode }).then(function(status) {
|
|
3816 if (status.error && status.error.message) {
|
|
3817 return error_handler(status.error);
|
|
3818 }
|
|
3819
|
|
3820 ref.hide_message(msgid);
|
|
3821 $(selector).addClass('mailvelope').children().not('iframe').hide();
|
|
3822
|
|
3823 // on success we can remove encrypted part from the attachments list
|
|
3824 if (ref.env.pgp_mime_part)
|
|
3825 $('#attach' + ref.env.pgp_mime_part).remove();
|
|
3826
|
|
3827 setTimeout(function() { $(window).resize(); }, 10);
|
|
3828 }, error_handler);
|
|
3829 };
|
|
3830
|
|
3831 // subroutine to query keyservers for public keys
|
|
3832 this.mailvelope_search_pubkeys = function(emails, resolve, import_handler)
|
|
3833 {
|
|
3834 // query with publickey.js
|
|
3835 var deferreds = [],
|
|
3836 pk = new PublicKey(),
|
|
3837 lock = ref.display_message(ref.get_label('loading'), 'loading');
|
|
3838
|
|
3839 $.each(emails, function(i, email) {
|
|
3840 var d = $.Deferred();
|
|
3841 pk.search(email, function(results, errorCode) {
|
|
3842 if (errorCode !== null) {
|
|
3843 // rejecting would make all fail
|
|
3844 // d.reject(email);
|
|
3845 d.resolve([email]);
|
|
3846 }
|
|
3847 else {
|
|
3848 d.resolve([email].concat(results));
|
|
3849 }
|
|
3850 });
|
|
3851 deferreds.push(d);
|
|
3852 });
|
|
3853
|
|
3854 $.when.apply($, deferreds).then(function() {
|
|
3855 var missing_keys = [],
|
|
3856 key_selection = [];
|
|
3857
|
|
3858 // alanyze results of all queries
|
|
3859 $.each(arguments, function(i, result) {
|
|
3860 var email = result.shift();
|
|
3861 if (!result.length) {
|
|
3862 missing_keys.push(email);
|
|
3863 }
|
|
3864 else {
|
|
3865 key_selection = key_selection.concat(result);
|
|
3866 }
|
|
3867 });
|
|
3868
|
|
3869 ref.hide_message(lock);
|
|
3870 resolve(true);
|
|
3871
|
|
3872 // show key import dialog
|
|
3873 if (key_selection.length) {
|
|
3874 ref.mailvelope_key_import_dialog(key_selection, import_handler);
|
|
3875 }
|
|
3876 // some keys could not be found
|
|
3877 if (missing_keys.length) {
|
|
3878 ref.display_message(ref.get_label('nopubkeyfor').replace('$email', missing_keys.join(', ')), 'warning');
|
|
3879 }
|
|
3880 }).fail(function() {
|
|
3881 console.error('Pubkey lookup failed with', arguments);
|
|
3882 ref.hide_message(lock);
|
|
3883 ref.display_message('pubkeysearcherror', 'error');
|
|
3884 resolve(false);
|
|
3885 });
|
|
3886 };
|
|
3887
|
|
3888 // list the given public keys in a dialog with options to import
|
|
3889 // them into the local Maivelope keyring
|
|
3890 this.mailvelope_key_import_dialog = function(candidates, import_handler)
|
|
3891 {
|
|
3892 var ul = $('<div>').addClass('listing pgpkeyimport');
|
|
3893 $.each(candidates, function(i, keyrec) {
|
|
3894 var li = $('<div>').addClass('key');
|
|
3895 if (keyrec.revoked) li.addClass('revoked');
|
|
3896 if (keyrec.disabled) li.addClass('disabled');
|
|
3897 if (keyrec.expired) li.addClass('expired');
|
|
3898
|
|
3899 li.append($('<label>').addClass('keyid').text(ref.get_label('keyid')));
|
|
3900 li.append($('<a>').text(keyrec.keyid.substr(-8).toUpperCase())
|
|
3901 .attr('href', keyrec.info)
|
|
3902 .attr('target', '_blank')
|
|
3903 .attr('tabindex', '-1'));
|
|
3904
|
|
3905 li.append($('<label>').addClass('keylen').text(ref.get_label('keylength')));
|
|
3906 li.append($('<span>').text(keyrec.keylen));
|
|
3907
|
|
3908 if (keyrec.expirationdate) {
|
|
3909 li.append($('<label>').addClass('keyexpired').text(ref.get_label('keyexpired')));
|
|
3910 li.append($('<span>').text(new Date(keyrec.expirationdate * 1000).toDateString()));
|
|
3911 }
|
|
3912
|
|
3913 if (keyrec.revoked) {
|
|
3914 li.append($('<span>').addClass('keyrevoked').text(ref.get_label('keyrevoked')));
|
|
3915 }
|
|
3916
|
|
3917 var ul_ = $('<ul>').addClass('uids');
|
|
3918 $.each(keyrec.uids, function(j, uid) {
|
|
3919 var li_ = $('<li>').addClass('uid');
|
|
3920 if (uid.revoked) li_.addClass('revoked');
|
|
3921 if (uid.disabled) li_.addClass('disabled');
|
|
3922 if (uid.expired) li_.addClass('expired');
|
|
3923
|
|
3924 ul_.append(li_.text(uid.uid));
|
|
3925 });
|
|
3926
|
|
3927 li.append(ul_);
|
|
3928 li.append($('<input>')
|
|
3929 .attr('type', 'button')
|
|
3930 .attr('rel', keyrec.keyid)
|
|
3931 .attr('value', ref.get_label('import'))
|
|
3932 .addClass('button importkey')
|
|
3933 .prop('disabled', keyrec.revoked || keyrec.disabled || keyrec.expired));
|
|
3934
|
|
3935 ul.append(li);
|
|
3936 });
|
|
3937
|
|
3938 // display dialog with missing keys
|
|
3939 ref.show_popup_dialog(
|
|
3940 $('<div>')
|
|
3941 .append($('<p>').html(ref.get_label('encryptpubkeysfound')))
|
|
3942 .append(ul),
|
|
3943 ref.get_label('importpubkeys'),
|
|
3944 [{
|
|
3945 text: ref.get_label('close'),
|
|
3946 click: function() {
|
|
3947 (ref.is_framed() ? parent.$ : $)(this).dialog('close');
|
|
3948 }
|
|
3949 }]
|
|
3950 );
|
|
3951
|
|
3952 // delegate handler for import button clicks
|
|
3953 ul.on('click', 'input.button.importkey', function() {
|
|
3954 var btn = $(this),
|
|
3955 keyid = btn.attr('rel'),
|
|
3956 pk = new PublicKey(),
|
|
3957 lock = ref.display_message(ref.get_label('loading'), 'loading');
|
|
3958
|
|
3959 // fetch from keyserver and import to Mailvelope keyring
|
|
3960 pk.get(keyid, function(armored, errorCode) {
|
|
3961 ref.hide_message(lock);
|
|
3962
|
|
3963 if (errorCode) {
|
|
3964 ref.display_message(ref.get_label('keyservererror'), 'error');
|
|
3965 return;
|
|
3966 }
|
|
3967
|
|
3968 if (import_handler) {
|
|
3969 import_handler(armored);
|
|
3970 return;
|
|
3971 }
|
|
3972
|
|
3973 // import to keyring
|
|
3974 ref.mailvelope_keyring.importPublicKey(armored).then(function(status) {
|
|
3975 if (status === 'REJECTED') {
|
|
3976 // alert(ref.get_label('Key import was rejected'));
|
|
3977 }
|
|
3978 else {
|
|
3979 var $key = keyid.substr(-8).toUpperCase();
|
|
3980 btn.closest('.key').fadeOut();
|
|
3981 ref.display_message(ref.get_label('keyimportsuccess').replace('$key', $key), 'confirmation');
|
|
3982 }
|
|
3983 }, function(err) {
|
|
3984 console.log(err);
|
|
3985 });
|
|
3986 });
|
|
3987 });
|
|
3988
|
|
3989 };
|
|
3990
|
|
3991
|
|
3992 /*********************************************************/
|
|
3993 /********* mailbox folders methods *********/
|
|
3994 /*********************************************************/
|
|
3995
|
|
3996 this.expunge_mailbox = function(mbox)
|
|
3997 {
|
|
3998 var lock, post_data = {_mbox: mbox};
|
|
3999
|
|
4000 // lock interface if it's the active mailbox
|
|
4001 if (mbox == this.env.mailbox) {
|
|
4002 lock = this.set_busy(true, 'loading');
|
|
4003 post_data._reload = 1;
|
|
4004 if (this.env.search_request)
|
|
4005 post_data._search = this.env.search_request;
|
|
4006 }
|
|
4007
|
|
4008 // send request to server
|
|
4009 this.http_post('expunge', post_data, lock);
|
|
4010 };
|
|
4011
|
|
4012 this.purge_mailbox = function(mbox)
|
|
4013 {
|
|
4014 var lock, post_data = {_mbox: mbox};
|
|
4015
|
|
4016 if (!confirm(this.get_label('purgefolderconfirm')))
|
|
4017 return false;
|
|
4018
|
|
4019 // lock interface if it's the active mailbox
|
|
4020 if (mbox == this.env.mailbox) {
|
|
4021 lock = this.set_busy(true, 'loading');
|
|
4022 post_data._reload = 1;
|
|
4023 }
|
|
4024
|
|
4025 // send request to server
|
|
4026 this.http_post('purge', post_data, lock);
|
|
4027 };
|
|
4028
|
|
4029 // test if purge command is allowed
|
|
4030 this.purge_mailbox_test = function()
|
|
4031 {
|
|
4032 return (this.env.exists && (
|
|
4033 this.env.mailbox == this.env.trash_mailbox
|
|
4034 || this.env.mailbox == this.env.junk_mailbox
|
|
4035 || this.env.mailbox.startsWith(this.env.trash_mailbox + this.env.delimiter)
|
|
4036 || this.env.mailbox.startsWith(this.env.junk_mailbox + this.env.delimiter)
|
|
4037 ));
|
|
4038 };
|
|
4039
|
|
4040 // Mark all messages as read in:
|
|
4041 // - selected folder (mode=cur)
|
|
4042 // - selected folder and its subfolders (mode=sub)
|
|
4043 // - all folders (mode=all)
|
|
4044 this.mark_all_read = function(mbox, mode)
|
|
4045 {
|
|
4046 var state, content, nodes = [],
|
|
4047 list = this.message_list,
|
|
4048 folder = mbox || this.env.mailbox,
|
|
4049 post_data = {_uid: '*', _flag: 'read', _mbox: folder, _folders: mode};
|
|
4050
|
|
4051 if (typeof mode != 'string') {
|
|
4052 state = this.mark_all_read_state(folder);
|
|
4053 if (!state)
|
|
4054 return;
|
|
4055
|
|
4056 if (state > 1) {
|
|
4057 // build content of the dialog
|
|
4058 $.each({cur: 1, sub: 2, all: 4}, function(i, v) {
|
|
4059 var label = $('<label>').attr('style', 'display:block; line-height:22px'),
|
|
4060 text = $('<span>').text(ref.get_label('folders-' + i)),
|
|
4061 input = $('<input>').attr({type: 'radio', value: i, name: 'mode'});
|
|
4062
|
|
4063 if (!(state & v)) {
|
|
4064 label.attr('class', 'disabled');
|
|
4065 input.attr('disabled', true);
|
|
4066 }
|
|
4067
|
|
4068 nodes.push(label.append(input).append(text));
|
|
4069 });
|
|
4070
|
|
4071 content = $('<div>').append(nodes);
|
|
4072 $('input:not([disabled]):first', content).attr('checked', true);
|
|
4073
|
|
4074 this.show_popup_dialog(content, this.get_label('markallread'),
|
|
4075 [{
|
|
4076 'class': 'mainaction',
|
|
4077 text: this.get_label('mark'),
|
|
4078 click: function() {
|
|
4079 ref.mark_all_read(folder, $('input:checked', this).val());
|
|
4080 $(this).dialog('close');
|
|
4081 }
|
|
4082 },
|
|
4083 {
|
|
4084 text: this.get_label('cancel'),
|
|
4085 click: function() {
|
|
4086 $(this).dialog('close');
|
|
4087 }
|
|
4088 }]
|
|
4089 );
|
|
4090
|
|
4091 return;
|
|
4092 }
|
|
4093
|
|
4094 post_data._folders = 'cur'; // only current folder has unread messages
|
|
4095 }
|
|
4096
|
|
4097 // mark messages on the list
|
|
4098 $.each(list ? list.rows : [], function(uid, row) {
|
|
4099 if (!row.unread)
|
|
4100 return;
|
|
4101
|
|
4102 var mbox = ref.env.messages[uid].mbox;
|
|
4103 if (mode == 'all' || mbox == ref.env.mailbox
|
|
4104 || (mode == 'sub' && mbox.startsWith(ref.env.mailbox + ref.env.delimiter))
|
|
4105 ) {
|
|
4106 ref.set_message(uid, 'unread', false);
|
|
4107 }
|
|
4108 });
|
|
4109
|
|
4110 // send the request
|
|
4111 this.http_post('mark', post_data, this.display_message(this.get_label('markingmessage'), 'loading'));
|
|
4112 };
|
|
4113
|
|
4114 // Enable/disable mark-all-read action depending on folders state
|
|
4115 this.mark_all_read_state = function(mbox)
|
|
4116 {
|
|
4117 var state = 0,
|
|
4118 li = this.treelist.get_item(mbox || this.env.mailbox),
|
|
4119 folder_item = $(li).is('.unread') ? 1 : 0,
|
|
4120 subfolder_items = $('li.unread', li).length,
|
|
4121 all_items = $('li.unread', ref.gui_objects.folderlist).length;
|
|
4122
|
|
4123 state += folder_item;
|
|
4124 state += subfolder_items ? 2 : 0;
|
|
4125 state += all_items > folder_item + subfolder_items ? 4 : 0;
|
|
4126
|
|
4127 this.enable_command('mark-all-read', state > 0);
|
|
4128
|
|
4129 return state;
|
|
4130 };
|
|
4131
|
|
4132
|
|
4133 /*********************************************************/
|
|
4134 /********* login form methods *********/
|
|
4135 /*********************************************************/
|
|
4136
|
|
4137 // handler for keyboard events on the _user field
|
|
4138 this.login_user_keyup = function(e)
|
|
4139 {
|
|
4140 var key = rcube_event.get_keycode(e),
|
|
4141 passwd = $('#rcmloginpwd');
|
|
4142
|
|
4143 // enter
|
|
4144 if (key == 13 && passwd.length && !passwd.val()) {
|
|
4145 passwd.focus();
|
|
4146 return rcube_event.cancel(e);
|
|
4147 }
|
|
4148
|
|
4149 return true;
|
|
4150 };
|
|
4151
|
|
4152
|
|
4153 /*********************************************************/
|
|
4154 /********* message compose methods *********/
|
|
4155 /*********************************************************/
|
|
4156
|
|
4157 this.open_compose_step = function(p)
|
|
4158 {
|
|
4159 var url = this.url('mail/compose', p);
|
|
4160
|
|
4161 // open new compose window
|
|
4162 if (this.env.compose_extwin && !this.env.extwin) {
|
|
4163 this.open_window(url);
|
|
4164 }
|
|
4165 else {
|
|
4166 this.redirect(url);
|
|
4167 if (this.env.extwin)
|
|
4168 window.resizeTo(Math.max(this.env.popup_width, $(window).width()), $(window).height() + 24);
|
|
4169 }
|
|
4170 };
|
|
4171
|
|
4172 // init message compose form: set focus and eventhandlers
|
|
4173 this.init_messageform = function()
|
|
4174 {
|
|
4175 if (!this.gui_objects.messageform)
|
|
4176 return false;
|
|
4177
|
|
4178 var i, elem, pos, input_from = $("[name='_from']"),
|
|
4179 input_to = $("[name='_to']"),
|
|
4180 input_subject = $("input[name='_subject']"),
|
|
4181 input_message = $("[name='_message']").get(0),
|
|
4182 html_mode = $("input[name='_is_html']").val() == '1',
|
|
4183 ac_fields = ['cc', 'bcc', 'replyto', 'followupto'],
|
|
4184 ac_props, opener_rc = this.opener();
|
|
4185
|
|
4186 // close compose step in opener
|
|
4187 if (opener_rc && opener_rc.env.action == 'compose') {
|
|
4188 setTimeout(function(){
|
|
4189 if (opener.history.length > 1)
|
|
4190 opener.history.back();
|
|
4191 else
|
|
4192 opener_rc.redirect(opener_rc.get_task_url('mail'));
|
|
4193 }, 100);
|
|
4194 this.env.opened_extwin = true;
|
|
4195 }
|
|
4196
|
|
4197 // configure parallel autocompletion
|
|
4198 if (this.env.autocomplete_threads > 0) {
|
|
4199 ac_props = {
|
|
4200 threads: this.env.autocomplete_threads,
|
|
4201 sources: this.env.autocomplete_sources
|
|
4202 };
|
|
4203 }
|
|
4204
|
|
4205 // init live search events
|
|
4206 this.init_address_input_events(input_to, ac_props);
|
|
4207 for (i in ac_fields) {
|
|
4208 this.init_address_input_events($("[name='_"+ac_fields[i]+"']"), ac_props);
|
|
4209 }
|
|
4210
|
|
4211 if (!html_mode) {
|
|
4212 pos = this.env.top_posting && this.env.compose_mode ? 0 : input_message.value.length;
|
|
4213
|
|
4214 // add signature according to selected identity
|
|
4215 // if we have HTML editor, signature is added in a callback
|
|
4216 if (input_from.prop('type') == 'select-one') {
|
|
4217 // for some reason the caret initially is not at pos=0 in Firefox 51 (#5628)
|
|
4218 this.set_caret_pos(input_message, 0);
|
|
4219 this.change_identity(input_from[0]);
|
|
4220 }
|
|
4221
|
|
4222 // set initial cursor position
|
|
4223 this.set_caret_pos(input_message, pos);
|
|
4224
|
|
4225 // scroll to the bottom of the textarea (#1490114)
|
|
4226 if (pos) {
|
|
4227 $(input_message).scrollTop(input_message.scrollHeight);
|
|
4228 }
|
|
4229 }
|
|
4230
|
|
4231 // check for locally stored compose data
|
|
4232 if (this.env.save_localstorage)
|
|
4233 this.compose_restore_dialog(0, html_mode)
|
|
4234
|
|
4235 if (input_to.val() == '')
|
|
4236 elem = input_to;
|
|
4237 else if (input_subject.val() == '')
|
|
4238 elem = input_subject;
|
|
4239 else if (input_message)
|
|
4240 elem = input_message;
|
|
4241
|
|
4242 // focus first empty element (need to be visible on IE8)
|
|
4243 this.env.compose_focus_elem = $(elem).filter(':visible').focus().get(0);
|
|
4244
|
|
4245 // get summary of all field values
|
|
4246 this.compose_field_hash(true);
|
|
4247
|
|
4248 // start the auto-save timer
|
|
4249 this.auto_save_start();
|
|
4250 };
|
|
4251
|
|
4252 this.compose_restore_dialog = function(j, html_mode)
|
|
4253 {
|
|
4254 var i, key, formdata, index = this.local_storage_get_item('compose.index', []);
|
|
4255
|
|
4256 var show_next = function(i) {
|
|
4257 if (++i < index.length)
|
|
4258 ref.compose_restore_dialog(i, html_mode)
|
|
4259 }
|
|
4260
|
|
4261 for (i = j || 0; i < index.length; i++) {
|
|
4262 key = index[i];
|
|
4263 formdata = this.local_storage_get_item('compose.' + key, null, true);
|
|
4264 if (!formdata) {
|
|
4265 continue;
|
|
4266 }
|
|
4267 // restore saved copy of current compose_id
|
|
4268 if (formdata.changed && key == this.env.compose_id) {
|
|
4269 this.restore_compose_form(key, html_mode);
|
|
4270 break;
|
|
4271 }
|
|
4272 // skip records from 'other' drafts
|
|
4273 if (this.env.draft_id && formdata.draft_id && formdata.draft_id != this.env.draft_id) {
|
|
4274 continue;
|
|
4275 }
|
|
4276 // skip records on reply
|
|
4277 if (this.env.reply_msgid && formdata.reply_msgid != this.env.reply_msgid) {
|
|
4278 continue;
|
|
4279 }
|
|
4280 // show dialog asking to restore the message
|
|
4281 if (formdata.changed && formdata.session != this.env.session_id) {
|
|
4282 this.show_popup_dialog(
|
|
4283 this.get_label('restoresavedcomposedata')
|
|
4284 .replace('$date', new Date(formdata.changed).toLocaleString())
|
|
4285 .replace('$subject', formdata._subject)
|
|
4286 .replace(/\n/g, '<br/>'),
|
|
4287 this.get_label('restoremessage'),
|
|
4288 [{
|
|
4289 text: this.get_label('restore'),
|
|
4290 'class': 'mainaction',
|
|
4291 click: function(){
|
|
4292 ref.restore_compose_form(key, html_mode);
|
|
4293 ref.remove_compose_data(key); // remove old copy
|
|
4294 ref.save_compose_form_local(); // save under current compose_id
|
|
4295 $(this).dialog('close');
|
|
4296 }
|
|
4297 },
|
|
4298 {
|
|
4299 text: this.get_label('delete'),
|
|
4300 'class': 'delete',
|
|
4301 click: function(){
|
|
4302 ref.remove_compose_data(key);
|
|
4303 $(this).dialog('close');
|
|
4304 show_next(i);
|
|
4305 }
|
|
4306 },
|
|
4307 {
|
|
4308 text: this.get_label('ignore'),
|
|
4309 click: function(){
|
|
4310 $(this).dialog('close');
|
|
4311 show_next(i);
|
|
4312 }
|
|
4313 }]
|
|
4314 );
|
|
4315 break;
|
|
4316 }
|
|
4317 }
|
|
4318 }
|
|
4319
|
|
4320 this.init_address_input_events = function(obj, props)
|
|
4321 {
|
|
4322 this.env.recipients_delimiter = this.env.recipients_separator + ' ';
|
|
4323
|
|
4324 obj.keydown(function(e) { return ref.ksearch_keydown(e, this, props); })
|
|
4325 .attr({ 'autocomplete': 'off', 'aria-autocomplete': 'list', 'aria-expanded': 'false', 'role': 'combobox' });
|
|
4326
|
|
4327 // hide the popup on any click
|
|
4328 $(document).on('click', function() { ref.ksearch_hide(); });
|
|
4329 };
|
|
4330
|
|
4331 this.submit_messageform = function(draft, saveonly)
|
|
4332 {
|
|
4333 var form = this.gui_objects.messageform;
|
|
4334
|
|
4335 if (!form)
|
|
4336 return;
|
|
4337
|
|
4338 // the message has been sent but not saved, ask the user what to do
|
|
4339 if (!saveonly && this.env.is_sent) {
|
|
4340 return this.simple_dialog(this.get_label('messageissent'), '',
|
|
4341 function() {
|
|
4342 ref.submit_messageform(false, true);
|
|
4343 return true;
|
|
4344 }
|
|
4345 );
|
|
4346 }
|
|
4347
|
|
4348 // delegate sending to Mailvelope routine
|
|
4349 if (this.mailvelope_editor) {
|
|
4350 return this.mailvelope_submit_messageform(draft, saveonly);
|
|
4351 }
|
|
4352
|
|
4353 // all checks passed, send message
|
|
4354 var msgid = this.set_busy(true, draft || saveonly ? 'savingmessage' : 'sendingmessage'),
|
|
4355 lang = this.spellcheck_lang(),
|
|
4356 files = [];
|
|
4357
|
|
4358 // send files list
|
|
4359 $('li', this.gui_objects.attachmentlist).each(function() { files.push(this.id.replace(/^rcmfile/, '')); });
|
|
4360 $('input[name="_attachments"]', form).val(files.join());
|
|
4361
|
|
4362 form.target = 'savetarget';
|
|
4363 form._draft.value = draft ? '1' : '';
|
|
4364 form.action = this.add_url(form.action, '_unlock', msgid);
|
|
4365 form.action = this.add_url(form.action, '_lang', lang);
|
|
4366 form.action = this.add_url(form.action, '_framed', 1);
|
|
4367
|
|
4368 if (saveonly) {
|
|
4369 form.action = this.add_url(form.action, '_saveonly', 1);
|
|
4370 }
|
|
4371
|
|
4372 // register timer to notify about connection timeout
|
|
4373 this.submit_timer = setTimeout(function(){
|
|
4374 ref.set_busy(false, null, msgid);
|
|
4375 ref.display_message(ref.get_label('requesttimedout'), 'error');
|
|
4376 }, this.env.request_timeout * 1000);
|
|
4377
|
|
4378 form.submit();
|
|
4379 };
|
|
4380
|
|
4381 this.compose_recipient_select = function(list)
|
|
4382 {
|
|
4383 var id, n, recipients = 0;
|
|
4384 for (n=0; n < list.selection.length; n++) {
|
|
4385 id = list.selection[n];
|
|
4386 if (this.env.contactdata[id])
|
|
4387 recipients++;
|
|
4388 }
|
|
4389 this.enable_command('add-recipient', recipients);
|
|
4390 };
|
|
4391
|
|
4392 this.compose_add_recipient = function(field)
|
|
4393 {
|
|
4394 // find last focused field name
|
|
4395 if (!field) {
|
|
4396 field = $(this.env.focused_field).filter(':visible');
|
|
4397 field = field.length ? field.attr('id').replace('_', '') : 'to';
|
|
4398 }
|
|
4399
|
|
4400 var recipients = [], input = $('#_'+field), delim = this.env.recipients_delimiter;
|
|
4401
|
|
4402 if (this.contact_list && this.contact_list.selection.length) {
|
|
4403 for (var id, n=0; n < this.contact_list.selection.length; n++) {
|
|
4404 id = this.contact_list.selection[n];
|
|
4405 if (id && this.env.contactdata[id]) {
|
|
4406 recipients.push(this.env.contactdata[id]);
|
|
4407
|
|
4408 // group is added, expand it
|
|
4409 if (id.charAt(0) == 'E' && this.env.contactdata[id].indexOf('@') < 0 && input.length) {
|
|
4410 var gid = id.substr(1);
|
|
4411 this.group2expand[gid] = { name:this.env.contactdata[id], input:input.get(0) };
|
|
4412 this.http_request('group-expand', {_source: this.env.source, _gid: gid}, false);
|
|
4413 }
|
|
4414 }
|
|
4415 }
|
|
4416 }
|
|
4417
|
|
4418 if (recipients.length && input.length) {
|
|
4419 var oldval = input.val(), rx = new RegExp(RegExp.escape(delim) + '\\s*$');
|
|
4420 if (oldval && !rx.test(oldval))
|
|
4421 oldval += delim + ' ';
|
|
4422 input.val(oldval + recipients.join(delim + ' ') + delim + ' ').change();
|
|
4423 this.triggerEvent('add-recipient', { field:field, recipients:recipients });
|
|
4424 }
|
|
4425
|
|
4426 return recipients.length;
|
|
4427 };
|
|
4428
|
|
4429 // checks the input fields before sending a message
|
|
4430 this.check_compose_input = function(cmd)
|
|
4431 {
|
|
4432 // check input fields
|
|
4433 var key, recipients, dialog,
|
|
4434 limit = this.env.max_disclosed_recipients,
|
|
4435 input_to = $("[name='_to']"),
|
|
4436 input_cc = $("[name='_cc']"),
|
|
4437 input_bcc = $("[name='_bcc']"),
|
|
4438 input_from = $("[name='_from']"),
|
|
4439 input_subject = $("[name='_subject']"),
|
|
4440 get_recipients = function(fields) {
|
|
4441 fields = $.map(fields, function(v) {
|
|
4442 v = $.trim(v.val());
|
|
4443 return v.length ? v : null;
|
|
4444 });
|
|
4445 return fields.join(',').replace(/^[\s,;]+/, '').replace(/[\s,;]+$/, '');
|
|
4446 };
|
|
4447
|
|
4448 // check sender (if have no identities)
|
|
4449 if (input_from.prop('type') == 'text' && !rcube_check_email(input_from.val(), true)) {
|
|
4450 alert(this.get_label('nosenderwarning'));
|
|
4451 input_from.focus();
|
|
4452 return false;
|
|
4453 }
|
|
4454
|
|
4455 // check for empty recipient
|
|
4456 if (!rcube_check_email(get_recipients([input_to, input_cc, input_bcc]), true)) {
|
|
4457 alert(this.get_label('norecipientwarning'));
|
|
4458 input_to.focus();
|
|
4459 return false;
|
|
4460 }
|
|
4461
|
|
4462 // check if all files has been uploaded
|
|
4463 for (key in this.env.attachments) {
|
|
4464 if (typeof this.env.attachments[key] === 'object' && !this.env.attachments[key].complete) {
|
|
4465 alert(this.get_label('notuploadedwarning'));
|
|
4466 return false;
|
|
4467 }
|
|
4468 }
|
|
4469
|
|
4470 // check disclosed recipients limit
|
|
4471 if (limit && !this.env.disclosed_recipients_warned
|
|
4472 && rcube_check_email(recipients = get_recipients([input_to, input_cc]), true, true) > limit
|
|
4473 ) {
|
|
4474 var save_func = function(move_to_bcc) {
|
|
4475 if (move_to_bcc) {
|
|
4476 var bcc = input_bcc.val();
|
|
4477 input_bcc.val((bcc ? (bcc + ', ') : '') + recipients).change();
|
|
4478 input_to.val('').change();
|
|
4479 input_cc.val('').change();
|
|
4480 }
|
|
4481
|
|
4482 dialog.dialog('close');
|
|
4483 if (ref.check_compose_input(cmd))
|
|
4484 ref.command(cmd, { nocheck:true }); // repeat command which triggered this
|
|
4485 };
|
|
4486
|
|
4487 dialog = this.show_popup_dialog(
|
|
4488 this.get_label('disclosedrecipwarning'),
|
|
4489 this.get_label('disclosedreciptitle'),
|
|
4490 [{
|
|
4491 text: this.get_label('sendmessage'),
|
|
4492 click: function() { save_func(false); },
|
|
4493 'class': 'mainaction'
|
|
4494 }, {
|
|
4495 text: this.get_label('bccinstead'),
|
|
4496 click: function() { save_func(true); }
|
|
4497 }, {
|
|
4498 text: this.get_label('cancel'),
|
|
4499 click: function() { dialog.dialog('close'); }
|
|
4500 }],
|
|
4501 {dialogClass: 'warning'}
|
|
4502 );
|
|
4503
|
|
4504 this.env.disclosed_recipients_warned = true;
|
|
4505 return false;
|
|
4506 }
|
|
4507
|
|
4508 // display localized warning for missing subject
|
|
4509 if (!this.env.nosubject_warned && input_subject.val() == '') {
|
|
4510 var prompt_value = $('<input>').attr({type: 'text', size: 40}),
|
|
4511 myprompt = $('<div class="prompt">')
|
|
4512 .append($('<div class="message">').text(this.get_label('nosubjectwarning')))
|
|
4513 .append(prompt_value),
|
|
4514 save_func = function() {
|
|
4515 input_subject.val(prompt_value.val());
|
|
4516 dialog.dialog('close');
|
|
4517 if (ref.check_compose_input(cmd))
|
|
4518 ref.command(cmd, { nocheck:true }); // repeat command which triggered this
|
|
4519 };
|
|
4520
|
|
4521 dialog = this.show_popup_dialog(
|
|
4522 myprompt,
|
|
4523 this.get_label('nosubjecttitle'),
|
|
4524 [{
|
|
4525 text: this.get_label('sendmessage'),
|
|
4526 click: function() { save_func(); },
|
|
4527 'class': 'mainaction'
|
|
4528 }, {
|
|
4529 text: this.get_label('cancel'),
|
|
4530 click: function() {
|
|
4531 input_subject.focus();
|
|
4532 dialog.dialog('close');
|
|
4533 }
|
|
4534 }],
|
|
4535 {dialogClass: 'warning'}
|
|
4536 );
|
|
4537
|
|
4538 prompt_value.select().keydown(function(e) {
|
|
4539 if (e.which == 13) save_func();
|
|
4540 });
|
|
4541
|
|
4542 this.env.nosubject_warned = true;
|
|
4543 return false;
|
|
4544 }
|
|
4545
|
|
4546 // check for empty body (only possible if not mailvelope encrypted)
|
|
4547 if (!this.mailvelope_editor && !this.editor.get_content() && !confirm(this.get_label('nobodywarning'))) {
|
|
4548 this.editor.focus();
|
|
4549 return false;
|
|
4550 }
|
|
4551
|
|
4552 // move body from html editor to textarea (just to be sure, #1485860)
|
|
4553 this.editor.save();
|
|
4554
|
|
4555 return true;
|
|
4556 };
|
|
4557
|
|
4558 this.toggle_editor = function(props, obj, e)
|
|
4559 {
|
|
4560 // @todo: this should work also with many editors on page
|
|
4561 var result = this.editor.toggle(props.html, props.noconvert || false);
|
|
4562
|
|
4563 // satisfy the expectations of aftertoggle-editor event subscribers
|
|
4564 props.mode = props.html ? 'html' : 'plain';
|
|
4565
|
|
4566 if (!result && e) {
|
|
4567 // fix selector value if operation failed
|
|
4568 props.mode = props.html ? 'plain' : 'html';
|
|
4569 $(e.target).filter('select').val(props.mode);
|
|
4570 }
|
|
4571
|
|
4572 if (result) {
|
|
4573 // update internal format flag
|
|
4574 $("input[name='_is_html']").val(props.html ? 1 : 0);
|
|
4575 }
|
|
4576
|
|
4577 return result;
|
|
4578 };
|
|
4579
|
|
4580 // Inserts a predefined response to the compose editor
|
|
4581 this.insert_response = function(key)
|
|
4582 {
|
|
4583 return this.editor.replace(this.env.textresponses[key]);
|
|
4584 };
|
|
4585
|
|
4586 /**
|
|
4587 * Open the dialog to save a new canned response
|
|
4588 */
|
|
4589 this.save_response = function()
|
|
4590 {
|
|
4591 // show dialog to enter a name and to modify the text to be saved
|
|
4592 var buttons = {}, text = this.editor.get_content({selection: true, format: 'text', nosig: true}),
|
|
4593 html = '<form class="propform">' +
|
|
4594 '<div class="prop block"><label>' + this.get_label('responsename') + '</label>' +
|
|
4595 '<input type="text" name="name" id="ffresponsename" size="40" /></div>' +
|
|
4596 '<div class="prop block"><label>' + this.get_label('responsetext') + '</label>' +
|
|
4597 '<textarea name="text" id="ffresponsetext" cols="40" rows="8"></textarea></div>' +
|
|
4598 '</form>';
|
|
4599
|
|
4600 buttons[this.get_label('save')] = function(e) {
|
|
4601 var name = $('#ffresponsename').val(),
|
|
4602 text = $('#ffresponsetext').val();
|
|
4603
|
|
4604 if (!text) {
|
|
4605 $('#ffresponsetext').select();
|
|
4606 return false;
|
|
4607 }
|
|
4608 if (!name)
|
|
4609 name = text.replace(/[\r\n]+/g, ' ').substring(0,40);
|
|
4610
|
|
4611 var lock = ref.display_message(ref.get_label('savingresponse'), 'loading');
|
|
4612 ref.http_post('settings/responses', { _insert:1, _name:name, _text:text }, lock);
|
|
4613 $(this).dialog('close');
|
|
4614 };
|
|
4615
|
|
4616 buttons[this.get_label('cancel')] = function() {
|
|
4617 $(this).dialog('close');
|
|
4618 };
|
|
4619
|
|
4620 this.show_popup_dialog(html, this.get_label('newresponse'), buttons, {button_classes: ['mainaction']});
|
|
4621
|
|
4622 $('#ffresponsetext').val(text);
|
|
4623 $('#ffresponsename').select();
|
|
4624 };
|
|
4625
|
|
4626 this.add_response_item = function(response)
|
|
4627 {
|
|
4628 var key = response.key;
|
|
4629 this.env.textresponses[key] = response;
|
|
4630
|
|
4631 // append to responses list
|
|
4632 if (this.gui_objects.responseslist) {
|
|
4633 var li = $('<li>').appendTo(this.gui_objects.responseslist);
|
|
4634 $('<a>').addClass('insertresponse active')
|
|
4635 .attr('href', '#')
|
|
4636 .attr('rel', key)
|
|
4637 .attr('tabindex', '0')
|
|
4638 .html(this.quote_html(response.name))
|
|
4639 .appendTo(li)
|
|
4640 .mousedown(function(e) {
|
|
4641 return rcube_event.cancel(e);
|
|
4642 })
|
|
4643 .on('mouseup keypress', function(e) {
|
|
4644 if (e.type == 'mouseup' || rcube_event.get_keycode(e) == 13) {
|
|
4645 ref.command('insert-response', $(this).attr('rel'));
|
|
4646 $(document.body).trigger('mouseup'); // hides the menu
|
|
4647 return rcube_event.cancel(e);
|
|
4648 }
|
|
4649 });
|
|
4650 }
|
|
4651 };
|
|
4652
|
|
4653 this.edit_responses = function()
|
|
4654 {
|
|
4655 // TODO: implement inline editing of responses
|
|
4656 };
|
|
4657
|
|
4658 this.delete_response = function(key)
|
|
4659 {
|
|
4660 if (!key && this.responses_list) {
|
|
4661 var selection = this.responses_list.get_selection();
|
|
4662 key = selection[0];
|
|
4663 }
|
|
4664
|
|
4665 // submit delete request
|
|
4666 if (key && confirm(this.get_label('deleteresponseconfirm'))) {
|
|
4667 this.http_post('settings/delete-response', { _key: key }, false);
|
|
4668 }
|
|
4669 };
|
|
4670
|
|
4671 // updates spellchecker buttons on state change
|
|
4672 this.spellcheck_state = function()
|
|
4673 {
|
|
4674 var active = this.editor.spellcheck_state();
|
|
4675
|
|
4676 $.each(this.buttons.spellcheck || [], function(i, v) {
|
|
4677 $('#' + v.id)[active ? 'addClass' : 'removeClass']('selected');
|
|
4678 });
|
|
4679
|
|
4680 return active;
|
|
4681 };
|
|
4682
|
|
4683 // get selected language
|
|
4684 this.spellcheck_lang = function()
|
|
4685 {
|
|
4686 return this.editor.get_language();
|
|
4687 };
|
|
4688
|
|
4689 this.spellcheck_lang_set = function(lang)
|
|
4690 {
|
|
4691 this.editor.set_language(lang);
|
|
4692 };
|
|
4693
|
|
4694 // resume spellchecking, highlight provided mispellings without new ajax request
|
|
4695 this.spellcheck_resume = function(data)
|
|
4696 {
|
|
4697 this.editor.spellcheck_resume(data);
|
|
4698 };
|
|
4699
|
|
4700 this.set_draft_id = function(id)
|
|
4701 {
|
|
4702 if (id && id != this.env.draft_id) {
|
|
4703 var filter = {task: 'mail', action: ''},
|
|
4704 rc = this.opener(false, filter) || this.opener(true, filter);
|
|
4705
|
|
4706 // refresh the drafts folder in the opener window
|
|
4707 if (rc && rc.env.mailbox == this.env.drafts_mailbox)
|
|
4708 rc.command('checkmail');
|
|
4709
|
|
4710 this.env.draft_id = id;
|
|
4711 $("input[name='_draft_saveid']").val(id);
|
|
4712
|
|
4713 // reset history of hidden iframe used for saving draft (#1489643)
|
|
4714 // but don't do this on timer-triggered draft-autosaving (#1489789)
|
|
4715 if (window.frames['savetarget'] && window.frames['savetarget'].history && !this.draft_autosave_submit && !this.mailvelope_editor) {
|
|
4716 window.frames['savetarget'].history.back();
|
|
4717 }
|
|
4718
|
|
4719 this.draft_autosave_submit = false;
|
|
4720 }
|
|
4721
|
|
4722 // always remove local copy upon saving as draft
|
|
4723 this.remove_compose_data(this.env.compose_id);
|
|
4724 this.compose_skip_unsavedcheck = false;
|
|
4725 };
|
|
4726
|
|
4727 this.auto_save_start = function()
|
|
4728 {
|
|
4729 if (this.env.draft_autosave) {
|
|
4730 this.draft_autosave_submit = false;
|
|
4731 this.save_timer = setTimeout(function(){
|
|
4732 ref.draft_autosave_submit = true; // set auto-saved flag (#1489789)
|
|
4733 ref.command("savedraft");
|
|
4734 }, this.env.draft_autosave * 1000);
|
|
4735 }
|
|
4736
|
|
4737 // save compose form content to local storage every 5 seconds
|
|
4738 if (!this.local_save_timer && window.localStorage && this.env.save_localstorage) {
|
|
4739 // track typing activity and only save on changes
|
|
4740 this.compose_type_activity = this.compose_type_activity_last = 0;
|
|
4741 $(document).keypress(function(e) { ref.compose_type_activity++; });
|
|
4742
|
|
4743 this.local_save_timer = setInterval(function(){
|
|
4744 if (ref.compose_type_activity > ref.compose_type_activity_last) {
|
|
4745 ref.save_compose_form_local();
|
|
4746 ref.compose_type_activity_last = ref.compose_type_activity;
|
|
4747 }
|
|
4748 }, 5000);
|
|
4749
|
|
4750 $(window).on('unload', function() {
|
|
4751 // remove copy from local storage if compose screen is left after warning
|
|
4752 if (!ref.env.server_error)
|
|
4753 ref.remove_compose_data(ref.env.compose_id);
|
|
4754 });
|
|
4755 }
|
|
4756
|
|
4757 // check for unsaved changes before leaving the compose page
|
|
4758 if (!window.onbeforeunload) {
|
|
4759 window.onbeforeunload = function() {
|
|
4760 if (!ref.compose_skip_unsavedcheck && ref.cmp_hash != ref.compose_field_hash()) {
|
|
4761 return ref.get_label('notsentwarning');
|
|
4762 }
|
|
4763 };
|
|
4764 }
|
|
4765
|
|
4766 // Unlock interface now that saving is complete
|
|
4767 this.busy = false;
|
|
4768 };
|
|
4769
|
|
4770 this.compose_field_hash = function(save)
|
|
4771 {
|
|
4772 // check input fields
|
|
4773 var i, id, val, str = '', hash_fields = ['to', 'cc', 'bcc', 'subject'];
|
|
4774
|
|
4775 for (i=0; i<hash_fields.length; i++)
|
|
4776 if (val = $('[name="_' + hash_fields[i] + '"]').val())
|
|
4777 str += val + ':';
|
|
4778
|
|
4779 str += this.editor.get_content({refresh: false});
|
|
4780
|
|
4781 if (this.env.attachments)
|
|
4782 for (id in this.env.attachments)
|
|
4783 str += id;
|
|
4784
|
|
4785 // we can't detect changes in the Mailvelope editor so assume it changed
|
|
4786 if (this.mailvelope_editor) {
|
|
4787 str += ';' + new Date().getTime();
|
|
4788 }
|
|
4789
|
|
4790 if (save)
|
|
4791 this.cmp_hash = str;
|
|
4792
|
|
4793 return str;
|
|
4794 };
|
|
4795
|
|
4796 // store the contents of the compose form to localstorage
|
|
4797 this.save_compose_form_local = function()
|
|
4798 {
|
|
4799 // feature is disabled
|
|
4800 if (!this.env.save_localstorage)
|
|
4801 return;
|
|
4802
|
|
4803 var formdata = { session:this.env.session_id, changed:new Date().getTime() },
|
|
4804 ed, empty = true;
|
|
4805
|
|
4806 // get fresh content from editor
|
|
4807 this.editor.save();
|
|
4808
|
|
4809 if (this.env.draft_id) {
|
|
4810 formdata.draft_id = this.env.draft_id;
|
|
4811 }
|
|
4812 if (this.env.reply_msgid) {
|
|
4813 formdata.reply_msgid = this.env.reply_msgid;
|
|
4814 }
|
|
4815
|
|
4816 $('input, select, textarea', this.gui_objects.messageform).each(function(i, elem) {
|
|
4817 switch (elem.tagName.toLowerCase()) {
|
|
4818 case 'input':
|
|
4819 if (elem.type == 'button' || elem.type == 'submit' || (elem.type == 'hidden' && elem.name != '_is_html')) {
|
|
4820 break;
|
|
4821 }
|
|
4822 formdata[elem.name] = elem.type != 'checkbox' || elem.checked ? $(elem).val() : '';
|
|
4823
|
|
4824 if (formdata[elem.name] != '' && elem.type != 'hidden')
|
|
4825 empty = false;
|
|
4826 break;
|
|
4827
|
|
4828 case 'select':
|
|
4829 formdata[elem.name] = $('option:checked', elem).val();
|
|
4830 break;
|
|
4831
|
|
4832 default:
|
|
4833 formdata[elem.name] = $(elem).val();
|
|
4834 if (formdata[elem.name] != '')
|
|
4835 empty = false;
|
|
4836 }
|
|
4837 });
|
|
4838
|
|
4839 if (!empty) {
|
|
4840 var index = this.local_storage_get_item('compose.index', []),
|
|
4841 key = this.env.compose_id;
|
|
4842
|
|
4843 if ($.inArray(key, index) < 0) {
|
|
4844 index.push(key);
|
|
4845 }
|
|
4846
|
|
4847 this.local_storage_set_item('compose.' + key, formdata, true);
|
|
4848 this.local_storage_set_item('compose.index', index);
|
|
4849 }
|
|
4850 };
|
|
4851
|
|
4852 // write stored compose data back to form
|
|
4853 this.restore_compose_form = function(key, html_mode)
|
|
4854 {
|
|
4855 var ed, formdata = this.local_storage_get_item('compose.' + key, true);
|
|
4856
|
|
4857 if (formdata && typeof formdata == 'object') {
|
|
4858 $.each(formdata, function(k, value) {
|
|
4859 if (k[0] == '_') {
|
|
4860 var elem = $("*[name='"+k+"']");
|
|
4861 if (elem[0] && elem[0].type == 'checkbox') {
|
|
4862 elem.prop('checked', value != '');
|
|
4863 }
|
|
4864 else {
|
|
4865 elem.val(value);
|
|
4866 }
|
|
4867 }
|
|
4868 });
|
|
4869
|
|
4870 // initialize HTML editor
|
|
4871 if ((formdata._is_html == '1' && !html_mode) || (formdata._is_html != '1' && html_mode)) {
|
|
4872 this.command('toggle-editor', {id: this.env.composebody, html: !html_mode, noconvert: true});
|
|
4873 }
|
|
4874 }
|
|
4875 };
|
|
4876
|
|
4877 // remove stored compose data from localStorage
|
|
4878 this.remove_compose_data = function(key)
|
|
4879 {
|
|
4880 var index = this.local_storage_get_item('compose.index', []);
|
|
4881
|
|
4882 if ($.inArray(key, index) >= 0) {
|
|
4883 this.local_storage_remove_item('compose.' + key);
|
|
4884 this.local_storage_set_item('compose.index', $.grep(index, function(val,i) { return val != key; }));
|
|
4885 }
|
|
4886 };
|
|
4887
|
|
4888 // clear all stored compose data of this user
|
|
4889 this.clear_compose_data = function()
|
|
4890 {
|
|
4891 var i, index = this.local_storage_get_item('compose.index', []);
|
|
4892
|
|
4893 for (i=0; i < index.length; i++) {
|
|
4894 this.local_storage_remove_item('compose.' + index[i]);
|
|
4895 }
|
|
4896
|
|
4897 this.local_storage_remove_item('compose.index');
|
|
4898 };
|
|
4899
|
|
4900 this.change_identity = function(obj, show_sig)
|
|
4901 {
|
|
4902 if (!obj || !obj.options)
|
|
4903 return false;
|
|
4904
|
|
4905 if (!show_sig)
|
|
4906 show_sig = this.env.show_sig;
|
|
4907
|
|
4908 var id = obj.options[obj.selectedIndex].value,
|
|
4909 sig = this.env.identity,
|
|
4910 delim = this.env.recipients_separator,
|
|
4911 rx_delim = RegExp.escape(delim);
|
|
4912
|
|
4913 // enable manual signature insert
|
|
4914 if (this.env.signatures && this.env.signatures[id]) {
|
|
4915 this.enable_command('insert-sig', true);
|
|
4916 this.env.compose_commands.push('insert-sig');
|
|
4917 }
|
|
4918 else
|
|
4919 this.enable_command('insert-sig', false);
|
|
4920
|
|
4921 // first function execution
|
|
4922 if (!this.env.identities_initialized) {
|
|
4923 this.env.identities_initialized = true;
|
|
4924 if (this.env.show_sig_later)
|
|
4925 this.env.show_sig = true;
|
|
4926 if (this.env.opened_extwin)
|
|
4927 return;
|
|
4928 }
|
|
4929
|
|
4930 // update reply-to/bcc fields with addresses defined in identities
|
|
4931 $.each(['replyto', 'bcc'], function() {
|
|
4932 var rx, key = this,
|
|
4933 old_val = sig && ref.env.identities[sig] ? ref.env.identities[sig][key] : '',
|
|
4934 new_val = id && ref.env.identities[id] ? ref.env.identities[id][key] : '',
|
|
4935 input = $('[name="_'+key+'"]'), input_val = input.val();
|
|
4936
|
|
4937 // remove old address(es)
|
|
4938 if (old_val && input_val) {
|
|
4939 rx = new RegExp('\\s*' + RegExp.escape(old_val) + '\\s*');
|
|
4940 input_val = input_val.replace(rx, '');
|
|
4941 }
|
|
4942
|
|
4943 // cleanup
|
|
4944 rx = new RegExp(rx_delim + '\\s*' + rx_delim, 'g');
|
|
4945 input_val = String(input_val).replace(rx, delim);
|
|
4946 rx = new RegExp('^[\\s' + rx_delim + ']+');
|
|
4947 input_val = input_val.replace(rx, '');
|
|
4948
|
|
4949 // add new address(es)
|
|
4950 if (new_val && input_val.indexOf(new_val) == -1 && input_val.indexOf(new_val.replace(/"/g, '')) == -1) {
|
|
4951 if (input_val) {
|
|
4952 rx = new RegExp('[' + rx_delim + '\\s]+$')
|
|
4953 input_val = input_val.replace(rx, '') + delim + ' ';
|
|
4954 }
|
|
4955
|
|
4956 input_val += new_val + delim + ' ';
|
|
4957 }
|
|
4958
|
|
4959 if (old_val || new_val)
|
|
4960 input.val(input_val).change();
|
|
4961 });
|
|
4962
|
|
4963 this.editor.change_signature(id, show_sig);
|
|
4964 this.env.identity = id;
|
|
4965 this.triggerEvent('change_identity');
|
|
4966 return true;
|
|
4967 };
|
|
4968
|
|
4969 // Open file selection dialog for defined upload form
|
|
4970 // Works only on click and only with smart-upload forms
|
|
4971 this.upload_input = function(name)
|
|
4972 {
|
|
4973 $('#' + name + ' input[type="file"]').click();
|
|
4974 };
|
|
4975
|
|
4976 // upload (attachment) file
|
|
4977 this.upload_file = function(form, action, lock)
|
|
4978 {
|
|
4979 if (!form)
|
|
4980 return;
|
|
4981
|
|
4982 // count files and size on capable browser
|
|
4983 var size = 0, numfiles = 0;
|
|
4984
|
|
4985 $.each($(form).get(0).elements || [], function() {
|
|
4986 if (this.type != 'file')
|
|
4987 return;
|
|
4988
|
|
4989 var i, files = this.files ? this.files.length : (this.value ? 1 : 0);
|
|
4990
|
|
4991 // check file size
|
|
4992 if (this.files) {
|
|
4993 for (i=0; i < files; i++)
|
|
4994 size += this.files[i].size;
|
|
4995 }
|
|
4996
|
|
4997 numfiles += files;
|
|
4998 });
|
|
4999
|
|
5000 // create hidden iframe and post upload form
|
|
5001 if (numfiles) {
|
|
5002 if (this.env.max_filesize && this.env.filesizeerror && size > this.env.max_filesize) {
|
|
5003 this.display_message(this.env.filesizeerror, 'error');
|
|
5004 return false;
|
|
5005 }
|
|
5006
|
|
5007 if (this.env.max_filecount && this.env.filecounterror && numfiles > this.env.max_filecount) {
|
|
5008 this.display_message(this.env.filecounterror, 'error');
|
|
5009 return false;
|
|
5010 }
|
|
5011
|
|
5012 var frame_name = this.async_upload_form(form, action || 'upload', function(e) {
|
|
5013 var d, content = '';
|
|
5014 try {
|
|
5015 if (this.contentDocument) {
|
|
5016 d = this.contentDocument;
|
|
5017 } else if (this.contentWindow) {
|
|
5018 d = this.contentWindow.document;
|
|
5019 }
|
|
5020 content = d.childNodes[1].innerHTML;
|
|
5021 } catch (err) {}
|
|
5022
|
|
5023 if (!content.match(/add2attachment/) && (!bw.opera || (ref.env.uploadframe && ref.env.uploadframe == e.data.ts))) {
|
|
5024 if (!content.match(/display_message/))
|
|
5025 ref.display_message(ref.get_label('fileuploaderror'), 'error');
|
|
5026 ref.remove_from_attachment_list(e.data.ts);
|
|
5027
|
|
5028 if (lock)
|
|
5029 ref.set_busy(false, null, lock);
|
|
5030 }
|
|
5031 // Opera hack: handle double onload
|
|
5032 if (bw.opera)
|
|
5033 ref.env.uploadframe = e.data.ts;
|
|
5034 });
|
|
5035
|
|
5036 // display upload indicator and cancel button
|
|
5037 var content = '<span>' + this.get_label('uploading' + (numfiles > 1 ? 'many' : '')) + '</span>',
|
|
5038 ts = frame_name.replace(/^rcmupload/, '');
|
|
5039
|
|
5040 this.add2attachment_list(ts, { name:'', html:content, classname:'uploading', frame:frame_name, complete:false });
|
|
5041
|
|
5042 // upload progress support
|
|
5043 if (this.env.upload_progress_time) {
|
|
5044 this.upload_progress_start('upload', ts);
|
|
5045 }
|
|
5046
|
|
5047 // set reference to the form object
|
|
5048 this.gui_objects.attachmentform = form;
|
|
5049 return true;
|
|
5050 }
|
|
5051 };
|
|
5052
|
|
5053 // add file name to attachment list
|
|
5054 // called from upload page
|
|
5055 this.add2attachment_list = function(name, att, upload_id)
|
|
5056 {
|
|
5057 if (upload_id)
|
|
5058 this.triggerEvent('fileuploaded', {name: name, attachment: att, id: upload_id});
|
|
5059
|
|
5060 if (!this.env.attachments)
|
|
5061 this.env.attachments = {};
|
|
5062
|
|
5063 if (upload_id && this.env.attachments[upload_id])
|
|
5064 delete this.env.attachments[upload_id];
|
|
5065
|
|
5066 this.env.attachments[name] = att;
|
|
5067
|
|
5068 if (!this.gui_objects.attachmentlist)
|
|
5069 return false;
|
|
5070
|
|
5071 if (!att.complete && this.env.loadingicon)
|
|
5072 att.html = '<img src="'+this.env.loadingicon+'" alt="" class="uploading" />' + att.html;
|
|
5073
|
|
5074 if (!att.complete && att.frame)
|
|
5075 att.html = '<a title="'+this.get_label('cancel')+'" onclick="return rcmail.cancel_attachment_upload(\''+name+'\', \''+att.frame+'\');" href="#cancelupload" class="cancelupload">'
|
|
5076 + (this.env.cancelicon ? '<img src="'+this.env.cancelicon+'" alt="'+this.get_label('cancel')+'" />' : this.get_label('cancel')) + '</a>' + att.html;
|
|
5077
|
|
5078 var indicator, li = $('<li>');
|
|
5079
|
|
5080 li.attr('id', name)
|
|
5081 .addClass(att.classname)
|
|
5082 .html(att.html)
|
|
5083 .on('mouseover', function() { rcube_webmail.long_subject_title_ex(this); });
|
|
5084
|
|
5085 // replace indicator's li
|
|
5086 if (upload_id && (indicator = document.getElementById(upload_id))) {
|
|
5087 li.replaceAll(indicator);
|
|
5088 }
|
|
5089 else { // add new li
|
|
5090 li.appendTo(this.gui_objects.attachmentlist);
|
|
5091 }
|
|
5092
|
|
5093 // set tabindex attribute
|
|
5094 var tabindex = $(this.gui_objects.attachmentlist).attr('data-tabindex') || '0';
|
|
5095 li.find('a').attr('tabindex', tabindex);
|
|
5096
|
|
5097 this.triggerEvent('fileappended', {name: name, attachment: att, id: upload_id, item: li});
|
|
5098
|
|
5099 return true;
|
|
5100 };
|
|
5101
|
|
5102 this.remove_from_attachment_list = function(name)
|
|
5103 {
|
|
5104 if (this.env.attachments) {
|
|
5105 delete this.env.attachments[name];
|
|
5106 $('#'+name).remove();
|
|
5107 }
|
|
5108 };
|
|
5109
|
|
5110 this.remove_attachment = function(name)
|
|
5111 {
|
|
5112 if (name && this.env.attachments[name])
|
|
5113 this.http_post('remove-attachment', { _id:this.env.compose_id, _file:name });
|
|
5114
|
|
5115 return false;
|
|
5116 };
|
|
5117
|
|
5118 this.cancel_attachment_upload = function(name, frame_name)
|
|
5119 {
|
|
5120 if (!name || !frame_name)
|
|
5121 return false;
|
|
5122
|
|
5123 this.remove_from_attachment_list(name);
|
|
5124 $("iframe[name='"+frame_name+"']").remove();
|
|
5125 return false;
|
|
5126 };
|
|
5127
|
|
5128 this.upload_progress_start = function(action, name)
|
|
5129 {
|
|
5130 setTimeout(function() { ref.http_request(action, {_progress: name}); },
|
|
5131 this.env.upload_progress_time * 1000);
|
|
5132 };
|
|
5133
|
|
5134 this.upload_progress_update = function(param)
|
|
5135 {
|
|
5136 var elem = $('#'+param.name + ' > span');
|
|
5137
|
|
5138 if (!elem.length || !param.text)
|
|
5139 return;
|
|
5140
|
|
5141 elem.text(param.text);
|
|
5142
|
|
5143 if (!param.done)
|
|
5144 this.upload_progress_start(param.action, param.name);
|
|
5145 };
|
|
5146
|
|
5147 // rename uploaded attachment (in compose)
|
|
5148 this.rename_attachment = function(id)
|
|
5149 {
|
|
5150 var attachment = this.env.attachments ? this.env.attachments[id] : null;
|
|
5151
|
|
5152 if (!attachment)
|
|
5153 return;
|
|
5154
|
|
5155 var input = $('<input>').attr({type: 'text', size: 50}).val(attachment.name),
|
|
5156 content = $('<label>').text(this.get_label('namex')).append(input);
|
|
5157
|
|
5158 this.simple_dialog(content, 'attachmentrename', function() {
|
|
5159 var name;
|
|
5160 if ((name = input.val()) && name != attachment.name) {
|
|
5161 ref.http_post('rename-attachment', {_id: ref.env.compose_id, _file: id, _name: name},
|
|
5162 ref.set_busy(true, 'loading'));
|
|
5163 return true;
|
|
5164 }
|
|
5165 },
|
|
5166 {open: function() { input.select(); }}
|
|
5167 );
|
|
5168 };
|
|
5169
|
|
5170 // update attachments list with the new name
|
|
5171 this.rename_attachment_handler = function(id, name)
|
|
5172 {
|
|
5173 var attachment = this.env.attachments ? this.env.attachments[id] : null,
|
|
5174 item = $('#' + id + ' > a.filename'),
|
|
5175 link = $('<a>');
|
|
5176
|
|
5177 if (!attachment || !name)
|
|
5178 return;
|
|
5179
|
|
5180 attachment.name = name;
|
|
5181
|
|
5182 // update attachments list
|
|
5183 if (item.length == 1) {
|
|
5184 // create a new element with new attachment name and cloned size
|
|
5185 link.text(name).append($('span', item).clone());
|
|
5186 // update attachment name element
|
|
5187 item.html(link.html());
|
|
5188 // reset parent's title which may contain the old name
|
|
5189 item.parent().attr('title', '');
|
|
5190 }
|
|
5191 };
|
|
5192
|
|
5193 // send remote request to add a new contact
|
|
5194 this.add_contact = function(value)
|
|
5195 {
|
|
5196 if (value)
|
|
5197 this.http_post('addcontact', {_address: value});
|
|
5198
|
|
5199 return true;
|
|
5200 };
|
|
5201
|
|
5202 // send remote request to search mail or contacts
|
|
5203 this.qsearch = function(value)
|
|
5204 {
|
|
5205 // Note: Some plugins would like to do search without value,
|
|
5206 // so we keep value != '' check to allow that use-case. Which means
|
|
5207 // e.g. that qsearch() with no argument will execute the search.
|
|
5208 if (value != '' || $(this.gui_objects.qsearchbox).val() || $(this.gui_objects.search_interval).val()) {
|
|
5209 var r, lock = this.set_busy(true, 'searching'),
|
|
5210 url = this.search_params(value),
|
|
5211 action = this.env.action == 'compose' && this.contact_list ? 'search-contacts' : 'search';
|
|
5212
|
|
5213 if (this.message_list)
|
|
5214 this.clear_message_list();
|
|
5215 else if (this.contact_list)
|
|
5216 this.list_contacts_clear();
|
|
5217
|
|
5218 if (this.env.source)
|
|
5219 url._source = this.env.source;
|
|
5220 if (this.env.group)
|
|
5221 url._gid = this.env.group;
|
|
5222
|
|
5223 // reset vars
|
|
5224 this.env.current_page = 1;
|
|
5225
|
|
5226 r = this.http_request(action, url, lock);
|
|
5227
|
|
5228 this.env.qsearch = {lock: lock, request: r};
|
|
5229 this.enable_command('set-listmode', this.env.threads && (this.env.search_scope || 'base') == 'base');
|
|
5230
|
|
5231 return true;
|
|
5232 }
|
|
5233
|
|
5234 return false;
|
|
5235 };
|
|
5236
|
|
5237 this.continue_search = function(request_id)
|
|
5238 {
|
|
5239 var lock = this.set_busy(true, 'stillsearching');
|
|
5240
|
|
5241 setTimeout(function() {
|
|
5242 var url = ref.search_params();
|
|
5243 url._continue = request_id;
|
|
5244 ref.env.qsearch = { lock: lock, request: ref.http_request('search', url, lock) };
|
|
5245 }, 100);
|
|
5246 };
|
|
5247
|
|
5248 // build URL params for search
|
|
5249 this.search_params = function(search, filter)
|
|
5250 {
|
|
5251 var n, url = {}, mods_arr = [],
|
|
5252 mods = this.env.search_mods,
|
|
5253 scope = this.env.search_scope || 'base',
|
|
5254 mbox = scope == 'all' ? '*' : this.env.mailbox;
|
|
5255
|
|
5256 if (!filter && this.gui_objects.search_filter)
|
|
5257 filter = this.gui_objects.search_filter.value;
|
|
5258
|
|
5259 if (!search && this.gui_objects.qsearchbox)
|
|
5260 search = this.gui_objects.qsearchbox.value;
|
|
5261
|
|
5262 if (filter)
|
|
5263 url._filter = filter;
|
|
5264
|
|
5265 if (this.gui_objects.search_interval)
|
|
5266 url._interval = $(this.gui_objects.search_interval).val();
|
|
5267
|
|
5268 if (search) {
|
|
5269 url._q = search;
|
|
5270
|
|
5271 if (mods && this.message_list)
|
|
5272 mods = mods[mbox] || mods['*'];
|
|
5273
|
|
5274 if (mods) {
|
|
5275 for (n in mods)
|
|
5276 mods_arr.push(n);
|
|
5277 url._headers = mods_arr.join(',');
|
|
5278 }
|
|
5279 }
|
|
5280
|
|
5281 if (scope)
|
|
5282 url._scope = scope;
|
|
5283 if (mbox && scope != 'all')
|
|
5284 url._mbox = mbox;
|
|
5285
|
|
5286 return url;
|
|
5287 };
|
|
5288
|
|
5289 // reset search filter
|
|
5290 this.reset_search_filter = function()
|
|
5291 {
|
|
5292 this.filter_disabled = true;
|
|
5293 if (this.gui_objects.search_filter)
|
|
5294 $(this.gui_objects.search_filter).val('ALL').change();
|
|
5295 this.filter_disabled = false;
|
|
5296 };
|
|
5297
|
|
5298 // reset quick-search form
|
|
5299 this.reset_qsearch = function(all)
|
|
5300 {
|
|
5301 if (this.gui_objects.qsearchbox)
|
|
5302 this.gui_objects.qsearchbox.value = '';
|
|
5303
|
|
5304 if (this.gui_objects.search_interval)
|
|
5305 $(this.gui_objects.search_interval).val('');
|
|
5306
|
|
5307 if (this.env.qsearch)
|
|
5308 this.abort_request(this.env.qsearch);
|
|
5309
|
|
5310 if (all) {
|
|
5311 this.env.search_scope = 'base';
|
|
5312 this.reset_search_filter();
|
|
5313 }
|
|
5314
|
|
5315 this.env.qsearch = null;
|
|
5316 this.env.search_request = null;
|
|
5317 this.env.search_id = null;
|
|
5318 this.select_all_mode = false;
|
|
5319
|
|
5320 this.enable_command('set-listmode', this.env.threads);
|
|
5321 };
|
|
5322
|
|
5323 this.set_searchscope = function(scope)
|
|
5324 {
|
|
5325 var old = this.env.search_scope;
|
|
5326 this.env.search_scope = scope;
|
|
5327
|
|
5328 // re-send search query with new scope
|
|
5329 if (scope != old && this.env.search_request) {
|
|
5330 if (!this.qsearch(this.gui_objects.qsearchbox.value) && this.env.search_filter && this.env.search_filter != 'ALL')
|
|
5331 this.filter_mailbox(this.env.search_filter);
|
|
5332 if (scope != 'all')
|
|
5333 this.select_folder(this.env.mailbox, '', true);
|
|
5334 }
|
|
5335 };
|
|
5336
|
|
5337 this.set_searchinterval = function(interval)
|
|
5338 {
|
|
5339 var old = this.env.search_interval;
|
|
5340 this.env.search_interval = interval;
|
|
5341
|
|
5342 // re-send search query with new interval
|
|
5343 if (interval != old && this.env.search_request) {
|
|
5344 if (!this.qsearch(this.gui_objects.qsearchbox.value) && this.env.search_filter && this.env.search_filter != 'ALL')
|
|
5345 this.filter_mailbox(this.env.search_filter);
|
|
5346 if (interval)
|
|
5347 this.select_folder(this.env.mailbox, '', true);
|
|
5348 }
|
|
5349 };
|
|
5350
|
|
5351 this.set_searchmods = function(mods)
|
|
5352 {
|
|
5353 var mbox = this.env.mailbox,
|
|
5354 scope = this.env.search_scope || 'base';
|
|
5355
|
|
5356 if (scope == 'all')
|
|
5357 mbox = '*';
|
|
5358
|
|
5359 if (!this.env.search_mods)
|
|
5360 this.env.search_mods = {};
|
|
5361
|
|
5362 if (mbox)
|
|
5363 this.env.search_mods[mbox] = mods;
|
|
5364 };
|
|
5365
|
|
5366 this.is_multifolder_listing = function()
|
|
5367 {
|
|
5368 return this.env.multifolder_listing !== undefined ? this.env.multifolder_listing :
|
|
5369 (this.env.search_request && (this.env.search_scope || 'base') != 'base');
|
|
5370 };
|
|
5371
|
|
5372 // action executed after mail is sent
|
|
5373 this.sent_successfully = function(type, msg, folders, save_error)
|
|
5374 {
|
|
5375 this.display_message(msg, type);
|
|
5376 this.compose_skip_unsavedcheck = true;
|
|
5377
|
|
5378 if (this.env.extwin) {
|
|
5379 if (!save_error)
|
|
5380 this.lock_form(this.gui_objects.messageform);
|
|
5381
|
|
5382 var filter = {task: 'mail', action: ''},
|
|
5383 rc = this.opener(false, filter) || this.opener(true, filter);
|
|
5384
|
|
5385 if (rc) {
|
|
5386 rc.display_message(msg, type);
|
|
5387 // refresh the folder where sent message was saved or replied message comes from
|
|
5388 if (folders && $.inArray(rc.env.mailbox, folders) >= 0) {
|
|
5389 rc.command('checkmail');
|
|
5390 }
|
|
5391 }
|
|
5392
|
|
5393 if (!save_error)
|
|
5394 setTimeout(function() { window.close(); }, 1000);
|
|
5395 }
|
|
5396 else if (!save_error) {
|
|
5397 // before redirect we need to wait some time for Chrome (#1486177)
|
|
5398 setTimeout(function() { ref.list_mailbox(); }, 500);
|
|
5399 }
|
|
5400
|
|
5401 if (save_error)
|
|
5402 this.env.is_sent = true;
|
|
5403 };
|
|
5404
|
|
5405
|
|
5406 /*********************************************************/
|
|
5407 /********* keyboard live-search methods *********/
|
|
5408 /*********************************************************/
|
|
5409
|
|
5410 // handler for keyboard events on address-fields
|
|
5411 this.ksearch_keydown = function(e, obj, props)
|
|
5412 {
|
|
5413 if (this.ksearch_timer)
|
|
5414 clearTimeout(this.ksearch_timer);
|
|
5415
|
|
5416 var key = rcube_event.get_keycode(e);
|
|
5417
|
|
5418 switch (key) {
|
|
5419 case 38: // arrow up
|
|
5420 case 40: // arrow down
|
|
5421 if (!this.ksearch_visible())
|
|
5422 return;
|
|
5423
|
|
5424 var dir = key == 38 ? 1 : 0,
|
|
5425 highlight = document.getElementById('rcmkSearchItem' + this.ksearch_selected);
|
|
5426
|
|
5427 if (!highlight)
|
|
5428 highlight = this.ksearch_pane.__ul.firstChild;
|
|
5429
|
|
5430 if (highlight)
|
|
5431 this.ksearch_select(dir ? highlight.previousSibling : highlight.nextSibling);
|
|
5432
|
|
5433 return rcube_event.cancel(e);
|
|
5434
|
|
5435 case 9: // tab
|
|
5436 if (rcube_event.get_modifier(e) == SHIFT_KEY || !this.ksearch_visible()) {
|
|
5437 this.ksearch_hide();
|
|
5438 return;
|
|
5439 }
|
|
5440
|
|
5441 case 13: // enter
|
|
5442 if (!this.ksearch_visible())
|
|
5443 return false;
|
|
5444
|
|
5445 // insert selected address and hide ksearch pane
|
|
5446 this.insert_recipient(this.ksearch_selected);
|
|
5447 this.ksearch_hide();
|
|
5448
|
|
5449 // Don't cancel on Tab, we want to jump to the next field (#5659)
|
|
5450 return key == 9 ? null : rcube_event.cancel(e);
|
|
5451
|
|
5452 case 27: // escape
|
|
5453 this.ksearch_hide();
|
|
5454 return;
|
|
5455
|
|
5456 case 37: // left
|
|
5457 case 39: // right
|
|
5458 return;
|
|
5459 }
|
|
5460
|
|
5461 // start timer
|
|
5462 this.ksearch_timer = setTimeout(function(){ ref.ksearch_get_results(props); }, 200);
|
|
5463 this.ksearch_input = obj;
|
|
5464
|
|
5465 return true;
|
|
5466 };
|
|
5467
|
|
5468 this.ksearch_visible = function()
|
|
5469 {
|
|
5470 return this.ksearch_selected !== null && this.ksearch_selected !== undefined && this.ksearch_value;
|
|
5471 };
|
|
5472
|
|
5473 this.ksearch_select = function(node)
|
|
5474 {
|
|
5475 if (this.ksearch_pane && node) {
|
|
5476 this.ksearch_pane.find('li.selected').removeClass('selected').removeAttr('aria-selected');
|
|
5477 }
|
|
5478
|
|
5479 if (node) {
|
|
5480 $(node).addClass('selected').attr('aria-selected', 'true');
|
|
5481 this.ksearch_selected = node._rcm_id;
|
|
5482 $(this.ksearch_input).attr('aria-activedescendant', 'rcmkSearchItem' + this.ksearch_selected);
|
|
5483 }
|
|
5484 };
|
|
5485
|
|
5486 this.insert_recipient = function(id)
|
|
5487 {
|
|
5488 if (id === null || !this.env.contacts[id] || !this.ksearch_input)
|
|
5489 return;
|
|
5490
|
|
5491 // get cursor pos
|
|
5492 var inp_value = this.ksearch_input.value,
|
|
5493 cpos = this.get_caret_pos(this.ksearch_input),
|
|
5494 p = inp_value.lastIndexOf(this.ksearch_value, cpos),
|
|
5495 trigger = false,
|
|
5496 insert = '',
|
|
5497 // replace search string with full address
|
|
5498 pre = inp_value.substring(0, p),
|
|
5499 end = inp_value.substring(p+this.ksearch_value.length, inp_value.length);
|
|
5500
|
|
5501 this.ksearch_destroy();
|
|
5502
|
|
5503 // insert all members of a group
|
|
5504 if (typeof this.env.contacts[id] === 'object' && this.env.contacts[id].type == 'group' && !this.env.contacts[id].email) {
|
|
5505 insert += this.env.contacts[id].name + this.env.recipients_delimiter;
|
|
5506 this.group2expand[this.env.contacts[id].id] = $.extend({ input: this.ksearch_input }, this.env.contacts[id]);
|
|
5507 this.http_request('mail/group-expand', {_source: this.env.contacts[id].source, _gid: this.env.contacts[id].id}, false);
|
|
5508 }
|
|
5509 else if (typeof this.env.contacts[id] === 'object' && this.env.contacts[id].name) {
|
|
5510 insert = this.env.contacts[id].name + this.env.recipients_delimiter;
|
|
5511 trigger = true;
|
|
5512 }
|
|
5513 else if (typeof this.env.contacts[id] === 'string') {
|
|
5514 insert = this.env.contacts[id] + this.env.recipients_delimiter;
|
|
5515 trigger = true;
|
|
5516 }
|
|
5517
|
|
5518 this.ksearch_input.value = pre + insert + end;
|
|
5519
|
|
5520 // set caret to insert pos
|
|
5521 this.set_caret_pos(this.ksearch_input, p + insert.length);
|
|
5522
|
|
5523 if (trigger) {
|
|
5524 this.triggerEvent('autocomplete_insert', { field:this.ksearch_input, insert:insert, data:this.env.contacts[id], search:this.ksearch_value_last, result_type:'person' });
|
|
5525 this.ksearch_value_last = null;
|
|
5526 this.compose_type_activity++;
|
|
5527 }
|
|
5528 };
|
|
5529
|
|
5530 this.replace_group_recipients = function(id, recipients)
|
|
5531 {
|
|
5532 if (this.group2expand[id]) {
|
|
5533 this.group2expand[id].input.value = this.group2expand[id].input.value.replace(this.group2expand[id].name, recipients);
|
|
5534 this.triggerEvent('autocomplete_insert', { field:this.group2expand[id].input, insert:recipients, data:this.group2expand[id], search:this.ksearch_value_last, result_type:'group' });
|
|
5535 this.ksearch_value_last = null;
|
|
5536 this.group2expand[id] = null;
|
|
5537 this.compose_type_activity++;
|
|
5538 }
|
|
5539 };
|
|
5540
|
|
5541 // address search processor
|
|
5542 this.ksearch_get_results = function(props)
|
|
5543 {
|
|
5544 var inp_value = this.ksearch_input ? this.ksearch_input.value : null;
|
|
5545
|
|
5546 if (inp_value === null)
|
|
5547 return;
|
|
5548
|
|
5549 if (this.ksearch_pane && this.ksearch_pane.is(":visible"))
|
|
5550 this.ksearch_pane.hide();
|
|
5551
|
|
5552 // get string from current cursor pos to last comma
|
|
5553 var cpos = this.get_caret_pos(this.ksearch_input),
|
|
5554 p = inp_value.lastIndexOf(this.env.recipients_separator, cpos-1),
|
|
5555 q = inp_value.substring(p+1, cpos),
|
|
5556 min = this.env.autocomplete_min_length,
|
|
5557 data = this.ksearch_data;
|
|
5558
|
|
5559 // trim query string
|
|
5560 q = $.trim(q);
|
|
5561
|
|
5562 // Don't (re-)search if the last results are still active
|
|
5563 if (q == this.ksearch_value)
|
|
5564 return;
|
|
5565
|
|
5566 this.ksearch_destroy();
|
|
5567
|
|
5568 if (q.length && q.length < min) {
|
|
5569 if (!this.ksearch_info) {
|
|
5570 this.ksearch_info = this.display_message(
|
|
5571 this.get_label('autocompletechars').replace('$min', min));
|
|
5572 }
|
|
5573 return;
|
|
5574 }
|
|
5575
|
|
5576 var old_value = this.ksearch_value;
|
|
5577 this.ksearch_value = q;
|
|
5578 this.ksearch_value_last = q; // Group expansion clears ksearch_value before calling autocomplete_insert trigger, therefore store it in separate variable for later consumption.
|
|
5579
|
|
5580 // ...string is empty
|
|
5581 if (!q.length)
|
|
5582 return;
|
|
5583
|
|
5584 // ...new search value contains old one and previous search was not finished or its result was empty
|
|
5585 if (old_value && old_value.length && q.startsWith(old_value) && (!data || data.num <= 0) && this.env.contacts && !this.env.contacts.length)
|
|
5586 return;
|
|
5587
|
|
5588 var sources = props && props.sources ? props.sources : [''];
|
|
5589 var reqid = this.multi_thread_http_request({
|
|
5590 items: sources,
|
|
5591 threads: props && props.threads ? props.threads : 1,
|
|
5592 action: props && props.action ? props.action : 'mail/autocomplete',
|
|
5593 postdata: { _search:q, _source:'%s' },
|
|
5594 lock: this.display_message(this.get_label('searching'), 'loading')
|
|
5595 });
|
|
5596
|
|
5597 this.ksearch_data = { id:reqid, sources:sources.slice(), num:sources.length };
|
|
5598 };
|
|
5599
|
|
5600 this.ksearch_query_results = function(results, search, reqid)
|
|
5601 {
|
|
5602 // trigger multi-thread http response callback
|
|
5603 this.multi_thread_http_response(results, reqid);
|
|
5604
|
|
5605 // search stopped in meantime?
|
|
5606 if (!this.ksearch_value)
|
|
5607 return;
|
|
5608
|
|
5609 // ignore this outdated search response
|
|
5610 if (this.ksearch_input && search != this.ksearch_value)
|
|
5611 return;
|
|
5612
|
|
5613 // display search results
|
|
5614 var i, id, len, ul, text, type, init,
|
|
5615 value = this.ksearch_value,
|
|
5616 maxlen = this.env.autocomplete_max ? this.env.autocomplete_max : 15;
|
|
5617
|
|
5618 // create results pane if not present
|
|
5619 if (!this.ksearch_pane) {
|
|
5620 ul = $('<ul>');
|
|
5621 this.ksearch_pane = $('<div>').attr('id', 'rcmKSearchpane').attr('role', 'listbox')
|
|
5622 .css({ position:'absolute', 'z-index':30000 }).append(ul).appendTo(document.body);
|
|
5623 this.ksearch_pane.__ul = ul[0];
|
|
5624 }
|
|
5625
|
|
5626 ul = this.ksearch_pane.__ul;
|
|
5627
|
|
5628 // remove all search results or add to existing list if parallel search
|
|
5629 if (reqid && this.ksearch_pane.data('reqid') == reqid) {
|
|
5630 maxlen -= ul.childNodes.length;
|
|
5631 }
|
|
5632 else {
|
|
5633 this.ksearch_pane.data('reqid', reqid);
|
|
5634 init = 1;
|
|
5635 // reset content
|
|
5636 ul.innerHTML = '';
|
|
5637 this.env.contacts = [];
|
|
5638 // move the results pane right under the input box
|
|
5639 var pos = $(this.ksearch_input).offset();
|
|
5640 this.ksearch_pane.css({ left:pos.left+'px', top:(pos.top + this.ksearch_input.offsetHeight)+'px', display: 'none'});
|
|
5641 }
|
|
5642
|
|
5643 // add each result line to list
|
|
5644 if (results && (len = results.length)) {
|
|
5645 for (i=0; i < len && maxlen > 0; i++) {
|
|
5646 text = typeof results[i] === 'object' ? (results[i].display || results[i].name) : results[i];
|
|
5647 type = typeof results[i] === 'object' ? results[i].type : '';
|
|
5648 id = i + this.env.contacts.length;
|
|
5649 $('<li>').attr('id', 'rcmkSearchItem' + id)
|
|
5650 .attr('role', 'option')
|
|
5651 .html('<i class="icon"></i>' + this.quote_html(text.replace(new RegExp('('+RegExp.escape(value)+')', 'ig'), '##$1%%')).replace(/##([^%]+)%%/g, '<b>$1</b>'))
|
|
5652 .addClass(type || '')
|
|
5653 .appendTo(ul)
|
|
5654 .mouseover(function() { ref.ksearch_select(this); })
|
|
5655 .mouseup(function() { ref.ksearch_click(this); })
|
|
5656 .get(0)._rcm_id = id;
|
|
5657 maxlen -= 1;
|
|
5658 }
|
|
5659 }
|
|
5660
|
|
5661 if (ul.childNodes.length) {
|
|
5662 // set the right aria-* attributes to the input field
|
|
5663 $(this.ksearch_input)
|
|
5664 .attr('aria-haspopup', 'true')
|
|
5665 .attr('aria-expanded', 'true')
|
|
5666 .attr('aria-owns', 'rcmKSearchpane');
|
|
5667
|
|
5668 this.ksearch_pane.show();
|
|
5669
|
|
5670 // select the first
|
|
5671 if (!this.env.contacts.length) {
|
|
5672 this.ksearch_select($('li:first', ul).get(0));
|
|
5673 }
|
|
5674 }
|
|
5675
|
|
5676 if (len)
|
|
5677 this.env.contacts = this.env.contacts.concat(results);
|
|
5678
|
|
5679 if (this.ksearch_data.id == reqid)
|
|
5680 this.ksearch_data.num--;
|
|
5681 };
|
|
5682
|
|
5683 this.ksearch_click = function(node)
|
|
5684 {
|
|
5685 if (this.ksearch_input)
|
|
5686 this.ksearch_input.focus();
|
|
5687
|
|
5688 this.insert_recipient(node._rcm_id);
|
|
5689 this.ksearch_hide();
|
|
5690 };
|
|
5691
|
|
5692 this.ksearch_blur = function()
|
|
5693 {
|
|
5694 if (this.ksearch_timer)
|
|
5695 clearTimeout(this.ksearch_timer);
|
|
5696
|
|
5697 this.ksearch_input = null;
|
|
5698 this.ksearch_hide();
|
|
5699 };
|
|
5700
|
|
5701 this.ksearch_hide = function()
|
|
5702 {
|
|
5703 this.ksearch_selected = null;
|
|
5704 this.ksearch_value = '';
|
|
5705
|
|
5706 if (this.ksearch_pane)
|
|
5707 this.ksearch_pane.hide();
|
|
5708
|
|
5709 $(this.ksearch_input)
|
|
5710 .attr('aria-haspopup', 'false')
|
|
5711 .attr('aria-expanded', 'false')
|
|
5712 .removeAttr('aria-activedescendant')
|
|
5713 .removeAttr('aria-owns');
|
|
5714
|
|
5715 this.ksearch_destroy();
|
|
5716 };
|
|
5717
|
|
5718 // Clears autocomplete data/requests
|
|
5719 this.ksearch_destroy = function()
|
|
5720 {
|
|
5721 if (this.ksearch_data)
|
|
5722 this.multi_thread_request_abort(this.ksearch_data.id);
|
|
5723
|
|
5724 if (this.ksearch_info)
|
|
5725 this.hide_message(this.ksearch_info);
|
|
5726
|
|
5727 if (this.ksearch_msg)
|
|
5728 this.hide_message(this.ksearch_msg);
|
|
5729
|
|
5730 this.ksearch_data = null;
|
|
5731 this.ksearch_info = null;
|
|
5732 this.ksearch_msg = null;
|
|
5733 };
|
|
5734
|
|
5735
|
|
5736 /*********************************************************/
|
|
5737 /********* address book methods *********/
|
|
5738 /*********************************************************/
|
|
5739
|
|
5740 this.contactlist_keypress = function(list)
|
|
5741 {
|
|
5742 if (list.key_pressed == list.DELETE_KEY)
|
|
5743 this.command('delete');
|
|
5744 };
|
|
5745
|
|
5746 this.contactlist_select = function(list)
|
|
5747 {
|
|
5748 if (this.preview_timer)
|
|
5749 clearTimeout(this.preview_timer);
|
|
5750
|
|
5751 var n, id, sid, contact, writable = false,
|
|
5752 selected = list.selection.length,
|
|
5753 source = this.env.source ? this.env.address_sources[this.env.source] : null;
|
|
5754
|
|
5755 // we don't have dblclick handler here, so use 50 instead of this.dblclick_time
|
|
5756 if (this.env.contentframe && !list.multi_selecting && (id = list.get_single_selection()))
|
|
5757 this.preview_timer = setTimeout(function() { ref.load_contact(id, 'show'); }, this.preview_delay_click);
|
|
5758 else if (this.env.contentframe)
|
|
5759 this.show_contentframe(false);
|
|
5760
|
|
5761 if (selected) {
|
|
5762 list.draggable = false;
|
|
5763
|
|
5764 // no source = search result, we'll need to detect if any of
|
|
5765 // selected contacts are in writable addressbook to enable edit/delete
|
|
5766 // we'll also need to know sources used in selection for copy
|
|
5767 // and group-addmember operations (drag&drop)
|
|
5768 this.env.selection_sources = [];
|
|
5769
|
|
5770 if (source) {
|
|
5771 this.env.selection_sources.push(this.env.source);
|
|
5772 }
|
|
5773
|
|
5774 for (n in list.selection) {
|
|
5775 contact = list.data[list.selection[n]];
|
|
5776 if (!source) {
|
|
5777 sid = String(list.selection[n]).replace(/^[^-]+-/, '');
|
|
5778 if (sid && this.env.address_sources[sid]) {
|
|
5779 writable = writable || (!this.env.address_sources[sid].readonly && !contact.readonly);
|
|
5780 this.env.selection_sources.push(sid);
|
|
5781 }
|
|
5782 }
|
|
5783 else {
|
|
5784 writable = writable || (!source.readonly && !contact.readonly);
|
|
5785 }
|
|
5786
|
|
5787 if (contact._type != 'group')
|
|
5788 list.draggable = true;
|
|
5789 }
|
|
5790
|
|
5791 this.env.selection_sources = $.unique(this.env.selection_sources);
|
|
5792 }
|
|
5793
|
|
5794 // if a group is currently selected, and there is at least one contact selected
|
|
5795 // thend we can enable the group-remove-selected command
|
|
5796 this.enable_command('group-remove-selected', this.env.group && selected && writable);
|
|
5797 this.enable_command('compose', this.env.group || selected);
|
|
5798 this.enable_command('print', selected == 1);
|
|
5799 this.enable_command('export-selected', 'copy', selected > 0);
|
|
5800 this.enable_command('edit', id && writable);
|
|
5801 this.enable_command('delete', 'move', selected && writable);
|
|
5802
|
|
5803 return false;
|
|
5804 };
|
|
5805
|
|
5806 this.list_contacts = function(src, group, page, search)
|
|
5807 {
|
|
5808 var win, folder, index = -1, url = {},
|
|
5809 refresh = src === undefined && group === undefined && page === undefined,
|
|
5810 target = window;
|
|
5811
|
|
5812 if (!src)
|
|
5813 src = this.env.source;
|
|
5814
|
|
5815 if (refresh)
|
|
5816 group = this.env.group;
|
|
5817
|
|
5818 if (src != this.env.source) {
|
|
5819 page = this.env.current_page = 1;
|
|
5820 this.reset_qsearch();
|
|
5821 }
|
|
5822 else if (!refresh && group != this.env.group)
|
|
5823 page = this.env.current_page = 1;
|
|
5824
|
|
5825 if (this.env.search_id)
|
|
5826 folder = 'S'+this.env.search_id;
|
|
5827 else if (!this.env.search_request)
|
|
5828 folder = group ? 'G'+src+group : src;
|
|
5829
|
|
5830 this.env.source = src;
|
|
5831 this.env.group = group;
|
|
5832
|
|
5833 // truncate groups listing stack
|
|
5834 $.each(this.env.address_group_stack, function(i, v) {
|
|
5835 if (ref.env.group == v.id) {
|
|
5836 index = i;
|
|
5837 return false;
|
|
5838 }
|
|
5839 });
|
|
5840
|
|
5841 this.env.address_group_stack = index < 0 ? [] : this.env.address_group_stack.slice(0, index);
|
|
5842
|
|
5843 // make sure the current group is on top of the stack
|
|
5844 if (this.env.group) {
|
|
5845 if (!search) search = {};
|
|
5846 search.id = this.env.group;
|
|
5847 this.env.address_group_stack.push(search);
|
|
5848
|
|
5849 // mark the first group on the stack as selected in the directory list
|
|
5850 folder = 'G'+src+this.env.address_group_stack[0].id;
|
|
5851 }
|
|
5852 else if (this.gui_objects.addresslist_title) {
|
|
5853 $(this.gui_objects.addresslist_title).text(this.get_label('contacts'));
|
|
5854 }
|
|
5855
|
|
5856 if (!this.env.search_id)
|
|
5857 this.select_folder(folder, '', true);
|
|
5858
|
|
5859 // load contacts remotely
|
|
5860 if (this.gui_objects.contactslist) {
|
|
5861 this.list_contacts_remote(src, group, page);
|
|
5862 return;
|
|
5863 }
|
|
5864
|
|
5865 if (win = this.get_frame_window(this.env.contentframe)) {
|
|
5866 target = win;
|
|
5867 url._framed = 1;
|
|
5868 }
|
|
5869
|
|
5870 if (group)
|
|
5871 url._gid = group;
|
|
5872 if (page)
|
|
5873 url._page = page;
|
|
5874 if (src)
|
|
5875 url._source = src;
|
|
5876
|
|
5877 // also send search request to get the correct listing
|
|
5878 if (this.env.search_request)
|
|
5879 url._search = this.env.search_request;
|
|
5880
|
|
5881 this.set_busy(true, 'loading');
|
|
5882 this.location_href(url, target);
|
|
5883 };
|
|
5884
|
|
5885 // send remote request to load contacts list
|
|
5886 this.list_contacts_remote = function(src, group, page)
|
|
5887 {
|
|
5888 // clear message list first
|
|
5889 this.list_contacts_clear();
|
|
5890
|
|
5891 // send request to server
|
|
5892 var url = {}, lock = this.set_busy(true, 'loading');
|
|
5893
|
|
5894 if (src)
|
|
5895 url._source = src;
|
|
5896 if (page)
|
|
5897 url._page = page;
|
|
5898 if (group)
|
|
5899 url._gid = group;
|
|
5900
|
|
5901 this.env.source = src;
|
|
5902 this.env.group = group;
|
|
5903
|
|
5904 // also send search request to get the right records
|
|
5905 if (this.env.search_request)
|
|
5906 url._search = this.env.search_request;
|
|
5907
|
|
5908 this.http_request(this.env.task == 'mail' ? 'list-contacts' : 'list', url, lock);
|
|
5909
|
|
5910 if (this.env.task != 'mail')
|
|
5911 this.update_state({_source: src, _page: page && page > 1 ? page : null, _gid: group});
|
|
5912 };
|
|
5913
|
|
5914 this.list_contacts_clear = function()
|
|
5915 {
|
|
5916 this.contact_list.data = {};
|
|
5917 this.contact_list.clear(true);
|
|
5918 this.show_contentframe(false);
|
|
5919 this.enable_command('delete', 'move', 'copy', 'print', false);
|
|
5920 this.enable_command('compose', this.env.group);
|
|
5921 };
|
|
5922
|
|
5923 this.set_group_prop = function(prop)
|
|
5924 {
|
|
5925 if (this.gui_objects.addresslist_title) {
|
|
5926 var boxtitle = $(this.gui_objects.addresslist_title).html(''); // clear contents
|
|
5927
|
|
5928 // add link to pop back to parent group
|
|
5929 if (this.env.address_group_stack.length > 1
|
|
5930 || (this.env.address_group_stack.length == 1 && this.env.address_group_stack[0].search_request)
|
|
5931 ) {
|
|
5932 $('<a href="#list">...</a>')
|
|
5933 .attr('title', this.get_label('uponelevel'))
|
|
5934 .addClass('poplink')
|
|
5935 .appendTo(boxtitle)
|
|
5936 .click(function(e){ return ref.command('popgroup','',this); });
|
|
5937 boxtitle.append(' » ');
|
|
5938 }
|
|
5939
|
|
5940 boxtitle.append($('<span>').text(prop ? prop.name : this.get_label('contacts')));
|
|
5941 }
|
|
5942 };
|
|
5943
|
|
5944 // load contact record
|
|
5945 this.load_contact = function(cid, action, framed)
|
|
5946 {
|
|
5947 var win, url = {}, target = window,
|
|
5948 rec = this.contact_list ? this.contact_list.data[cid] : null;
|
|
5949
|
|
5950 if (win = this.get_frame_window(this.env.contentframe)) {
|
|
5951 url._framed = 1;
|
|
5952 target = win;
|
|
5953 this.show_contentframe(true);
|
|
5954
|
|
5955 // load dummy content, unselect selected row(s)
|
|
5956 if (!cid)
|
|
5957 this.contact_list.clear_selection();
|
|
5958
|
|
5959 this.enable_command('compose', rec && rec.email);
|
|
5960 this.enable_command('export-selected', 'print', rec && rec._type != 'group');
|
|
5961 }
|
|
5962 else if (framed)
|
|
5963 return false;
|
|
5964
|
|
5965 if (action && (cid || action == 'add') && !this.drag_active) {
|
|
5966 if (this.env.group)
|
|
5967 url._gid = this.env.group;
|
|
5968
|
|
5969 if (this.env.search_request)
|
|
5970 url._search = this.env.search_request;
|
|
5971
|
|
5972 url._action = action;
|
|
5973 url._source = this.env.source;
|
|
5974 url._cid = cid;
|
|
5975
|
|
5976 this.location_href(url, target, true);
|
|
5977 }
|
|
5978
|
|
5979 return true;
|
|
5980 };
|
|
5981
|
|
5982 // add/delete member to/from the group
|
|
5983 this.group_member_change = function(what, cid, source, gid)
|
|
5984 {
|
|
5985 if (what != 'add')
|
|
5986 what = 'del';
|
|
5987
|
|
5988 var label = this.get_label(what == 'add' ? 'addingmember' : 'removingmember'),
|
|
5989 lock = this.display_message(label, 'loading'),
|
|
5990 post_data = {_cid: cid, _source: source, _gid: gid};
|
|
5991
|
|
5992 this.http_post('group-'+what+'members', post_data, lock);
|
|
5993 };
|
|
5994
|
|
5995 this.contacts_drag_menu = function(e, to)
|
|
5996 {
|
|
5997 var dest = to.type == 'group' ? to.source : to.id,
|
|
5998 source = this.env.source;
|
|
5999
|
|
6000 if (!this.env.address_sources[dest] || this.env.address_sources[dest].readonly)
|
|
6001 return true;
|
|
6002
|
|
6003 // search result may contain contacts from many sources, but if there is only one...
|
|
6004 if (source == '' && this.env.selection_sources.length == 1)
|
|
6005 source = this.env.selection_sources[0];
|
|
6006
|
|
6007 if (to.type == 'group' && dest == source) {
|
|
6008 var cid = this.contact_list.get_selection().join(',');
|
|
6009 this.group_member_change('add', cid, dest, to.id);
|
|
6010 return true;
|
|
6011 }
|
|
6012 // move action is not possible, "redirect" to copy if menu wasn't requested
|
|
6013 else if (!this.commands.move && rcube_event.get_modifier(e) != SHIFT_KEY) {
|
|
6014 this.copy_contacts(to);
|
|
6015 return true;
|
|
6016 }
|
|
6017
|
|
6018 return this.drag_menu(e, to);
|
|
6019 };
|
|
6020
|
|
6021 // copy contact(s) to the specified target (group or directory)
|
|
6022 this.copy_contacts = function(to)
|
|
6023 {
|
|
6024 var dest = to.type == 'group' ? to.source : to.id,
|
|
6025 source = this.env.source,
|
|
6026 group = this.env.group ? this.env.group : '',
|
|
6027 cid = this.contact_list.get_selection().join(',');
|
|
6028
|
|
6029 if (!cid || !this.env.address_sources[dest] || this.env.address_sources[dest].readonly)
|
|
6030 return;
|
|
6031
|
|
6032 // search result may contain contacts from many sources, but if there is only one...
|
|
6033 if (source == '' && this.env.selection_sources.length == 1)
|
|
6034 source = this.env.selection_sources[0];
|
|
6035
|
|
6036 // tagret is a group
|
|
6037 if (to.type == 'group') {
|
|
6038 if (dest == source)
|
|
6039 return;
|
|
6040
|
|
6041 var lock = this.display_message(this.get_label('copyingcontact'), 'loading'),
|
|
6042 post_data = {_cid: cid, _source: this.env.source, _to: dest, _togid: to.id, _gid: group};
|
|
6043
|
|
6044 this.http_post('copy', post_data, lock);
|
|
6045 }
|
|
6046 // target is an addressbook
|
|
6047 else if (to.id != source) {
|
|
6048 var lock = this.display_message(this.get_label('copyingcontact'), 'loading'),
|
|
6049 post_data = {_cid: cid, _source: this.env.source, _to: to.id, _gid: group};
|
|
6050
|
|
6051 this.http_post('copy', post_data, lock);
|
|
6052 }
|
|
6053 };
|
|
6054
|
|
6055 // move contact(s) to the specified target (group or directory)
|
|
6056 this.move_contacts = function(to)
|
|
6057 {
|
|
6058 var dest = to.type == 'group' ? to.source : to.id,
|
|
6059 source = this.env.source,
|
|
6060 group = this.env.group ? this.env.group : '';
|
|
6061
|
|
6062 if (!this.env.address_sources[dest] || this.env.address_sources[dest].readonly)
|
|
6063 return;
|
|
6064
|
|
6065 // search result may contain contacts from many sources, but if there is only one...
|
|
6066 if (source == '' && this.env.selection_sources.length == 1)
|
|
6067 source = this.env.selection_sources[0];
|
|
6068
|
|
6069 if (to.type == 'group') {
|
|
6070 if (dest == source)
|
|
6071 return;
|
|
6072
|
|
6073 this._with_selected_contacts('move', {_to: dest, _togid: to.id});
|
|
6074 }
|
|
6075 // target is an addressbook
|
|
6076 else if (to.id != source)
|
|
6077 this._with_selected_contacts('move', {_to: to.id});
|
|
6078 };
|
|
6079
|
|
6080 // delete contact(s)
|
|
6081 this.delete_contacts = function()
|
|
6082 {
|
|
6083 var undelete = this.env.source && this.env.address_sources[this.env.source].undelete;
|
|
6084
|
|
6085 if (!undelete && !confirm(this.get_label('deletecontactconfirm')))
|
|
6086 return;
|
|
6087
|
|
6088 return this._with_selected_contacts('delete');
|
|
6089 };
|
|
6090
|
|
6091 this._with_selected_contacts = function(action, post_data)
|
|
6092 {
|
|
6093 var selection = this.contact_list ? this.contact_list.get_selection() : [];
|
|
6094
|
|
6095 // exit if no contact specified or if selection is empty
|
|
6096 if (!selection.length && !this.env.cid)
|
|
6097 return;
|
|
6098
|
|
6099 var n, a_cids = [],
|
|
6100 label = action == 'delete' ? 'contactdeleting' : 'movingcontact',
|
|
6101 lock = this.display_message(this.get_label(label), 'loading');
|
|
6102
|
|
6103 if (this.env.cid)
|
|
6104 a_cids.push(this.env.cid);
|
|
6105 else {
|
|
6106 for (n=0; n<selection.length; n++) {
|
|
6107 id = selection[n];
|
|
6108 a_cids.push(id);
|
|
6109 this.contact_list.remove_row(id, (n == selection.length-1));
|
|
6110 }
|
|
6111
|
|
6112 // hide content frame if we delete the currently displayed contact
|
|
6113 if (selection.length == 1)
|
|
6114 this.show_contentframe(false);
|
|
6115 }
|
|
6116
|
|
6117 if (!post_data)
|
|
6118 post_data = {};
|
|
6119
|
|
6120 post_data._source = this.env.source;
|
|
6121 post_data._from = this.env.action;
|
|
6122 post_data._cid = a_cids.join(',');
|
|
6123
|
|
6124 if (this.env.group)
|
|
6125 post_data._gid = this.env.group;
|
|
6126
|
|
6127 // also send search request to get the right records from the next page
|
|
6128 if (this.env.search_request)
|
|
6129 post_data._search = this.env.search_request;
|
|
6130
|
|
6131 // send request to server
|
|
6132 this.http_post(action, post_data, lock)
|
|
6133
|
|
6134 return true;
|
|
6135 };
|
|
6136
|
|
6137 // update a contact record in the list
|
|
6138 this.update_contact_row = function(cid, cols_arr, newcid, source, data)
|
|
6139 {
|
|
6140 var list = this.contact_list;
|
|
6141
|
|
6142 cid = this.html_identifier(cid);
|
|
6143
|
|
6144 // when in searching mode, concat cid with the source name
|
|
6145 if (!list.rows[cid]) {
|
|
6146 cid = cid + '-' + source;
|
|
6147 if (newcid)
|
|
6148 newcid = newcid + '-' + source;
|
|
6149 }
|
|
6150
|
|
6151 list.update_row(cid, cols_arr, newcid, true);
|
|
6152 list.data[cid] = data;
|
|
6153 };
|
|
6154
|
|
6155 // add row to contacts list
|
|
6156 this.add_contact_row = function(cid, cols, classes, data)
|
|
6157 {
|
|
6158 if (!this.gui_objects.contactslist)
|
|
6159 return false;
|
|
6160
|
|
6161 var c, col, list = this.contact_list,
|
|
6162 row = { cols:[] };
|
|
6163
|
|
6164 row.id = 'rcmrow' + this.html_identifier(cid);
|
|
6165 row.className = 'contact ' + (classes || '');
|
|
6166
|
|
6167 if (list.in_selection(cid))
|
|
6168 row.className += ' selected';
|
|
6169
|
|
6170 // add each submitted col
|
|
6171 for (c in cols) {
|
|
6172 col = {};
|
|
6173 col.className = String(c).toLowerCase();
|
|
6174 col.innerHTML = cols[c];
|
|
6175 row.cols.push(col);
|
|
6176 }
|
|
6177
|
|
6178 // store data in list member
|
|
6179 list.data[cid] = data;
|
|
6180 list.insert_row(row);
|
|
6181
|
|
6182 this.enable_command('export', list.rowcount > 0);
|
|
6183 };
|
|
6184
|
|
6185 this.init_contact_form = function()
|
|
6186 {
|
|
6187 var col;
|
|
6188
|
|
6189 if (this.env.coltypes) {
|
|
6190 this.set_photo_actions($('#ff_photo').val());
|
|
6191 for (col in this.env.coltypes)
|
|
6192 this.init_edit_field(col, null);
|
|
6193 }
|
|
6194
|
|
6195 $('.contactfieldgroup .row a.deletebutton').click(function() {
|
|
6196 ref.delete_edit_field(this);
|
|
6197 return false;
|
|
6198 });
|
|
6199
|
|
6200 $('select.addfieldmenu').change(function() {
|
|
6201 ref.insert_edit_field($(this).val(), $(this).attr('rel'), this);
|
|
6202 this.selectedIndex = 0;
|
|
6203 });
|
|
6204
|
|
6205 // enable date pickers on date fields
|
|
6206 if ($.datepicker && this.env.date_format) {
|
|
6207 $.datepicker.setDefaults({
|
|
6208 dateFormat: this.env.date_format,
|
|
6209 changeMonth: true,
|
|
6210 changeYear: true,
|
|
6211 yearRange: '-120:+10',
|
|
6212 showOtherMonths: true,
|
|
6213 selectOtherMonths: true
|
|
6214 // onSelect: function(dateText) { $(this).focus().val(dateText); }
|
|
6215 });
|
|
6216 $('input.datepicker').datepicker();
|
|
6217 }
|
|
6218
|
|
6219 // Submit search form on Enter
|
|
6220 if (this.env.action == 'search')
|
|
6221 $(this.gui_objects.editform).append($('<input type="submit">').hide())
|
|
6222 .submit(function() { $('input.mainaction').click(); return false; });
|
|
6223 };
|
|
6224
|
|
6225 // group creation dialog
|
|
6226 this.group_create = function()
|
|
6227 {
|
|
6228 var input = $('<input>').attr('type', 'text'),
|
|
6229 content = $('<label>').text(this.get_label('namex')).append(input),
|
|
6230 source = this.env.source;
|
|
6231
|
|
6232 this.simple_dialog(content, 'newgroup',
|
|
6233 function() {
|
|
6234 var name;
|
|
6235 if (name = input.val()) {
|
|
6236 ref.http_post('group-create', {_source: source, _name: name},
|
|
6237 ref.set_busy(true, 'loading'));
|
|
6238 return true;
|
|
6239 }
|
|
6240 });
|
|
6241 };
|
|
6242
|
|
6243 // group rename dialog
|
|
6244 this.group_rename = function()
|
|
6245 {
|
|
6246 if (!this.env.group)
|
|
6247 return;
|
|
6248
|
|
6249 var group_name = this.env.contactgroups['G' + this.env.source + this.env.group].name,
|
|
6250 input = $('<input>').attr('type', 'text').val(group_name),
|
|
6251 content = $('<label>').text(this.get_label('namex')).append(input),
|
|
6252 source = this.env.source,
|
|
6253 group = this.env.group;
|
|
6254
|
|
6255 this.simple_dialog(content, 'grouprename',
|
|
6256 function() {
|
|
6257 var name;
|
|
6258 if ((name = input.val()) && name != group_name) {
|
|
6259 ref.http_post('group-rename', {_source: source, _gid: group, _name: name},
|
|
6260 ref.set_busy(true, 'loading'));
|
|
6261 return true;
|
|
6262 }
|
|
6263 },
|
|
6264 {open: function() { input.select(); }}
|
|
6265 );
|
|
6266 };
|
|
6267
|
|
6268 this.group_delete = function()
|
|
6269 {
|
|
6270 if (this.env.group && confirm(this.get_label('deletegroupconfirm'))) {
|
|
6271 var lock = this.set_busy(true, 'groupdeleting');
|
|
6272 this.http_post('group-delete', {_source: this.env.source, _gid: this.env.group}, lock);
|
|
6273 }
|
|
6274 };
|
|
6275
|
|
6276 // callback from server upon group-delete command
|
|
6277 this.remove_group_item = function(prop)
|
|
6278 {
|
|
6279 var key = 'G'+prop.source+prop.id;
|
|
6280
|
|
6281 if (this.treelist.remove(key)) {
|
|
6282 this.triggerEvent('group_delete', { source:prop.source, id:prop.id });
|
|
6283 delete this.env.contactfolders[key];
|
|
6284 delete this.env.contactgroups[key];
|
|
6285 }
|
|
6286
|
|
6287 if (prop.source == this.env.source && prop.id == this.env.group)
|
|
6288 this.list_contacts(prop.source, 0);
|
|
6289 };
|
|
6290
|
|
6291 //remove selected contacts from current active group
|
|
6292 this.group_remove_selected = function()
|
|
6293 {
|
|
6294 this.http_post('group-delmembers', {_cid: this.contact_list.selection,
|
|
6295 _source: this.env.source, _gid: this.env.group});
|
|
6296 };
|
|
6297
|
|
6298 //callback after deleting contact(s) from current group
|
|
6299 this.remove_group_contacts = function(props)
|
|
6300 {
|
|
6301 if (this.env.group !== undefined && (this.env.group === props.gid)) {
|
|
6302 var n, selection = this.contact_list.get_selection();
|
|
6303 for (n=0; n<selection.length; n++) {
|
|
6304 id = selection[n];
|
|
6305 this.contact_list.remove_row(id, (n == selection.length-1));
|
|
6306 }
|
|
6307 }
|
|
6308 };
|
|
6309
|
|
6310 // callback for creating a new contact group
|
|
6311 this.insert_contact_group = function(prop)
|
|
6312 {
|
|
6313 prop.type = 'group';
|
|
6314
|
|
6315 var key = 'G'+prop.source+prop.id,
|
|
6316 link = $('<a>').attr('href', '#')
|
|
6317 .attr('rel', prop.source+':'+prop.id)
|
|
6318 .click(function() { return ref.command('listgroup', prop, this); })
|
|
6319 .html(prop.name);
|
|
6320
|
|
6321 this.env.contactfolders[key] = this.env.contactgroups[key] = prop;
|
|
6322 this.treelist.insert({ id:key, html:link, classes:['contactgroup'] }, prop.source, 'contactgroup');
|
|
6323
|
|
6324 this.triggerEvent('group_insert', { id:prop.id, source:prop.source, name:prop.name, li:this.treelist.get_item(key) });
|
|
6325 };
|
|
6326
|
|
6327 // callback for renaming a contact group
|
|
6328 this.update_contact_group = function(prop)
|
|
6329 {
|
|
6330 var key = 'G'+prop.source+prop.id,
|
|
6331 newnode = {};
|
|
6332
|
|
6333 // group ID has changed, replace link node and identifiers
|
|
6334 if (prop.newid) {
|
|
6335 var newkey = 'G'+prop.source+prop.newid,
|
|
6336 newprop = $.extend({}, prop);
|
|
6337
|
|
6338 this.env.contactfolders[newkey] = this.env.contactfolders[key];
|
|
6339 this.env.contactfolders[newkey].id = prop.newid;
|
|
6340 this.env.group = prop.newid;
|
|
6341
|
|
6342 delete this.env.contactfolders[key];
|
|
6343 delete this.env.contactgroups[key];
|
|
6344
|
|
6345 newprop.id = prop.newid;
|
|
6346 newprop.type = 'group';
|
|
6347
|
|
6348 newnode.id = newkey;
|
|
6349 newnode.html = $('<a>').attr('href', '#')
|
|
6350 .attr('rel', prop.source+':'+prop.newid)
|
|
6351 .click(function() { return ref.command('listgroup', newprop, this); })
|
|
6352 .html(prop.name);
|
|
6353 }
|
|
6354 // update displayed group name
|
|
6355 else {
|
|
6356 $(this.treelist.get_item(key)).children().first().html(prop.name);
|
|
6357 this.env.contactfolders[key].name = this.env.contactgroups[key].name = prop.name;
|
|
6358
|
|
6359 if (prop.source == this.env.source && prop.id == this.env.group)
|
|
6360 this.set_group_prop(prop);
|
|
6361 }
|
|
6362
|
|
6363 // update list node and re-sort it
|
|
6364 this.treelist.update(key, newnode, true);
|
|
6365
|
|
6366 this.triggerEvent('group_update', { id:prop.id, source:prop.source, name:prop.name, li:this.treelist.get_item(key), newid:prop.newid });
|
|
6367 };
|
|
6368
|
|
6369 this.update_group_commands = function()
|
|
6370 {
|
|
6371 var source = this.env.source != '' ? this.env.address_sources[this.env.source] : null,
|
|
6372 supported = source && source.groups && !source.readonly;
|
|
6373
|
|
6374 this.enable_command('group-create', supported);
|
|
6375 this.enable_command('group-rename', 'group-delete', supported && this.env.group);
|
|
6376 };
|
|
6377
|
|
6378 this.init_edit_field = function(col, elem)
|
|
6379 {
|
|
6380 var label = this.env.coltypes[col].label;
|
|
6381
|
|
6382 if (!elem)
|
|
6383 elem = $('.ff_' + col);
|
|
6384
|
|
6385 if (label)
|
|
6386 elem.placeholder(label);
|
|
6387 };
|
|
6388
|
|
6389 this.insert_edit_field = function(col, section, menu)
|
|
6390 {
|
|
6391 // just make pre-defined input field visible
|
|
6392 var elem = $('#ff_'+col);
|
|
6393 if (elem.length) {
|
|
6394 elem.show().focus();
|
|
6395 $(menu).children('option[value="'+col+'"]').prop('disabled', true);
|
|
6396 }
|
|
6397 else {
|
|
6398 var lastelem = $('.ff_'+col),
|
|
6399 appendcontainer = $('#contactsection'+section+' .contactcontroller'+col);
|
|
6400
|
|
6401 if (!appendcontainer.length) {
|
|
6402 var sect = $('#contactsection'+section),
|
|
6403 lastgroup = $('.contactfieldgroup', sect).last();
|
|
6404 appendcontainer = $('<fieldset>').addClass('contactfieldgroup contactcontroller'+col);
|
|
6405 if (lastgroup.length)
|
|
6406 appendcontainer.insertAfter(lastgroup);
|
|
6407 else
|
|
6408 sect.prepend(appendcontainer);
|
|
6409 }
|
|
6410
|
|
6411 if (appendcontainer.length && appendcontainer.get(0).nodeName == 'FIELDSET') {
|
|
6412 var input, colprop = this.env.coltypes[col],
|
|
6413 input_id = 'ff_' + col + (colprop.count || 0),
|
|
6414 row = $('<div>').addClass('row'),
|
|
6415 cell = $('<div>').addClass('contactfieldcontent data'),
|
|
6416 label = $('<div>').addClass('contactfieldlabel label');
|
|
6417
|
|
6418 if (colprop.subtypes_select)
|
|
6419 label.html(colprop.subtypes_select);
|
|
6420 else
|
|
6421 label.html('<label for="' + input_id + '">' + colprop.label + '</label>');
|
|
6422
|
|
6423 var name_suffix = colprop.limit != 1 ? '[]' : '';
|
|
6424
|
|
6425 if (colprop.type == 'text' || colprop.type == 'date') {
|
|
6426 input = $('<input>')
|
|
6427 .addClass('ff_'+col)
|
|
6428 .attr({type: 'text', name: '_'+col+name_suffix, size: colprop.size, id: input_id})
|
|
6429 .appendTo(cell);
|
|
6430
|
|
6431 this.init_edit_field(col, input);
|
|
6432
|
|
6433 if (colprop.type == 'date' && $.datepicker)
|
|
6434 input.datepicker();
|
|
6435 }
|
|
6436 else if (colprop.type == 'textarea') {
|
|
6437 input = $('<textarea>')
|
|
6438 .addClass('ff_'+col)
|
|
6439 .attr({ name: '_'+col+name_suffix, cols:colprop.size, rows:colprop.rows, id: input_id })
|
|
6440 .appendTo(cell);
|
|
6441
|
|
6442 this.init_edit_field(col, input);
|
|
6443 }
|
|
6444 else if (colprop.type == 'composite') {
|
|
6445 var i, childcol, cp, first, templ, cols = [], suffices = [];
|
|
6446
|
|
6447 // read template for composite field order
|
|
6448 if ((templ = this.env[col+'_template'])) {
|
|
6449 for (i=0; i < templ.length; i++) {
|
|
6450 cols.push(templ[i][1]);
|
|
6451 suffices.push(templ[i][2]);
|
|
6452 }
|
|
6453 }
|
|
6454 else { // list fields according to appearance in colprop
|
|
6455 for (childcol in colprop.childs)
|
|
6456 cols.push(childcol);
|
|
6457 }
|
|
6458
|
|
6459 for (i=0; i < cols.length; i++) {
|
|
6460 childcol = cols[i];
|
|
6461 cp = colprop.childs[childcol];
|
|
6462 input = $('<input>')
|
|
6463 .addClass('ff_'+childcol)
|
|
6464 .attr({ type: 'text', name: '_'+childcol+name_suffix, size: cp.size })
|
|
6465 .appendTo(cell);
|
|
6466 cell.append(suffices[i] || " ");
|
|
6467 this.init_edit_field(childcol, input);
|
|
6468 if (!first) first = input;
|
|
6469 }
|
|
6470 input = first; // set focus to the first of this composite fields
|
|
6471 }
|
|
6472 else if (colprop.type == 'select') {
|
|
6473 input = $('<select>')
|
|
6474 .addClass('ff_'+col)
|
|
6475 .attr({ 'name': '_'+col+name_suffix, id: input_id })
|
|
6476 .appendTo(cell);
|
|
6477
|
|
6478 var options = input.attr('options');
|
|
6479 options[options.length] = new Option('---', '');
|
|
6480 if (colprop.options)
|
|
6481 $.each(colprop.options, function(i, val){ options[options.length] = new Option(val, i); });
|
|
6482 }
|
|
6483
|
|
6484 if (input) {
|
|
6485 var delbutton = $('<a href="#del"></a>')
|
|
6486 .addClass('contactfieldbutton deletebutton')
|
|
6487 .attr({title: this.get_label('delete'), rel: col})
|
|
6488 .html(this.env.delbutton)
|
|
6489 .click(function(){ ref.delete_edit_field(this); return false })
|
|
6490 .appendTo(cell);
|
|
6491
|
|
6492 row.append(label).append(cell).appendTo(appendcontainer.show());
|
|
6493 input.first().focus();
|
|
6494
|
|
6495 // disable option if limit reached
|
|
6496 if (!colprop.count) colprop.count = 0;
|
|
6497 if (++colprop.count == colprop.limit && colprop.limit)
|
|
6498 $(menu).children('option[value="'+col+'"]').prop('disabled', true);
|
|
6499 }
|
|
6500 }
|
|
6501 }
|
|
6502 };
|
|
6503
|
|
6504 this.delete_edit_field = function(elem)
|
|
6505 {
|
|
6506 var col = $(elem).attr('rel'),
|
|
6507 colprop = this.env.coltypes[col],
|
|
6508 fieldset = $(elem).parents('fieldset.contactfieldgroup'),
|
|
6509 addmenu = fieldset.parent().find('select.addfieldmenu');
|
|
6510
|
|
6511 // just clear input but don't hide the last field
|
|
6512 if (--colprop.count <= 0 && colprop.visible)
|
|
6513 $(elem).parent().children('input').val('').blur();
|
|
6514 else {
|
|
6515 $(elem).parents('div.row').remove();
|
|
6516 // hide entire fieldset if no more rows
|
|
6517 if (!fieldset.children('div.row').length)
|
|
6518 fieldset.hide();
|
|
6519 }
|
|
6520
|
|
6521 // enable option in add-field selector or insert it if necessary
|
|
6522 if (addmenu.length) {
|
|
6523 var option = addmenu.children('option[value="'+col+'"]');
|
|
6524 if (option.length)
|
|
6525 option.prop('disabled', false);
|
|
6526 else
|
|
6527 option = $('<option>').attr('value', col).html(colprop.label).appendTo(addmenu);
|
|
6528 addmenu.show();
|
|
6529 }
|
|
6530 };
|
|
6531
|
|
6532 this.upload_contact_photo = function(form)
|
|
6533 {
|
|
6534 if (form && form.elements._photo.value) {
|
|
6535 this.async_upload_form(form, 'upload-photo', function(e) {
|
|
6536 ref.set_busy(false, null, ref.file_upload_id);
|
|
6537 });
|
|
6538
|
|
6539 // display upload indicator
|
|
6540 this.file_upload_id = this.set_busy(true, 'uploading');
|
|
6541 }
|
|
6542 };
|
|
6543
|
|
6544 this.replace_contact_photo = function(id)
|
|
6545 {
|
|
6546 var img_src = id == '-del-' ? this.env.photo_placeholder :
|
|
6547 this.env.comm_path + '&_action=photo&_source=' + this.env.source + '&_cid=' + (this.env.cid || 0) + '&_photo=' + id;
|
|
6548
|
|
6549 this.set_photo_actions(id);
|
|
6550 $(this.gui_objects.contactphoto).children('img').attr('src', img_src);
|
|
6551 };
|
|
6552
|
|
6553 this.photo_upload_end = function()
|
|
6554 {
|
|
6555 this.set_busy(false, null, this.file_upload_id);
|
|
6556 delete this.file_upload_id;
|
|
6557 };
|
|
6558
|
|
6559 this.set_photo_actions = function(id)
|
|
6560 {
|
|
6561 var n, buttons = this.buttons['upload-photo'];
|
|
6562 for (n=0; buttons && n < buttons.length; n++)
|
|
6563 $('a#'+buttons[n].id).html(this.get_label(id == '-del-' ? 'addphoto' : 'replacephoto'));
|
|
6564
|
|
6565 $('#ff_photo').val(id);
|
|
6566 this.enable_command('upload-photo', this.env.coltypes.photo ? true : false);
|
|
6567 this.enable_command('delete-photo', this.env.coltypes.photo && id != '-del-');
|
|
6568 };
|
|
6569
|
|
6570 // load advanced search page
|
|
6571 this.advanced_search = function()
|
|
6572 {
|
|
6573 var win, url = {_form: 1, _action: 'search'}, target = window;
|
|
6574
|
|
6575 if (win = this.get_frame_window(this.env.contentframe)) {
|
|
6576 url._framed = 1;
|
|
6577 target = win;
|
|
6578 this.contact_list.clear_selection();
|
|
6579 }
|
|
6580
|
|
6581 this.location_href(url, target, true);
|
|
6582
|
|
6583 return true;
|
|
6584 };
|
|
6585
|
|
6586 // unselect directory/group
|
|
6587 this.unselect_directory = function()
|
|
6588 {
|
|
6589 this.select_folder('');
|
|
6590 this.enable_command('search-delete', false);
|
|
6591 };
|
|
6592
|
|
6593 // callback for creating a new saved search record
|
|
6594 this.insert_saved_search = function(name, id)
|
|
6595 {
|
|
6596 var key = 'S'+id,
|
|
6597 link = $('<a>').attr('href', '#')
|
|
6598 .attr('rel', id)
|
|
6599 .click(function() { return ref.command('listsearch', id, this); })
|
|
6600 .html(name),
|
|
6601 prop = { name:name, id:id };
|
|
6602
|
|
6603 this.savedsearchlist.insert({ id:key, html:link, classes:['contactsearch'] }, null, 'contactsearch');
|
|
6604 this.select_folder(key,'',true);
|
|
6605 this.enable_command('search-delete', true);
|
|
6606 this.env.search_id = id;
|
|
6607
|
|
6608 this.triggerEvent('abook_search_insert', prop);
|
|
6609 };
|
|
6610
|
|
6611 // creates a dialog for saved search
|
|
6612 this.search_create = function()
|
|
6613 {
|
|
6614 var input = $('<input>').attr('type', 'text'),
|
|
6615 content = $('<label>').text(this.get_label('namex')).append(input);
|
|
6616
|
|
6617 this.simple_dialog(content, 'searchsave',
|
|
6618 function() {
|
|
6619 var name;
|
|
6620 if (name = input.val()) {
|
|
6621 ref.http_post('search-create', {_search: ref.env.search_request, _name: name},
|
|
6622 ref.set_busy(true, 'loading'));
|
|
6623 return true;
|
|
6624 }
|
|
6625 }
|
|
6626 );
|
|
6627 };
|
|
6628
|
|
6629 this.search_delete = function()
|
|
6630 {
|
|
6631 if (this.env.search_request) {
|
|
6632 var lock = this.set_busy(true, 'savedsearchdeleting');
|
|
6633 this.http_post('search-delete', {_sid: this.env.search_id}, lock);
|
|
6634 }
|
|
6635 };
|
|
6636
|
|
6637 // callback from server upon search-delete command
|
|
6638 this.remove_search_item = function(id)
|
|
6639 {
|
|
6640 var li, key = 'S'+id;
|
|
6641 if (this.savedsearchlist.remove(key)) {
|
|
6642 this.triggerEvent('search_delete', { id:id, li:li });
|
|
6643 }
|
|
6644
|
|
6645 this.env.search_id = null;
|
|
6646 this.env.search_request = null;
|
|
6647 this.list_contacts_clear();
|
|
6648 this.reset_qsearch();
|
|
6649 this.enable_command('search-delete', 'search-create', false);
|
|
6650 };
|
|
6651
|
|
6652 this.listsearch = function(id)
|
|
6653 {
|
|
6654 var lock = this.set_busy(true, 'searching');
|
|
6655
|
|
6656 if (this.contact_list) {
|
|
6657 this.list_contacts_clear();
|
|
6658 }
|
|
6659
|
|
6660 this.reset_qsearch();
|
|
6661
|
|
6662 if (this.savedsearchlist) {
|
|
6663 this.treelist.select('');
|
|
6664 this.savedsearchlist.select('S'+id);
|
|
6665 }
|
|
6666 else
|
|
6667 this.select_folder('S'+id, '', true);
|
|
6668
|
|
6669 // reset vars
|
|
6670 this.env.current_page = 1;
|
|
6671 this.http_request('search', {_sid: id}, lock);
|
|
6672 };
|
|
6673
|
|
6674 // display a dialog with QR code image
|
|
6675 this.qrcode = function()
|
|
6676 {
|
|
6677 var title = this.get_label('qrcode'),
|
|
6678 buttons = [{
|
|
6679 text: this.get_label('close'),
|
|
6680 'class': 'mainaction',
|
|
6681 click: function() {
|
|
6682 (ref.is_framed() ? parent.$ : $)(this).dialog('destroy');
|
|
6683 }
|
|
6684 }],
|
|
6685 img = new Image(300, 300);
|
|
6686
|
|
6687 img.src = this.url('addressbook/qrcode', {_source: this.env.source, _cid: this.env.cid});
|
|
6688
|
|
6689 return this.show_popup_dialog(img, title, buttons, {width: 310, height: 410});
|
|
6690 };
|
|
6691
|
|
6692
|
|
6693 /*********************************************************/
|
|
6694 /********* user settings methods *********/
|
|
6695 /*********************************************************/
|
|
6696
|
|
6697 // preferences section select and load options frame
|
|
6698 this.section_select = function(list)
|
|
6699 {
|
|
6700 var win, id = list.get_single_selection(), target = window,
|
|
6701 url = {_action: 'edit-prefs', _section: id};
|
|
6702
|
|
6703 if (id) {
|
|
6704 if (win = this.get_frame_window(this.env.contentframe)) {
|
|
6705 url._framed = 1;
|
|
6706 target = win;
|
|
6707 }
|
|
6708 this.location_href(url, target, true);
|
|
6709 }
|
|
6710
|
|
6711 return true;
|
|
6712 };
|
|
6713
|
|
6714 this.identity_select = function(list)
|
|
6715 {
|
|
6716 var id;
|
|
6717 if (id = list.get_single_selection()) {
|
|
6718 this.enable_command('delete', list.rowcount > 1 && this.env.identities_level < 2);
|
|
6719 this.load_identity(id, 'edit-identity');
|
|
6720 }
|
|
6721 };
|
|
6722
|
|
6723 // load identity record
|
|
6724 this.load_identity = function(id, action)
|
|
6725 {
|
|
6726 if (action == 'edit-identity' && (!id || id == this.env.iid))
|
|
6727 return false;
|
|
6728
|
|
6729 var win, target = window,
|
|
6730 url = {_action: action, _iid: id};
|
|
6731
|
|
6732 if (win = this.get_frame_window(this.env.contentframe)) {
|
|
6733 url._framed = 1;
|
|
6734 target = win;
|
|
6735 }
|
|
6736
|
|
6737 if (id || action == 'add-identity') {
|
|
6738 this.location_href(url, target, true);
|
|
6739 }
|
|
6740
|
|
6741 return true;
|
|
6742 };
|
|
6743
|
|
6744 this.delete_identity = function(id)
|
|
6745 {
|
|
6746 // exit if no identity is specified or if selection is empty
|
|
6747 var selection = this.identity_list.get_selection();
|
|
6748 if (!(selection.length || this.env.iid))
|
|
6749 return;
|
|
6750
|
|
6751 if (!id)
|
|
6752 id = this.env.iid ? this.env.iid : selection[0];
|
|
6753
|
|
6754 // submit request with appended token
|
|
6755 if (id && confirm(this.get_label('deleteidentityconfirm')))
|
|
6756 this.http_post('settings/delete-identity', { _iid: id }, true);
|
|
6757 };
|
|
6758
|
|
6759 this.update_identity_row = function(id, name, add)
|
|
6760 {
|
|
6761 var list = this.identity_list,
|
|
6762 rid = this.html_identifier(id);
|
|
6763
|
|
6764 if (add) {
|
|
6765 list.insert_row({ id:'rcmrow'+rid, cols:[ { className:'mail', innerHTML:name } ] });
|
|
6766 list.select(rid);
|
|
6767 }
|
|
6768 else {
|
|
6769 list.update_row(rid, [ name ]);
|
|
6770 }
|
|
6771 };
|
|
6772
|
|
6773 this.update_response_row = function(response, oldkey)
|
|
6774 {
|
|
6775 var list = this.responses_list;
|
|
6776
|
|
6777 if (list && oldkey) {
|
|
6778 list.update_row(oldkey, [ response.name ], response.key, true);
|
|
6779 }
|
|
6780 else if (list) {
|
|
6781 list.insert_row({ id:'rcmrow'+response.key, cols:[ { className:'name', innerHTML:response.name } ] });
|
|
6782 list.select(response.key);
|
|
6783 }
|
|
6784 };
|
|
6785
|
|
6786 this.remove_response = function(key)
|
|
6787 {
|
|
6788 var frame;
|
|
6789
|
|
6790 if (this.env.textresponses) {
|
|
6791 delete this.env.textresponses[key];
|
|
6792 }
|
|
6793
|
|
6794 if (this.responses_list) {
|
|
6795 this.responses_list.remove_row(key);
|
|
6796 if (frame = this.get_frame_window(this.env.contentframe)) {
|
|
6797 frame.location.href = this.env.blankpage;
|
|
6798 }
|
|
6799 }
|
|
6800
|
|
6801 this.enable_command('delete', false);
|
|
6802 };
|
|
6803
|
|
6804 this.remove_identity = function(id)
|
|
6805 {
|
|
6806 var frame, list = this.identity_list,
|
|
6807 rid = this.html_identifier(id);
|
|
6808
|
|
6809 if (list && id) {
|
|
6810 list.remove_row(rid);
|
|
6811 if (frame = this.get_frame_window(this.env.contentframe)) {
|
|
6812 frame.location.href = this.env.blankpage;
|
|
6813 }
|
|
6814 }
|
|
6815
|
|
6816 this.enable_command('delete', false);
|
|
6817 };
|
|
6818
|
|
6819
|
|
6820 /*********************************************************/
|
|
6821 /********* folder manager methods *********/
|
|
6822 /*********************************************************/
|
|
6823
|
|
6824 this.init_subscription_list = function()
|
|
6825 {
|
|
6826 var delim = RegExp.escape(this.env.delimiter);
|
|
6827
|
|
6828 this.last_sub_rx = RegExp('['+delim+']?[^'+delim+']+$');
|
|
6829
|
|
6830 this.subscription_list = new rcube_treelist_widget(this.gui_objects.subscriptionlist, {
|
|
6831 selectable: true,
|
|
6832 tabexit: false,
|
|
6833 parent_focus: true,
|
|
6834 id_prefix: 'rcmli',
|
|
6835 id_encode: this.html_identifier_encode,
|
|
6836 id_decode: this.html_identifier_decode,
|
|
6837 searchbox: '#foldersearch'
|
|
6838 });
|
|
6839
|
|
6840 this.subscription_list
|
|
6841 .addEventListener('select', function(node) { ref.subscription_select(node.id); })
|
|
6842 .addEventListener('collapse', function(node) { ref.folder_collapsed(node) })
|
|
6843 .addEventListener('expand', function(node) { ref.folder_collapsed(node) })
|
|
6844 .addEventListener('search', function(p) { if (p.query) ref.subscription_select(); })
|
|
6845 .draggable({cancel: 'li.mailbox.root,input,div.treetoggle'})
|
|
6846 .droppable({
|
|
6847 // @todo: find better way, accept callback is executed for every folder
|
|
6848 // on the list when dragging starts (and stops), this is slow, but
|
|
6849 // I didn't find a method to check droptarget on over event
|
|
6850 accept: function(node) {
|
|
6851 if (!$(node).is('.mailbox'))
|
|
6852 return false;
|
|
6853
|
|
6854 var source_folder = ref.folder_id2name($(node).attr('id')),
|
|
6855 dest_folder = ref.folder_id2name(this.id),
|
|
6856 source = ref.env.subscriptionrows[source_folder],
|
|
6857 dest = ref.env.subscriptionrows[dest_folder];
|
|
6858
|
|
6859 return source && !source[2]
|
|
6860 && dest_folder != source_folder.replace(ref.last_sub_rx, '')
|
|
6861 && !dest_folder.startsWith(source_folder + ref.env.delimiter);
|
|
6862 },
|
|
6863 drop: function(e, ui) {
|
|
6864 var source = ref.folder_id2name(ui.draggable.attr('id')),
|
|
6865 dest = ref.folder_id2name(this.id);
|
|
6866
|
|
6867 ref.subscription_move_folder(source, dest);
|
|
6868 }
|
|
6869 });
|
|
6870 };
|
|
6871
|
|
6872 this.folder_id2name = function(id)
|
|
6873 {
|
|
6874 return id ? ref.html_identifier_decode(id.replace(/^rcmli/, '')) : null;
|
|
6875 };
|
|
6876
|
|
6877 this.subscription_select = function(id)
|
|
6878 {
|
|
6879 var folder;
|
|
6880
|
|
6881 if (id && id != '*' && (folder = this.env.subscriptionrows[id])) {
|
|
6882 this.env.mailbox = id;
|
|
6883 this.show_folder(id);
|
|
6884 this.enable_command('delete-folder', !folder[2]);
|
|
6885 }
|
|
6886 else {
|
|
6887 this.env.mailbox = null;
|
|
6888 this.show_contentframe(false);
|
|
6889 this.enable_command('delete-folder', 'purge', false);
|
|
6890 }
|
|
6891 };
|
|
6892
|
|
6893 this.subscription_move_folder = function(from, to)
|
|
6894 {
|
|
6895 if (from && to !== null && from != to && to != from.replace(this.last_sub_rx, '')) {
|
|
6896 var path = from.split(this.env.delimiter),
|
|
6897 basename = path.pop(),
|
|
6898 newname = to === '' || to === '*' ? basename : to + this.env.delimiter + basename;
|
|
6899
|
|
6900 if (newname != from) {
|
|
6901 this.http_post('rename-folder', {_folder_oldname: from, _folder_newname: newname},
|
|
6902 this.set_busy(true, 'foldermoving'));
|
|
6903 }
|
|
6904 }
|
|
6905 };
|
|
6906
|
|
6907 // tell server to create and subscribe a new mailbox
|
|
6908 this.create_folder = function()
|
|
6909 {
|
|
6910 this.show_folder('', this.env.mailbox);
|
|
6911 };
|
|
6912
|
|
6913 // delete a specific mailbox with all its messages
|
|
6914 this.delete_folder = function(name)
|
|
6915 {
|
|
6916 if (!name)
|
|
6917 name = this.env.mailbox;
|
|
6918
|
|
6919 if (name && confirm(this.get_label('deletefolderconfirm'))) {
|
|
6920 this.http_post('delete-folder', {_mbox: name}, this.set_busy(true, 'folderdeleting'));
|
|
6921 }
|
|
6922 };
|
|
6923
|
|
6924 // Add folder row to the table and initialize it
|
|
6925 this.add_folder_row = function (id, name, display_name, is_protected, subscribed, class_name, refrow, subfolders)
|
|
6926 {
|
|
6927 if (!this.gui_objects.subscriptionlist)
|
|
6928 return false;
|
|
6929
|
|
6930 // reset searching
|
|
6931 if (this.subscription_list.is_search()) {
|
|
6932 this.subscription_select();
|
|
6933 this.subscription_list.reset_search();
|
|
6934 }
|
|
6935
|
|
6936 // disable drag-n-drop temporarily
|
|
6937 this.subscription_list.draggable('destroy').droppable('destroy');
|
|
6938
|
|
6939 var row, n, tmp, tmp_name, rowid, collator, pos, p, parent = '',
|
|
6940 folders = [], list = [], slist = [],
|
|
6941 list_element = $(this.gui_objects.subscriptionlist);
|
|
6942 row = refrow ? refrow : $($('li', list_element).get(1)).clone(true);
|
|
6943
|
|
6944 if (!row.length) {
|
|
6945 // Refresh page if we don't have a table row to clone
|
|
6946 this.goto_url('folders');
|
|
6947 return false;
|
|
6948 }
|
|
6949
|
|
6950 // set ID, reset css class
|
|
6951 row.attr({id: 'rcmli' + this.html_identifier_encode(id), 'class': class_name});
|
|
6952
|
|
6953 if (!refrow || !refrow.length) {
|
|
6954 // remove old data, subfolders and toggle
|
|
6955 $('ul,div.treetoggle', row).remove();
|
|
6956 row.removeData('filtered');
|
|
6957 }
|
|
6958
|
|
6959 // set folder name
|
|
6960 $('a:first', row).text(display_name);
|
|
6961
|
|
6962 // update subscription checkbox
|
|
6963 $('input[name="_subscribed[]"]:first', row).val(id)
|
|
6964 .prop({checked: subscribed ? true : false, disabled: is_protected ? true : false});
|
|
6965
|
|
6966 // add to folder/row-ID map
|
|
6967 this.env.subscriptionrows[id] = [name, display_name, false];
|
|
6968
|
|
6969 // copy folders data to an array for sorting
|
|
6970 $.each(this.env.subscriptionrows, function(k, v) { v[3] = k; folders.push(v); });
|
|
6971
|
|
6972 try {
|
|
6973 // use collator if supported (FF29, IE11, Opera15, Chrome24)
|
|
6974 collator = new Intl.Collator(this.env.locale.replace('_', '-'));
|
|
6975 }
|
|
6976 catch (e) {};
|
|
6977
|
|
6978 // sort folders
|
|
6979 folders.sort(function(a, b) {
|
|
6980 var i, f1, f2,
|
|
6981 path1 = a[0].split(ref.env.delimiter),
|
|
6982 path2 = b[0].split(ref.env.delimiter),
|
|
6983 len = path1.length;
|
|
6984
|
|
6985 for (i=0; i<len; i++) {
|
|
6986 f1 = path1[i];
|
|
6987 f2 = path2[i];
|
|
6988
|
|
6989 if (f1 !== f2) {
|
|
6990 if (f2 === undefined)
|
|
6991 return 1;
|
|
6992 if (collator)
|
|
6993 return collator.compare(f1, f2);
|
|
6994 else
|
|
6995 return f1 < f2 ? -1 : 1;
|
|
6996 }
|
|
6997 else if (i == len-1) {
|
|
6998 return -1
|
|
6999 }
|
|
7000 }
|
|
7001 });
|
|
7002
|
|
7003 for (n in folders) {
|
|
7004 p = folders[n][3];
|
|
7005 // protected folder
|
|
7006 if (folders[n][2]) {
|
|
7007 tmp_name = p + this.env.delimiter;
|
|
7008 // prefix namespace cannot have subfolders (#1488349)
|
|
7009 if (tmp_name == this.env.prefix_ns)
|
|
7010 continue;
|
|
7011 slist.push(p);
|
|
7012 tmp = tmp_name;
|
|
7013 }
|
|
7014 // protected folder's child
|
|
7015 else if (tmp && p.startsWith(tmp))
|
|
7016 slist.push(p);
|
|
7017 // other
|
|
7018 else {
|
|
7019 list.push(p);
|
|
7020 tmp = null;
|
|
7021 }
|
|
7022 }
|
|
7023
|
|
7024 // check if subfolder of a protected folder
|
|
7025 for (n=0; n<slist.length; n++) {
|
|
7026 if (id.startsWith(slist[n] + this.env.delimiter))
|
|
7027 rowid = slist[n];
|
|
7028 }
|
|
7029
|
|
7030 // find folder position after sorting
|
|
7031 for (n=0; !rowid && n<list.length; n++) {
|
|
7032 if (n && list[n] == id)
|
|
7033 rowid = list[n-1];
|
|
7034 }
|
|
7035
|
|
7036 // add row to the table
|
|
7037 if (rowid && (n = this.subscription_list.get_item(rowid, true))) {
|
|
7038 // find parent folder
|
|
7039 if (pos = id.lastIndexOf(this.env.delimiter)) {
|
|
7040 parent = id.substring(0, pos);
|
|
7041 parent = this.subscription_list.get_item(parent, true);
|
|
7042
|
|
7043 // add required tree elements to the parent if not already there
|
|
7044 if (!$('div.treetoggle', parent).length) {
|
|
7045 $('<div> </div>').addClass('treetoggle collapsed').appendTo(parent);
|
|
7046 }
|
|
7047 if (!$('ul', parent).length) {
|
|
7048 $('<ul>').css('display', 'none').appendTo(parent);
|
|
7049 }
|
|
7050 }
|
|
7051
|
|
7052 if (parent && n == parent) {
|
|
7053 $('ul:first', parent).append(row);
|
|
7054 }
|
|
7055 else {
|
|
7056 while (p = $(n).parent().parent().get(0)) {
|
|
7057 if (parent && p == parent)
|
|
7058 break;
|
|
7059 if (!$(p).is('li.mailbox'))
|
|
7060 break;
|
|
7061 n = p;
|
|
7062 }
|
|
7063
|
|
7064 $(n).after(row);
|
|
7065 }
|
|
7066 }
|
|
7067 else {
|
|
7068 list_element.append(row);
|
|
7069 }
|
|
7070
|
|
7071 // add subfolders
|
|
7072 $.extend(this.env.subscriptionrows, subfolders || {});
|
|
7073
|
|
7074 // update list widget
|
|
7075 this.subscription_list.reset(true);
|
|
7076 this.subscription_select();
|
|
7077
|
|
7078 // expand parent
|
|
7079 if (parent) {
|
|
7080 this.subscription_list.expand(this.folder_id2name(parent.id));
|
|
7081 }
|
|
7082
|
|
7083 row = row.show().get(0);
|
|
7084 if (row.scrollIntoView)
|
|
7085 row.scrollIntoView();
|
|
7086
|
|
7087 return row;
|
|
7088 };
|
|
7089
|
|
7090 // replace an existing table row with a new folder line (with subfolders)
|
|
7091 this.replace_folder_row = function(oldid, id, name, display_name, is_protected, class_name)
|
|
7092 {
|
|
7093 if (!this.gui_objects.subscriptionlist) {
|
|
7094 if (this.is_framed()) {
|
|
7095 // @FIXME: for some reason this 'parent' variable need to be prefixed with 'window.'
|
|
7096 return window.parent.rcmail.replace_folder_row(oldid, id, name, display_name, is_protected, class_name);
|
|
7097 }
|
|
7098
|
|
7099 return false;
|
|
7100 }
|
|
7101
|
|
7102 // reset searching
|
|
7103 if (this.subscription_list.is_search()) {
|
|
7104 this.subscription_select();
|
|
7105 this.subscription_list.reset_search();
|
|
7106 }
|
|
7107
|
|
7108 var subfolders = {},
|
|
7109 row = this.subscription_list.get_item(oldid, true),
|
|
7110 parent = $(row).parent(),
|
|
7111 old_folder = this.env.subscriptionrows[oldid],
|
|
7112 prefix_len_id = oldid.length,
|
|
7113 prefix_len_name = old_folder[0].length,
|
|
7114 subscribed = $('input[name="_subscribed[]"]:first', row).prop('checked');
|
|
7115
|
|
7116 // no renaming, only update class_name
|
|
7117 if (oldid == id) {
|
|
7118 $(row).attr('class', class_name || '');
|
|
7119 return;
|
|
7120 }
|
|
7121
|
|
7122 // update subfolders
|
|
7123 $('li', row).each(function() {
|
|
7124 var fname = ref.folder_id2name(this.id),
|
|
7125 folder = ref.env.subscriptionrows[fname],
|
|
7126 newid = id + fname.slice(prefix_len_id);
|
|
7127
|
|
7128 this.id = 'rcmli' + ref.html_identifier_encode(newid);
|
|
7129 $('input[name="_subscribed[]"]:first', this).val(newid);
|
|
7130 folder[0] = name + folder[0].slice(prefix_len_name);
|
|
7131
|
|
7132 subfolders[newid] = folder;
|
|
7133 delete ref.env.subscriptionrows[fname];
|
|
7134 });
|
|
7135
|
|
7136 // get row off the list
|
|
7137 row = $(row).detach();
|
|
7138
|
|
7139 delete this.env.subscriptionrows[oldid];
|
|
7140
|
|
7141 // remove parent list/toggle elements if not needed
|
|
7142 if (parent.get(0) != this.gui_objects.subscriptionlist && !$('li', parent).length) {
|
|
7143 $('ul,div.treetoggle', parent.parent()).remove();
|
|
7144 }
|
|
7145
|
|
7146 // move the existing table row
|
|
7147 this.add_folder_row(id, name, display_name, is_protected, subscribed, class_name, row, subfolders);
|
|
7148 };
|
|
7149
|
|
7150 // remove the table row of a specific mailbox from the table
|
|
7151 this.remove_folder_row = function(folder)
|
|
7152 {
|
|
7153 // reset searching
|
|
7154 if (this.subscription_list.is_search()) {
|
|
7155 this.subscription_select();
|
|
7156 this.subscription_list.reset_search();
|
|
7157 }
|
|
7158
|
|
7159 var list = [], row = this.subscription_list.get_item(folder, true);
|
|
7160
|
|
7161 // get subfolders if any
|
|
7162 $('li', row).each(function() { list.push(ref.folder_id2name(this.id)); });
|
|
7163
|
|
7164 // remove folder row (and subfolders)
|
|
7165 this.subscription_list.remove(folder);
|
|
7166
|
|
7167 // update local list variable
|
|
7168 list.push(folder);
|
|
7169 $.each(list, function(i, v) { delete ref.env.subscriptionrows[v]; });
|
|
7170 };
|
|
7171
|
|
7172 this.subscribe = function(folder)
|
|
7173 {
|
|
7174 if (folder) {
|
|
7175 var lock = this.display_message(this.get_label('foldersubscribing'), 'loading');
|
|
7176 this.http_post('subscribe', {_mbox: folder}, lock);
|
|
7177 }
|
|
7178 };
|
|
7179
|
|
7180 this.unsubscribe = function(folder)
|
|
7181 {
|
|
7182 if (folder) {
|
|
7183 var lock = this.display_message(this.get_label('folderunsubscribing'), 'loading');
|
|
7184 this.http_post('unsubscribe', {_mbox: folder}, lock);
|
|
7185 }
|
|
7186 };
|
|
7187
|
|
7188 // when user select a folder in manager
|
|
7189 this.show_folder = function(folder, path, force)
|
|
7190 {
|
|
7191 var win, target = window,
|
|
7192 url = '&_action=edit-folder&_mbox='+urlencode(folder);
|
|
7193
|
|
7194 if (path)
|
|
7195 url += '&_path='+urlencode(path);
|
|
7196
|
|
7197 if (win = this.get_frame_window(this.env.contentframe)) {
|
|
7198 target = win;
|
|
7199 url += '&_framed=1';
|
|
7200 }
|
|
7201
|
|
7202 if (String(target.location.href).indexOf(url) >= 0 && !force)
|
|
7203 this.show_contentframe(true);
|
|
7204 else
|
|
7205 this.location_href(this.env.comm_path+url, target, true);
|
|
7206 };
|
|
7207
|
|
7208 // disables subscription checkbox (for protected folder)
|
|
7209 this.disable_subscription = function(folder)
|
|
7210 {
|
|
7211 var row = this.subscription_list.get_item(folder, true);
|
|
7212 if (row)
|
|
7213 $('input[name="_subscribed[]"]:first', row).prop('disabled', true);
|
|
7214 };
|
|
7215
|
|
7216 // resets state of subscription checkbox (e.g. on error)
|
|
7217 this.reset_subscription = function(folder, state)
|
|
7218 {
|
|
7219 var row = this.subscription_list.get_item(folder, true);
|
|
7220 if (row)
|
|
7221 $('input[name="_subscribed[]"]:first', row).prop('checked', state);
|
|
7222 };
|
|
7223
|
|
7224 this.folder_size = function(folder)
|
|
7225 {
|
|
7226 var lock = this.set_busy(true, 'loading');
|
|
7227 this.http_post('folder-size', {_mbox: folder}, lock);
|
|
7228 };
|
|
7229
|
|
7230 this.folder_size_update = function(size)
|
|
7231 {
|
|
7232 $('#folder-size').replaceWith(size);
|
|
7233 };
|
|
7234
|
|
7235 // filter folders by namespace
|
|
7236 this.folder_filter = function(prefix)
|
|
7237 {
|
|
7238 this.subscription_list.reset_search();
|
|
7239
|
|
7240 this.subscription_list.container.children('li').each(function() {
|
|
7241 var i, folder = ref.folder_id2name(this.id);
|
|
7242 // show all folders
|
|
7243 if (prefix == '---') {
|
|
7244 }
|
|
7245 // got namespace prefix
|
|
7246 else if (prefix) {
|
|
7247 if (folder !== prefix) {
|
|
7248 $(this).data('filtered', true).hide();
|
|
7249 return
|
|
7250 }
|
|
7251 }
|
|
7252 // no namespace prefix, filter out all other namespaces
|
|
7253 else {
|
|
7254 // first get all namespace roots
|
|
7255 for (i in ref.env.ns_roots) {
|
|
7256 if (folder === ref.env.ns_roots[i]) {
|
|
7257 $(this).data('filtered', true).hide();
|
|
7258 return;
|
|
7259 }
|
|
7260 }
|
|
7261 }
|
|
7262
|
|
7263 $(this).removeData('filtered').show();
|
|
7264 });
|
|
7265 };
|
|
7266
|
|
7267 /*********************************************************/
|
|
7268 /********* GUI functionality *********/
|
|
7269 /*********************************************************/
|
|
7270
|
|
7271 var init_button = function(cmd, prop)
|
|
7272 {
|
|
7273 var elm = document.getElementById(prop.id);
|
|
7274 if (!elm)
|
|
7275 return;
|
|
7276
|
|
7277 var preload = false;
|
|
7278 if (prop.type == 'image') {
|
|
7279 elm = elm.parentNode;
|
|
7280 preload = true;
|
|
7281 }
|
|
7282
|
|
7283 elm._command = cmd;
|
|
7284 elm._id = prop.id;
|
|
7285 if (prop.sel) {
|
|
7286 elm.onmousedown = function(e) { return ref.button_sel(this._command, this._id); };
|
|
7287 elm.onmouseup = function(e) { return ref.button_out(this._command, this._id); };
|
|
7288 if (preload)
|
|
7289 new Image().src = prop.sel;
|
|
7290 }
|
|
7291 if (prop.over) {
|
|
7292 elm.onmouseover = function(e) { return ref.button_over(this._command, this._id); };
|
|
7293 elm.onmouseout = function(e) { return ref.button_out(this._command, this._id); };
|
|
7294 if (preload)
|
|
7295 new Image().src = prop.over;
|
|
7296 }
|
|
7297 };
|
|
7298
|
|
7299 // set event handlers on registered buttons
|
|
7300 this.init_buttons = function()
|
|
7301 {
|
|
7302 for (var cmd in this.buttons) {
|
|
7303 if (typeof cmd !== 'string')
|
|
7304 continue;
|
|
7305
|
|
7306 for (var i=0; i<this.buttons[cmd].length; i++) {
|
|
7307 init_button(cmd, this.buttons[cmd][i]);
|
|
7308 }
|
|
7309 }
|
|
7310 };
|
|
7311
|
|
7312 // set button to a specific state
|
|
7313 this.set_button = function(command, state)
|
|
7314 {
|
|
7315 var n, button, obj, $obj, a_buttons = this.buttons[command],
|
|
7316 len = a_buttons ? a_buttons.length : 0;
|
|
7317
|
|
7318 for (n=0; n<len; n++) {
|
|
7319 button = a_buttons[n];
|
|
7320 obj = document.getElementById(button.id);
|
|
7321
|
|
7322 if (!obj || button.status === state)
|
|
7323 continue;
|
|
7324
|
|
7325 // get default/passive setting of the button
|
|
7326 if (button.type == 'image' && !button.status) {
|
|
7327 button.pas = obj._original_src ? obj._original_src : obj.src;
|
|
7328 // respect PNG fix on IE browsers
|
|
7329 if (obj.runtimeStyle && obj.runtimeStyle.filter && obj.runtimeStyle.filter.match(/src=['"]([^'"]+)['"]/))
|
|
7330 button.pas = RegExp.$1;
|
|
7331 }
|
|
7332 else if (!button.status)
|
|
7333 button.pas = String(obj.className);
|
|
7334
|
|
7335 button.status = state;
|
|
7336
|
|
7337 // set image according to button state
|
|
7338 if (button.type == 'image' && button[state]) {
|
|
7339 obj.src = button[state];
|
|
7340 }
|
|
7341 // set class name according to button state
|
|
7342 else if (button[state] !== undefined) {
|
|
7343 obj.className = button[state];
|
|
7344 }
|
|
7345 // disable/enable input buttons
|
|
7346 if (button.type == 'input') {
|
|
7347 obj.disabled = state == 'pas';
|
|
7348 }
|
|
7349 else if (button.type == 'uibutton') {
|
|
7350 button.status = state;
|
|
7351 $(obj).button('option', 'disabled', state == 'pas');
|
|
7352 }
|
|
7353 else {
|
|
7354 $obj = $(obj);
|
|
7355 $obj
|
|
7356 .attr('tabindex', state == 'pas' || state == 'sel' ? '-1' : ($obj.attr('data-tabindex') || '0'))
|
|
7357 .attr('aria-disabled', state == 'pas' || state == 'sel' ? 'true' : 'false');
|
|
7358 }
|
|
7359 }
|
|
7360 };
|
|
7361
|
|
7362 // display a specific alttext
|
|
7363 this.set_alttext = function(command, label)
|
|
7364 {
|
|
7365 var n, button, obj, link, a_buttons = this.buttons[command],
|
|
7366 len = a_buttons ? a_buttons.length : 0;
|
|
7367
|
|
7368 for (n=0; n<len; n++) {
|
|
7369 button = a_buttons[n];
|
|
7370 obj = document.getElementById(button.id);
|
|
7371
|
|
7372 if (button.type == 'image' && obj) {
|
|
7373 obj.setAttribute('alt', this.get_label(label));
|
|
7374 if ((link = obj.parentNode) && link.tagName.toLowerCase() == 'a')
|
|
7375 link.setAttribute('title', this.get_label(label));
|
|
7376 }
|
|
7377 else if (obj)
|
|
7378 obj.setAttribute('title', this.get_label(label));
|
|
7379 }
|
|
7380 };
|
|
7381
|
|
7382 // mouse over button
|
|
7383 this.button_over = function(command, id)
|
|
7384 {
|
|
7385 this.button_event(command, id, 'over');
|
|
7386 };
|
|
7387
|
|
7388 // mouse down on button
|
|
7389 this.button_sel = function(command, id)
|
|
7390 {
|
|
7391 this.button_event(command, id, 'sel');
|
|
7392 };
|
|
7393
|
|
7394 // mouse out of button
|
|
7395 this.button_out = function(command, id)
|
|
7396 {
|
|
7397 this.button_event(command, id, 'act');
|
|
7398 };
|
|
7399
|
|
7400 // event of button
|
|
7401 this.button_event = function(command, id, event)
|
|
7402 {
|
|
7403 var n, button, obj, a_buttons = this.buttons[command],
|
|
7404 len = a_buttons ? a_buttons.length : 0;
|
|
7405
|
|
7406 for (n=0; n<len; n++) {
|
|
7407 button = a_buttons[n];
|
|
7408 if (button.id == id && button.status == 'act') {
|
|
7409 if (button[event] && (obj = document.getElementById(button.id))) {
|
|
7410 obj[button.type == 'image' ? 'src' : 'className'] = button[event];
|
|
7411 }
|
|
7412
|
|
7413 if (event == 'sel') {
|
|
7414 this.buttons_sel[id] = command;
|
|
7415 }
|
|
7416 }
|
|
7417 }
|
|
7418 };
|
|
7419
|
|
7420 // write to the document/window title
|
|
7421 this.set_pagetitle = function(title)
|
|
7422 {
|
|
7423 if (title && document.title)
|
|
7424 document.title = title;
|
|
7425 };
|
|
7426
|
|
7427 // display a system message, list of types in common.css (below #message definition)
|
|
7428 this.display_message = function(msg, type, timeout, key)
|
|
7429 {
|
|
7430 // pass command to parent window
|
|
7431 if (this.is_framed())
|
|
7432 return parent.rcmail.display_message(msg, type, timeout);
|
|
7433
|
|
7434 if (!this.gui_objects.message) {
|
|
7435 // save message in order to display after page loaded
|
|
7436 if (type != 'loading')
|
|
7437 this.pending_message = [msg, type, timeout, key];
|
|
7438 return 1;
|
|
7439 }
|
|
7440
|
|
7441 if (!type)
|
|
7442 type = 'notice';
|
|
7443
|
|
7444 if (!key)
|
|
7445 key = this.html_identifier(msg);
|
|
7446
|
|
7447 var date = new Date(),
|
|
7448 id = type + date.getTime();
|
|
7449
|
|
7450 if (!timeout) {
|
|
7451 switch (type) {
|
|
7452 case 'error':
|
|
7453 case 'warning':
|
|
7454 timeout = this.message_time * 2;
|
|
7455 break;
|
|
7456
|
|
7457 case 'uploading':
|
|
7458 timeout = 0;
|
|
7459 break;
|
|
7460
|
|
7461 default:
|
|
7462 timeout = this.message_time;
|
|
7463 }
|
|
7464 }
|
|
7465
|
|
7466 if (type == 'loading') {
|
|
7467 key = 'loading';
|
|
7468 timeout = this.env.request_timeout * 1000;
|
|
7469 if (!msg)
|
|
7470 msg = this.get_label('loading');
|
|
7471 }
|
|
7472
|
|
7473 // The same message is already displayed
|
|
7474 if (this.messages[key]) {
|
|
7475 // replace label
|
|
7476 if (this.messages[key].obj)
|
|
7477 this.messages[key].obj.html(msg);
|
|
7478 // store label in stack
|
|
7479 if (type == 'loading') {
|
|
7480 this.messages[key].labels.push({'id': id, 'msg': msg});
|
|
7481 }
|
|
7482 // add element and set timeout
|
|
7483 this.messages[key].elements.push(id);
|
|
7484 setTimeout(function() { ref.hide_message(id, type == 'loading'); }, timeout);
|
|
7485 return id;
|
|
7486 }
|
|
7487
|
|
7488 // create DOM object and display it
|
|
7489 var obj = $('<div>').addClass(type).html(msg).data('key', key),
|
|
7490 cont = $(this.gui_objects.message).append(obj).show();
|
|
7491
|
|
7492 this.messages[key] = {'obj': obj, 'elements': [id]};
|
|
7493
|
|
7494 if (type == 'loading') {
|
|
7495 this.messages[key].labels = [{'id': id, 'msg': msg}];
|
|
7496 }
|
|
7497 else if (type != 'uploading') {
|
|
7498 obj.click(function() { return ref.hide_message(obj); })
|
|
7499 .attr('role', 'alert');
|
|
7500 }
|
|
7501
|
|
7502 this.triggerEvent('message', { message:msg, type:type, timeout:timeout, object:obj });
|
|
7503
|
|
7504 if (timeout > 0)
|
|
7505 setTimeout(function() { ref.hide_message(id, type != 'loading'); }, timeout);
|
|
7506
|
|
7507 return id;
|
|
7508 };
|
|
7509
|
|
7510 // make a message to disapear
|
|
7511 this.hide_message = function(obj, fade)
|
|
7512 {
|
|
7513 // pass command to parent window
|
|
7514 if (this.is_framed())
|
|
7515 return parent.rcmail.hide_message(obj, fade);
|
|
7516
|
|
7517 if (!this.gui_objects.message)
|
|
7518 return;
|
|
7519
|
|
7520 var k, n, i, o, m = this.messages;
|
|
7521
|
|
7522 // Hide message by object, don't use for 'loading'!
|
|
7523 if (typeof obj === 'object') {
|
|
7524 o = $(obj);
|
|
7525 k = o.data('key');
|
|
7526 this.hide_message_object(o, fade);
|
|
7527 if (m[k])
|
|
7528 delete m[k];
|
|
7529 }
|
|
7530 // Hide message by id
|
|
7531 else {
|
|
7532 for (k in m) {
|
|
7533 for (n in m[k].elements) {
|
|
7534 if (m[k] && m[k].elements[n] == obj) {
|
|
7535 m[k].elements.splice(n, 1);
|
|
7536 // hide DOM element if last instance is removed
|
|
7537 if (!m[k].elements.length) {
|
|
7538 this.hide_message_object(m[k].obj, fade);
|
|
7539 delete m[k];
|
|
7540 }
|
|
7541 // set pending action label for 'loading' message
|
|
7542 else if (k == 'loading') {
|
|
7543 for (i in m[k].labels) {
|
|
7544 if (m[k].labels[i].id == obj) {
|
|
7545 delete m[k].labels[i];
|
|
7546 }
|
|
7547 else {
|
|
7548 o = m[k].labels[i].msg;
|
|
7549 m[k].obj.html(o);
|
|
7550 }
|
|
7551 }
|
|
7552 }
|
|
7553 }
|
|
7554 }
|
|
7555 }
|
|
7556 }
|
|
7557 };
|
|
7558
|
|
7559 // hide message object and remove from the DOM
|
|
7560 this.hide_message_object = function(o, fade)
|
|
7561 {
|
|
7562 if (fade)
|
|
7563 o.fadeOut(600, function() {$(this).remove(); });
|
|
7564 else
|
|
7565 o.hide().remove();
|
|
7566 };
|
|
7567
|
|
7568 // remove all messages immediately
|
|
7569 this.clear_messages = function()
|
|
7570 {
|
|
7571 // pass command to parent window
|
|
7572 if (this.is_framed())
|
|
7573 return parent.rcmail.clear_messages();
|
|
7574
|
|
7575 var k, n, m = this.messages;
|
|
7576
|
|
7577 for (k in m)
|
|
7578 for (n in m[k].elements)
|
|
7579 if (m[k].obj)
|
|
7580 this.hide_message_object(m[k].obj);
|
|
7581
|
|
7582 this.messages = {};
|
|
7583 };
|
|
7584
|
|
7585 // display uploading message with progress indicator
|
|
7586 // data should contain: name, total, current, percent, text
|
|
7587 this.display_progress = function(data)
|
|
7588 {
|
|
7589 if (!data || !data.name)
|
|
7590 return;
|
|
7591
|
|
7592 var msg = this.messages['progress' + data.name];
|
|
7593
|
|
7594 if (!data.label)
|
|
7595 data.label = this.get_label('uploadingmany');
|
|
7596
|
|
7597 if (!msg) {
|
|
7598 if (!data.percent || data.percent < 100)
|
|
7599 this.display_message(data.label, 'uploading', 0, 'progress' + data.name);
|
|
7600 return;
|
|
7601 }
|
|
7602
|
|
7603 if (!data.total || data.percent >= 100) {
|
|
7604 this.hide_message(msg.obj);
|
|
7605 return;
|
|
7606 }
|
|
7607
|
|
7608 if (data.text)
|
|
7609 data.label += ' ' + data.text;
|
|
7610
|
|
7611 msg.obj.text(data.label);
|
|
7612 };
|
|
7613
|
|
7614 // open a jquery UI dialog with the given content
|
|
7615 this.show_popup_dialog = function(content, title, buttons, options)
|
|
7616 {
|
|
7617 // forward call to parent window
|
|
7618 if (this.is_framed()) {
|
|
7619 return parent.rcmail.show_popup_dialog(content, title, buttons, options);
|
|
7620 }
|
|
7621
|
|
7622 var popup = $('<div class="popup">');
|
|
7623
|
|
7624 if (typeof content == 'object')
|
|
7625 popup.append(content);
|
|
7626 else
|
|
7627 popup.html(content);
|
|
7628
|
|
7629 options = $.extend({
|
|
7630 title: title,
|
|
7631 buttons: buttons,
|
|
7632 modal: true,
|
|
7633 resizable: true,
|
|
7634 width: 500,
|
|
7635 close: function(event, ui) { $(this).remove(); }
|
|
7636 }, options || {});
|
|
7637
|
|
7638 popup.dialog(options);
|
|
7639
|
|
7640 // resize and center popup
|
|
7641 var win = $(window), w = win.width(), h = win.height(),
|
|
7642 width = popup.width(), height = popup.height();
|
|
7643
|
|
7644 popup.dialog('option', {
|
|
7645 height: Math.min(h - 40, height + 75 + (buttons ? 50 : 0)),
|
|
7646 width: Math.min(w - 20, width + 36)
|
|
7647 });
|
|
7648
|
|
7649 // assign special classes to dialog buttons
|
|
7650 $.each(options.button_classes || [], function(i, v) {
|
|
7651 if (v) $($('.ui-dialog-buttonpane button.ui-button', popup.parent()).get(i)).addClass(v);
|
|
7652 });
|
|
7653
|
|
7654 return popup;
|
|
7655 };
|
|
7656
|
|
7657 // show_popup_dialog() wrapper for simple dialogs with Save and Cancel buttons
|
|
7658 this.simple_dialog = function(content, title, button_func, options)
|
|
7659 {
|
|
7660 var title = this.get_label(title),
|
|
7661 buttons = [{
|
|
7662 text: this.get_label((options || {}).button || 'save'),
|
|
7663 'class': 'mainaction',
|
|
7664 click: function() {
|
|
7665 if (button_func())
|
|
7666 $(this).dialog('close');
|
|
7667 }
|
|
7668 },
|
|
7669 {
|
|
7670 text: ref.get_label('cancel'),
|
|
7671 click: function() {
|
|
7672 $(this).dialog('close');
|
|
7673 }
|
|
7674 }];
|
|
7675
|
|
7676 return this.show_popup_dialog(content, title, buttons, options);
|
|
7677 };
|
|
7678
|
|
7679 // enable/disable buttons for page shifting
|
|
7680 this.set_page_buttons = function()
|
|
7681 {
|
|
7682 this.enable_command('nextpage', 'lastpage', this.env.pagecount > this.env.current_page);
|
|
7683 this.enable_command('previouspage', 'firstpage', this.env.current_page > 1);
|
|
7684
|
|
7685 this.update_pagejumper();
|
|
7686 };
|
|
7687
|
|
7688 // mark a mailbox as selected and set environment variable
|
|
7689 this.select_folder = function(name, prefix, encode)
|
|
7690 {
|
|
7691 if (this.savedsearchlist) {
|
|
7692 this.savedsearchlist.select('');
|
|
7693 }
|
|
7694
|
|
7695 if (this.treelist) {
|
|
7696 this.treelist.select(name);
|
|
7697 }
|
|
7698 else if (this.gui_objects.folderlist) {
|
|
7699 $('li.selected', this.gui_objects.folderlist).removeClass('selected');
|
|
7700 $(this.get_folder_li(name, prefix, encode)).addClass('selected');
|
|
7701
|
|
7702 // trigger event hook
|
|
7703 this.triggerEvent('selectfolder', { folder:name, prefix:prefix });
|
|
7704 }
|
|
7705 };
|
|
7706
|
|
7707 // adds a class to selected folder
|
|
7708 this.mark_folder = function(name, class_name, prefix, encode)
|
|
7709 {
|
|
7710 $(this.get_folder_li(name, prefix, encode)).addClass(class_name);
|
|
7711 this.triggerEvent('markfolder', {folder: name, mark: class_name, status: true});
|
|
7712 };
|
|
7713
|
|
7714 // adds a class to selected folder
|
|
7715 this.unmark_folder = function(name, class_name, prefix, encode)
|
|
7716 {
|
|
7717 $(this.get_folder_li(name, prefix, encode)).removeClass(class_name);
|
|
7718 this.triggerEvent('markfolder', {folder: name, mark: class_name, status: false});
|
|
7719 };
|
|
7720
|
|
7721 // helper method to find a folder list item
|
|
7722 this.get_folder_li = function(name, prefix, encode)
|
|
7723 {
|
|
7724 if (!prefix)
|
|
7725 prefix = 'rcmli';
|
|
7726
|
|
7727 if (this.gui_objects.folderlist) {
|
|
7728 name = this.html_identifier(name, encode);
|
|
7729 return document.getElementById(prefix+name);
|
|
7730 }
|
|
7731 };
|
|
7732
|
|
7733 // for reordering column array (Konqueror workaround)
|
|
7734 // and for setting some message list global variables
|
|
7735 this.set_message_coltypes = function(listcols, repl, smart_col)
|
|
7736 {
|
|
7737 var list = this.message_list,
|
|
7738 thead = list ? list.thead : null,
|
|
7739 repl, cell, col, n, len, tr;
|
|
7740
|
|
7741 this.env.listcols = listcols;
|
|
7742
|
|
7743 if (!this.env.coltypes)
|
|
7744 this.env.coltypes = {};
|
|
7745
|
|
7746 // replace old column headers
|
|
7747 if (thead) {
|
|
7748 if (repl) {
|
|
7749 thead.innerHTML = '';
|
|
7750 tr = document.createElement('tr');
|
|
7751
|
|
7752 for (c=0, len=repl.length; c < len; c++) {
|
|
7753 cell = document.createElement('th');
|
|
7754 cell.innerHTML = repl[c].html || '';
|
|
7755 if (repl[c].id) cell.id = repl[c].id;
|
|
7756 if (repl[c].className) cell.className = repl[c].className;
|
|
7757 tr.appendChild(cell);
|
|
7758 }
|
|
7759 thead.appendChild(tr);
|
|
7760 }
|
|
7761
|
|
7762 for (n=0, len=this.env.listcols.length; n<len; n++) {
|
|
7763 col = this.env.listcols[n];
|
|
7764 if ((cell = thead.rows[0].cells[n]) && (col == 'from' || col == 'to' || col == 'fromto')) {
|
|
7765 $(cell).attr('rel', col).find('span,a').text(this.get_label(col == 'fromto' ? smart_col : col));
|
|
7766 }
|
|
7767 }
|
|
7768 }
|
|
7769
|
|
7770 this.env.subject_col = null;
|
|
7771 this.env.flagged_col = null;
|
|
7772 this.env.status_col = null;
|
|
7773
|
|
7774 if (this.env.coltypes.folder)
|
|
7775 this.env.coltypes.folder.hidden = !(this.env.search_request || this.env.search_id) || this.env.search_scope == 'base';
|
|
7776
|
|
7777 if ((n = $.inArray('subject', this.env.listcols)) >= 0) {
|
|
7778 this.env.subject_col = n;
|
|
7779 if (list)
|
|
7780 list.subject_col = n;
|
|
7781 }
|
|
7782 if ((n = $.inArray('flag', this.env.listcols)) >= 0)
|
|
7783 this.env.flagged_col = n;
|
|
7784 if ((n = $.inArray('status', this.env.listcols)) >= 0)
|
|
7785 this.env.status_col = n;
|
|
7786
|
|
7787 if (list) {
|
|
7788 list.hide_column('folder', (this.env.coltypes.folder && this.env.coltypes.folder.hidden) || $.inArray('folder', this.env.listcols) < 0);
|
|
7789 list.init_header();
|
|
7790 }
|
|
7791 };
|
|
7792
|
|
7793 // replace content of row count display
|
|
7794 this.set_rowcount = function(text, mbox)
|
|
7795 {
|
|
7796 // #1487752
|
|
7797 if (mbox && mbox != this.env.mailbox)
|
|
7798 return false;
|
|
7799
|
|
7800 $(this.gui_objects.countdisplay).html(text);
|
|
7801
|
|
7802 // update page navigation buttons
|
|
7803 this.set_page_buttons();
|
|
7804 };
|
|
7805
|
|
7806 // replace content of mailboxname display
|
|
7807 this.set_mailboxname = function(content)
|
|
7808 {
|
|
7809 if (this.gui_objects.mailboxname && content)
|
|
7810 this.gui_objects.mailboxname.innerHTML = content;
|
|
7811 };
|
|
7812
|
|
7813 // replace content of quota display
|
|
7814 this.set_quota = function(content)
|
|
7815 {
|
|
7816 if (this.gui_objects.quotadisplay && content && content.type == 'text')
|
|
7817 $(this.gui_objects.quotadisplay).text((content.percent||0) + '%').attr('title', content.title);
|
|
7818
|
|
7819 this.triggerEvent('setquota', content);
|
|
7820 this.env.quota_content = content;
|
|
7821 };
|
|
7822
|
|
7823 // update trash folder state
|
|
7824 this.set_trash_count = function(count)
|
|
7825 {
|
|
7826 this[(count ? 'un' : '') + 'mark_folder'](this.env.trash_mailbox, 'empty', '', true);
|
|
7827 };
|
|
7828
|
|
7829 // update the mailboxlist
|
|
7830 this.set_unread_count = function(mbox, count, set_title, mark)
|
|
7831 {
|
|
7832 if (!this.gui_objects.mailboxlist)
|
|
7833 return false;
|
|
7834
|
|
7835 this.env.unread_counts[mbox] = count;
|
|
7836 this.set_unread_count_display(mbox, set_title);
|
|
7837
|
|
7838 if (mark)
|
|
7839 this.mark_folder(mbox, mark, '', true);
|
|
7840 else if (!count)
|
|
7841 this.unmark_folder(mbox, 'recent', '', true);
|
|
7842
|
|
7843 this.mark_all_read_state();
|
|
7844 };
|
|
7845
|
|
7846 // update the mailbox count display
|
|
7847 this.set_unread_count_display = function(mbox, set_title)
|
|
7848 {
|
|
7849 var reg, link, text_obj, item, mycount, childcount, div;
|
|
7850
|
|
7851 if (item = this.get_folder_li(mbox, '', true)) {
|
|
7852 mycount = this.env.unread_counts[mbox] ? this.env.unread_counts[mbox] : 0;
|
|
7853 link = $(item).children('a').eq(0);
|
|
7854 text_obj = link.children('span.unreadcount');
|
|
7855 if (!text_obj.length && mycount)
|
|
7856 text_obj = $('<span>').addClass('unreadcount').appendTo(link);
|
|
7857 reg = /\s+\([0-9]+\)$/i;
|
|
7858
|
|
7859 childcount = 0;
|
|
7860 if ((div = item.getElementsByTagName('div')[0]) &&
|
|
7861 div.className.match(/collapsed/)) {
|
|
7862 // add children's counters
|
|
7863 for (var k in this.env.unread_counts)
|
|
7864 if (k.startsWith(mbox + this.env.delimiter))
|
|
7865 childcount += this.env.unread_counts[k];
|
|
7866 }
|
|
7867
|
|
7868 if (mycount && text_obj.length)
|
|
7869 text_obj.html(this.env.unreadwrap.replace(/%[sd]/, mycount));
|
|
7870 else if (text_obj.length)
|
|
7871 text_obj.remove();
|
|
7872
|
|
7873 // set parent's display
|
|
7874 reg = new RegExp(RegExp.escape(this.env.delimiter) + '[^' + RegExp.escape(this.env.delimiter) + ']+$');
|
|
7875 if (mbox.match(reg))
|
|
7876 this.set_unread_count_display(mbox.replace(reg, ''), false);
|
|
7877
|
|
7878 // set the right classes
|
|
7879 if ((mycount+childcount)>0)
|
|
7880 $(item).addClass('unread');
|
|
7881 else
|
|
7882 $(item).removeClass('unread');
|
|
7883 }
|
|
7884
|
|
7885 // set unread count to window title
|
|
7886 reg = /^\([0-9]+\)\s+/i;
|
|
7887 if (set_title && document.title) {
|
|
7888 var new_title = '',
|
|
7889 doc_title = String(document.title);
|
|
7890
|
|
7891 if (mycount && doc_title.match(reg))
|
|
7892 new_title = doc_title.replace(reg, '('+mycount+') ');
|
|
7893 else if (mycount)
|
|
7894 new_title = '('+mycount+') '+doc_title;
|
|
7895 else
|
|
7896 new_title = doc_title.replace(reg, '');
|
|
7897
|
|
7898 this.set_pagetitle(new_title);
|
|
7899 }
|
|
7900 };
|
|
7901
|
|
7902 // display fetched raw headers
|
|
7903 this.set_headers = function(content)
|
|
7904 {
|
|
7905 if (this.gui_objects.all_headers_row && this.gui_objects.all_headers_box && content)
|
|
7906 $(this.gui_objects.all_headers_box).html(content).show();
|
|
7907 };
|
|
7908
|
|
7909 // display all-headers row and fetch raw message headers
|
|
7910 this.show_headers = function(props, elem)
|
|
7911 {
|
|
7912 if (!this.gui_objects.all_headers_row || !this.gui_objects.all_headers_box || !this.env.uid)
|
|
7913 return;
|
|
7914
|
|
7915 $(elem).removeClass('show-headers').addClass('hide-headers');
|
|
7916 $(this.gui_objects.all_headers_row).show();
|
|
7917 elem.onclick = function() { ref.command('hide-headers', '', elem); };
|
|
7918
|
|
7919 // fetch headers only once
|
|
7920 if (!this.gui_objects.all_headers_box.innerHTML) {
|
|
7921 this.http_post('headers', {_uid: this.env.uid, _mbox: this.env.mailbox},
|
|
7922 this.display_message(this.get_label('loading'), 'loading')
|
|
7923 );
|
|
7924 }
|
|
7925 };
|
|
7926
|
|
7927 // hide all-headers row
|
|
7928 this.hide_headers = function(props, elem)
|
|
7929 {
|
|
7930 if (!this.gui_objects.all_headers_row || !this.gui_objects.all_headers_box)
|
|
7931 return;
|
|
7932
|
|
7933 $(elem).removeClass('hide-headers').addClass('show-headers');
|
|
7934 $(this.gui_objects.all_headers_row).hide();
|
|
7935 elem.onclick = function() { ref.command('show-headers', '', elem); };
|
|
7936 };
|
|
7937
|
|
7938 // create folder selector popup, position and display it
|
|
7939 this.folder_selector = function(event, callback)
|
|
7940 {
|
|
7941 var container = this.folder_selector_element;
|
|
7942
|
|
7943 if (!container) {
|
|
7944 var rows = [],
|
|
7945 delim = this.env.delimiter,
|
|
7946 ul = $('<ul class="toolbarmenu">'),
|
|
7947 link = document.createElement('a');
|
|
7948
|
|
7949 container = $('<div id="folder-selector" class="popupmenu"></div>');
|
|
7950 link.href = '#';
|
|
7951 link.className = 'icon';
|
|
7952
|
|
7953 // loop over sorted folders list
|
|
7954 $.each(this.env.mailboxes_list, function() {
|
|
7955 var n = 0, s = 0,
|
|
7956 folder = ref.env.mailboxes[this],
|
|
7957 id = folder.id,
|
|
7958 a = $(link.cloneNode(false)),
|
|
7959 row = $('<li>');
|
|
7960
|
|
7961 if (folder.virtual)
|
|
7962 a.addClass('virtual').attr('aria-disabled', 'true').attr('tabindex', '-1');
|
|
7963 else
|
|
7964 a.addClass('active').data('id', folder.id);
|
|
7965
|
|
7966 if (folder['class'])
|
|
7967 a.addClass(folder['class']);
|
|
7968
|
|
7969 // calculate/set indentation level
|
|
7970 while ((s = id.indexOf(delim, s)) >= 0) {
|
|
7971 n++; s++;
|
|
7972 }
|
|
7973 a.css('padding-left', n ? (n * 16) + 'px' : 0);
|
|
7974
|
|
7975 // add folder name element
|
|
7976 a.append($('<span>').text(folder.name));
|
|
7977
|
|
7978 row.append(a);
|
|
7979 rows.push(row);
|
|
7980 });
|
|
7981
|
|
7982 ul.append(rows).appendTo(container);
|
|
7983
|
|
7984 // temporarily show element to calculate its size
|
|
7985 container.css({left: '-1000px', top: '-1000px'})
|
|
7986 .appendTo($('body')).show();
|
|
7987
|
|
7988 // set max-height if the list is long
|
|
7989 if (rows.length > 10)
|
|
7990 container.css('max-height', $('li', container)[0].offsetHeight * 10 + 9);
|
|
7991
|
|
7992 // register delegate event handler for folder item clicks
|
|
7993 container.on('click', 'a.active', function(e){
|
|
7994 container.data('callback')($(this).data('id'));
|
|
7995 return false;
|
|
7996 });
|
|
7997
|
|
7998 this.folder_selector_element = container;
|
|
7999 }
|
|
8000
|
|
8001 container.data('callback', callback);
|
|
8002
|
|
8003 // position menu on the screen
|
|
8004 this.show_menu('folder-selector', true, event);
|
|
8005 };
|
|
8006
|
|
8007
|
|
8008 /***********************************************/
|
|
8009 /********* popup menu functions *********/
|
|
8010 /***********************************************/
|
|
8011
|
|
8012 // Show/hide a specific popup menu
|
|
8013 this.show_menu = function(prop, show, event)
|
|
8014 {
|
|
8015 var name = typeof prop == 'object' ? prop.menu : prop,
|
|
8016 obj = $('#'+name),
|
|
8017 ref = event && event.target ? $(event.target) : $(obj.attr('rel') || '#'+name+'link'),
|
|
8018 keyboard = rcube_event.is_keyboard(event),
|
|
8019 align = obj.attr('data-align') || '',
|
|
8020 stack = false;
|
|
8021
|
|
8022 // find "real" button element
|
|
8023 if (ref.get(0).tagName != 'A' && ref.closest('a').length)
|
|
8024 ref = ref.closest('a');
|
|
8025
|
|
8026 if (typeof prop == 'string')
|
|
8027 prop = { menu:name };
|
|
8028
|
|
8029 // let plugins or skins provide the menu element
|
|
8030 if (!obj.length) {
|
|
8031 obj = this.triggerEvent('menu-get', { name:name, props:prop, originalEvent:event });
|
|
8032 }
|
|
8033
|
|
8034 if (!obj || !obj.length) {
|
|
8035 // just delegate the action to subscribers
|
|
8036 return this.triggerEvent(show === false ? 'menu-close' : 'menu-open', { name:name, props:prop, originalEvent:event });
|
|
8037 }
|
|
8038
|
|
8039 // move element to top for proper absolute positioning
|
|
8040 obj.appendTo(document.body);
|
|
8041
|
|
8042 if (typeof show == 'undefined')
|
|
8043 show = obj.is(':visible') ? false : true;
|
|
8044
|
|
8045 if (show && ref.length) {
|
|
8046 var win = $(window),
|
|
8047 pos = ref.offset(),
|
|
8048 above = align.indexOf('bottom') >= 0;
|
|
8049
|
|
8050 stack = ref.attr('role') == 'menuitem' || ref.closest('[role=menuitem]').length > 0;
|
|
8051
|
|
8052 ref.offsetWidth = ref.outerWidth();
|
|
8053 ref.offsetHeight = ref.outerHeight();
|
|
8054 if (!above && pos.top + ref.offsetHeight + obj.height() > win.height()) {
|
|
8055 above = true;
|
|
8056 }
|
|
8057 if (align.indexOf('right') >= 0) {
|
|
8058 pos.left = pos.left + ref.outerWidth() - obj.width();
|
|
8059 }
|
|
8060 else if (stack) {
|
|
8061 pos.left = pos.left + ref.offsetWidth - 5;
|
|
8062 pos.top -= ref.offsetHeight;
|
|
8063 }
|
|
8064 if (pos.left + obj.width() > win.width()) {
|
|
8065 pos.left = win.width() - obj.width() - 12;
|
|
8066 }
|
|
8067 pos.top = Math.max(0, pos.top + (above ? -obj.height() : ref.offsetHeight));
|
|
8068 obj.css({ left:pos.left+'px', top:pos.top+'px' });
|
|
8069 }
|
|
8070
|
|
8071 // add menu to stack
|
|
8072 if (show) {
|
|
8073 // truncate stack down to the one containing the ref link
|
|
8074 for (var i = this.menu_stack.length - 1; stack && i >= 0; i--) {
|
|
8075 if (!$(ref).parents('#'+this.menu_stack[i]).length && $(event.target).parent().attr('role') != 'menuitem')
|
|
8076 this.hide_menu(this.menu_stack[i], event);
|
|
8077 }
|
|
8078 if (stack && this.menu_stack.length) {
|
|
8079 obj.data('parent', $.last(this.menu_stack));
|
|
8080 obj.css('z-index', ($('#'+$.last(this.menu_stack)).css('z-index') || 0) + 1);
|
|
8081 }
|
|
8082 else if (!stack && this.menu_stack.length) {
|
|
8083 this.hide_menu(this.menu_stack[0], event);
|
|
8084 }
|
|
8085
|
|
8086 obj.show().attr('aria-hidden', 'false').data('opener', ref.attr('aria-expanded', 'true').get(0));
|
|
8087 this.triggerEvent('menu-open', { name:name, obj:obj, props:prop, originalEvent:event });
|
|
8088 this.menu_stack.push(name);
|
|
8089
|
|
8090 this.menu_keyboard_active = show && keyboard;
|
|
8091 if (this.menu_keyboard_active) {
|
|
8092 this.focused_menu = name;
|
|
8093 obj.find('a,input:not(:disabled)').not('[aria-disabled=true]').first().focus();
|
|
8094 }
|
|
8095 }
|
|
8096 else { // close menu
|
|
8097 this.hide_menu(name, event);
|
|
8098 }
|
|
8099
|
|
8100 return show;
|
|
8101 };
|
|
8102
|
|
8103 // hide the given popup menu (and it's childs)
|
|
8104 this.hide_menu = function(name, event)
|
|
8105 {
|
|
8106 if (!this.menu_stack.length) {
|
|
8107 // delegate to subscribers
|
|
8108 this.triggerEvent('menu-close', { name:name, props:{ menu:name }, originalEvent:event });
|
|
8109 return;
|
|
8110 }
|
|
8111
|
|
8112 var obj, keyboard = rcube_event.is_keyboard(event);
|
|
8113 for (var j=this.menu_stack.length-1; j >= 0; j--) {
|
|
8114 obj = $('#' + this.menu_stack[j]).hide().attr('aria-hidden', 'true').data('parent', false);
|
|
8115 this.triggerEvent('menu-close', { name:this.menu_stack[j], obj:obj, props:{ menu:this.menu_stack[j] }, originalEvent:event });
|
|
8116 if (this.menu_stack[j] == name) {
|
|
8117 j = -1; // stop loop
|
|
8118 if (obj.data('opener')) {
|
|
8119 $(obj.data('opener')).attr('aria-expanded', 'false');
|
|
8120 if (keyboard)
|
|
8121 obj.data('opener').focus();
|
|
8122 }
|
|
8123 }
|
|
8124 this.menu_stack.pop();
|
|
8125 }
|
|
8126
|
|
8127 // focus previous menu in stack
|
|
8128 if (this.menu_stack.length && keyboard) {
|
|
8129 this.menu_keyboard_active = true;
|
|
8130 this.focused_menu = $.last(this.menu_stack);
|
|
8131 if (!obj || !obj.data('opener'))
|
|
8132 $('#'+this.focused_menu).find('a,input:not(:disabled)').not('[aria-disabled=true]').first().focus();
|
|
8133 }
|
|
8134 else {
|
|
8135 this.focused_menu = null;
|
|
8136 this.menu_keyboard_active = false;
|
|
8137 }
|
|
8138 }
|
|
8139
|
|
8140
|
|
8141 // position a menu element on the screen in relation to other object
|
|
8142 this.element_position = function(element, obj)
|
|
8143 {
|
|
8144 var obj = $(obj), win = $(window),
|
|
8145 width = obj.outerWidth(),
|
|
8146 height = obj.outerHeight(),
|
|
8147 menu_pos = obj.data('menu-pos'),
|
|
8148 win_height = win.height(),
|
|
8149 elem_height = $(element).height(),
|
|
8150 elem_width = $(element).width(),
|
|
8151 pos = obj.offset(),
|
|
8152 top = pos.top,
|
|
8153 left = pos.left + width;
|
|
8154
|
|
8155 if (menu_pos == 'bottom') {
|
|
8156 top += height;
|
|
8157 left -= width;
|
|
8158 }
|
|
8159 else
|
|
8160 left -= 5;
|
|
8161
|
|
8162 if (top + elem_height > win_height) {
|
|
8163 top -= elem_height - height;
|
|
8164 if (top < 0)
|
|
8165 top = Math.max(0, (win_height - elem_height) / 2);
|
|
8166 }
|
|
8167
|
|
8168 if (left + elem_width > win.width())
|
|
8169 left -= elem_width + width;
|
|
8170
|
|
8171 element.css({left: left + 'px', top: top + 'px'});
|
|
8172 };
|
|
8173
|
|
8174 // initialize HTML editor
|
|
8175 this.editor_init = function(config, id)
|
|
8176 {
|
|
8177 this.editor = new rcube_text_editor(config, id);
|
|
8178 };
|
|
8179
|
|
8180
|
|
8181 /********************************************************/
|
|
8182 /********* html to text conversion functions *********/
|
|
8183 /********************************************************/
|
|
8184
|
|
8185 this.html2plain = function(html, func)
|
|
8186 {
|
|
8187 return this.format_converter(html, 'html', func);
|
|
8188 };
|
|
8189
|
|
8190 this.plain2html = function(plain, func)
|
|
8191 {
|
|
8192 return this.format_converter(plain, 'plain', func);
|
|
8193 };
|
|
8194
|
|
8195 this.format_converter = function(text, format, func)
|
|
8196 {
|
|
8197 // warn the user (if converted content is not empty)
|
|
8198 if (!text
|
|
8199 || (format == 'html' && !(text.replace(/<[^>]+>| |\xC2\xA0|\s/g, '')).length)
|
|
8200 || (format != 'html' && !(text.replace(/\xC2\xA0|\s/g, '')).length)
|
|
8201 ) {
|
|
8202 // without setTimeout() here, textarea is filled with initial (onload) content
|
|
8203 if (func)
|
|
8204 setTimeout(function() { func(''); }, 50);
|
|
8205 return true;
|
|
8206 }
|
|
8207
|
|
8208 var confirmed = this.env.editor_warned || confirm(this.get_label('editorwarning'));
|
|
8209
|
|
8210 this.env.editor_warned = true;
|
|
8211
|
|
8212 if (!confirmed)
|
|
8213 return false;
|
|
8214
|
|
8215 var url = '?_task=utils&_action=' + (format == 'html' ? 'html2text' : 'text2html'),
|
|
8216 lock = this.set_busy(true, 'converting');
|
|
8217
|
|
8218 $.ajax({ type: 'POST', url: url, data: text, contentType: 'application/octet-stream',
|
|
8219 error: function(o, status, err) { ref.http_error(o, status, err, lock); },
|
|
8220 success: function(data) {
|
|
8221 ref.set_busy(false, null, lock);
|
|
8222 if (func) func(data);
|
|
8223 }
|
|
8224 });
|
|
8225
|
|
8226 return true;
|
|
8227 };
|
|
8228
|
|
8229
|
|
8230 /********************************************************/
|
|
8231 /********* remote request methods *********/
|
|
8232 /********************************************************/
|
|
8233
|
|
8234 // compose a valid url with the given parameters
|
|
8235 this.url = function(action, query)
|
|
8236 {
|
|
8237 var querystring = typeof query === 'string' ? query : '';
|
|
8238
|
|
8239 if (typeof action !== 'string')
|
|
8240 query = action;
|
|
8241 else if (!query || typeof query !== 'object')
|
|
8242 query = {};
|
|
8243
|
|
8244 if (action)
|
|
8245 query._action = action;
|
|
8246 else if (this.env.action)
|
|
8247 query._action = this.env.action;
|
|
8248
|
|
8249 var url = this.env.comm_path, k, param = {};
|
|
8250
|
|
8251 // overwrite task name
|
|
8252 if (action && action.match(/([a-z0-9_-]+)\/([a-z0-9-_.]+)/)) {
|
|
8253 query._action = RegExp.$2;
|
|
8254 url = url.replace(/\_task=[a-z0-9_-]+/, '_task=' + RegExp.$1);
|
|
8255 }
|
|
8256
|
|
8257 // remove undefined values
|
|
8258 for (k in query) {
|
|
8259 if (query[k] !== undefined && query[k] !== null)
|
|
8260 param[k] = query[k];
|
|
8261 }
|
|
8262
|
|
8263 if (param = $.param(param))
|
|
8264 url += (url.indexOf('?') > -1 ? '&' : '?') + param;
|
|
8265
|
|
8266 if (querystring)
|
|
8267 url += (url.indexOf('?') > -1 ? '&' : '?') + querystring;
|
|
8268
|
|
8269 return url;
|
|
8270 };
|
|
8271
|
|
8272 this.redirect = function(url, lock)
|
|
8273 {
|
|
8274 if (lock || lock === null)
|
|
8275 this.set_busy(true);
|
|
8276
|
|
8277 if (this.is_framed()) {
|
|
8278 parent.rcmail.redirect(url, lock);
|
|
8279 }
|
|
8280 else {
|
|
8281 if (this.env.extwin) {
|
|
8282 if (typeof url == 'string')
|
|
8283 url += (url.indexOf('?') < 0 ? '?' : '&') + '_extwin=1';
|
|
8284 else
|
|
8285 url._extwin = 1;
|
|
8286 }
|
|
8287 this.location_href(url, window);
|
|
8288 }
|
|
8289 };
|
|
8290
|
|
8291 this.goto_url = function(action, query, lock, secure)
|
|
8292 {
|
|
8293 var url = this.url(action, query)
|
|
8294 if (secure) url = this.secure_url(url);
|
|
8295 this.redirect(url, lock);
|
|
8296 };
|
|
8297
|
|
8298 this.location_href = function(url, target, frame)
|
|
8299 {
|
|
8300 if (frame)
|
|
8301 this.lock_frame();
|
|
8302
|
|
8303 if (typeof url == 'object')
|
|
8304 url = this.env.comm_path + '&' + $.param(url);
|
|
8305
|
|
8306 // simulate real link click to force IE to send referer header
|
|
8307 if (bw.ie && target == window)
|
|
8308 $('<a>').attr('href', url).appendTo(document.body).get(0).click();
|
|
8309 else
|
|
8310 target.location.href = url;
|
|
8311
|
|
8312 // reset keep-alive interval
|
|
8313 this.start_keepalive();
|
|
8314 };
|
|
8315
|
|
8316 // update browser location to remember current view
|
|
8317 this.update_state = function(query)
|
|
8318 {
|
|
8319 if (window.history.replaceState)
|
|
8320 try {
|
|
8321 // This may throw security exception in Firefox (#5400)
|
|
8322 window.history.replaceState({}, document.title, rcmail.url('', query));
|
|
8323 }
|
|
8324 catch(e) { /* ignore */ };
|
|
8325 };
|
|
8326
|
|
8327 // send a http request to the server
|
|
8328 this.http_request = function(action, data, lock, type)
|
|
8329 {
|
|
8330 if (type != 'POST')
|
|
8331 type = 'GET';
|
|
8332
|
|
8333 if (typeof data !== 'object')
|
|
8334 data = rcube_parse_query(data);
|
|
8335
|
|
8336 data._remote = 1;
|
|
8337 data._unlock = lock ? lock : 0;
|
|
8338
|
|
8339 // trigger plugin hook
|
|
8340 var result = this.triggerEvent('request' + action, data);
|
|
8341
|
|
8342 // abort if one of the handlers returned false
|
|
8343 if (result === false) {
|
|
8344 if (data._unlock)
|
|
8345 this.set_busy(false, null, data._unlock);
|
|
8346 return false;
|
|
8347 }
|
|
8348 else if (result !== undefined) {
|
|
8349 data = result;
|
|
8350 if (data._action) {
|
|
8351 action = data._action;
|
|
8352 delete data._action;
|
|
8353 }
|
|
8354 }
|
|
8355
|
|
8356 var url = this.url(action);
|
|
8357
|
|
8358 // reset keep-alive interval
|
|
8359 this.start_keepalive();
|
|
8360
|
|
8361 // send request
|
|
8362 return $.ajax({
|
|
8363 type: type, url: url, data: data, dataType: 'json',
|
|
8364 success: function(data) { ref.http_response(data); },
|
|
8365 error: function(o, status, err) { ref.http_error(o, status, err, lock, action); }
|
|
8366 });
|
|
8367 };
|
|
8368
|
|
8369 // send a http GET request to the server
|
|
8370 this.http_get = this.http_request;
|
|
8371
|
|
8372 // send a http POST request to the server
|
|
8373 this.http_post = function(action, data, lock)
|
|
8374 {
|
|
8375 return this.http_request(action, data, lock, 'POST');
|
|
8376 };
|
|
8377
|
|
8378 // aborts ajax request
|
|
8379 this.abort_request = function(r)
|
|
8380 {
|
|
8381 if (r.request)
|
|
8382 r.request.abort();
|
|
8383 if (r.lock)
|
|
8384 this.set_busy(false, null, r.lock);
|
|
8385 };
|
|
8386
|
|
8387 // handle HTTP response
|
|
8388 this.http_response = function(response)
|
|
8389 {
|
|
8390 if (!response)
|
|
8391 return;
|
|
8392
|
|
8393 if (response.unlock)
|
|
8394 this.set_busy(false);
|
|
8395
|
|
8396 this.triggerEvent('responsebefore', {response: response});
|
|
8397 this.triggerEvent('responsebefore'+response.action, {response: response});
|
|
8398
|
|
8399 // set env vars
|
|
8400 if (response.env)
|
|
8401 this.set_env(response.env);
|
|
8402
|
|
8403 var i;
|
|
8404
|
|
8405 // we have labels to add
|
|
8406 if (typeof response.texts === 'object') {
|
|
8407 for (i in response.texts)
|
|
8408 if (typeof response.texts[i] === 'string')
|
|
8409 this.add_label(i, response.texts[i]);
|
|
8410 }
|
|
8411
|
|
8412 // if we get javascript code from server -> execute it
|
|
8413 if (response.exec) {
|
|
8414 eval(response.exec);
|
|
8415 }
|
|
8416
|
|
8417 // execute callback functions of plugins
|
|
8418 if (response.callbacks && response.callbacks.length) {
|
|
8419 for (i=0; i < response.callbacks.length; i++)
|
|
8420 this.triggerEvent(response.callbacks[i][0], response.callbacks[i][1]);
|
|
8421 }
|
|
8422
|
|
8423 // process the response data according to the sent action
|
|
8424 switch (response.action) {
|
|
8425 case 'mark':
|
|
8426 // Mark the message as Seen also in the opener/parent
|
|
8427 if ((this.env.action == 'show' || this.env.action == 'preview') && this.env.last_flag == 'SEEN')
|
|
8428 this.set_unread_message(this.env.uid, this.env.mailbox);
|
|
8429 break;
|
|
8430
|
|
8431 case 'delete':
|
|
8432 if (this.task == 'addressbook') {
|
|
8433 var sid, uid = this.contact_list.get_selection(), writable = false;
|
|
8434
|
|
8435 if (uid && this.contact_list.rows[uid]) {
|
|
8436 // search results, get source ID from record ID
|
|
8437 if (this.env.source == '') {
|
|
8438 sid = String(uid).replace(/^[^-]+-/, '');
|
|
8439 writable = sid && this.env.address_sources[sid] && !this.env.address_sources[sid].readonly;
|
|
8440 }
|
|
8441 else {
|
|
8442 writable = !this.env.address_sources[this.env.source].readonly;
|
|
8443 }
|
|
8444 }
|
|
8445 this.enable_command('compose', (uid && this.contact_list.rows[uid]));
|
|
8446 this.enable_command('delete', 'edit', writable);
|
|
8447 this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
|
|
8448 this.enable_command('export-selected', 'print', false);
|
|
8449 }
|
|
8450
|
|
8451 case 'move':
|
|
8452 if (this.env.action == 'show') {
|
|
8453 // re-enable commands on move/delete error
|
|
8454 this.enable_command(this.env.message_commands, true);
|
|
8455 if (!this.env.list_post)
|
|
8456 this.enable_command('reply-list', false);
|
|
8457 }
|
|
8458 else if (this.task == 'addressbook') {
|
|
8459 this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
|
|
8460 }
|
|
8461
|
|
8462 case 'purge':
|
|
8463 case 'expunge':
|
|
8464 if (this.task == 'mail') {
|
|
8465 if (!this.env.exists) {
|
|
8466 // clear preview pane content
|
|
8467 if (this.env.contentframe)
|
|
8468 this.show_contentframe(false);
|
|
8469 // disable commands useless when mailbox is empty
|
|
8470 this.enable_command(this.env.message_commands, 'purge', 'expunge',
|
|
8471 'select-all', 'select-none', 'expand-all', 'expand-unread', 'collapse-all', false);
|
|
8472 }
|
|
8473 if (this.message_list)
|
|
8474 this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:this.message_list.rowcount });
|
|
8475 }
|
|
8476 break;
|
|
8477
|
|
8478 case 'refresh':
|
|
8479 case 'check-recent':
|
|
8480 // update message flags
|
|
8481 $.each(this.env.recent_flags || {}, function(uid, flags) {
|
|
8482 ref.set_message(uid, 'deleted', flags.deleted);
|
|
8483 ref.set_message(uid, 'replied', flags.answered);
|
|
8484 ref.set_message(uid, 'unread', !flags.seen);
|
|
8485 ref.set_message(uid, 'forwarded', flags.forwarded);
|
|
8486 ref.set_message(uid, 'flagged', flags.flagged);
|
|
8487 });
|
|
8488 delete this.env.recent_flags;
|
|
8489
|
|
8490 case 'getunread':
|
|
8491 case 'search':
|
|
8492 this.env.qsearch = null;
|
|
8493 case 'list':
|
|
8494 if (this.task == 'mail') {
|
|
8495 var is_multifolder = this.is_multifolder_listing(),
|
|
8496 list = this.message_list,
|
|
8497 uid = this.env.list_uid;
|
|
8498
|
|
8499 this.enable_command('show', 'select-all', 'select-none', this.env.messagecount > 0);
|
|
8500 this.enable_command('expunge', this.env.exists && !is_multifolder);
|
|
8501 this.enable_command('purge', this.purge_mailbox_test() && !is_multifolder);
|
|
8502 this.enable_command('import-messages', !is_multifolder);
|
|
8503 this.enable_command('expand-all', 'expand-unread', 'collapse-all', this.env.threading && this.env.messagecount && !is_multifolder);
|
|
8504
|
|
8505 if (list) {
|
|
8506 if (response.action == 'list' || response.action == 'search') {
|
|
8507 // highlight message row when we're back from message page
|
|
8508 if (uid) {
|
|
8509 if (!list.rows[uid])
|
|
8510 uid += '-' + this.env.mailbox;
|
|
8511 if (list.rows[uid]) {
|
|
8512 list.select(uid);
|
|
8513 }
|
|
8514 delete this.env.list_uid;
|
|
8515 }
|
|
8516
|
|
8517 this.enable_command('set-listmode', this.env.threads && !is_multifolder);
|
|
8518 if (list.rowcount > 0 && !$(document.activeElement).is('input,textarea'))
|
|
8519 list.focus();
|
|
8520
|
|
8521 // trigger 'select' so all dependent actions update its state
|
|
8522 // e.g. plugins use this event to activate buttons (#1490647)
|
|
8523 list.triggerEvent('select');
|
|
8524 }
|
|
8525
|
|
8526 if (response.action != 'getunread')
|
|
8527 this.triggerEvent('listupdate', { folder:this.env.mailbox, rowcount:list.rowcount });
|
|
8528 }
|
|
8529 }
|
|
8530 else if (this.task == 'addressbook') {
|
|
8531 this.enable_command('export', (this.contact_list && this.contact_list.rowcount > 0));
|
|
8532
|
|
8533 if (response.action == 'list' || response.action == 'search') {
|
|
8534 this.enable_command('search-create', this.env.source == '');
|
|
8535 this.enable_command('search-delete', this.env.search_id);
|
|
8536 this.update_group_commands();
|
|
8537 if (this.contact_list.rowcount > 0 && !$(document.activeElement).is('input,textarea'))
|
|
8538 this.contact_list.focus();
|
|
8539 this.triggerEvent('listupdate', { folder:this.env.source, rowcount:this.contact_list.rowcount });
|
|
8540 }
|
|
8541 }
|
|
8542 break;
|
|
8543
|
|
8544 case 'list-contacts':
|
|
8545 case 'search-contacts':
|
|
8546 if (this.contact_list && this.contact_list.rowcount > 0)
|
|
8547 this.contact_list.focus();
|
|
8548 break;
|
|
8549 }
|
|
8550
|
|
8551 if (response.unlock)
|
|
8552 this.hide_message(response.unlock);
|
|
8553
|
|
8554 this.triggerEvent('responseafter', {response: response});
|
|
8555 this.triggerEvent('responseafter'+response.action, {response: response});
|
|
8556
|
|
8557 // reset keep-alive interval
|
|
8558 this.start_keepalive();
|
|
8559 };
|
|
8560
|
|
8561 // handle HTTP request errors
|
|
8562 this.http_error = function(request, status, err, lock, action)
|
|
8563 {
|
|
8564 var errmsg = request.statusText;
|
|
8565
|
|
8566 this.set_busy(false, null, lock);
|
|
8567 request.abort();
|
|
8568
|
|
8569 // don't display error message on page unload (#1488547)
|
|
8570 if (this.unload)
|
|
8571 return;
|
|
8572
|
|
8573 if (request.status && errmsg)
|
|
8574 this.display_message(this.get_label('servererror') + ' (' + errmsg + ')', 'error');
|
|
8575 else if (status == 'timeout')
|
|
8576 this.display_message(this.get_label('requesttimedout'), 'error');
|
|
8577 else if (request.status == 0 && status != 'abort')
|
|
8578 this.display_message(this.get_label('connerror'), 'error');
|
|
8579
|
|
8580 // redirect to url specified in location header if not empty
|
|
8581 var location_url = request.getResponseHeader("Location");
|
|
8582 if (location_url && this.env.action != 'compose') // don't redirect on compose screen, contents might get lost (#1488926)
|
|
8583 this.redirect(location_url);
|
|
8584
|
|
8585 // 403 Forbidden response (CSRF prevention) - reload the page.
|
|
8586 // In case there's a new valid session it will be used, otherwise
|
|
8587 // login form will be presented (#1488960).
|
|
8588 if (request.status == 403) {
|
|
8589 (this.is_framed() ? parent : window).location.reload();
|
|
8590 return;
|
|
8591 }
|
|
8592
|
|
8593 // re-send keep-alive requests after 30 seconds
|
|
8594 if (action == 'keep-alive')
|
|
8595 setTimeout(function(){ ref.keep_alive(); ref.start_keepalive(); }, 30000);
|
|
8596 };
|
|
8597
|
|
8598 // handler for session errors detected on the server
|
|
8599 this.session_error = function(redirect_url)
|
|
8600 {
|
|
8601 this.env.server_error = 401;
|
|
8602
|
|
8603 // save message in local storage and do not redirect
|
|
8604 if (this.env.action == 'compose') {
|
|
8605 this.save_compose_form_local();
|
|
8606 this.compose_skip_unsavedcheck = true;
|
|
8607 // stop keep-alive and refresh processes
|
|
8608 this.env.session_lifetime = 0;
|
|
8609 if (this._keepalive)
|
|
8610 clearInterval(this._keepalive);
|
|
8611 if (this._refresh)
|
|
8612 clearInterval(this._refresh);
|
|
8613 }
|
|
8614 else if (redirect_url) {
|
|
8615 setTimeout(function(){ ref.redirect(redirect_url, true); }, 2000);
|
|
8616 }
|
|
8617 };
|
|
8618
|
|
8619 // callback when an iframe finished loading
|
|
8620 this.iframe_loaded = function(unlock)
|
|
8621 {
|
|
8622 this.set_busy(false, null, unlock);
|
|
8623
|
|
8624 if (this.submit_timer)
|
|
8625 clearTimeout(this.submit_timer);
|
|
8626 };
|
|
8627
|
|
8628 /**
|
|
8629 Send multi-threaded parallel HTTP requests to the server for a list if items.
|
|
8630 The string '%' in either a GET query or POST parameters will be replaced with the respective item value.
|
|
8631 This is the argument object expected: {
|
|
8632 items: ['foo','bar','gna'], // list of items to send requests for
|
|
8633 action: 'task/some-action', // Roudncube action to call
|
|
8634 query: { q:'%s' }, // GET query parameters
|
|
8635 postdata: { source:'%s' }, // POST data (sends a POST request if present)
|
|
8636 threads: 3, // max. number of concurrent requests
|
|
8637 onresponse: function(data){ }, // Callback function called for every response received from server
|
|
8638 whendone: function(alldata){ } // Callback function called when all requests have been sent
|
|
8639 }
|
|
8640 */
|
|
8641 this.multi_thread_http_request = function(prop)
|
|
8642 {
|
|
8643 var i, item, reqid = new Date().getTime(),
|
|
8644 threads = prop.threads || 1;
|
|
8645
|
|
8646 prop.reqid = reqid;
|
|
8647 prop.running = 0;
|
|
8648 prop.requests = [];
|
|
8649 prop.result = [];
|
|
8650 prop._items = $.extend([], prop.items); // copy items
|
|
8651
|
|
8652 if (!prop.lock)
|
|
8653 prop.lock = this.display_message(this.get_label('loading'), 'loading');
|
|
8654
|
|
8655 // add the request arguments to the jobs pool
|
|
8656 this.http_request_jobs[reqid] = prop;
|
|
8657
|
|
8658 // start n threads
|
|
8659 for (i=0; i < threads; i++) {
|
|
8660 item = prop._items.shift();
|
|
8661 if (item === undefined)
|
|
8662 break;
|
|
8663
|
|
8664 prop.running++;
|
|
8665 prop.requests.push(this.multi_thread_send_request(prop, item));
|
|
8666 }
|
|
8667
|
|
8668 return reqid;
|
|
8669 };
|
|
8670
|
|
8671 // helper method to send an HTTP request with the given iterator value
|
|
8672 this.multi_thread_send_request = function(prop, item)
|
|
8673 {
|
|
8674 var k, postdata, query;
|
|
8675
|
|
8676 // replace %s in post data
|
|
8677 if (prop.postdata) {
|
|
8678 postdata = {};
|
|
8679 for (k in prop.postdata) {
|
|
8680 postdata[k] = String(prop.postdata[k]).replace('%s', item);
|
|
8681 }
|
|
8682 postdata._reqid = prop.reqid;
|
|
8683 }
|
|
8684 // replace %s in query
|
|
8685 else if (typeof prop.query == 'string') {
|
|
8686 query = prop.query.replace('%s', item);
|
|
8687 query += '&_reqid=' + prop.reqid;
|
|
8688 }
|
|
8689 else if (typeof prop.query == 'object' && prop.query) {
|
|
8690 query = {};
|
|
8691 for (k in prop.query) {
|
|
8692 query[k] = String(prop.query[k]).replace('%s', item);
|
|
8693 }
|
|
8694 query._reqid = prop.reqid;
|
|
8695 }
|
|
8696
|
|
8697 // send HTTP GET or POST request
|
|
8698 return postdata ? this.http_post(prop.action, postdata) : this.http_request(prop.action, query);
|
|
8699 };
|
|
8700
|
|
8701 // callback function for multi-threaded http responses
|
|
8702 this.multi_thread_http_response = function(data, reqid)
|
|
8703 {
|
|
8704 var prop = this.http_request_jobs[reqid];
|
|
8705 if (!prop || prop.running <= 0 || prop.cancelled)
|
|
8706 return;
|
|
8707
|
|
8708 prop.running--;
|
|
8709
|
|
8710 // trigger response callback
|
|
8711 if (prop.onresponse && typeof prop.onresponse == 'function') {
|
|
8712 prop.onresponse(data);
|
|
8713 }
|
|
8714
|
|
8715 prop.result = $.extend(prop.result, data);
|
|
8716
|
|
8717 // send next request if prop.items is not yet empty
|
|
8718 var item = prop._items.shift();
|
|
8719 if (item !== undefined) {
|
|
8720 prop.running++;
|
|
8721 prop.requests.push(this.multi_thread_send_request(prop, item));
|
|
8722 }
|
|
8723 // trigger whendone callback and mark this request as done
|
|
8724 else if (prop.running == 0) {
|
|
8725 if (prop.whendone && typeof prop.whendone == 'function') {
|
|
8726 prop.whendone(prop.result);
|
|
8727 }
|
|
8728
|
|
8729 this.set_busy(false, '', prop.lock);
|
|
8730
|
|
8731 // remove from this.http_request_jobs pool
|
|
8732 delete this.http_request_jobs[reqid];
|
|
8733 }
|
|
8734 };
|
|
8735
|
|
8736 // abort a running multi-thread request with the given identifier
|
|
8737 this.multi_thread_request_abort = function(reqid)
|
|
8738 {
|
|
8739 var prop = this.http_request_jobs[reqid];
|
|
8740 if (prop) {
|
|
8741 for (var i=0; prop.running > 0 && i < prop.requests.length; i++) {
|
|
8742 if (prop.requests[i].abort)
|
|
8743 prop.requests[i].abort();
|
|
8744 }
|
|
8745
|
|
8746 prop.running = 0;
|
|
8747 prop.cancelled = true;
|
|
8748 this.set_busy(false, '', prop.lock);
|
|
8749 }
|
|
8750 };
|
|
8751
|
|
8752 // post the given form to a hidden iframe
|
|
8753 this.async_upload_form = function(form, action, onload)
|
|
8754 {
|
|
8755 // create hidden iframe
|
|
8756 var ts = new Date().getTime(),
|
|
8757 frame_name = 'rcmupload' + ts,
|
|
8758 frame = this.async_upload_form_frame(frame_name);
|
|
8759
|
|
8760 // upload progress support
|
|
8761 if (this.env.upload_progress_name) {
|
|
8762 var fname = this.env.upload_progress_name,
|
|
8763 field = $('input[name='+fname+']', form);
|
|
8764
|
|
8765 if (!field.length) {
|
|
8766 field = $('<input>').attr({type: 'hidden', name: fname});
|
|
8767 field.prependTo(form);
|
|
8768 }
|
|
8769
|
|
8770 field.val(ts);
|
|
8771 }
|
|
8772
|
|
8773 // handle upload errors by parsing iframe content in onload
|
|
8774 frame.on('load', {ts:ts}, onload);
|
|
8775
|
|
8776 $(form).attr({
|
|
8777 target: frame_name,
|
|
8778 action: this.url(action, {_id: this.env.compose_id || '', _uploadid: ts, _from: this.env.action}),
|
|
8779 method: 'POST'})
|
|
8780 .attr(form.encoding ? 'encoding' : 'enctype', 'multipart/form-data')
|
|
8781 .submit();
|
|
8782
|
|
8783 return frame_name;
|
|
8784 };
|
|
8785
|
|
8786 // create iframe element for files upload
|
|
8787 this.async_upload_form_frame = function(name)
|
|
8788 {
|
|
8789 return $('<iframe>').attr({name: name, style: 'border: none; width: 0; height: 0; visibility: hidden'})
|
|
8790 .appendTo(document.body);
|
|
8791 };
|
|
8792
|
|
8793 // html5 file-drop API
|
|
8794 this.document_drag_hover = function(e, over)
|
|
8795 {
|
|
8796 // don't e.preventDefault() here to not block text dragging on the page (#1490619)
|
|
8797 $(this.gui_objects.filedrop)[(over?'addClass':'removeClass')]('active');
|
|
8798 };
|
|
8799
|
|
8800 this.file_drag_hover = function(e, over)
|
|
8801 {
|
|
8802 e.preventDefault();
|
|
8803 e.stopPropagation();
|
|
8804 $(this.gui_objects.filedrop)[(over?'addClass':'removeClass')]('hover');
|
|
8805 };
|
|
8806
|
|
8807 // handler when files are dropped to a designated area.
|
|
8808 // compose a multipart form data and submit it to the server
|
|
8809 this.file_dropped = function(e)
|
|
8810 {
|
|
8811 // abort event and reset UI
|
|
8812 this.file_drag_hover(e, false);
|
|
8813
|
|
8814 // prepare multipart form data composition
|
|
8815 var uri, size = 0, numfiles = 0,
|
|
8816 files = e.target.files || e.dataTransfer.files,
|
|
8817 formdata = window.FormData ? new FormData() : null,
|
|
8818 fieldname = (this.env.filedrop.fieldname || '_file') + (this.env.filedrop.single ? '' : '[]'),
|
|
8819 boundary = '------multipartformboundary' + (new Date).getTime(),
|
|
8820 dashdash = '--', crlf = '\r\n',
|
|
8821 multipart = dashdash + boundary + crlf,
|
|
8822 args = {_id: this.env.compose_id || this.env.cid || '', _remote: 1, _from: this.env.action};
|
|
8823
|
|
8824 if (!files || !files.length) {
|
|
8825 // Roundcube attachment, pass its uri to the backend and attach
|
|
8826 if (uri = e.dataTransfer.getData('roundcube-uri')) {
|
|
8827 var ts = new Date().getTime(),
|
|
8828 // jQuery way to escape filename (#1490530)
|
|
8829 content = $('<span>').text(e.dataTransfer.getData('roundcube-name') || this.get_label('attaching')).html();
|
|
8830
|
|
8831 args._uri = uri;
|
|
8832 args._uploadid = ts;
|
|
8833
|
|
8834 // add to attachments list
|
|
8835 if (!this.add2attachment_list(ts, {name: '', html: content, classname: 'uploading', complete: false}))
|
|
8836 this.file_upload_id = this.set_busy(true, 'attaching');
|
|
8837
|
|
8838 this.http_post(this.env.filedrop.action || 'upload', args);
|
|
8839 }
|
|
8840 return;
|
|
8841 }
|
|
8842
|
|
8843 // inline function to submit the files to the server
|
|
8844 var submit_data = function() {
|
|
8845 if (ref.env.max_filesize && ref.env.filesizeerror && size > ref.env.max_filesize) {
|
|
8846 ref.display_message(ref.env.filesizeerror, 'error');
|
|
8847 return;
|
|
8848 }
|
|
8849
|
|
8850 if (ref.env.max_filecount && ref.env.filecounterror && numfiles > ref.env.max_filecount) {
|
|
8851 ref.display_message(ref.env.filecounterror, 'error');
|
|
8852 return;
|
|
8853 }
|
|
8854
|
|
8855 var multiple = files.length > 1,
|
|
8856 ts = new Date().getTime(),
|
|
8857 // jQuery way to escape filename (#1490530)
|
|
8858 content = $('<span>').text(multiple ? ref.get_label('uploadingmany') : files[0].name).html();
|
|
8859
|
|
8860 // add to attachments list
|
|
8861 if (!ref.add2attachment_list(ts, { name:'', html:content, classname:'uploading', complete:false }))
|
|
8862 ref.file_upload_id = ref.set_busy(true, 'uploading');
|
|
8863
|
|
8864 // complete multipart content and post request
|
|
8865 multipart += dashdash + boundary + dashdash + crlf;
|
|
8866
|
|
8867 args._uploadid = ts;
|
|
8868
|
|
8869 $.ajax({
|
|
8870 type: 'POST',
|
|
8871 dataType: 'json',
|
|
8872 url: ref.url(ref.env.filedrop.action || 'upload', args),
|
|
8873 contentType: formdata ? false : 'multipart/form-data; boundary=' + boundary,
|
|
8874 processData: false,
|
|
8875 timeout: 0, // disable default timeout set in ajaxSetup()
|
|
8876 data: formdata || multipart,
|
|
8877 headers: {'X-Roundcube-Request': ref.env.request_token},
|
|
8878 xhr: function() { var xhr = jQuery.ajaxSettings.xhr(); if (!formdata && xhr.sendAsBinary) xhr.send = xhr.sendAsBinary; return xhr; },
|
|
8879 success: function(data){ ref.http_response(data); },
|
|
8880 error: function(o, status, err) { ref.http_error(o, status, err, null, 'attachment'); }
|
|
8881 });
|
|
8882 };
|
|
8883
|
|
8884 // get contents of all dropped files
|
|
8885 var last = this.env.filedrop.single ? 0 : files.length - 1;
|
|
8886 for (var j=0, i=0, f; j <= last && (f = files[i]); i++) {
|
|
8887 if (!f.name) f.name = f.fileName;
|
|
8888 if (!f.size) f.size = f.fileSize;
|
|
8889 if (!f.type) f.type = 'application/octet-stream';
|
|
8890
|
|
8891 // file name contains non-ASCII characters, do UTF8-binary string conversion.
|
|
8892 if (!formdata && /[^\x20-\x7E]/.test(f.name))
|
|
8893 f.name_bin = unescape(encodeURIComponent(f.name));
|
|
8894
|
|
8895 // filter by file type if requested
|
|
8896 if (this.env.filedrop.filter && !f.type.match(new RegExp(this.env.filedrop.filter))) {
|
|
8897 // TODO: show message to user
|
|
8898 continue;
|
|
8899 }
|
|
8900
|
|
8901 size += f.size;
|
|
8902 numfiles++;
|
|
8903
|
|
8904 // do it the easy way with FormData (FF 4+, Chrome 5+, Safari 5+)
|
|
8905 if (formdata) {
|
|
8906 formdata.append(fieldname, f);
|
|
8907 if (j == last)
|
|
8908 return submit_data();
|
|
8909 }
|
|
8910 // use FileReader supporetd by Firefox 3.6
|
|
8911 else if (window.FileReader) {
|
|
8912 var reader = new FileReader();
|
|
8913
|
|
8914 // closure to pass file properties to async callback function
|
|
8915 reader.onload = (function(file, j) {
|
|
8916 return function(e) {
|
|
8917 multipart += 'Content-Disposition: form-data; name="' + fieldname + '"';
|
|
8918 multipart += '; filename="' + (f.name_bin || file.name) + '"' + crlf;
|
|
8919 multipart += 'Content-Length: ' + file.size + crlf;
|
|
8920 multipart += 'Content-Type: ' + file.type + crlf + crlf;
|
|
8921 multipart += reader.result + crlf;
|
|
8922 multipart += dashdash + boundary + crlf;
|
|
8923
|
|
8924 if (j == last) // we're done, submit the data
|
|
8925 return submit_data();
|
|
8926 }
|
|
8927 })(f,j);
|
|
8928 reader.readAsBinaryString(f);
|
|
8929 }
|
|
8930 // Firefox 3
|
|
8931 else if (f.getAsBinary) {
|
|
8932 multipart += 'Content-Disposition: form-data; name="' + fieldname + '"';
|
|
8933 multipart += '; filename="' + (f.name_bin || f.name) + '"' + crlf;
|
|
8934 multipart += 'Content-Length: ' + f.size + crlf;
|
|
8935 multipart += 'Content-Type: ' + f.type + crlf + crlf;
|
|
8936 multipart += f.getAsBinary() + crlf;
|
|
8937 multipart += dashdash + boundary +crlf;
|
|
8938
|
|
8939 if (j == last)
|
|
8940 return submit_data();
|
|
8941 }
|
|
8942
|
|
8943 j++;
|
|
8944 }
|
|
8945 };
|
|
8946
|
|
8947 // starts interval for keep-alive signal
|
|
8948 this.start_keepalive = function()
|
|
8949 {
|
|
8950 if (!this.env.session_lifetime || this.env.framed || this.env.extwin || this.task == 'login' || this.env.action == 'print')
|
|
8951 return;
|
|
8952
|
|
8953 if (this._keepalive)
|
|
8954 clearInterval(this._keepalive);
|
|
8955
|
|
8956 // use Math to prevent from an integer overflow (#5273)
|
|
8957 // maximum interval is 15 minutes, minimum is 30 seconds
|
|
8958 var interval = Math.min(1800, this.env.session_lifetime) * 0.5 * 1000;
|
|
8959 this._keepalive = setInterval(function() { ref.keep_alive(); }, interval < 30000 ? 30000 : interval);
|
|
8960 };
|
|
8961
|
|
8962 // starts interval for refresh signal
|
|
8963 this.start_refresh = function()
|
|
8964 {
|
|
8965 if (!this.env.refresh_interval || this.env.framed || this.env.extwin || this.task == 'login' || this.env.action == 'print')
|
|
8966 return;
|
|
8967
|
|
8968 if (this._refresh)
|
|
8969 clearInterval(this._refresh);
|
|
8970
|
|
8971 this._refresh = setInterval(function(){ ref.refresh(); }, this.env.refresh_interval * 1000);
|
|
8972 };
|
|
8973
|
|
8974 // sends keep-alive signal
|
|
8975 this.keep_alive = function()
|
|
8976 {
|
|
8977 if (!this.busy)
|
|
8978 this.http_request('keep-alive');
|
|
8979 };
|
|
8980
|
|
8981 // sends refresh signal
|
|
8982 this.refresh = function()
|
|
8983 {
|
|
8984 if (this.busy) {
|
|
8985 // try again after 10 seconds
|
|
8986 setTimeout(function(){ ref.refresh(); ref.start_refresh(); }, 10000);
|
|
8987 return;
|
|
8988 }
|
|
8989
|
|
8990 var params = {}, lock = this.set_busy(true, 'refreshing');
|
|
8991
|
|
8992 if (this.task == 'mail' && this.gui_objects.mailboxlist)
|
|
8993 params = this.check_recent_params();
|
|
8994
|
|
8995 params._last = Math.floor(this.env.lastrefresh.getTime() / 1000);
|
|
8996 this.env.lastrefresh = new Date();
|
|
8997
|
|
8998 // plugins should bind to 'requestrefresh' event to add own params
|
|
8999 this.http_post('refresh', params, lock);
|
|
9000 };
|
|
9001
|
|
9002 // returns check-recent request parameters
|
|
9003 this.check_recent_params = function()
|
|
9004 {
|
|
9005 var params = {_mbox: this.env.mailbox};
|
|
9006
|
|
9007 if (this.gui_objects.mailboxlist)
|
|
9008 params._folderlist = 1;
|
|
9009 if (this.gui_objects.quotadisplay)
|
|
9010 params._quota = 1;
|
|
9011 if (this.env.search_request)
|
|
9012 params._search = this.env.search_request;
|
|
9013
|
|
9014 if (this.gui_objects.messagelist) {
|
|
9015 params._list = 1;
|
|
9016
|
|
9017 // message uids for flag updates check
|
|
9018 params._uids = $.map(this.message_list.rows, function(row, uid) { return uid; }).join(',');
|
|
9019 }
|
|
9020
|
|
9021 return params;
|
|
9022 };
|
|
9023
|
|
9024
|
|
9025 /********************************************************/
|
|
9026 /********* helper methods *********/
|
|
9027 /********************************************************/
|
|
9028
|
|
9029 /**
|
|
9030 * Quote html entities
|
|
9031 */
|
|
9032 this.quote_html = function(str)
|
|
9033 {
|
|
9034 return String(str).replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
9035 };
|
|
9036
|
|
9037 // get window.opener.rcmail if available
|
|
9038 this.opener = function(deep, filter)
|
|
9039 {
|
|
9040 var i, win = window.opener;
|
|
9041
|
|
9042 // catch Error: Permission denied to access property rcmail
|
|
9043 try {
|
|
9044 if (win && !win.closed) {
|
|
9045 // try parent of the opener window, e.g. preview frame
|
|
9046 if (deep && (!win.rcmail || win.rcmail.env.framed) && win.parent && win.parent.rcmail)
|
|
9047 win = win.parent;
|
|
9048
|
|
9049 if (win.rcmail && filter)
|
|
9050 for (i in filter)
|
|
9051 if (win.rcmail.env[i] != filter[i])
|
|
9052 return;
|
|
9053
|
|
9054 return win.rcmail;
|
|
9055 }
|
|
9056 }
|
|
9057 catch (e) {}
|
|
9058 };
|
|
9059
|
|
9060 // check if we're in show mode or if we have a unique selection
|
|
9061 // and return the message uid
|
|
9062 this.get_single_uid = function()
|
|
9063 {
|
|
9064 var uid = this.env.uid || (this.message_list ? this.message_list.get_single_selection() : null);
|
|
9065 var result = ref.triggerEvent('get_single_uid', { uid: uid });
|
|
9066 return result || uid;
|
|
9067 };
|
|
9068
|
|
9069 // same as above but for contacts
|
|
9070 this.get_single_cid = function()
|
|
9071 {
|
|
9072 var cid = this.env.cid || (this.contact_list ? this.contact_list.get_single_selection() : null);
|
|
9073 var result = ref.triggerEvent('get_single_cid', { cid: cid });
|
|
9074 return result || cid;
|
|
9075 };
|
|
9076
|
|
9077 // get the IMP mailbox of the message with the given UID
|
|
9078 this.get_message_mailbox = function(uid)
|
|
9079 {
|
|
9080 var msg = (this.env.messages && uid ? this.env.messages[uid] : null) || {};
|
|
9081 return msg.mbox || this.env.mailbox;
|
|
9082 };
|
|
9083
|
|
9084 // build request parameters from single message id (maybe with mailbox name)
|
|
9085 this.params_from_uid = function(uid, params)
|
|
9086 {
|
|
9087 if (!params)
|
|
9088 params = {};
|
|
9089
|
|
9090 params._uid = String(uid).split('-')[0];
|
|
9091 params._mbox = this.get_message_mailbox(uid);
|
|
9092
|
|
9093 return params;
|
|
9094 };
|
|
9095
|
|
9096 // gets cursor position
|
|
9097 this.get_caret_pos = function(obj)
|
|
9098 {
|
|
9099 if (obj.selectionEnd !== undefined)
|
|
9100 return obj.selectionEnd;
|
|
9101
|
|
9102 return obj.value.length;
|
|
9103 };
|
|
9104
|
|
9105 // moves cursor to specified position
|
|
9106 this.set_caret_pos = function(obj, pos)
|
|
9107 {
|
|
9108 try {
|
|
9109 if (obj.setSelectionRange)
|
|
9110 obj.setSelectionRange(pos, pos);
|
|
9111 }
|
|
9112 catch(e) {} // catch Firefox exception if obj is hidden
|
|
9113 };
|
|
9114
|
|
9115 // get selected text from an input field
|
|
9116 this.get_input_selection = function(obj)
|
|
9117 {
|
|
9118 var start = 0, end = 0, normalizedValue = '';
|
|
9119
|
|
9120 if (typeof obj.selectionStart == "number" && typeof obj.selectionEnd == "number") {
|
|
9121 normalizedValue = obj.value;
|
|
9122 start = obj.selectionStart;
|
|
9123 end = obj.selectionEnd;
|
|
9124 }
|
|
9125
|
|
9126 return {start: start, end: end, text: normalizedValue.substr(start, end-start)};
|
|
9127 };
|
|
9128
|
|
9129 // disable/enable all fields of a form
|
|
9130 this.lock_form = function(form, lock)
|
|
9131 {
|
|
9132 if (!form || !form.elements)
|
|
9133 return;
|
|
9134
|
|
9135 var n, len, elm;
|
|
9136
|
|
9137 if (lock)
|
|
9138 this.disabled_form_elements = [];
|
|
9139
|
|
9140 for (n=0, len=form.elements.length; n<len; n++) {
|
|
9141 elm = form.elements[n];
|
|
9142
|
|
9143 if (elm.type == 'hidden')
|
|
9144 continue;
|
|
9145 // remember which elem was disabled before lock
|
|
9146 if (lock && elm.disabled)
|
|
9147 this.disabled_form_elements.push(elm);
|
|
9148 else if (lock || $.inArray(elm, this.disabled_form_elements) < 0)
|
|
9149 elm.disabled = lock;
|
|
9150 }
|
|
9151 };
|
|
9152
|
|
9153 this.mailto_handler_uri = function()
|
|
9154 {
|
|
9155 return location.href.split('?')[0] + '?_task=mail&_action=compose&_to=%s';
|
|
9156 };
|
|
9157
|
|
9158 this.register_protocol_handler = function(name)
|
|
9159 {
|
|
9160 try {
|
|
9161 window.navigator.registerProtocolHandler('mailto', this.mailto_handler_uri(), name);
|
|
9162 }
|
|
9163 catch(e) {
|
|
9164 this.display_message(String(e), 'error');
|
|
9165 }
|
|
9166 };
|
|
9167
|
|
9168 this.check_protocol_handler = function(name, elem)
|
|
9169 {
|
|
9170 var nav = window.navigator;
|
|
9171
|
|
9172 if (!nav || (typeof nav.registerProtocolHandler != 'function')) {
|
|
9173 $(elem).addClass('disabled').click(function(){ return false; });
|
|
9174 }
|
|
9175 else if (typeof nav.isProtocolHandlerRegistered == 'function') {
|
|
9176 var status = nav.isProtocolHandlerRegistered('mailto', this.mailto_handler_uri());
|
|
9177 if (status)
|
|
9178 $(elem).parent().find('.mailtoprotohandler-status').html(status);
|
|
9179 }
|
|
9180 else {
|
|
9181 $(elem).click(function() { ref.register_protocol_handler(name); return false; });
|
|
9182 }
|
|
9183 };
|
|
9184
|
|
9185 // Checks browser capabilities eg. PDF support, TIF support
|
|
9186 this.browser_capabilities_check = function()
|
|
9187 {
|
|
9188 if (!this.env.browser_capabilities)
|
|
9189 this.env.browser_capabilities = {};
|
|
9190
|
|
9191 $.each(['pdf', 'flash', 'tiff', 'webp'], function() {
|
|
9192 if (ref.env.browser_capabilities[this] === undefined)
|
|
9193 ref.env.browser_capabilities[this] = ref[this + '_support_check']();
|
|
9194 });
|
|
9195 };
|
|
9196
|
|
9197 // Returns browser capabilities string
|
|
9198 this.browser_capabilities = function()
|
|
9199 {
|
|
9200 if (!this.env.browser_capabilities)
|
|
9201 return '';
|
|
9202
|
|
9203 var n, ret = [];
|
|
9204
|
|
9205 for (n in this.env.browser_capabilities)
|
|
9206 ret.push(n + '=' + this.env.browser_capabilities[n]);
|
|
9207
|
|
9208 return ret.join();
|
|
9209 };
|
|
9210
|
|
9211 this.tiff_support_check = function()
|
|
9212 {
|
|
9213 this.image_support_check('tiff');
|
|
9214 return 0;
|
|
9215 };
|
|
9216
|
|
9217 this.webp_support_check = function()
|
|
9218 {
|
|
9219 this.image_support_check('webp');
|
|
9220 return 0;
|
|
9221 };
|
|
9222
|
|
9223 this.image_support_check = function(type)
|
|
9224 {
|
|
9225 window.setTimeout(function() {
|
|
9226 var img = new Image();
|
|
9227 img.onload = function() { ref.env.browser_capabilities[type] = 1; };
|
|
9228 img.onerror = function() { ref.env.browser_capabilities[type] = 0; };
|
|
9229 img.src = ref.assets_path('program/resources/blank.' + type);
|
|
9230 }, 10);
|
|
9231 };
|
|
9232
|
|
9233 this.pdf_support_check = function()
|
|
9234 {
|
|
9235 var i, plugin = navigator.mimeTypes ? navigator.mimeTypes["application/pdf"] : {},
|
|
9236 plugins = navigator.plugins,
|
|
9237 len = plugins.length,
|
|
9238 regex = /Adobe Reader|PDF|Acrobat/i;
|
|
9239
|
|
9240 if (plugin && plugin.enabledPlugin)
|
|
9241 return 1;
|
|
9242
|
|
9243 if ('ActiveXObject' in window) {
|
|
9244 try {
|
|
9245 if (plugin = new ActiveXObject("AcroPDF.PDF"))
|
|
9246 return 1;
|
|
9247 }
|
|
9248 catch (e) {}
|
|
9249 try {
|
|
9250 if (plugin = new ActiveXObject("PDF.PdfCtrl"))
|
|
9251 return 1;
|
|
9252 }
|
|
9253 catch (e) {}
|
|
9254 }
|
|
9255
|
|
9256 for (i=0; i<len; i++) {
|
|
9257 plugin = plugins[i];
|
|
9258 if (typeof plugin === 'String') {
|
|
9259 if (regex.test(plugin))
|
|
9260 return 1;
|
|
9261 }
|
|
9262 else if (plugin.name && regex.test(plugin.name))
|
|
9263 return 1;
|
|
9264 }
|
|
9265
|
|
9266 window.setTimeout(function() {
|
|
9267 $('<object>').attr({
|
|
9268 data: ref.assets_path('program/resources/dummy.pdf'),
|
|
9269 type: 'application/pdf',
|
|
9270 style: 'position: "absolute"; top: -1000px; height: 1px; width: 1px'
|
|
9271 })
|
|
9272 .on('load error', function(e) {
|
|
9273 ref.env.browser_capabilities.pdf = e.type == 'load' ? 1 : 0;
|
|
9274 $(this).remove();
|
|
9275 })
|
|
9276 .appendTo($('body'));
|
|
9277 }, 10);
|
|
9278
|
|
9279 return 0;
|
|
9280 };
|
|
9281
|
|
9282 this.flash_support_check = function()
|
|
9283 {
|
|
9284 var plugin = navigator.mimeTypes ? navigator.mimeTypes["application/x-shockwave-flash"] : {};
|
|
9285
|
|
9286 if (plugin && plugin.enabledPlugin)
|
|
9287 return 1;
|
|
9288
|
|
9289 if ('ActiveXObject' in window) {
|
|
9290 try {
|
|
9291 if (plugin = new ActiveXObject("ShockwaveFlash.ShockwaveFlash"))
|
|
9292 return 1;
|
|
9293 }
|
|
9294 catch (e) {}
|
|
9295 }
|
|
9296
|
|
9297 return 0;
|
|
9298 };
|
|
9299
|
|
9300 this.assets_path = function(path)
|
|
9301 {
|
|
9302 if (this.env.assets_path && !path.startsWith(this.env.assets_path)) {
|
|
9303 path = this.env.assets_path + path;
|
|
9304 }
|
|
9305
|
|
9306 return path;
|
|
9307 };
|
|
9308
|
|
9309 // Cookie setter
|
|
9310 this.set_cookie = function(name, value, expires)
|
|
9311 {
|
|
9312 setCookie(name, value, expires, this.env.cookie_path, this.env.cookie_domain, this.env.cookie_secure);
|
|
9313 };
|
|
9314
|
|
9315 this.get_local_storage_prefix = function()
|
|
9316 {
|
|
9317 if (!this.local_storage_prefix)
|
|
9318 this.local_storage_prefix = 'roundcube.' + (this.env.user_id || 'anonymous') + '.';
|
|
9319
|
|
9320 return this.local_storage_prefix;
|
|
9321 };
|
|
9322
|
|
9323 // wrapper for localStorage.getItem(key)
|
|
9324 this.local_storage_get_item = function(key, deflt, encrypted)
|
|
9325 {
|
|
9326 var item, result;
|
|
9327
|
|
9328 // TODO: add encryption
|
|
9329 try {
|
|
9330 item = localStorage.getItem(this.get_local_storage_prefix() + key);
|
|
9331 result = JSON.parse(item);
|
|
9332 }
|
|
9333 catch (e) { }
|
|
9334
|
|
9335 return result || deflt || null;
|
|
9336 };
|
|
9337
|
|
9338 // wrapper for localStorage.setItem(key, data)
|
|
9339 this.local_storage_set_item = function(key, data, encrypted)
|
|
9340 {
|
|
9341 // try/catch to handle no localStorage support, but also error
|
|
9342 // in Safari-in-private-browsing-mode where localStorage exists
|
|
9343 // but can't be used (#1489996)
|
|
9344 try {
|
|
9345 // TODO: add encryption
|
|
9346 localStorage.setItem(this.get_local_storage_prefix() + key, JSON.stringify(data));
|
|
9347 return true;
|
|
9348 }
|
|
9349 catch (e) {
|
|
9350 return false;
|
|
9351 }
|
|
9352 };
|
|
9353
|
|
9354 // wrapper for localStorage.removeItem(key)
|
|
9355 this.local_storage_remove_item = function(key)
|
|
9356 {
|
|
9357 try {
|
|
9358 localStorage.removeItem(this.get_local_storage_prefix() + key);
|
|
9359 return true;
|
|
9360 }
|
|
9361 catch (e) {
|
|
9362 return false;
|
|
9363 }
|
|
9364 };
|
|
9365
|
|
9366 this.print_dialog = function()
|
|
9367 {
|
|
9368 if (bw.safari)
|
|
9369 setTimeout('window.print()', 10);
|
|
9370 else
|
|
9371 window.print();
|
|
9372 };
|
|
9373 } // end object rcube_webmail
|
|
9374
|
|
9375
|
|
9376 // some static methods
|
|
9377 rcube_webmail.long_subject_title = function(elem, indent)
|
|
9378 {
|
|
9379 if (!elem.title) {
|
|
9380 var $elem = $(elem);
|
|
9381 if ($elem.width() + (indent || 0) * 15 > $elem.parent().width())
|
|
9382 elem.title = rcube_webmail.subject_text(elem);
|
|
9383 }
|
|
9384 };
|
|
9385
|
|
9386 rcube_webmail.long_subject_title_ex = function(elem)
|
|
9387 {
|
|
9388 if (!elem.title) {
|
|
9389 var $elem = $(elem),
|
|
9390 txt = $.trim($elem.text()),
|
|
9391 tmp = $('<span>').text(txt)
|
|
9392 .css({'position': 'absolute', 'float': 'left', 'visibility': 'hidden',
|
|
9393 'font-size': $elem.css('font-size'), 'font-weight': $elem.css('font-weight')})
|
|
9394 .appendTo($('body')),
|
|
9395 w = tmp.width();
|
|
9396
|
|
9397 tmp.remove();
|
|
9398 if (w + $('span.branch', $elem).width() * 15 > $elem.width())
|
|
9399 elem.title = rcube_webmail.subject_text(elem);
|
|
9400 }
|
|
9401 };
|
|
9402
|
|
9403 rcube_webmail.subject_text = function(elem)
|
|
9404 {
|
|
9405 var t = $(elem).clone();
|
|
9406 t.find('.skip-on-drag,.skip-content,.voice').remove();
|
|
9407 return t.text();
|
|
9408 };
|
|
9409
|
|
9410 // set event handlers on all iframe elements (and their contents)
|
|
9411 rcube_webmail.set_iframe_events = function(events)
|
|
9412 {
|
|
9413 $('iframe').each(function() {
|
|
9414 var frame = $(this);
|
|
9415 $.each(events, function(event_name, event_handler) {
|
|
9416 frame.on('load', function(e) {
|
|
9417 try { $(this).contents().on(event_name, event_handler); }
|
|
9418 catch (e) {/* catch possible permission error in IE */ }
|
|
9419 });
|
|
9420
|
|
9421 try { frame.contents().on(event_name, event_handler); }
|
|
9422 catch (e) {/* catch possible permission error in IE */ }
|
|
9423 });
|
|
9424 });
|
|
9425 };
|
|
9426
|
|
9427 rcube_webmail.prototype.get_cookie = getCookie;
|
|
9428
|
|
9429 // copy event engine prototype
|
|
9430 rcube_webmail.prototype.addEventListener = rcube_event_engine.prototype.addEventListener;
|
|
9431 rcube_webmail.prototype.removeEventListener = rcube_event_engine.prototype.removeEventListener;
|
|
9432 rcube_webmail.prototype.triggerEvent = rcube_event_engine.prototype.triggerEvent;
|