/program/js/app.js

https://bitbucket.org/gencer/roundcubemail · JavaScript · 8051 lines · 6584 code · 922 blank · 545 comment · 1733 complexity · f366bf2993d9b6e49680930ff75594b8 MD5 · raw file

Large files are truncated click here to view the full file

  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-2014, The Roundcube Dev Team
  10. * Copyright (C) 2011-2014, 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. function rcube_webmail()
  36. {
  37. this.labels = {};
  38. this.buttons = {};
  39. this.buttons_sel = {};
  40. this.gui_objects = {};
  41. this.gui_containers = {};
  42. this.commands = {};
  43. this.command_handlers = {};
  44. this.onloads = [];
  45. this.messages = {};
  46. this.group2expand = {};
  47. this.http_request_jobs = {};
  48. this.menu_stack = [];
  49. // webmail client settings
  50. this.dblclick_time = 500;
  51. this.message_time = 5000;
  52. this.identifier_expr = /[^0-9a-z_-]/gi;
  53. // environment defaults
  54. this.env = {
  55. request_timeout: 180, // seconds
  56. draft_autosave: 0, // seconds
  57. comm_path: './',
  58. blankpage: 'program/resources/blank.gif',
  59. recipients_separator: ',',
  60. recipients_delimiter: ', ',
  61. popup_width: 1150,
  62. popup_width_small: 900
  63. };
  64. // create protected reference to myself
  65. this.ref = 'rcmail';
  66. var ref = this;
  67. // set jQuery ajax options
  68. $.ajaxSetup({
  69. cache: false,
  70. timeout: this.env.request_timeout * 1000,
  71. error: function(request, status, err){ ref.http_error(request, status, err); },
  72. beforeSend: function(xmlhttp){ xmlhttp.setRequestHeader('X-Roundcube-Request', ref.env.request_token); }
  73. });
  74. // unload fix
  75. $(window).bind('beforeunload', function() { ref.unload = true; });
  76. // set environment variable(s)
  77. this.set_env = function(p, value)
  78. {
  79. if (p != null && typeof p === 'object' && !value)
  80. for (var n in p)
  81. this.env[n] = p[n];
  82. else
  83. this.env[p] = value;
  84. };
  85. // add a localized label to the client environment
  86. this.add_label = function(p, value)
  87. {
  88. if (typeof p == 'string')
  89. this.labels[p] = value;
  90. else if (typeof p == 'object')
  91. $.extend(this.labels, p);
  92. };
  93. // add a button to the button list
  94. this.register_button = function(command, id, type, act, sel, over)
  95. {
  96. var button_prop = {id:id, type:type};
  97. if (act) button_prop.act = act;
  98. if (sel) button_prop.sel = sel;
  99. if (over) button_prop.over = over;
  100. if (!this.buttons[command])
  101. this.buttons[command] = [];
  102. this.buttons[command].push(button_prop);
  103. if (this.loaded)
  104. init_button(command, button_prop);
  105. };
  106. // register a specific gui object
  107. this.gui_object = function(name, id)
  108. {
  109. this.gui_objects[name] = this.loaded ? rcube_find_object(id) : id;
  110. };
  111. // register a container object
  112. this.gui_container = function(name, id)
  113. {
  114. this.gui_containers[name] = id;
  115. };
  116. // add a GUI element (html node) to a specified container
  117. this.add_element = function(elm, container)
  118. {
  119. if (this.gui_containers[container] && this.gui_containers[container].jquery)
  120. this.gui_containers[container].append(elm);
  121. };
  122. // register an external handler for a certain command
  123. this.register_command = function(command, callback, enable)
  124. {
  125. this.command_handlers[command] = callback;
  126. if (enable)
  127. this.enable_command(command, true);
  128. };
  129. // execute the given script on load
  130. this.add_onload = function(f)
  131. {
  132. this.onloads.push(f);
  133. };
  134. // initialize webmail client
  135. this.init = function()
  136. {
  137. var n;
  138. this.task = this.env.task;
  139. // check browser
  140. if (this.env.server_error != 409 && (!bw.dom || !bw.xmlhttp_test() || (bw.mz && bw.vendver < 1.9) || (bw.ie && bw.vendver < 7))) {
  141. this.goto_url('error', '_code=0x199');
  142. return;
  143. }
  144. // find all registered gui containers
  145. for (n in this.gui_containers)
  146. this.gui_containers[n] = $('#'+this.gui_containers[n]);
  147. // find all registered gui objects
  148. for (n in this.gui_objects)
  149. this.gui_objects[n] = rcube_find_object(this.gui_objects[n]);
  150. // clickjacking protection
  151. if (this.env.x_frame_options) {
  152. try {
  153. // bust frame if not allowed
  154. if (this.env.x_frame_options == 'deny' && top.location.href != self.location.href)
  155. top.location.href = self.location.href;
  156. else if (top.location.hostname != self.location.hostname)
  157. throw 1;
  158. } catch (e) {
  159. // possible clickjacking attack: disable all form elements
  160. $('form').each(function(){ ref.lock_form(this, true); });
  161. this.display_message("Blocked: possible clickjacking attack!", 'error');
  162. return;
  163. }
  164. }
  165. // init registered buttons
  166. this.init_buttons();
  167. // tell parent window that this frame is loaded
  168. if (this.is_framed()) {
  169. parent.rcmail.set_busy(false, null, parent.rcmail.env.frame_lock);
  170. parent.rcmail.env.frame_lock = null;
  171. }
  172. // enable general commands
  173. this.enable_command('close', 'logout', 'mail', 'addressbook', 'settings', 'save-pref',
  174. 'compose', 'undo', 'about', 'switch-task', 'menu-open', 'menu-close', 'menu-save', true);
  175. // set active task button
  176. this.set_button(this.task, 'sel');
  177. if (this.env.permaurl)
  178. this.enable_command('permaurl', 'extwin', true);
  179. switch (this.task) {
  180. case 'mail':
  181. // enable mail commands
  182. this.enable_command('list', 'checkmail', 'add-contact', 'search', 'reset-search', 'collapse-folder', 'import-messages', true);
  183. if (this.gui_objects.messagelist) {
  184. this.message_list = new rcube_list_widget(this.gui_objects.messagelist, {
  185. multiselect:true, multiexpand:true, draggable:true, keyboard:true,
  186. column_movable:this.env.col_movable, dblclick_time:this.dblclick_time
  187. });
  188. this.message_list
  189. .addEventListener('initrow', function(o) { ref.init_message_row(o); })
  190. .addEventListener('dblclick', function(o) { ref.msglist_dbl_click(o); })
  191. .addEventListener('click', function(o) { ref.msglist_click(o); })
  192. .addEventListener('keypress', function(o) { ref.msglist_keypress(o); })
  193. .addEventListener('select', function(o) { ref.msglist_select(o); })
  194. .addEventListener('dragstart', function(o) { ref.drag_start(o); })
  195. .addEventListener('dragmove', function(e) { ref.drag_move(e); })
  196. .addEventListener('dragend', function(e) { ref.drag_end(e); })
  197. .addEventListener('expandcollapse', function(o) { ref.msglist_expand(o); })
  198. .addEventListener('column_replace', function(o) { ref.msglist_set_coltypes(o); })
  199. .addEventListener('listupdate', function(o) { ref.triggerEvent('listupdate', o); })
  200. .init();
  201. // TODO: this should go into the list-widget code
  202. $(this.message_list.thead).on('click', 'a.sortcol', function(e){
  203. return ref.command('sort', $(this).attr('rel'), this);
  204. });
  205. this.enable_command('toggle_status', 'toggle_flag', 'sort', true);
  206. this.enable_command('set-listmode', this.env.threads && !this.is_multifolder_listing());
  207. // load messages
  208. this.command('list');
  209. $(this.gui_objects.qsearchbox).val(this.env.search_text).focusin(function() { ref.message_list.blur(); });
  210. }
  211. this.set_button_titles();
  212. this.env.message_commands = ['show', 'reply', 'reply-all', 'reply-list',
  213. 'move', 'copy', 'delete', 'open', 'mark', 'edit', 'viewsource',
  214. 'print', 'load-attachment', 'download-attachment', 'show-headers', 'hide-headers', 'download',
  215. 'forward', 'forward-inline', 'forward-attachment', 'change-format'];
  216. if (this.env.action == 'show' || this.env.action == 'preview') {
  217. this.enable_command(this.env.message_commands, this.env.uid);
  218. this.enable_command('reply-list', this.env.list_post);
  219. if (this.env.action == 'show') {
  220. this.http_request('pagenav', {_uid: this.env.uid, _mbox: this.env.mailbox, _search: this.env.search_request},
  221. this.display_message('', 'loading'));
  222. }
  223. if (this.env.blockedobjects) {
  224. if (this.gui_objects.remoteobjectsmsg)
  225. this.gui_objects.remoteobjectsmsg.style.display = 'block';
  226. this.enable_command('load-images', 'always-load', true);
  227. }
  228. // make preview/message frame visible
  229. if (this.env.action == 'preview' && this.is_framed()) {
  230. this.enable_command('compose', 'add-contact', false);
  231. parent.rcmail.show_contentframe(true);
  232. }
  233. }
  234. else if (this.env.action == 'compose') {
  235. this.env.address_group_stack = [];
  236. this.env.compose_commands = ['send-attachment', 'remove-attachment', 'send', 'cancel',
  237. 'toggle-editor', 'list-adresses', 'pushgroup', 'search', 'reset-search', 'extwin',
  238. 'insert-response', 'save-response', 'menu-open', 'menu-close'];
  239. if (this.env.drafts_mailbox)
  240. this.env.compose_commands.push('savedraft')
  241. this.enable_command(this.env.compose_commands, 'identities', 'responses', true);
  242. // add more commands (not enabled)
  243. $.merge(this.env.compose_commands, ['add-recipient', 'firstpage', 'previouspage', 'nextpage', 'lastpage']);
  244. if (window.googie) {
  245. this.env.editor_config.spellchecker = googie;
  246. this.env.editor_config.spellcheck_observer = function(s) { ref.spellcheck_state(); };
  247. this.env.compose_commands.push('spellcheck')
  248. this.enable_command('spellcheck', true);
  249. }
  250. // initialize HTML editor
  251. this.editor_init(this.env.editor_config, this.env.composebody);
  252. // init canned response functions
  253. if (this.gui_objects.responseslist) {
  254. $('a.insertresponse', this.gui_objects.responseslist)
  255. .attr('unselectable', 'on')
  256. .mousedown(function(e){ return rcube_event.cancel(e); })
  257. .bind('mouseup keypress', function(e){
  258. if (e.type == 'mouseup' || rcube_event.get_keycode(e) == 13) {
  259. ref.command('insert-response', $(this).attr('rel'));
  260. $(document.body).trigger('mouseup'); // hides the menu
  261. return rcube_event.cancel(e);
  262. }
  263. });
  264. // avoid textarea loosing focus when hitting the save-response button/link
  265. $.each(this.buttons['save-response'] || [], function (i, v) {
  266. $('#' + v.id).mousedown(function(e){ return rcube_event.cancel(e); })
  267. });
  268. }
  269. // init message compose form
  270. this.init_messageform();
  271. }
  272. else if (this.env.action == 'get')
  273. this.enable_command('download', 'print', true);
  274. // show printing dialog
  275. else if (this.env.action == 'print' && this.env.uid) {
  276. if (bw.safari)
  277. setTimeout('window.print()', 10);
  278. else
  279. window.print();
  280. }
  281. // get unread count for each mailbox
  282. if (this.gui_objects.mailboxlist) {
  283. this.env.unread_counts = {};
  284. this.gui_objects.folderlist = this.gui_objects.mailboxlist;
  285. this.http_request('getunread');
  286. }
  287. // init address book widget
  288. if (this.gui_objects.contactslist) {
  289. this.contact_list = new rcube_list_widget(this.gui_objects.contactslist,
  290. { multiselect:true, draggable:false, keyboard:true });
  291. this.contact_list
  292. .addEventListener('initrow', function(o) { ref.triggerEvent('insertrow', { cid:o.uid, row:o }); })
  293. .addEventListener('select', function(o) { ref.compose_recipient_select(o); })
  294. .addEventListener('dblclick', function(o) { ref.compose_add_recipient(); })
  295. .addEventListener('keypress', function(o) {
  296. if (o.key_pressed == o.ENTER_KEY) {
  297. if (!ref.compose_add_recipient()) {
  298. // execute link action on <enter> if not a recipient entry
  299. if (o.last_selected && String(o.last_selected).charAt(0) == 'G') {
  300. $(o.rows[o.last_selected].obj).find('a').first().click();
  301. }
  302. }
  303. }
  304. })
  305. .init();
  306. // remember last focused address field
  307. $('#_to,#_cc,#_bcc').focus(function() { ref.env.focused_field = this; });
  308. }
  309. if (this.gui_objects.addressbookslist) {
  310. this.gui_objects.folderlist = this.gui_objects.addressbookslist;
  311. this.enable_command('list-adresses', true);
  312. }
  313. // ask user to send MDN
  314. if (this.env.mdn_request && this.env.uid) {
  315. var postact = 'sendmdn',
  316. postdata = {_uid: this.env.uid, _mbox: this.env.mailbox};
  317. if (!confirm(this.get_label('mdnrequest'))) {
  318. postdata._flag = 'mdnsent';
  319. postact = 'mark';
  320. }
  321. this.http_post(postact, postdata);
  322. }
  323. // detect browser capabilities
  324. if (!this.is_framed() && !this.env.extwin)
  325. this.browser_capabilities_check();
  326. break;
  327. case 'addressbook':
  328. this.env.address_group_stack = [];
  329. if (this.gui_objects.folderlist)
  330. this.env.contactfolders = $.extend($.extend({}, this.env.address_sources), this.env.contactgroups);
  331. this.enable_command('add', 'import', this.env.writable_source);
  332. this.enable_command('list', 'listgroup', 'pushgroup', 'popgroup', 'listsearch', 'search', 'reset-search', 'advanced-search', true);
  333. if (this.gui_objects.contactslist) {
  334. this.contact_list = new rcube_list_widget(this.gui_objects.contactslist,
  335. {multiselect:true, draggable:this.gui_objects.folderlist?true:false, keyboard:true});
  336. this.contact_list
  337. .addEventListener('initrow', function(o) { ref.triggerEvent('insertrow', { cid:o.uid, row:o }); })
  338. .addEventListener('keypress', function(o) { ref.contactlist_keypress(o); })
  339. .addEventListener('select', function(o) { ref.contactlist_select(o); })
  340. .addEventListener('dragstart', function(o) { ref.drag_start(o); })
  341. .addEventListener('dragmove', function(e) { ref.drag_move(e); })
  342. .addEventListener('dragend', function(e) { ref.drag_end(e); })
  343. .init();
  344. $(this.gui_objects.qsearchbox).focusin(function() { ref.contact_list.blur(); });
  345. this.update_group_commands();
  346. this.command('list');
  347. }
  348. if (this.gui_objects.savedsearchlist) {
  349. this.savedsearchlist = new rcube_treelist_widget(this.gui_objects.savedsearchlist, {
  350. id_prefix: 'rcmli',
  351. id_encode: this.html_identifier_encode,
  352. id_decode: this.html_identifier_decode
  353. });
  354. this.savedsearchlist.addEventListener('select', function(node) {
  355. ref.triggerEvent('selectfolder', { folder:node.id, prefix:'rcmli' }); });
  356. }
  357. this.set_page_buttons();
  358. if (this.env.cid) {
  359. this.enable_command('show', 'edit', true);
  360. // register handlers for group assignment via checkboxes
  361. if (this.gui_objects.editform) {
  362. $('input.groupmember').change(function() {
  363. ref.group_member_change(this.checked ? 'add' : 'del', ref.env.cid, ref.env.source, this.value);
  364. });
  365. }
  366. }
  367. if (this.gui_objects.editform) {
  368. this.enable_command('save', true);
  369. if (this.env.action == 'add' || this.env.action == 'edit' || this.env.action == 'search')
  370. this.init_contact_form();
  371. }
  372. break;
  373. case 'settings':
  374. this.enable_command('preferences', 'identities', 'responses', 'save', 'folders', true);
  375. if (this.env.action == 'identities') {
  376. this.enable_command('add', this.env.identities_level < 2);
  377. }
  378. else if (this.env.action == 'edit-identity' || this.env.action == 'add-identity') {
  379. this.enable_command('save', 'edit', 'toggle-editor', true);
  380. this.enable_command('delete', this.env.identities_level < 2);
  381. // initialize HTML editor
  382. this.editor_init(this.env.editor_config, 'rcmfd_signature');
  383. }
  384. else if (this.env.action == 'folders') {
  385. this.enable_command('subscribe', 'unsubscribe', 'create-folder', 'rename-folder', true);
  386. }
  387. else if (this.env.action == 'edit-folder' && this.gui_objects.editform) {
  388. this.enable_command('save', 'folder-size', true);
  389. parent.rcmail.env.exists = this.env.messagecount;
  390. parent.rcmail.enable_command('purge', this.env.messagecount);
  391. }
  392. else if (this.env.action == 'responses') {
  393. this.enable_command('add', true);
  394. }
  395. if (this.gui_objects.identitieslist) {
  396. this.identity_list = new rcube_list_widget(this.gui_objects.identitieslist,
  397. {multiselect:false, draggable:false, keyboard:true});
  398. this.identity_list
  399. .addEventListener('select', function(o) { ref.identity_select(o); })
  400. .addEventListener('keypress', function(o) {
  401. if (o.key_pressed == o.ENTER_KEY) {
  402. ref.identity_select(o);
  403. }
  404. })
  405. .init()
  406. .focus();
  407. }
  408. else if (this.gui_objects.sectionslist) {
  409. this.sections_list = new rcube_list_widget(this.gui_objects.sectionslist, {multiselect:false, draggable:false, keyboard:true});
  410. this.sections_list
  411. .addEventListener('select', function(o) { ref.section_select(o); })
  412. .addEventListener('keypress', function(o) { if (o.key_pressed == o.ENTER_KEY) ref.section_select(o); })
  413. .init()
  414. .focus();
  415. }
  416. else if (this.gui_objects.subscriptionlist) {
  417. this.init_subscription_list();
  418. }
  419. else if (this.gui_objects.responseslist) {
  420. this.responses_list = new rcube_list_widget(this.gui_objects.responseslist, {multiselect:false, draggable:false, keyboard:true});
  421. this.responses_list
  422. .addEventListener('select', function(list) {
  423. var win, id = list.get_single_selection();
  424. ref.enable_command('delete', !!id && $.inArray(id, ref.env.readonly_responses) < 0);
  425. if (id && (win = ref.get_frame_window(ref.env.contentframe))) {
  426. ref.set_busy(true);
  427. ref.location_href({ _action:'edit-response', _key:id, _framed:1 }, win);
  428. }
  429. })
  430. .init()
  431. .focus();
  432. }
  433. break;
  434. case 'login':
  435. var input_user = $('#rcmloginuser');
  436. input_user.bind('keyup', function(e){ return ref.login_user_keyup(e); });
  437. if (input_user.val() == '')
  438. input_user.focus();
  439. else
  440. $('#rcmloginpwd').focus();
  441. // detect client timezone
  442. if (window.jstz) {
  443. var timezone = jstz.determine();
  444. if (timezone.name())
  445. $('#rcmlogintz').val(timezone.name());
  446. }
  447. else {
  448. $('#rcmlogintz').val(new Date().getStdTimezoneOffset() / -60);
  449. }
  450. // display 'loading' message on form submit, lock submit button
  451. $('form').submit(function () {
  452. $('input[type=submit]', this).prop('disabled', true);
  453. ref.clear_messages();
  454. ref.display_message('', 'loading');
  455. });
  456. this.enable_command('login', true);
  457. break;
  458. }
  459. // select first input field in an edit form
  460. if (this.gui_objects.editform)
  461. $("input,select,textarea", this.gui_objects.editform)
  462. .not(':hidden').not(':disabled').first().select().focus();
  463. // unset contentframe variable if preview_pane is enabled
  464. if (this.env.contentframe && !$('#' + this.env.contentframe).is(':visible'))
  465. this.env.contentframe = null;
  466. // prevent from form submit with Enter key in file input fields
  467. if (bw.ie)
  468. $('input[type=file]').keydown(function(e) { if (e.keyCode == '13') e.preventDefault(); });
  469. // flag object as complete
  470. this.loaded = true;
  471. this.env.lastrefresh = new Date();
  472. // show message
  473. if (this.pending_message)
  474. this.display_message(this.pending_message[0], this.pending_message[1], this.pending_message[2]);
  475. // init treelist widget
  476. if (this.gui_objects.folderlist && window.rcube_treelist_widget) {
  477. this.treelist = new rcube_treelist_widget(this.gui_objects.folderlist, {
  478. selectable: true,
  479. id_prefix: 'rcmli',
  480. id_encode: this.html_identifier_encode,
  481. id_decode: this.html_identifier_decode,
  482. check_droptarget: function(node) { return !node.virtual && ref.check_droptarget(node.id) }
  483. });
  484. this.treelist
  485. .addEventListener('collapse', function(node) { ref.folder_collapsed(node) })
  486. .addEventListener('expand', function(node) { ref.folder_collapsed(node) })
  487. .addEventListener('select', function(node) { ref.triggerEvent('selectfolder', { folder:node.id, prefix:'rcmli' }) });
  488. }
  489. // activate html5 file drop feature (if browser supports it and if configured)
  490. if (this.gui_objects.filedrop && this.env.filedrop && ((window.XMLHttpRequest && XMLHttpRequest.prototype && XMLHttpRequest.prototype.sendAsBinary) || window.FormData)) {
  491. $(document.body).bind('dragover dragleave drop', function(e){ return ref.document_drag_hover(e, e.type == 'dragover'); });
  492. $(this.gui_objects.filedrop).addClass('droptarget')
  493. .bind('dragover dragleave', function(e){ return ref.file_drag_hover(e, e.type == 'dragover'); })
  494. .get(0).addEventListener('drop', function(e){ return ref.file_dropped(e); }, false);
  495. }
  496. // catch document (and iframe) mouse clicks
  497. var body_mouseup = function(e){ return ref.doc_mouse_up(e); };
  498. $(document.body)
  499. .bind('mouseup', body_mouseup)
  500. .bind('keydown', function(e){ return ref.doc_keypress(e); });
  501. $('iframe').load(function(e) {
  502. try { $(this.contentDocument || this.contentWindow).on('mouseup', body_mouseup); }
  503. catch (e) {/* catch possible "Permission denied" error in IE */ }
  504. })
  505. .contents().on('mouseup', body_mouseup);
  506. // trigger init event hook
  507. this.triggerEvent('init', { task:this.task, action:this.env.action });
  508. // execute all foreign onload scripts
  509. // @deprecated
  510. for (n in this.onloads) {
  511. if (typeof this.onloads[n] === 'string')
  512. eval(this.onloads[n]);
  513. else if (typeof this.onloads[n] === 'function')
  514. this.onloads[n]();
  515. }
  516. // start keep-alive and refresh intervals
  517. this.start_refresh();
  518. this.start_keepalive();
  519. };
  520. this.log = function(msg)
  521. {
  522. if (window.console && console.log)
  523. console.log(msg);
  524. };
  525. /*********************************************************/
  526. /********* client command interface *********/
  527. /*********************************************************/
  528. // execute a specific command on the web client
  529. this.command = function(command, props, obj, event)
  530. {
  531. var ret, uid, cid, url, flag, aborted = false;
  532. if (obj && obj.blur && !(event && rcube_event.is_keyboard(event)))
  533. obj.blur();
  534. // do nothing if interface is locked by other command (with exception for searching reset)
  535. if (this.busy && !(command == 'reset-search' && this.last_command == 'search'))
  536. return false;
  537. // let the browser handle this click (shift/ctrl usually opens the link in a new window/tab)
  538. if ((obj && obj.href && String(obj.href).indexOf('#') < 0) && rcube_event.get_modifier(event)) {
  539. return true;
  540. }
  541. // command not supported or allowed
  542. if (!this.commands[command]) {
  543. // pass command to parent window
  544. if (this.is_framed())
  545. parent.rcmail.command(command, props);
  546. return false;
  547. }
  548. // check input before leaving compose step
  549. if (this.task == 'mail' && this.env.action == 'compose' && $.inArray(command, this.env.compose_commands) < 0 && !this.env.server_error) {
  550. if (this.cmp_hash != this.compose_field_hash() && !confirm(this.get_label('notsentwarning')))
  551. return false;
  552. // remove copy from local storage if compose screen is left intentionally
  553. this.remove_compose_data(this.env.compose_id);
  554. this.compose_skip_unsavedcheck = true;
  555. }
  556. this.last_command = command;
  557. // process external commands
  558. if (typeof this.command_handlers[command] === 'function') {
  559. ret = this.command_handlers[command](props, obj, event);
  560. return ret !== undefined ? ret : (obj ? false : true);
  561. }
  562. else if (typeof this.command_handlers[command] === 'string') {
  563. ret = window[this.command_handlers[command]](props, obj, event);
  564. return ret !== undefined ? ret : (obj ? false : true);
  565. }
  566. // trigger plugin hooks
  567. this.triggerEvent('actionbefore', {props:props, action:command, originalEvent:event});
  568. ret = this.triggerEvent('before'+command, props || event);
  569. if (ret !== undefined) {
  570. // abort if one of the handlers returned false
  571. if (ret === false)
  572. return false;
  573. else
  574. props = ret;
  575. }
  576. ret = undefined;
  577. // process internal command
  578. switch (command) {
  579. case 'login':
  580. if (this.gui_objects.loginform)
  581. this.gui_objects.loginform.submit();
  582. break;
  583. // commands to switch task
  584. case 'logout':
  585. case 'mail':
  586. case 'addressbook':
  587. case 'settings':
  588. this.switch_task(command);
  589. break;
  590. case 'about':
  591. this.redirect('?_task=settings&_action=about', false);
  592. break;
  593. case 'permaurl':
  594. if (obj && obj.href && obj.target)
  595. return true;
  596. else if (this.env.permaurl)
  597. parent.location.href = this.env.permaurl;
  598. break;
  599. case 'extwin':
  600. if (this.env.action == 'compose') {
  601. var form = this.gui_objects.messageform,
  602. win = this.open_window('');
  603. if (win) {
  604. this.save_compose_form_local();
  605. this.compose_skip_unsavedcheck = true;
  606. $("input[name='_action']", form).val('compose');
  607. form.action = this.url('mail/compose', { _id: this.env.compose_id, _extwin: 1 });
  608. form.target = win.name;
  609. form.submit();
  610. }
  611. }
  612. else {
  613. this.open_window(this.env.permaurl, true);
  614. }
  615. break;
  616. case 'change-format':
  617. url = this.env.permaurl + '&_format=' + props;
  618. if (this.env.action == 'preview')
  619. url = url.replace(/_action=show/, '_action=preview') + '&_framed=1';
  620. if (this.env.extwin)
  621. url += '&_extwin=1';
  622. location.href = url;
  623. break;
  624. case 'menu-open':
  625. if (props && props.menu == 'attachmentmenu') {
  626. var mimetype = this.env.attachments[props.id];
  627. this.enable_command('open-attachment', mimetype && this.env.mimetypes && $.inArray(mimetype, this.env.mimetypes) >= 0);
  628. }
  629. this.show_menu(props, props.show || undefined, event);
  630. break;
  631. case 'menu-close':
  632. this.hide_menu(props, event);
  633. break;
  634. case 'menu-save':
  635. this.triggerEvent(command, {props:props, originalEvent:event});
  636. return false;
  637. case 'open':
  638. if (uid = this.get_single_uid()) {
  639. obj.href = this.url('show', {_mbox: this.get_message_mailbox(uid), _uid: uid});
  640. return true;
  641. }
  642. break;
  643. case 'close':
  644. if (this.env.extwin)
  645. window.close();
  646. break;
  647. case 'list':
  648. if (props && props != '') {
  649. this.reset_qsearch();
  650. }
  651. if (this.env.action == 'compose' && this.env.extwin) {
  652. window.close();
  653. }
  654. else if (this.task == 'mail') {
  655. this.list_mailbox(props);
  656. this.set_button_titles();
  657. }
  658. else if (this.task == 'addressbook')
  659. this.list_contacts(props);
  660. break;
  661. case 'set-listmode':
  662. this.set_list_options(null, undefined, undefined, props == 'threads' ? 1 : 0);
  663. break;
  664. case 'sort':
  665. var sort_order = this.env.sort_order,
  666. sort_col = !this.env.disabled_sort_col ? props : this.env.sort_col;
  667. if (!this.env.disabled_sort_order)
  668. sort_order = this.env.sort_col == sort_col && sort_order == 'ASC' ? 'DESC' : 'ASC';
  669. // set table header and update env
  670. this.set_list_sorting(sort_col, sort_order);
  671. // reload message list
  672. this.list_mailbox('', '', sort_col+'_'+sort_order);
  673. break;
  674. case 'nextpage':
  675. this.list_page('next');
  676. break;
  677. case 'lastpage':
  678. this.list_page('last');
  679. break;
  680. case 'previouspage':
  681. this.list_page('prev');
  682. break;
  683. case 'firstpage':
  684. this.list_page('first');
  685. break;
  686. case 'expunge':
  687. if (this.env.exists)
  688. this.expunge_mailbox(this.env.mailbox);
  689. break;
  690. case 'purge':
  691. case 'empty-mailbox':
  692. if (this.env.exists)
  693. this.purge_mailbox(this.env.mailbox);
  694. break;
  695. // common commands used in multiple tasks
  696. case 'show':
  697. if (this.task == 'mail') {
  698. uid = this.get_single_uid();
  699. if (uid && (!this.env.uid || uid != this.env.uid)) {
  700. if (this.env.mailbox == this.env.drafts_mailbox)
  701. this.open_compose_step({ _draft_uid: uid, _mbox: this.env.mailbox });
  702. else
  703. this.show_message(uid);
  704. }
  705. }
  706. else if (this.task == 'addressbook') {
  707. cid = props ? props : this.get_single_cid();
  708. if (cid && !(this.env.action == 'show' && cid == this.env.cid))
  709. this.load_contact(cid, 'show');
  710. }
  711. break;
  712. case 'add':
  713. if (this.task == 'addressbook')
  714. this.load_contact(0, 'add');
  715. else if (this.task == 'settings' && this.env.action == 'responses') {
  716. var frame;
  717. if ((frame = this.get_frame_window(this.env.contentframe))) {
  718. this.set_busy(true);
  719. this.location_href({ _action:'add-response', _framed:1 }, frame);
  720. }
  721. }
  722. else if (this.task == 'settings') {
  723. this.identity_list.clear_selection();
  724. this.load_identity(0, 'add-identity');
  725. }
  726. break;
  727. case 'edit':
  728. if (this.task == 'addressbook' && (cid = this.get_single_cid()))
  729. this.load_contact(cid, 'edit');
  730. else if (this.task == 'settings' && props)
  731. this.load_identity(props, 'edit-identity');
  732. else if (this.task == 'mail' && (uid = this.get_single_uid())) {
  733. url = { _mbox: this.get_message_mailbox(uid) };
  734. url[this.env.mailbox == this.env.drafts_mailbox && props != 'new' ? '_draft_uid' : '_uid'] = uid;
  735. this.open_compose_step(url);
  736. }
  737. break;
  738. case 'save':
  739. var input, form = this.gui_objects.editform;
  740. if (form) {
  741. // adv. search
  742. if (this.env.action == 'search') {
  743. }
  744. // user prefs
  745. else if ((input = $("input[name='_pagesize']", form)) && input.length && isNaN(parseInt(input.val()))) {
  746. alert(this.get_label('nopagesizewarning'));
  747. input.focus();
  748. break;
  749. }
  750. // contacts/identities
  751. else {
  752. // reload form
  753. if (props == 'reload') {
  754. form.action += '?_reload=1';
  755. }
  756. else if (this.task == 'settings' && (this.env.identities_level % 2) == 0 &&
  757. (input = $("input[name='_email']", form)) && input.length && !rcube_check_email(input.val())
  758. ) {
  759. alert(this.get_label('noemailwarning'));
  760. input.focus();
  761. break;
  762. }
  763. // clear empty input fields
  764. $('input.placeholder').each(function(){ if (this.value == this._placeholder) this.value = ''; });
  765. }
  766. // add selected source (on the list)
  767. if (parent.rcmail && parent.rcmail.env.source)
  768. form.action = this.add_url(form.action, '_orig_source', parent.rcmail.env.source);
  769. form.submit();
  770. }
  771. break;
  772. case 'delete':
  773. // mail task
  774. if (this.task == 'mail')
  775. this.delete_messages(event);
  776. // addressbook task
  777. else if (this.task == 'addressbook')
  778. this.delete_contacts();
  779. // settings: canned response
  780. else if (this.task == 'settings' && this.env.action == 'responses')
  781. this.delete_response();
  782. // settings: user identities
  783. else if (this.task == 'settings')
  784. this.delete_identity();
  785. break;
  786. // mail task commands
  787. case 'move':
  788. case 'moveto': // deprecated
  789. if (this.task == 'mail')
  790. this.move_messages(props, event);
  791. else if (this.task == 'addressbook')
  792. this.move_contacts(props);
  793. break;
  794. case 'copy':
  795. if (this.task == 'mail')
  796. this.copy_messages(props, event);
  797. else if (this.task == 'addressbook')
  798. this.copy_contacts(props);
  799. break;
  800. case 'mark':
  801. if (props)
  802. this.mark_message(props);
  803. break;
  804. case 'toggle_status':
  805. case 'toggle_flag':
  806. flag = command == 'toggle_flag' ? 'flagged' : 'read';
  807. if (uid = props) {
  808. // toggle flagged/unflagged
  809. if (flag == 'flagged') {
  810. if (this.message_list.rows[uid].flagged)
  811. flag = 'unflagged';
  812. }
  813. // toggle read/unread
  814. else if (this.message_list.rows[uid].deleted)
  815. flag = 'undelete';
  816. else if (!this.message_list.rows[uid].unread)
  817. flag = 'unread';
  818. this.mark_message(flag, uid);
  819. }
  820. break;
  821. case 'always-load':
  822. if (this.env.uid && this.env.sender) {
  823. this.add_contact(this.env.sender);
  824. setTimeout(function(){ ref.command('load-images'); }, 300);
  825. break;
  826. }
  827. case 'load-images':
  828. if (this.env.uid)
  829. this.show_message(this.env.uid, true, this.env.action=='preview');
  830. break;
  831. case 'load-attachment':
  832. case 'open-attachment':
  833. case 'download-attachment':
  834. var qstring = '_mbox='+urlencode(this.env.mailbox)+'&_uid='+this.env.uid+'&_part='+props,
  835. mimetype = this.env.attachments[props];
  836. // open attachment in frame if it's of a supported mimetype
  837. if (command != 'download-attachment' && mimetype && this.env.mimetypes && $.inArray(mimetype, this.env.mimetypes) >= 0) {
  838. if (this.open_window(this.env.comm_path+'&_action=get&'+qstring+'&_frame=1'))
  839. break;
  840. }
  841. this.goto_url('get', qstring+'&_download=1', false);
  842. break;
  843. case 'select-all':
  844. this.select_all_mode = props ? false : true;
  845. this.dummy_select = true; // prevent msg opening if there's only one msg on the list
  846. if (props == 'invert')
  847. this.message_list.invert_selection();
  848. else
  849. this.message_list.select_all(props == 'page' ? '' : props);
  850. this.dummy_select = null;
  851. break;
  852. case 'select-none':
  853. this.select_all_mode = false;
  854. this.message_list.clear_selection();
  855. break;
  856. case 'expand-all':
  857. this.env.autoexpand_threads = 1;
  858. this.message_list.expand_all();
  859. break;
  860. case 'expand-unread':
  861. this.env.autoexpand_threads = 2;
  862. this.message_list.collapse_all();
  863. this.expand_unread();
  864. break;
  865. case 'collapse-all':
  866. this.env.autoexpand_threads = 0;
  867. this.message_list.collapse_all();
  868. break;
  869. case 'nextmessage':
  870. if (this.env.next_uid)
  871. this.show_message(this.env.next_uid, false, this.env.action == 'preview');
  872. break;
  873. case 'lastmessage':
  874. if (this.env.last_uid)
  875. this.show_message(this.env.last_uid);
  876. break;
  877. case 'previousmessage':
  878. if (this.env.prev_uid)
  879. this.show_message(this.env.prev_uid, false, this.env.action == 'preview');
  880. break;
  881. case 'firstmessage':
  882. if (this.env.first_uid)
  883. this.show_message(this.env.first_uid);
  884. break;
  885. case 'compose':
  886. url = {};
  887. if (this.task == 'mail') {
  888. url._mbox = this.env.mailbox;
  889. if (props)
  890. url._to = props;
  891. // also send search request so we can go back to search result after message is sent
  892. if (this.env.search_request)
  893. url._search = this.env.search_request;
  894. }
  895. // modify url if we're in addressbook
  896. else if (this.task == 'addressbook') {
  897. // switch to mail compose step directly
  898. if (props && props.indexOf('@') > 0) {
  899. url._to = props;
  900. }
  901. else {
  902. var a_cids = [];
  903. // use contact id passed as command parameter
  904. if (props)
  905. a_cids.push(props);
  906. // get selected contacts
  907. else if (this.contact_list)
  908. a_cids = this.contact_list.get_selection();
  909. if (a_cids.length)
  910. this.http_post('mailto', { _cid: a_cids.join(','), _source: this.env.source }, true);
  911. else if (this.env.group)
  912. this.http_post('mailto', { _gid: this.env.group, _source: this.env.source }, true);
  913. break;
  914. }
  915. }
  916. else if (props && typeof props == 'string') {
  917. url._to = props;
  918. }
  919. else if (props && typeof props == 'object') {
  920. $.extend(url, props);
  921. }
  922. this.open_compose_step(url);
  923. break;
  924. case 'spellcheck':
  925. if (this.spellcheck_state()) {
  926. this.editor.spellcheck_stop();
  927. }
  928. else {
  929. this.editor.spellcheck_start();
  930. }
  931. break;
  932. case 'savedraft':
  933. // Reset the auto-save timer
  934. clearTimeout(this.save_timer);
  935. // compose form did not change (and draft wasn't saved already)
  936. if (this.env.draft_id && this.cmp_hash == this.compose_field_hash()) {
  937. this.auto_save_start();
  938. break;
  939. }
  940. this.submit_messageform(true);
  941. break;
  942. case 'send':
  943. if (!props.nocheck && !this.check_compose_input(command))
  944. break;
  945. // Reset the auto-save timer
  946. clearTimeout(this.save_timer);
  947. this.submit_messageform();
  948. break;
  949. case 'send-attachment':
  950. // Reset the auto-save timer
  951. clearTimeout(this.save_timer);
  952. if (!(flag = this.upload_file(props || this.gui_objects.uploadform, 'upload'))) {
  953. if (flag !== false)
  954. alert(this.get_label('selectimportfile'));
  955. aborted = true;
  956. }
  957. break;
  958. case 'insert-sig':
  959. this.change_identity($("[name='_from']")[0], true);
  960. break;
  961. case 'list-adresses':
  962. this.list_contacts(props);
  963. this.enable_command('add-recipient', false);
  964. break;
  965. case 'add-recipient':
  966. this.compose_add_recipient(props);
  967. break;
  968. case 'reply-all':
  969. case 'reply-list':
  970. case 'reply':
  971. if (uid = this.get_single_uid()) {
  972. url = {_reply_uid: uid, _mbox: this.get_message_mailbox(uid)};
  973. if (command == 'reply-all')
  974. // do reply-list, when list is detected and popup menu wasn't used
  975. url._all = (!props && this.env.reply_all_mode == 1 && this.commands['reply-list'] ? 'list' : 'all');
  976. else if (command == 'reply-list')
  977. url._all = 'list';
  978. this.open_compose_step(url);
  979. }
  980. break;
  981. case 'forward-attachment':
  982. case 'forward-inline':
  983. case 'forward':
  984. var uids = this.env.uid ? [this.env.uid] : (this.message_list ? this.message_list.get_selection() : []);
  985. if (uids.length) {
  986. url = { _forward_uid: this.uids_to_list(uids), _mbox: this.env.mailbox, _search: this.env.search_request };
  987. if (command == 'forward-attachment' || (!props && this.env.forward_attachment) || uids.length > 1)
  988. url._attachment = 1;
  989. this.open_compose_step(url);
  990. }
  991. break;
  992. case 'print':
  993. if (this.env.action == 'get') {
  994. this.gui_objects.messagepartframe.contentWindow.print();
  995. }
  996. else if (uid = this.get_single_uid()) {
  997. url = '&_action=print&_uid='+uid+'&_mbox='+urlencode(this.get_message_mailbox(uid))+(this.env.safemode ? '&_safe=1' : '');
  998. if (this.open_window(this.env.comm_path + url, true, true)) {
  999. if (this.env.action != 'show')
  1000. this.mark_message('read', uid);
  1001. }
  1002. }
  1003. break;
  1004. case 'viewsource':
  1005. if (uid = this.get_single_uid())
  1006. this.open_window(this.env.comm_path+'&_action=viewsource&_uid='+uid+'&_mbox='+urlencode(this.env.mailbox), true, true);
  1007. break;
  1008. case 'download':
  1009. if (this.env.action == 'get') {
  1010. location.href = location.href.replace(/_frame=/, '_download=');
  1011. }
  1012. else if (uid = this.get_single_uid()) {
  1013. this.goto_url('viewsource', { _uid: uid, _mbox: this.get_message_mailbox(uid), _save: 1 });
  1014. }
  1015. break;
  1016. // quicksearch
  1017. case 'search':
  1018. if (!props && this.gui_objects.qsearchbox)
  1019. props = this.gui_objects.qsearchbox.value;
  1020. if (props) {
  1021. this.qsearch(props);
  1022. break;
  1023. }
  1024. // reset quicksearch
  1025. case 'reset-search':
  1026. var n, s = this.env.search_request || this.env.qsearch;
  1027. this.reset_qsearch();
  1028. this.select_all_mode = false;
  1029. if (s && this.env.action == 'compose') {
  1030. if (this.contact_list)
  1031. this.list_contacts_clear();
  1032. }
  1033. else if (s && this.env.mailbox) {
  1034. this.list_mailbox(this.env.mailbox, 1);
  1035. }
  1036. else if (s && this.task == 'addressbook') {
  1037. if (this.env.source == '') {
  1038. for (n in this.env.address_sources) break;
  1039. this.env.source = n;
  1040. this.env.group = '';
  1041. }
  1042. this.list_contacts(this.env.source, this.env.group, 1);
  1043. }
  1044. break;
  1045. case 'pushgroup':
  1046. // add group ID to stack
  1047. this.env.address_group_stack.push(props.id);
  1048. if (obj && event)
  1049. rcube_event.cancel(event);
  1050. case 'listgroup':
  1051. this.reset_qsearch();
  1052. this.list_contacts(props.source, props.id);
  1053. break;
  1054. case 'popgroup':
  1055. if (this.env.address_group_stack.length > 1) {
  1056. this.env.address_group_stack.pop();
  1057. this.reset_qsearch();
  1058. this.list_contacts(props.source, this.env.address_group_stack[this.env.address_group_stack.length-1]);
  1059. }
  1060. break;
  1061. case 'import-messages':
  1062. var form = props || this.gui_objects.importform,
  1063. importlock = this.set_busy(true, 'importwait');
  1064. $('input[name="_unlock"]', form).val(importlock);
  1065. if (!(flag = this.upload_file(form, 'import'))) {
  1066. this.set_busy(false, null, importlock);
  1067. if (flag !== false)
  1068. alert(this.get_label('selectimportfile'));
  1069. aborted = true;
  1070. }
  1071. break;
  1072. case 'import':
  1073. if (this.env.action == 'import' && this.gui_objects.importform) {
  1074. var file = document.getElementById('rcmimportfile');
  1075. if (file && !file.value) {
  1076. alert(this.get_label('selectimportfile'));
  1077. aborted = true;
  1078. break;
  1079. }
  1080. this.gui_objects.importform.submit();
  1081. this.set_busy(true, 'importwait');
  1082. this.lock_form(this.gui_objects.importform, true);
  1083. }
  1084. else
  1085. this.goto_url('import', (this.env.source ? '_target='+urlencode(this.env.source)+'&' : ''));
  1086. break;
  1087. case 'export':
  1088. if (this.contact_list.rowcount > 0) {
  1089. this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _search: this.env.search_request });
  1090. }
  1091. break;
  1092. case 'export-selected':
  1093. if (this.contact_list.rowcount > 0) {
  1094. this.goto_url('export', { _source: this.env.source, _gid: this.env.group, _cid: this.contact_list.get_selection().join(',') });
  1095. }
  1096. break;
  1097. case 'upload-photo':
  1098. this.upload_contact_photo(props || this.gui_objects.uploadform);
  1099. break;
  1100. case 'delete-photo':
  1101. this.replace_contact_photo('-del-');
  1102. break;
  1103. // user settings commands
  1104. case 'preferences':
  1105. case 'identities':
  1106. case 'responses':
  1107. case 'folders':
  1108. this.goto_url('settings/' + command);
  1109. break;
  1110. case 'undo':
  1111. this.http_request('undo', '', this.display_message('', 'loading'));
  1112. break;
  1113. // unified command call (command name == function name)
  1114. default:
  1115. var func = command.replace(/-/g, '_');
  1116. if (this[func] && typeof this[func] === 'function') {
  1117. ret = this[func](props, obj, event);
  1118. }
  1119. break;
  1120. }
  1121. if (!aborted && this.triggerEvent('after'+command, props) === false)
  1122. ret = false;
  1123. this.triggerEvent('actionafter', { props:props, action:command, aborted:aborted });
  1124. return ret === false ? false : obj ? false : true;
  1125. };
  1126. // set command(s) enabled or disabled
  1127. this.enable_command = function()
  1128. {
  1129. var i, n, args = Array.prototype.slice.call(arguments),
  1130. enable = args.pop(), cmd;
  1131. for (n=0; n<args.length; n++) {
  1132. cmd = args[n];
  1133. // argument of type array
  1134. if (typeof cmd === 'string') {
  1135. this.commands[cmd] = enable;
  1136. this.set_button(cmd, (enable ? 'act' : 'pas'));
  1137. this.triggerEvent('enable-command', {command: cmd, status: enable});
  1138. }
  1139. // push array elements into commands array
  1140. else {
  1141. for (i in cmd)
  1142. args.push(cmd[i]);
  1143. }
  1144. }
  1145. };
  1146. this.command_enabled = function(cmd)
  1147. {
  1148. return this.commands[cmd];
  1149. };
  1150. // lock/unlock interface
  1151. this.set_busy = function(a, message, id)
  1152. {
  1153. if (a && message) {
  1154. var msg = this.get_label(message);
  1155. if (msg == message)
  1156. msg = 'Loading...';
  1157. id = this.display_message(msg, 'loading');
  1158. }
  1159. else if (!a && id) {
  1160. this.hide_message(id);
  1161. }
  1162. this.busy = a;
  1163. //document.body.style.cursor = a ? 'wait' : 'default';
  1164. if (this.gui_objects.editform)
  1165. this.lock_form(this.gui_objects.editform, a);
  1166. return id;
  1167. };
  1168. // return a localized string
  1169. this.get_label = function(name, domain)
  1170. {
  1171. if (domain && this.labels[domain+'.'+name])
  1172. return this.labels[domain+'.'+name];
  1173. else if (this.labels[name])
  1174. return this.labels[name];
  1175. else
  1176. return name;
  1177. };
  1178. // alias for convenience reasons
  1179. this.gettext = this.get_label;
  1180. // switch to another application task
  1181. this.switch_task = function(task)
  1182. {
  1183. if (this.task === task && task != 'mail')
  1184. return;
  1185. var url = this.get_task_url(task);
  1186. if (task == 'mail')
  1187. url += '&_mbox=INBOX';
  1188. else if (task == 'logout' && !this.env.server_error)
  1189. this.clear_compose_data();
  1190. this.redirect(url);
  1191. };
  1192. this.get_task_url = function(task, url)
  1193. {
  1194. if (!url)
  1195. url = this.env.comm_path;
  1196. return url.replace(/_task=[a-z0-9_-]+/i, '_task='+task);
  1197. };
  1198. this.reload = function(delay)
  1199. {
  1200. if (this.is_framed())
  1201. parent.rcmail.reload(delay);
  1202. else if (delay)
  1203. setTimeout(function() { ref.reload(); }, delay);
  1204. else if (window.location)
  1205. location.href = this.env.comm_path + (this.env.action ? '&_action='+this.env.action : '');
  1206. };
  1207. // Add variable to GET string, replace old value if exists
  1208. this.add_url = function(url, name, value)
  1209. {
  1210. value = urlencode(value);
  1211. if (/(\?.*)$/.test(url)) {
  1212. var urldata = RegExp.$1,
  1213. datax = RegExp('((\\?|&)'+RegExp.escape(name)+'=[^&]*)');
  1214. if (datax.test(urldata)) {
  1215. urldata = urldata.replace(datax, RegExp.$2 + name + '=' + value);
  1216. }
  1217. else
  1218. urldata += '&' + name + '=' + value
  1219. return url.replace(/(\?.*)$/, urldata);
  1220. }
  1221. return url + '?' + name + '=' + value;
  1222. };
  1223. this.is_framed = function()
  1224. {
  1225. return (this.env.framed && parent.rcmail && parent.rcmail != this && parent.rcmail.command);
  1226. };
  1227. this.save_pref = function(prop)
  1228. {
  1229. var request = {_name: prop.name, _value: prop.value};
  1230. if (prop.session)
  1231. request._session = prop.session;
  1232. if (prop.env)
  1233. this.env[prop.env] = prop.value;
  1234. this.http_post('save-pref', req