Mercurial > hg > rc2
comparison program/js/app.js @ 0:4681f974d28b
vanilla 1.3.3 distro, I hope
author | Charlie Root |
---|---|
date | Thu, 04 Jan 2018 15:52:31 -0500 |
parents | |
children | 0cac1d1e799f |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4681f974d28b |
---|---|
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']}, | |
224 {className: 'subject', cells: ['fromto', 'date', 'status', 'subject']}, | |
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; |