You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

functions.js 31KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110
  1. /**
  2. * Roundcube functions for default skin interface
  3. *
  4. * @licstart The following is the entire license notice for the
  5. * JavaScript code in this file.
  6. *
  7. * Copyright (c) 2006-2014, The Roundcube Dev Team
  8. *
  9. * The JavaScript code in this page is free software: you can redistribute it
  10. * and/or modify it under the terms of the GNU General Public License
  11. * as published by the Free Software Foundation, either version 3 of
  12. * the License, or (at your option) any later version.
  13. *
  14. * @licend The above is the entire license notice
  15. * for the JavaScript code in this file.
  16. */
  17. /**
  18. * Settings
  19. */
  20. function rcube_init_settings_tabs()
  21. {
  22. var el, cl, container = $('#tabsbar'),
  23. last_tab = $('span:last', container),
  24. tab = '#settingstabpreferences',
  25. action = window.rcmail && rcmail.env.action ? rcmail.env.action : null;
  26. // move About tab to the end
  27. if (last_tab && last_tab.attr('id') != 'settingstababout' && (el = $('#settingstababout'))) {
  28. cl = el.clone(true);
  29. el.remove();
  30. last_tab.after(cl);
  31. }
  32. // get selected tab
  33. if (action)
  34. tab = '#settingstab' + (action.indexOf('identity')>0 ? 'identities' : action.replace(/\./g, ''));
  35. $(tab).addClass('tablink-selected');
  36. $('a', tab).removeAttr('onclick').click(function() { return false; });
  37. }
  38. // Fieldsets-to-tabs converter
  39. // Warning: don't place "caller" <script> inside page element (id)
  40. function rcube_init_tabs(id, current)
  41. {
  42. var content = $('#'+id),
  43. fs = content.children('fieldset');
  44. if (!fs.length)
  45. return;
  46. current = current ? current : 0;
  47. // first hide not selected tabs
  48. fs.each(function(idx) { if (idx != current) $(this).hide(); });
  49. // create tabs container
  50. var tabs = $('<div>').addClass('tabsbar').appendTo(content);
  51. // convert fildsets into tabs
  52. fs.each(function(idx) {
  53. var tab, a, elm = $(this), legend = elm.children('legend');
  54. // create a tab
  55. a = $('<a>').text(legend.text()).attr('href', '#');
  56. tab = $('<span>').attr({'id': 'tab'+idx, 'class': 'tablink'})
  57. .click(function() { rcube_show_tab(id, idx); return false })
  58. // remove legend
  59. legend.remove();
  60. // style fieldset
  61. elm.addClass('tabbed');
  62. // style selected tab
  63. if (idx == current)
  64. tab.addClass('tablink-selected');
  65. // add the tab to container
  66. tab.append(a).appendTo(tabs);
  67. });
  68. }
  69. function rcube_show_tab(id, index)
  70. {
  71. var fs = $('#'+id).children('fieldset');
  72. fs.each(function(idx) {
  73. // Show/hide fieldset (tab content)
  74. $(this)[index==idx ? 'show' : 'hide']();
  75. // Select/unselect tab
  76. $('#tab'+idx).toggleClass('tablink-selected', idx==index);
  77. });
  78. }
  79. /**
  80. * Mail UI
  81. */
  82. function rcube_mail_ui()
  83. {
  84. this.popups = {
  85. markmenu: {id:'markmessagemenu'},
  86. replyallmenu: {id:'replyallmenu'},
  87. forwardmenu: {id:'forwardmenu', editable:1},
  88. searchmenu: {id:'searchmenu', editable:1},
  89. messagemenu: {id:'messagemenu'},
  90. attachmentmenu: {id:'attachmentmenu'},
  91. dragmenu: {id:'dragmenu', sticky:1},
  92. groupmenu: {id:'groupoptionsmenu', above:1},
  93. mailboxmenu: {id:'mailboxoptionsmenu', above:1},
  94. composemenu: {id:'composeoptionsmenu', editable:1, overlap:1},
  95. spellmenu: {id:'spellmenu'},
  96. responsesmenu: {id:'responsesmenu'},
  97. // toggle: #1486823, #1486930
  98. uploadmenu: {id:'attachment-form', editable:1, above:1, toggle:!bw.ie&&!bw.linux },
  99. uploadform: {id:'upload-form', editable:1, toggle:!bw.ie&&!bw.linux }
  100. };
  101. var obj;
  102. for (var k in this.popups) {
  103. obj = $('#'+this.popups[k].id)
  104. if (obj.length)
  105. this.popups[k].obj = obj;
  106. else {
  107. delete this.popups[k];
  108. }
  109. }
  110. }
  111. rcube_mail_ui.prototype = {
  112. show_popup: function(popup, show, config)
  113. {
  114. var obj;
  115. // auto-register menu object
  116. if (!this.popups[popup] && (obj = $('#'+popup)) && obj.length)
  117. this.popups[popup] = $.extend(config, {id: popup, obj: obj});
  118. if (typeof this[popup] == 'function')
  119. return this[popup](show);
  120. else
  121. return this.show_popupmenu(popup, show);
  122. },
  123. show_popupmenu: function(popup, show)
  124. {
  125. var obj = this.popups[popup].obj,
  126. above = this.popups[popup].above,
  127. ref = $(this.popups[popup].link ? this.popups[popup].link : rcube_find_object(popup+'link'));
  128. if (typeof show == 'undefined')
  129. show = obj.is(':visible') ? false : true;
  130. else if (this.popups[popup].toggle && show && this.popups[popup].obj.is(':visible') )
  131. show = false;
  132. if (show && ref.length) {
  133. var parent = ref.parent(),
  134. win = $(window),
  135. pos = parent.hasClass('dropbutton') ? parent.offset() : ref.offset();
  136. if (!above && pos.top + ref.height() + obj.height() > win.height())
  137. above = true;
  138. if (pos.left + obj.width() > win.width())
  139. pos.left = win.width() - obj.width() - 30;
  140. obj.css({ left:pos.left, top:(pos.top + (above ? -obj.height() : ref.height())) });
  141. }
  142. obj[show?'show':'hide']();
  143. },
  144. dragmenu: function(show)
  145. {
  146. this.popups.dragmenu.obj[show?'show':'hide']();
  147. },
  148. forwardmenu: function(show)
  149. {
  150. $("input[name='forwardtype'][value="+(rcmail.env.forward_attachment ? 1 : 0)+"]", this.popups.forwardmenu.obj)
  151. .prop('checked', true);
  152. this.show_popupmenu('forwardmenu', show);
  153. },
  154. uploadmenu: function(show)
  155. {
  156. if (typeof show == 'object') // called as event handler
  157. show = false;
  158. // clear upload form
  159. if (!show) {
  160. try { $('#attachment-form form')[0].reset(); }
  161. catch(e){} // ignore errors
  162. }
  163. if (rcmail.mailvelope_editor)
  164. return;
  165. this.show_popupmenu('uploadmenu', show);
  166. if (!document.all && this.popups.uploadmenu.obj.is(':visible'))
  167. $('#attachment-form input[type=file]').click();
  168. },
  169. searchmenu: function(show)
  170. {
  171. var obj = this.popups.searchmenu.obj,
  172. ref = rcube_find_object('searchmenulink');
  173. if (typeof show == 'undefined')
  174. show = obj.is(':visible') ? false : true;
  175. if (show && ref) {
  176. var pos = $(ref).offset();
  177. obj.css({left:pos.left, top:(pos.top + ref.offsetHeight + 2)});
  178. if (rcmail.env.search_mods) {
  179. var n, all,
  180. list = $('input:checkbox[name="s_mods[]"]', obj),
  181. mbox = rcmail.env.mailbox,
  182. mods = rcmail.env.search_mods,
  183. scope = rcmail.env.search_scope || 'base';
  184. if (rcmail.env.task == 'mail') {
  185. mods = mods[mbox] ? mods[mbox] : mods['*'];
  186. all = 'text';
  187. $('input:radio[name="s_scope"]').prop('checked', false).filter('#s_scope_'+scope).prop('checked', true);
  188. }
  189. else {
  190. all = '*';
  191. }
  192. if (mods[all])
  193. list.map(function() {
  194. this.checked = true;
  195. this.disabled = this.value != all;
  196. });
  197. else {
  198. list.prop('disabled', false).prop('checked', false);
  199. for (n in mods)
  200. $('#s_mod_' + n).prop('checked', true);
  201. }
  202. }
  203. }
  204. obj[show?'show':'hide']();
  205. },
  206. set_searchmod: function(elem)
  207. {
  208. var all, m, task = rcmail.env.task,
  209. mods = rcmail.env.search_mods,
  210. mbox = rcmail.env.mailbox,
  211. scope = $('input[name="s_scope"]:checked').val();
  212. if (scope == 'all')
  213. mbox = '*';
  214. if (!mods)
  215. mods = {};
  216. if (task == 'mail') {
  217. if (!mods[mbox])
  218. mods[mbox] = rcube_clone_object(mods['*']);
  219. m = mods[mbox];
  220. all = 'text';
  221. }
  222. else { //addressbook
  223. m = mods;
  224. all = '*';
  225. }
  226. if (!elem.checked)
  227. delete(m[elem.value]);
  228. else
  229. m[elem.value] = 1;
  230. // mark all fields
  231. if (elem.value == all) {
  232. $('input:checkbox[name="s_mods[]"]').map(function() {
  233. if (this == elem)
  234. return;
  235. this.checked = true;
  236. if (elem.checked) {
  237. this.disabled = true;
  238. delete m[this.value];
  239. }
  240. else {
  241. this.disabled = false;
  242. m[this.value] = 1;
  243. }
  244. });
  245. }
  246. rcmail.set_searchmods(m);
  247. },
  248. show_listmenu: function(p)
  249. {
  250. var self = this, buttons = {}, $dialog = $('#listmenu');
  251. // close the dialog
  252. if ($dialog.is(':visible')) {
  253. $dialog.dialog('close', p.originalEvent);
  254. return;
  255. }
  256. // set form values
  257. $('input[name="sort_col"][value="'+rcmail.env.sort_col+'"]').prop('checked', true);
  258. $('input[name="sort_ord"][value="DESC"]').prop('checked', rcmail.env.sort_order == 'DESC');
  259. $('input[name="sort_ord"][value="ASC"]').prop('checked', rcmail.env.sort_order != 'DESC');
  260. $('input[name="view"][value="thread"]').prop('checked', rcmail.env.threading ? true : false);
  261. $('input[name="view"][value="list"]').prop('checked', rcmail.env.threading ? false : true);
  262. // set checkboxes
  263. $('input[name="list_col[]"]').each(function() {
  264. $(this).prop('checked', $.inArray(this.value, rcmail.env.listcols) != -1);
  265. });
  266. $.each(['widescreen', 'desktop', 'list'], function() {
  267. $('input[name="layout"][value="' + this + '"]').prop('checked', rcmail.env.layout == this);
  268. });
  269. $('#listoptions-columns', $dialog)[rcmail.env.layout == 'widescreen' ? 'hide' : 'show']();
  270. buttons[rcmail.gettext('save')] = function(e) {
  271. $dialog.dialog('close', e);
  272. self.save_listmenu();
  273. };
  274. $dialog.dialog({
  275. modal: true,
  276. resizable: false,
  277. closeOnEscape: true,
  278. title: null,
  279. open: function(e) {
  280. var maxheight = 0;
  281. $('#listmenu fieldset').each(function() {
  282. var height = $(this).height();
  283. if (height > maxheight) {
  284. maxheight = height;
  285. }
  286. }).css("min-height", maxheight+"px").height(maxheight);
  287. setTimeout(function() { $dialog.find('a, input:not(:disabled)').not('[aria-disabled=true]').first().focus(); }, 100);
  288. },
  289. close: function(e) {
  290. $dialog.dialog('destroy').hide();
  291. if (e.originalEvent && rcube_event.is_keyboard(e.originalEvent))
  292. $('#listmenulink').focus();
  293. },
  294. buttons: buttons,
  295. minWidth: 500,
  296. width: $dialog.width()+20
  297. }).show();
  298. },
  299. save_listmenu: function()
  300. {
  301. var sort = $('input[name="sort_col"]:checked').val(),
  302. ord = $('input[name="sort_ord"]:checked').val(),
  303. thread = $('input[name="view"]:checked').val(),
  304. layout = $('input[name="layout"]:checked').val(),
  305. cols = $('input[name="list_col[]"]:checked')
  306. .map(function(){ return this.value; }).get();
  307. rcmail.set_list_options(cols, sort, ord, thread == 'thread' ? 1 : 0, layout);
  308. },
  309. spellmenu: function(show)
  310. {
  311. var link, li,
  312. lang = rcmail.spellcheck_lang(),
  313. menu = this.popups.spellmenu.obj,
  314. ul = $('ul', menu);
  315. if (!ul.length) {
  316. ul = $('<ul>');
  317. for (i in rcmail.env.spell_langs) {
  318. li = $('<li>');
  319. link = $('<a href="#"></a>').text(rcmail.env.spell_langs[i])
  320. .addClass('active').data('lang', i)
  321. .click(function() {
  322. rcmail.spellcheck_lang_set($(this).data('lang'));
  323. });
  324. link.appendTo(li);
  325. li.appendTo(ul);
  326. }
  327. ul.appendTo(menu);
  328. }
  329. // select current language
  330. $('li', ul).each(function() {
  331. var el = $('a', this);
  332. if (el.data('lang') == lang)
  333. el.addClass('selected');
  334. else if (el.hasClass('selected'))
  335. el.removeClass('selected');
  336. });
  337. this.show_popupmenu('spellmenu', show);
  338. },
  339. show_attachmentmenu: function(elem, event)
  340. {
  341. var id = elem.parentNode.id.replace(/^attach/, '');
  342. $.each(['open', 'download', 'rename'], function() {
  343. var action = this;
  344. $('#attachmenu' + action).off('click').attr('onclick', '').click(function(e) {
  345. return rcmail.command(action + '-attachment', id, this);
  346. });
  347. });
  348. this.popups.attachmentmenu.link = elem;
  349. rcmail.command('menu-open', {menu: 'attachmentmenu', id: id}, elem, event);
  350. },
  351. menu_open: function(p)
  352. {
  353. if (p && p.name == 'messagelistmenu')
  354. this.show_listmenu();
  355. },
  356. body_mouseup: function(e)
  357. {
  358. var target = e.target; ref = this;
  359. $.each(this.popups, function(i, popup) {
  360. if (popup.obj.is(':visible') && target != rcube_find_object(i + 'link')
  361. && !popup.toggle
  362. && target != popup.obj.get(0) // check if scroll bar was clicked (#1489832)
  363. && (!popup.editable || !ref.target_overlaps(target, popup.id))
  364. && (!popup.sticky || !rcube_mouse_is_over(e, rcube_find_object(popup.id)))
  365. && !$(target).is('.folder-selector-link') && !$(target).children('.folder-selector-link').length
  366. ) {
  367. window.setTimeout('rcmail_ui.show_popup("'+i+'",false);', 50);
  368. }
  369. });
  370. },
  371. target_overlaps: function (target, elementid)
  372. {
  373. var element = rcube_find_object(elementid);
  374. while (target.parentNode) {
  375. if (target.parentNode == element)
  376. return true;
  377. target = target.parentNode;
  378. }
  379. return false;
  380. },
  381. body_keydown: function(e)
  382. {
  383. if (e.keyCode == 27) {
  384. for (var k in this.popups) {
  385. if (this.popups[k].obj.is(':visible'))
  386. this.show_popup(k, false);
  387. }
  388. }
  389. },
  390. // Mail view layout initialization and change handler
  391. set_layout: function(p)
  392. {
  393. var layout = p ? p.new_layout : rcmail.env.layout,
  394. top = $('#mailcontframe'),
  395. bottom = $('#mailpreviewframe');
  396. if (p)
  397. $('#mailrightcontainer').removeClass().addClass(layout);
  398. if (!this.mailviewsplitv) {
  399. this.mailviewsplitv = new rcube_splitter({id:'mailviewsplitterv', p1: 'mailleftcontainer', p2: 'mailrightcontainer',
  400. orientation: 'v', relative: true, start: 165, callback: rcube_render_mailboxlist });
  401. this.mailviewsplitv.init();
  402. }
  403. $('#mailviewsplitter')[layout == 'desktop' ? 'show' : 'hide']();
  404. $('#mailviewsplitter2')[layout == 'widescreen' ? 'show' : 'hide']();
  405. $('#mailpreviewframe')[layout != 'list' ? 'show' : 'hide']();
  406. rcmail.env.contentframe = layout == 'list' ? null : 'messagecontframe';
  407. if (layout == 'widescreen') {
  408. $('#countcontrols').detach().appendTo($('#messagelistheader'));
  409. top.css({height: 'auto', width: 400});
  410. bottom.css({top: 0, left: 410, height: 'auto'}).show();
  411. if (!this.mailviewsplit2) {
  412. this.mailviewsplit2 = new rcube_splitter({id:'mailviewsplitter2', p1: 'mailcontframe', p2: 'mailpreviewframe',
  413. orientation: 'v', relative: true, start: 405});
  414. this.mailviewsplit2.init();
  415. }
  416. else
  417. this.mailviewsplit2.resize();
  418. }
  419. else if (layout == 'desktop') {
  420. top.css({height: 200, width: '100%'});
  421. bottom.css({left: 0, top: 210, height: 'auto'}).show();
  422. if (!this.mailviewsplit) {
  423. this.mailviewsplit = new rcube_splitter({id:'mailviewsplitter', p1: 'mailcontframe', p2: 'mailpreviewframe',
  424. orientation: 'h', relative: true, start: 205});
  425. this.mailviewsplit.init();
  426. }
  427. else
  428. this.mailviewsplit.resize();
  429. }
  430. else { // layout == 'list'
  431. top.css({height: 'auto', width: '100%'});
  432. bottom.hide();
  433. }
  434. if (p && p.old_layout == 'widescreen') {
  435. $('#countcontrols').detach().appendTo($('#messagelistfooter'));
  436. }
  437. },
  438. /* Message composing */
  439. init_compose_form: function()
  440. {
  441. var f, v, field, fields = ['cc', 'bcc', 'replyto', 'followupto'],
  442. div = document.getElementById('compose-div'),
  443. headers_div = document.getElementById('compose-headers-div');
  444. // Show input elements with non-empty value
  445. for (f=0; f<fields.length; f++) {
  446. v = fields[f]; field = $('#_'+v);
  447. if (field.length) {
  448. field.on('change', {v:v}, function(e) { if (this.value) rcmail_ui.show_header_form(e.data.v); });
  449. if (field.val() != '')
  450. rcmail_ui.show_header_form(v);
  451. }
  452. }
  453. // prevent from form data loss when pressing ESC key in IE
  454. if (bw.ie) {
  455. var form = rcube_find_object('form');
  456. form.onkeydown = function (e) {
  457. if (rcube_event.get_keycode(e) == 27)
  458. rcube_event.cancel(e);
  459. };
  460. }
  461. $(window).resize(function() {
  462. rcmail_ui.resize_compose_body();
  463. });
  464. $('#compose-container').resize(function() {
  465. rcmail_ui.resize_compose_body();
  466. });
  467. div.style.top = (parseInt(headers_div.offsetHeight, 10) + 3) + 'px';
  468. $(window).resize();
  469. // fixes contacts-table position when there's more than one addressbook
  470. $('#contacts-table').css('top', $('#directorylist').height() + 24 + 'px');
  471. // contacts search submit
  472. $('#quicksearchbox').keydown(function(e) {
  473. if (rcube_event.get_keycode(e) == 13)
  474. rcmail.command('search');
  475. });
  476. },
  477. resize_compose_body: function()
  478. {
  479. var div = $('#compose-div .boxlistcontent'),
  480. w = div.width() - 6,
  481. h = div.height() - 2,
  482. x = bw.ie || bw.opera ? 4 : 0;
  483. $('#compose-body_ifr').width(w + 6).height(h - 1 - $('div.mce-toolbar').height());
  484. $('#compose-body').width(w-x).height(h);
  485. $('#googie_edit_layer').width(w).height(h);
  486. },
  487. resize_compose_body_ev: function()
  488. {
  489. window.setTimeout(function(){rcmail_ui.resize_compose_body();}, 100);
  490. },
  491. show_header_form: function(id)
  492. {
  493. var row, s,
  494. link = document.getElementById(id + '-link');
  495. if ((s = this.next_sibling(link)))
  496. s.style.display = 'none';
  497. else if ((s = this.prev_sibling(link)))
  498. s.style.display = 'none';
  499. link.style.display = 'none';
  500. if ((row = document.getElementById('compose-' + id))) {
  501. var div = document.getElementById('compose-div'),
  502. headers_div = document.getElementById('compose-headers-div');
  503. $(row).show();
  504. div.style.top = (parseInt(headers_div.offsetHeight, 10) + 3) + 'px';
  505. this.resize_compose_body();
  506. }
  507. return false;
  508. },
  509. hide_header_form: function(id)
  510. {
  511. var row, ns,
  512. link = document.getElementById(id + '-link'),
  513. parent = link.parentNode,
  514. links = parent.getElementsByTagName('a');
  515. link.style.display = '';
  516. for (var i=0; i<links.length; i++)
  517. if (links[i].style.display != 'none')
  518. for (var j=i+1; j<links.length; j++)
  519. if (links[j].style.display != 'none')
  520. if ((ns = this.next_sibling(links[i]))) {
  521. ns.style.display = '';
  522. break;
  523. }
  524. document.getElementById('_' + id).value = '';
  525. if ((row = document.getElementById('compose-' + id))) {
  526. var div = document.getElementById('compose-div'),
  527. headers_div = document.getElementById('compose-headers-div');
  528. row.style.display = 'none';
  529. div.style.top = (parseInt(headers_div.offsetHeight, 10) + 1) + 'px';
  530. this.resize_compose_body();
  531. }
  532. return false;
  533. },
  534. next_sibling: function(elm)
  535. {
  536. var ns = elm.nextSibling;
  537. while (ns && ns.nodeType == 3)
  538. ns = ns.nextSibling;
  539. return ns;
  540. },
  541. prev_sibling: function(elm)
  542. {
  543. var ps = elm.previousSibling;
  544. while (ps && ps.nodeType == 3)
  545. ps = ps.previousSibling;
  546. return ps;
  547. },
  548. enable_command: function(p)
  549. {
  550. if (p.command == 'reply-list' && rcmail.env.reply_all_mode == 1) {
  551. var label = rcmail.gettext(p.status ? 'replylist' : 'replyall');
  552. $('a.button.replyAll').attr('title', label);
  553. }
  554. else if (p.command == 'compose-encrypted') {
  555. // show the toolbar button for Mailvelope
  556. $('#messagetoolbar > a.encrypt').show();
  557. }
  558. },
  559. folder_search_init: function(container)
  560. {
  561. // animation to unfold list search box
  562. $('.boxtitle a.search', container).click(function(e) {
  563. var title = $('.boxtitle', container),
  564. box = $('.listsearchbox', container),
  565. dir = box.is(':visible') ? -1 : 1,
  566. height = 24 + ($('select', box).length ? 24 : 0);
  567. box.slideToggle({
  568. duration: 160,
  569. progress: function(animation, progress) {
  570. if (dir < 0) progress = 1 - progress;
  571. $('.boxlistcontent', container).css('top', (title.outerHeight() + height * progress) + 'px');
  572. },
  573. complete: function() {
  574. box.toggleClass('expanded');
  575. if (box.is(':visible')) {
  576. box.find('input[type=text]').focus();
  577. }
  578. else {
  579. $('a.reset', box).click();
  580. }
  581. // TODO: save state in cookie
  582. }
  583. });
  584. return false;
  585. });
  586. }
  587. };
  588. /**
  589. * Roundcube generic layer (floating box) class
  590. *
  591. * @constructor
  592. */
  593. function rcube_layer(id, attributes)
  594. {
  595. this.name = id;
  596. // create a new layer in the current document
  597. this.create = function(arg)
  598. {
  599. var l = (arg.x) ? arg.x : 0,
  600. t = (arg.y) ? arg.y : 0,
  601. w = arg.width,
  602. h = arg.height,
  603. z = arg.zindex,
  604. vis = arg.vis,
  605. parent = arg.parent,
  606. obj = document.createElement('DIV');
  607. obj.id = this.name;
  608. obj.style.position = 'absolute';
  609. obj.style.visibility = (vis) ? (vis==2) ? 'inherit' : 'visible' : 'hidden';
  610. obj.style.left = l+'px';
  611. obj.style.top = t+'px';
  612. if (w)
  613. obj.style.width = w.toString().match(/\%$/) ? w : w+'px';
  614. if (h)
  615. obj.style.height = h.toString().match(/\%$/) ? h : h+'px';
  616. if (z)
  617. obj.style.zIndex = z;
  618. if (parent)
  619. parent.appendChild(obj);
  620. else
  621. document.body.appendChild(obj);
  622. this.elm = obj;
  623. };
  624. // create new layer
  625. if (attributes != null) {
  626. this.create(attributes);
  627. this.name = this.elm.id;
  628. }
  629. else // just refer to the object
  630. this.elm = document.getElementById(id);
  631. if (!this.elm)
  632. return false;
  633. // ********* layer object properties *********
  634. this.css = this.elm.style;
  635. this.event = this.elm;
  636. this.width = this.elm.offsetWidth;
  637. this.height = this.elm.offsetHeight;
  638. this.x = parseInt(this.elm.offsetLeft);
  639. this.y = parseInt(this.elm.offsetTop);
  640. this.visible = (this.css.visibility=='visible' || this.css.visibility=='show' || this.css.visibility=='inherit') ? true : false;
  641. // ********* layer object methods *********
  642. // move the layer to a specific position
  643. this.move = function(x, y)
  644. {
  645. this.x = x;
  646. this.y = y;
  647. this.css.left = Math.round(this.x)+'px';
  648. this.css.top = Math.round(this.y)+'px';
  649. };
  650. // change the layers width and height
  651. this.resize = function(w,h)
  652. {
  653. this.css.width = w+'px';
  654. this.css.height = h+'px';
  655. this.width = w;
  656. this.height = h;
  657. };
  658. // show or hide the layer
  659. this.show = function(a)
  660. {
  661. if(a == 1) {
  662. this.css.visibility = 'visible';
  663. this.visible = true;
  664. }
  665. else if(a == 2) {
  666. this.css.visibility = 'inherit';
  667. this.visible = true;
  668. }
  669. else {
  670. this.css.visibility = 'hidden';
  671. this.visible = false;
  672. }
  673. };
  674. // write new content into a Layer
  675. this.write = function(cont)
  676. {
  677. this.elm.innerHTML = cont;
  678. };
  679. };
  680. /**
  681. * Scroller
  682. *
  683. * @deprecated Use treelist widget
  684. */
  685. function rcmail_scroller(list, top, bottom)
  686. {
  687. var ref = this;
  688. this.list = $(list);
  689. this.top = $(top);
  690. this.bottom = $(bottom);
  691. this.step_size = 6;
  692. this.step_time = 20;
  693. this.delay = 500;
  694. this.top
  695. .mouseenter(function() { ref.ts = window.setTimeout(function() { ref.scroll('down'); }, ref.delay); })
  696. .mouseout(function() { if (ref.ts) window.clearTimeout(ref.ts); });
  697. this.bottom
  698. .mouseenter(function() { ref.ts = window.setTimeout(function() { ref.scroll('up'); }, ref.delay); })
  699. .mouseout(function() { if (ref.ts) window.clearTimeout(ref.ts); });
  700. this.scroll = function(dir)
  701. {
  702. var ref = this, size = this.step_size;
  703. if (!rcmail.drag_active)
  704. return;
  705. if (dir == 'down')
  706. size *= -1;
  707. this.list.get(0).scrollTop += size;
  708. this.ts = window.setTimeout(function() { ref.scroll(dir); }, this.step_time);
  709. };
  710. };
  711. // Abbreviate mailbox names to fit width of the container
  712. function rcube_render_mailboxlist()
  713. {
  714. var list = $('#mailboxlist > li > a, #mailboxlist ul:visible > li > a');
  715. // it's too slow with really big number of folders
  716. if (list.length > 100)
  717. return;
  718. list.each(function() {
  719. var elem = $(this),
  720. text = elem.data('text');
  721. if (!text) {
  722. text = elem.text().replace(/\s+\([0-9]+\)$/, '');
  723. elem.data('text', text);
  724. }
  725. if (text.length < 6)
  726. return;
  727. var abbrev = fit_string_to_size(text, elem, elem.width() - elem.children('span.unreadcount').width() - 16);
  728. if (abbrev != text)
  729. elem.attr('title', text);
  730. elem.contents().filter(function(){ return (this.nodeType == 3); }).get(0).data = abbrev;
  731. });
  732. };
  733. // inspired by https://gist.github.com/24261/7fdb113f1e26111bd78c0c6fe515f6c0bf418af5
  734. function fit_string_to_size(str, elem, len)
  735. {
  736. var w, span, $span, result = str, ellip = '...';
  737. if (!rcmail.env.tmp_span) {
  738. // it should be appended to elem to use the same css style
  739. // but for performance reasons we'll append it to body (once)
  740. span = $('<b>').css({visibility: 'hidden', padding: '0px',
  741. 'font-family': elem.css('font-family'),
  742. 'font-size': elem.css('font-size')})
  743. .appendTo($('body', document)).get(0);
  744. rcmail.env.tmp_span = span;
  745. }
  746. else {
  747. span = rcmail.env.tmp_span;
  748. }
  749. $span = $(span);
  750. $span.text(result);
  751. // on first run, check if string fits into the length already.
  752. w = span.offsetWidth;
  753. if (w > len) {
  754. var cut = Math.max(1, Math.floor(str.length * ((w - len) / w) / 2)),
  755. mid = Math.floor(str.length / 2),
  756. offLeft = mid,
  757. offRight = mid;
  758. while (true) {
  759. offLeft = mid - cut;
  760. offRight = mid + cut;
  761. $span.text(str.substring(0,offLeft) + ellip + str.substring(offRight));
  762. // break loop if string fits size
  763. if (offLeft < 3 || span.offsetWidth)
  764. break;
  765. cut++;
  766. }
  767. // build resulting string
  768. result = str.substring(0,offLeft) + ellip + str.substring(offRight);
  769. }
  770. return result;
  771. };
  772. function update_quota(data)
  773. {
  774. percent_indicator(rcmail.gui_objects.quotadisplay, data);
  775. if (data.table) {
  776. var menu = $('#quotamenu');
  777. if (!menu.length)
  778. menu = $('<div id="quotamenu" class="popupmenu">').appendTo($('body'));
  779. menu.html(data.table);
  780. $('#quotaimg').css('cursor', 'pointer').off('click').on('click', function(e) {
  781. return rcmail.command('menu-open', 'quotamenu', e.target, e);
  782. });
  783. }
  784. };
  785. // percent (quota) indicator
  786. function percent_indicator(obj, data)
  787. {
  788. if (!data || !obj)
  789. return false;
  790. var limit_high = 80,
  791. limit_mid = 55,
  792. width = data.width ? data.width : rcmail.env.indicator_width ? rcmail.env.indicator_width : 100,
  793. height = data.height ? data.height : rcmail.env.indicator_height ? rcmail.env.indicator_height : 14,
  794. quota = data.percent ? Math.abs(parseInt(data.percent)) : 0,
  795. quota_width = parseInt(quota / 100 * width),
  796. pos = $(obj).position();
  797. // workarounds for Opera and Webkit bugs
  798. pos.top = Math.max(0, pos.top);
  799. pos.left = Math.max(0, pos.left);
  800. rcmail.env.indicator_width = width;
  801. rcmail.env.indicator_height = height;
  802. // overlimit
  803. if (quota_width > width) {
  804. quota_width = width;
  805. quota = 100;
  806. }
  807. if (data.title)
  808. data.title = rcmail.get_label('quota') + ': ' + data.title;
  809. // main div
  810. var main = $('<div>');
  811. main.css({position: 'absolute', top: pos.top, left: pos.left,
  812. width: width + 'px', height: height + 'px', zIndex: 100, lineHeight: height + 'px'})
  813. .attr('title', data.title).addClass('quota_text').html(quota + '%');
  814. // used bar
  815. var bar1 = $('<div>');
  816. bar1.css({position: 'absolute', top: pos.top + 1, left: pos.left + 1,
  817. width: quota_width + 'px', height: height + 'px', zIndex: 99});
  818. // background
  819. var bar2 = $('<div>');
  820. bar2.css({position: 'absolute', top: pos.top + 1, left: pos.left + 1,
  821. width: width + 'px', height: height + 'px', zIndex: 98})
  822. .addClass('quota_bg');
  823. if (quota >= limit_high) {
  824. main.addClass(' quota_text_high');
  825. bar1.addClass('quota_high');
  826. }
  827. else if(quota >= limit_mid) {
  828. main.addClass(' quota_text_mid');
  829. bar1.addClass('quota_mid');
  830. }
  831. else {
  832. main.addClass(' quota_text_low');
  833. bar1.addClass('quota_low');
  834. }
  835. // replace quota image
  836. $(obj).html('').append(bar1).append(bar2).append(main);
  837. // update #quotaimg title
  838. $('#quotaimg').attr('title', data.title);
  839. };
  840. function attachment_menu_append(item)
  841. {
  842. $(item).append(
  843. $('<a class="drop"></a>').on('click keypress', function(e) {
  844. if (e.type != 'keypress' || e.which == 13) {
  845. rcmail_ui.show_attachmentmenu(this, e);
  846. return false;
  847. }
  848. })
  849. );
  850. };
  851. // Optional parameters used by TinyMCE
  852. var rcmail_editor_settings = {};
  853. var rcmail_ui;
  854. function rcube_init_mail_ui()
  855. {
  856. rcmail_ui = new rcube_mail_ui();
  857. $(document.body).mouseup(function(e) { rcmail_ui.body_mouseup(e); })
  858. .mousedown(function(e) { rcmail_ui.body_keydown(e); });
  859. rcmail.addEventListener('init', function() {
  860. if (rcmail.env.quota_content)
  861. update_quota(rcmail.env.quota_content);
  862. rcmail.addEventListener('setquota', update_quota);
  863. rcube_webmail.set_iframe_events({mouseup: function(e) { return rcmail_ui.body_mouseup(e); }});
  864. if (rcmail.env.task == 'mail') {
  865. rcmail.addEventListener('enable-command', 'enable_command', rcmail_ui)
  866. .addEventListener('menu-open', 'menu_open', rcmail_ui)
  867. .addEventListener('aftersend-attachment', 'uploadmenu', rcmail_ui)
  868. .addEventListener('aftertoggle-editor', 'resize_compose_body_ev', rcmail_ui)
  869. .gui_object('dragmenu', 'dragmenu');
  870. if (rcmail.gui_objects.mailboxlist) {
  871. rcmail.treelist.addEventListener('expand', rcube_render_mailboxlist);
  872. rcmail.addEventListener('responseaftermark', rcube_render_mailboxlist)
  873. .addEventListener('responseaftergetunread', rcube_render_mailboxlist)
  874. .addEventListener('responseaftercheck-recent', rcube_render_mailboxlist)
  875. .addEventListener('responseafterrefresh', rcube_render_mailboxlist)
  876. .addEventListener('afterimport-messages', function(){ rcmail_ui.show_popup('uploadform', false); });
  877. }
  878. rcmail.init_pagejumper('#pagejumper');
  879. // fix message list header on window resize (#1490213)
  880. if (bw.ie && rcmail.message_list)
  881. $(window).resize(function() {
  882. setTimeout(function() { rcmail.message_list.resize(); }, 10);
  883. });
  884. if (rcmail.env.action == 'list' || !rcmail.env.action) {
  885. rcmail.addEventListener('layout-change', 'set_layout', rcmail_ui);
  886. rcmail_ui.set_layout();
  887. }
  888. else if (rcmail.env.action == 'compose') {
  889. rcmail_ui.init_compose_form();
  890. rcmail.addEventListener('compose-encrypted', function(e) {
  891. $("a.button.encrypt")[(e.active ? 'addClass' : 'removeClass')]('selected');
  892. $("select[name='editorSelector']").prop('disabled', e.active);
  893. $('a.button.attach, a.button.responses, a.button.attach, #uploadmenulink')[(e.active ? 'addClass' : 'removeClass')]('buttonPas disabled');
  894. $('#responseslist a.insertresponse')[(e.active ? 'removeClass' : 'addClass')]('active');
  895. });
  896. rcmail.addEventListener('fileappended', function(e) {
  897. if (e.attachment.complete)
  898. attachment_menu_append(e.item);
  899. });
  900. // add menu link for each attachment
  901. $('#attachmentslist > li').each(function() {
  902. attachment_menu_append(this);
  903. });
  904. }
  905. else if (rcmail.env.action == 'show' || rcmail.env.action == 'preview') {
  906. // add menu link for each attachment
  907. $('#attachment-list > li[id^="attach"]').each(function() {
  908. attachment_menu_append(this);
  909. });
  910. $(window).resize(function() {
  911. if (!$('#attachment-list > li[id^="attach"]').length)
  912. $('#attachment-list').hide();
  913. var mvlpe = $('#messagebody.mailvelope');
  914. if (mvlpe.length) {
  915. var content = $('#messageframe'),
  916. h = (content.length ? content.height() + content.offset().top - 25 : $(this).height()) - mvlpe.offset().top - 20;
  917. mvlpe.height(h);
  918. }
  919. });
  920. }
  921. }
  922. else if (rcmail.env.task == 'addressbook') {
  923. rcmail.addEventListener('afterupload-photo', function(){ rcmail_ui.show_popup('uploadform', false); })
  924. .gui_object('dragmenu', 'dragmenu');
  925. }
  926. else if (rcmail.env.task == 'settings') {
  927. if (rcmail.env.action == 'folders') {
  928. rcmail_ui.folder_search_init($('#folder-manager'));
  929. }
  930. }
  931. });
  932. }