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.

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495
  1. /**
  2. * Roundcube functions for default skin interface
  3. *
  4. * Copyright (c) 2013, The Roundcube Dev Team
  5. *
  6. * The contents are subject to the Creative Commons Attribution-ShareAlike
  7. * License. It is allowed to copy, distribute, transmit and to adapt the work
  8. * by keeping credits to the original autors in the README file.
  9. * See http://creativecommons.org/licenses/by-sa/3.0/ for details.
  10. *
  11. * @license magnet:?xt=urn:btih:90dc5c0be029de84e523b9b3922520e79e0e6f08&dn=cc0.txt CC0-1.0
  12. */
  13. function rcube_mail_ui()
  14. {
  15. var env = {};
  16. var popups = {};
  17. var popupconfig = {
  18. forwardmenu: { editable:1 },
  19. searchmenu: { editable:1, callback:searchmenu },
  20. attachmentmenu: { },
  21. listoptions: { editable:1 },
  22. groupmenu: { above:1 },
  23. mailboxmenu: { above:1 },
  24. spellmenu: { callback: spellmenu },
  25. 'folder-selector': { iconized:1 }
  26. };
  27. var me = this;
  28. var mailviewsplit;
  29. var compose_headers = {};
  30. var prefs;
  31. // export public methods
  32. this.set = setenv;
  33. this.init = init;
  34. this.init_tabs = init_tabs;
  35. this.show_about = show_about;
  36. this.show_popup = show_popup;
  37. this.toggle_popup = toggle_popup;
  38. this.add_popup = add_popup;
  39. this.set_searchmod = set_searchmod;
  40. this.set_searchscope = set_searchscope;
  41. this.show_uploadform = show_uploadform;
  42. this.show_header_row = show_header_row;
  43. this.hide_header_row = hide_header_row;
  44. this.update_quota = update_quota;
  45. this.get_pref = get_pref;
  46. this.save_pref = save_pref;
  47. this.folder_search_init = folder_search_init;
  48. // set minimal mode on small screens (don't wait for document.ready)
  49. if (window.$ && document.body) {
  50. var minmode = get_pref('minimalmode');
  51. if (parseInt(minmode) || (minmode === null && $(window).height() < 850)) {
  52. $(document.body).addClass('minimal');
  53. }
  54. if (bw.tablet) {
  55. $('#viewport').attr('content', "width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0");
  56. }
  57. }
  58. /**
  59. *
  60. */
  61. function setenv(key, val)
  62. {
  63. env[key] = val;
  64. }
  65. /**
  66. * Get preference stored in browser
  67. */
  68. function get_pref(key)
  69. {
  70. if (!prefs) {
  71. prefs = rcmail.local_storage_get_item('prefs.larry', {});
  72. }
  73. // fall-back to cookies
  74. if (prefs[key] == null) {
  75. var cookie = rcmail.get_cookie(key);
  76. if (cookie != null) {
  77. prefs[key] = cookie;
  78. // copy value to local storage and remove cookie (if localStorage is supported)
  79. if (rcmail.local_storage_set_item('prefs.larry', prefs)) {
  80. rcmail.set_cookie(key, cookie, new Date()); // expire cookie
  81. }
  82. }
  83. }
  84. return prefs[key];
  85. }
  86. /**
  87. * Saves preference value to browser storage
  88. */
  89. function save_pref(key, val)
  90. {
  91. prefs[key] = val;
  92. // write prefs to local storage (if supported)
  93. if (!rcmail.local_storage_set_item('prefs.larry', prefs)) {
  94. // store value in cookie
  95. var exp = new Date();
  96. exp.setYear(exp.getFullYear() + 1);
  97. rcmail.set_cookie(key, val, exp);
  98. }
  99. }
  100. /**
  101. * Initialize UI
  102. * Called on document.ready
  103. */
  104. function init()
  105. {
  106. rcmail.addEventListener('message', message_displayed);
  107. /*** prepare minmode functions ***/
  108. $('#taskbar a').each(function(i,elem){
  109. $(elem).append('<span class="tooltip">' + $('.button-inner', this).html() + '</span>')
  110. });
  111. $('#taskbar .minmodetoggle').click(function(e){
  112. var ismin = $(document.body).toggleClass('minimal').hasClass('minimal');
  113. save_pref('minimalmode', ismin?1:0);
  114. $(window).resize();
  115. });
  116. /*** mail task ***/
  117. if (rcmail.env.task == 'mail') {
  118. rcmail.addEventListener('menu-open', menu_toggle)
  119. .addEventListener('menu-close', menu_toggle)
  120. .addEventListener('menu-save', save_listoptions)
  121. .addEventListener('enable-command', enable_command)
  122. .addEventListener('responseafterlist', function(e){ switch_view_mode(rcmail.env.threading ? 'thread' : 'list', true) })
  123. .addEventListener('responseaftersearch', function(e){ switch_view_mode(rcmail.env.threading ? 'thread' : 'list', true) });
  124. var dragmenu = $('#dragmessagemenu');
  125. if (dragmenu.length) {
  126. rcmail.gui_object('dragmenu', 'dragmessagemenu');
  127. popups.dragmenu = dragmenu;
  128. }
  129. if (rcmail.env.action == 'show' || rcmail.env.action == 'preview') {
  130. rcmail.addEventListener('aftershow-headers', function() { layout_messageview(); })
  131. .addEventListener('afterhide-headers', function() { layout_messageview(); });
  132. $('#previewheaderstoggle').click(function(e) {
  133. toggle_preview_headers();
  134. if (this.blur && !rcube_event.is_keyboard(e))
  135. this.blur();
  136. return false;
  137. });
  138. // add menu link for each attachment
  139. $('#attachment-list > li').each(function() {
  140. $(this).append($('<a class="drop" tabindex="0" aria-haspopup="true">Show options</a>')
  141. .on('click keypress', function(e) {
  142. if (e.type != 'keypress' || rcube_event.get_keycode(e) == 13) {
  143. attachmentmenu(this, e);
  144. return false;
  145. }
  146. })
  147. );
  148. });
  149. if (get_pref('previewheaders') == '1') {
  150. toggle_preview_headers();
  151. }
  152. }
  153. else if (rcmail.env.action == 'compose') {
  154. rcmail.addEventListener('aftersend-attachment', show_uploadform)
  155. .addEventListener('aftertoggle-editor', function(e) {
  156. window.setTimeout(function(){ layout_composeview() }, 200);
  157. if (e && e.mode)
  158. $("select[name='editorSelector']").val(e.mode);
  159. })
  160. .addEventListener('compose-encrypted', function(e) {
  161. $("select[name='editorSelector']").prop('disabled', e.active);
  162. $('a.button.attach, a.button.responses')[(e.active?'addClass':'removeClass')]('disabled');
  163. $('#responseslist a.insertresponse')[(e.active?'removeClass':'addClass')]('active');
  164. });
  165. // Show input elements with non-empty value
  166. var f, v, field, fields = ['cc', 'bcc', 'replyto', 'followupto'];
  167. for (f=0; f < fields.length; f++) {
  168. v = fields[f]; field = $('#_'+v);
  169. if (field.length) {
  170. field.on('change', {v: v}, function(e) { if (this.value) show_header_row(e.data.v, true); });
  171. if (field.val() != '')
  172. show_header_row(v, true);
  173. }
  174. }
  175. $('#composeoptionstoggle').click(function(e){
  176. var expanded = $('#composeoptions').toggle().is(':visible');
  177. $('#composeoptionstoggle').toggleClass('remove').attr('aria-expanded', expanded ? 'true' : 'false');
  178. layout_composeview();
  179. save_pref('composeoptions', expanded ? '1' : '0');
  180. if (!rcube_event.is_keyboard(e))
  181. this.blur();
  182. return false;
  183. }).css('cursor', 'pointer');
  184. if (get_pref('composeoptions') !== '0') {
  185. $('#composeoptionstoggle').click();
  186. }
  187. // adjust hight when textarea starts to scroll
  188. $("textarea[name='_to'], textarea[name='_cc'], textarea[name='_bcc']").change(function(e){ adjust_compose_editfields(this); }).change();
  189. rcmail.addEventListener('autocomplete_insert', function(p){ adjust_compose_editfields(p.field); });
  190. // toggle compose options if opened in new window and they were visible before
  191. var opener_rc = rcmail.opener();
  192. if (opener_rc && opener_rc.env.action == 'compose' && $('#composeoptionstoggle', opener.document).hasClass('remove'))
  193. $('#composeoptionstoggle').click();
  194. new rcube_splitter({ id:'composesplitterv', p1:'#composeview-left', p2:'#composeview-right',
  195. orientation:'v', relative:true, start:206, min:170, size:12, render:layout_composeview }).init();
  196. }
  197. else if (rcmail.env.action == 'list' || !rcmail.env.action) {
  198. var previewframe = $('#mailpreviewframe').is(':visible');
  199. $('#mailpreviewtoggle').addClass(previewframe ? 'enabled' : 'closed').attr('aria-expanded', previewframe ? 'true' : 'false')
  200. .click(function(e) { toggle_preview_pane(e); return false; });
  201. $('#maillistmode').addClass(rcmail.env.threading ? '' : 'selected').click(function(e) { switch_view_mode('list'); return false; });
  202. $('#mailthreadmode').addClass(rcmail.env.threading ? 'selected' : '').click(function(e) { switch_view_mode('thread'); return false; });
  203. mailviewsplit = new rcube_splitter({ id:'mailviewsplitter', p1:'#mailview-top', p2:'#mailview-bottom',
  204. orientation:'h', relative:true, start:276, min:150, size:12, offset:4 });
  205. if (previewframe)
  206. mailviewsplit.init();
  207. rcmail.init_pagejumper('#pagejumper');
  208. rcmail.addEventListener('setquota', update_quota)
  209. .addEventListener('afterimport-messages', show_uploadform);
  210. }
  211. else if (rcmail.env.action == 'get') {
  212. new rcube_splitter({ id:'mailpartsplitterv', p1:'#messagepartheader', p2:'#messagepartcontainer',
  213. orientation:'v', relative:true, start:226, min:150, size:12}).init();
  214. }
  215. if ($('#mailview-left').length) {
  216. new rcube_splitter({ id:'mailviewsplitterv', p1:'#mailview-left', p2:'#mailview-right',
  217. orientation:'v', relative:true, start:206, min:150, size:12, callback:render_mailboxlist, render:resize_leftcol }).init();
  218. }
  219. }
  220. /*** settings task ***/
  221. else if (rcmail.env.task == 'settings') {
  222. rcmail.addEventListener('init', function(){
  223. var tab = '#settingstabpreferences';
  224. if (rcmail.env.action)
  225. tab = '#settingstab' + (rcmail.env.action.indexOf('identity')>0 ? 'identities' : rcmail.env.action.replace(/\./g, ''));
  226. $(tab).addClass('selected')
  227. .children().first().removeAttr('onclick').click(function() { return false; });
  228. });
  229. if (rcmail.env.action == 'folders') {
  230. new rcube_splitter({ id:'folderviewsplitter', p1:'#folderslist', p2:'#folder-details',
  231. orientation:'v', relative:true, start:266, min:180, size:12 }).init();
  232. rcmail.addEventListener('setquota', update_quota);
  233. folder_search_init($('#folderslist'));
  234. }
  235. else if (rcmail.env.action == 'identities') {
  236. new rcube_splitter({ id:'identviewsplitter', p1:'#identitieslist', p2:'#identity-details',
  237. orientation:'v', relative:true, start:266, min:180, size:12 }).init();
  238. }
  239. else if (rcmail.env.action == 'responses') {
  240. new rcube_splitter({ id:'responseviewsplitter', p1:'#identitieslist', p2:'#identity-details',
  241. orientation:'v', relative:true, start:266, min:180, size:12 }).init();
  242. }
  243. else if (rcmail.env.action == 'preferences' || !rcmail.env.action) {
  244. new rcube_splitter({ id:'prefviewsplitter', p1:'#sectionslist', p2:'#preferences-box',
  245. orientation:'v', relative:true, start:266, min:180, size:12 }).init();
  246. }
  247. else if (rcmail.env.action == 'edit-prefs') {
  248. var legend = $('#preferences-details fieldset.advanced legend'),
  249. toggle = $('<a href="#toggle"></a>')
  250. .text(env.toggleoptions)
  251. .attr('title', env.toggleoptions)
  252. .addClass('advanced-toggle');
  253. legend.click(function(e) {
  254. toggle.html($(this).hasClass('collapsed') ? '&#9650;' : '&#9660;');
  255. $(this).toggleClass('collapsed')
  256. .closest('fieldset').children('.propform').toggle()
  257. }).append(toggle).addClass('collapsed')
  258. // this magically fixes incorrect position of toggle link created above in Firefox 3.6
  259. if (bw.mz)
  260. legend.parents('form').css('display', 'inline');
  261. }
  262. }
  263. /*** addressbook task ***/
  264. else if (rcmail.env.task == 'addressbook') {
  265. rcmail.addEventListener('afterupload-photo', show_uploadform)
  266. .addEventListener('beforepushgroup', push_contactgroup)
  267. .addEventListener('beforepopgroup', pop_contactgroup)
  268. .addEventListener('menu-open', menu_toggle)
  269. .addEventListener('menu-close', menu_toggle);
  270. if (rcmail.env.action == '') {
  271. new rcube_splitter({ id:'addressviewsplitterd', p1:'#addressview-left', p2:'#addressview-right',
  272. orientation:'v', relative:true, start:206, min:150, size:12, render:resize_leftcol }).init();
  273. new rcube_splitter({ id:'addressviewsplitter', p1:'#addresslist', p2:'#contacts-box',
  274. orientation:'v', relative:true, start:266, min:260, size:12 }).init();
  275. }
  276. var dragmenu = $('#dragcontactmenu');
  277. if (dragmenu.length) {
  278. rcmail.gui_object('dragmenu', 'dragcontactmenu');
  279. popups.dragmenu = dragmenu;
  280. }
  281. }
  282. // turn a group of fieldsets into tabs
  283. $('.tabbed').each(function(idx, elem){ init_tabs(elem); })
  284. // decorate select elements
  285. $('select.decorated').each(function(){
  286. if (bw.opera) {
  287. $(this).removeClass('decorated');
  288. return;
  289. }
  290. var select = $(this),
  291. parent = select.parent(),
  292. height = Math.max(select.height(), 26) - 2,
  293. width = select.width() - 22,
  294. title = $('option', this).first().text();
  295. if ($('option:selected', this).val() != '')
  296. title = $('option:selected', this).text();
  297. var overlay = $('<a class="menuselector" tabindex="-1"><span class="handle">' + title + '</span></a>')
  298. .css('position', 'absolute')
  299. .offset(select.position())
  300. .insertAfter(select);
  301. overlay.children().width(width).height(height).css('line-height', (height - 1) + 'px');
  302. if (parent.css('position') != 'absolute')
  303. parent.css('position', 'relative');
  304. // re-set original select width to fix click action and options width in some browsers
  305. select.width(overlay.width())
  306. .on(bw.mz ? 'change keyup' : 'change', function() {
  307. var val = $('option:selected', this).text();
  308. $(this).next().children().text(val);
  309. });
  310. select
  311. .on('focus', function(e){ overlay.addClass('focus'); })
  312. .on('blur', function(e){ overlay.removeClass('focus'); });
  313. });
  314. // set min-width to show all toolbar buttons
  315. var screen = $('body.minwidth');
  316. if (screen.length) {
  317. screen.css('min-width', $('.toolbar').width() + $('#quicksearchbar').width() + $('#searchfilter').width() + 30);
  318. }
  319. // don't use $(window).resize() due to some unwanted side-effects
  320. window.onresize = resize;
  321. resize();
  322. }
  323. /**
  324. * Update UI on window resize
  325. */
  326. function resize(e)
  327. {
  328. // resize in intervals to prevent lags and double onresize calls in Chrome (#1489005)
  329. var interval = e ? 10 : 0;
  330. if (rcmail.resize_timeout)
  331. window.clearTimeout(rcmail.resize_timeout);
  332. rcmail.resize_timeout = window.setTimeout(function() {
  333. if (rcmail.env.task == 'mail') {
  334. if (rcmail.env.action == 'show' || rcmail.env.action == 'preview')
  335. layout_messageview();
  336. else if (rcmail.env.action == 'compose')
  337. layout_composeview();
  338. }
  339. // make iframe footer buttons float if scrolling is active
  340. $('body.iframe .footerleft').each(function(){
  341. var footer = $(this),
  342. body = $(document.body),
  343. floating = footer.hasClass('floating'),
  344. overflow = body.outerHeight(true) > $(window).height();
  345. if (overflow != floating) {
  346. var action = overflow ? 'addClass' : 'removeClass';
  347. footer[action]('floating');
  348. body[action]('floatingbuttons');
  349. }
  350. });
  351. }, interval);
  352. }
  353. /**
  354. * Triggered when a new user message is displayed
  355. */
  356. function message_displayed(p)
  357. {
  358. var siblings = $(p.object).siblings('div');
  359. if (siblings.length)
  360. $(p.object).insertBefore(siblings.first());
  361. // show a popup dialog on errors
  362. if (p.type == 'error' && rcmail.env.task != 'login') {
  363. // hide original message object, we don't want both
  364. rcmail.hide_message(p.object);
  365. if (me.message_timer) {
  366. window.clearTimeout(me.message_timer);
  367. }
  368. if (!me.messagedialog) {
  369. me.messagedialog = $('<div>').addClass('popupdialog').hide();
  370. }
  371. var msg = p.message,
  372. dialog_close = function() {
  373. // check if dialog is still displayed, to prevent from js error
  374. me.messagedialog.is(':visible') && me.messagedialog.dialog('destroy').hide();
  375. };
  376. if (me.messagedialog.is(':visible') && me.messagedialog.text() != msg)
  377. msg = me.messagedialog.html() + '<p>' + p.message + '</p>';
  378. me.messagedialog.html(msg)
  379. .dialog({
  380. resizable: false,
  381. closeOnEscape: true,
  382. dialogClass: 'popupmessage ' + p.type,
  383. title: env.errortitle,
  384. close: dialog_close,
  385. position: ['center', 'center'],
  386. hide: {effect: 'fadeOut'},
  387. width: 420,
  388. minHeight: 90
  389. }).show();
  390. me.messagedialog.closest('div[role=dialog]').attr('role', 'alertdialog');
  391. if (p.timeout > 0)
  392. me.message_timer = window.setTimeout(dialog_close, p.timeout);
  393. }
  394. }
  395. /**
  396. * Adjust UI objects of the mail view screen
  397. */
  398. function layout_messageview()
  399. {
  400. $('#messagecontent').css('top', ($('#messageheader').outerHeight() + 1) + 'px');
  401. $('#message-objects div a').addClass('button');
  402. if (!$('#attachment-list li').length) {
  403. $('div.rightcol').hide().attr('aria-hidden', 'true');
  404. $('div.leftcol').css('margin-right', '0');
  405. }
  406. var mvlpe = $('#messagebody.mailvelope, #messagebody > .mailvelope');
  407. if (mvlpe.length) {
  408. var h = $('#messagecontent').length ?
  409. $('#messagecontent').height() - 16 :
  410. $(window).height() - mvlpe.offset().top - 10;
  411. mvlpe.height(h);
  412. }
  413. }
  414. function render_mailboxlist(splitter)
  415. {
  416. // TODO: implement smart shortening of long folder names
  417. }
  418. function resize_leftcol(splitter)
  419. {
  420. // STUB
  421. }
  422. function adjust_compose_editfields(elem)
  423. {
  424. if (elem.nodeName == 'TEXTAREA') {
  425. var $elem = $(elem), line_height = 14, // hard-coded because some browsers only provide the outer height in elem.clientHeight
  426. content_height = elem.scrollHeight,
  427. rows = elem.value.length > 80 && content_height > line_height*1.5 ? 2 : 1;
  428. $elem.css('height', (line_height*rows) + 'px');
  429. layout_composeview();
  430. }
  431. }
  432. function layout_composeview()
  433. {
  434. var body = $('#composebody'),
  435. form = $('#compose-content'),
  436. bottom = $('#composeview-bottom'),
  437. w, h, bh, ovflw, btns = 0,
  438. minheight = 300,
  439. bh = form.height() - bottom.position().top;
  440. ovflw = minheight - bh;
  441. btns = ovflw > -100 ? 0 : 40;
  442. bottom.height(Math.max(minheight, bh));
  443. form.css('overflow', ovflw > 0 ? 'auto' : 'hidden');
  444. w = body.parent().width() - 5;
  445. h = body.parent().height() - 8;
  446. body.width(w).height(h);
  447. $('#composebodycontainer > div').width(w+8);
  448. $('#composebody_ifr').height(h + 4 - $('div.mce-toolbar').height());
  449. $('#googie_edit_layer').width(w).height(h);
  450. // $('#composebodycontainer')[(btns ? 'addClass' : 'removeClass')]('buttons');
  451. // $('#composeformbuttons')[(btns ? 'show' : 'hide')]();
  452. var abooks = $('#directorylist');
  453. if (abooks.length)
  454. $('#compose-contacts .scroller').css('top', abooks.position().top + abooks.outerHeight());
  455. }
  456. function update_quota(p)
  457. {
  458. var element = $('#quotadisplay'), menu = $('#quotamenu'),
  459. step = 24, step_count = 20,
  460. y = p.total ? Math.ceil(p.percent / 100 * step_count) * step : 0;
  461. // never show full-circle if quota is close to 100% but below.
  462. if (p.total && y == step * step_count && p.percent < 100)
  463. y -= step;
  464. element.css('background-position', '0 -' + y + 'px');
  465. element.attr('class', 'countdisplay p' + (Math.round(p.percent / 10) * 10));
  466. if (p.table) {
  467. if (!menu.length)
  468. menu = $('<div id="quotamenu" class="popupmenu">').appendTo($('body'));
  469. menu.html(p.table);
  470. element.css('cursor', 'pointer').off('click').on('click', function(e) {
  471. return rcmail.command('menu-open', 'quotamenu', e.target, e);
  472. });
  473. }
  474. }
  475. function folder_search_init(container)
  476. {
  477. // animation to unfold list search box
  478. $('.boxtitle a.search', container).click(function(e) {
  479. var title = $('.boxtitle', container),
  480. box = $('.listsearchbox', container),
  481. dir = box.is(':visible') ? -1 : 1,
  482. height = 34 + ($('select', box).length ? 22 : 0);
  483. box.slideToggle({
  484. duration: 160,
  485. progress: function(animation, progress) {
  486. if (dir < 0) progress = 1 - progress;
  487. $('.scroller', container).css('top', (title.outerHeight() + height * progress) + 'px');
  488. },
  489. complete: function() {
  490. box.toggleClass('expanded');
  491. if (box.is(':visible')) {
  492. box.find('input[type=text]').focus();
  493. height = 34 + ($('select', box).length ? $('select', box).outerHeight() + 4 : 0);
  494. $('.scroller', container).css('top', (title.outerHeight() + height) + 'px');
  495. }
  496. else {
  497. $('a.reset', box).click();
  498. }
  499. // TODO: save state in localStorage
  500. }
  501. });
  502. return false;
  503. });
  504. }
  505. function enable_command(p)
  506. {
  507. if (p.command == 'reply-list' && rcmail.env.reply_all_mode == 1) {
  508. var label = rcmail.gettext(p.status ? 'replylist' : 'replyall');
  509. if (rcmail.env.action == 'preview')
  510. $('a.button.replyall').attr('title', label);
  511. else
  512. $('a.button.reply-all').text(label).attr('title', label);
  513. }
  514. else if (p.command == 'compose-encrypted') {
  515. // show the toolbar button for Mailvelope
  516. $('a.button.encrypt').show();
  517. }
  518. }
  519. /**
  520. * Register a popup menu
  521. */
  522. function add_popup(popup, config)
  523. {
  524. var obj = popups[popup] = $('#'+popup);
  525. obj.appendTo(document.body); // move it to top for proper absolute positioning
  526. if (obj.length)
  527. popupconfig[popup] = $.extend(popupconfig[popup] || {}, config || {});
  528. }
  529. /**
  530. * Trigger for popup menus
  531. */
  532. function toggle_popup(popup, e, config)
  533. {
  534. // auto-register menu object
  535. if (config || !popupconfig[popup])
  536. add_popup(popup, config);
  537. return rcmail.command('menu-open', popup, e.target, e);
  538. }
  539. /**
  540. * (Deprecated) trigger for popup menus
  541. */
  542. function show_popup(popup, show, config)
  543. {
  544. // auto-register menu object
  545. if (config || !popupconfig[popup])
  546. add_popup(popup, config);
  547. config = popupconfig[popup] || {};
  548. var ref = $(config.link ? config.link : '#'+popup+'link'),
  549. pos = ref.offset();
  550. if (ref.has('.inner'))
  551. ref = ref.children('.inner');
  552. // fire command with simulated mouse click event
  553. return rcmail.command('menu-open',
  554. { menu:popup, show:show },
  555. ref.get(0),
  556. $.Event('click', { target:ref.get(0), pageX:pos.left, pageY:pos.top, clientX:pos.left, clientY:pos.top }));
  557. }
  558. /**
  559. * Show/hide the preview pane
  560. */
  561. function toggle_preview_pane(e)
  562. {
  563. var button = $(e.target),
  564. frame = $('#mailpreviewframe'),
  565. visible = !frame.is(':visible'),
  566. splitter = mailviewsplit.pos || parseInt(get_pref('mailviewsplitter') || 320),
  567. topstyles, bottomstyles, uid;
  568. frame.toggle();
  569. button.toggleClass('enabled closed').attr('aria-expanded', visible ? 'true' : 'false');
  570. if (visible) {
  571. $('#mailview-top').removeClass('fullheight').css({ bottom:'auto' });
  572. $('#mailview-bottom').css({ height:'auto' }).show();
  573. rcmail.env.contentframe = 'messagecontframe';
  574. if (uid = rcmail.message_list.get_single_selection())
  575. rcmail.show_message(uid, false, true);
  576. // let the splitter set the correct size and position
  577. if (mailviewsplit.handle) {
  578. mailviewsplit.handle.show();
  579. mailviewsplit.resize();
  580. }
  581. else
  582. mailviewsplit.init();
  583. }
  584. else {
  585. rcmail.env.contentframe = null;
  586. rcmail.show_contentframe(false);
  587. $('#mailview-top').addClass('fullheight').css({ height:'auto', bottom:'0px' });
  588. $('#mailview-bottom').css({ top:'auto', height:'0px' }).hide();
  589. if (mailviewsplit.handle)
  590. mailviewsplit.handle.hide();
  591. }
  592. if (rcmail.message_list) {
  593. if (visible && uid)
  594. rcmail.message_list.scrollto(uid);
  595. rcmail.message_list.resize();
  596. }
  597. rcmail.command('save-pref', { name:'preview_pane', value:(visible?1:0) });
  598. }
  599. /**
  600. * Switch between short and full headers display in message preview
  601. */
  602. function toggle_preview_headers()
  603. {
  604. $('#preview-shortheaders').toggle();
  605. var full = $('#preview-allheaders').toggle(),
  606. button = $('a#previewheaderstoggle');
  607. // add toggle button to full headers table
  608. if (full.is(':visible'))
  609. button.attr('href', '#hide').removeClass('add').addClass('remove').attr('aria-expanded', 'true');
  610. else
  611. button.attr('href', '#details').removeClass('remove').addClass('add').attr('aria-expanded', 'false');
  612. save_pref('previewheaders', full.is(':visible') ? '1' : '0');
  613. }
  614. /**
  615. *
  616. */
  617. function switch_view_mode(mode, force)
  618. {
  619. if (force || !$('#mail'+mode+'mode').hasClass('disabled')) {
  620. $('#maillistmode, #mailthreadmode').removeClass('selected').attr('tabindex', '0').attr('aria-disabled', 'false');
  621. $('#mail'+mode+'mode').addClass('selected').attr('tabindex', '-1').attr('aria-disabled', 'true');
  622. }
  623. }
  624. /**** popup menu callbacks ****/
  625. /**
  626. * Handler for menu-open and menu-close events
  627. */
  628. function menu_toggle(p)
  629. {
  630. if (p && p.name == 'messagelistmenu') {
  631. show_listoptions(p);
  632. }
  633. else if (p) {
  634. // adjust menu position according to config
  635. var config = popupconfig[p.name] || {},
  636. ref = $(config.link || '#'+p.name+'link'),
  637. visible = p.obj && p.obj.is(':visible'),
  638. above = config.above;
  639. // fix position according to config
  640. if (p.obj && visible && ref.length) {
  641. var parent = ref.parent(),
  642. win = $(window), pos;
  643. if (parent.hasClass('dropbutton'))
  644. ref = parent;
  645. if (config.above || ref.hasClass('dropbutton')) {
  646. pos = ref.offset();
  647. p.obj.css({ left:pos.left+'px', top:(pos.top + (config.above ? -p.obj.height() : ref.outerHeight()))+'px' });
  648. }
  649. }
  650. // add the right classes
  651. if (p.obj && config.iconized) {
  652. p.obj.children('ul').addClass('iconized');
  653. }
  654. // apply some data-attributes from menu config
  655. if (p.obj && config.editable)
  656. p.obj.attr('data-editable', 'true');
  657. // trigger callback function
  658. if (typeof config.callback == 'function') {
  659. config.callback(visible, p);
  660. }
  661. }
  662. }
  663. function searchmenu(show)
  664. {
  665. if (show && rcmail.env.search_mods) {
  666. var n, all,
  667. obj = popups['searchmenu'],
  668. list = $('input:checkbox[name="s_mods[]"]', obj),
  669. mbox = rcmail.env.mailbox,
  670. mods = rcmail.env.search_mods,
  671. scope = rcmail.env.search_scope || 'base';
  672. if (rcmail.env.task == 'mail') {
  673. if (scope == 'all')
  674. mbox = '*';
  675. mods = mods[mbox] ? mods[mbox] : mods['*'];
  676. all = 'text';
  677. $('input:radio[name="s_scope"]').prop('checked', false).filter('#s_scope_'+scope).prop('checked', true);
  678. }
  679. else {
  680. all = '*';
  681. }
  682. if (mods[all])
  683. list.map(function() {
  684. this.checked = true;
  685. this.disabled = this.value != all;
  686. });
  687. else {
  688. list.prop('disabled', false).prop('checked', false);
  689. for (n in mods)
  690. $('#s_mod_' + n).prop('checked', true);
  691. }
  692. }
  693. }
  694. function attachmentmenu(elem, event)
  695. {
  696. var id = elem.parentNode.id.replace(/^attach/, '');
  697. $('#attachmenuopen').off('click').attr('onclick', '').click(function(e) {
  698. return rcmail.command('open-attachment', id, this);
  699. });
  700. $('#attachmenudownload').off('click').attr('onclick', '').click(function() {
  701. rcmail.command('download-attachment', id, this);
  702. });
  703. popupconfig.attachmentmenu.link = elem;
  704. rcmail.command('menu-open', {menu: 'attachmentmenu', id: id}, elem, event);
  705. }
  706. function spellmenu(show, p)
  707. {
  708. var k, link, li,
  709. lang = rcmail.spellcheck_lang(),
  710. ul = $('ul', p.obj);
  711. if (!ul.length) {
  712. ul = $('<ul class="toolbarmenu selectable" role="menu">');
  713. for (k in rcmail.env.spell_langs) {
  714. li = $('<li role="menuitem">');
  715. link = $('<a href="#'+k+'" tabindex="0"></a>').text(rcmail.env.spell_langs[k])
  716. .addClass('active').data('lang', k)
  717. .on('click keypress', function(e) {
  718. if (e.type != 'keypress' || rcube_event.get_keycode(e) == 13) {
  719. rcmail.spellcheck_lang_set($(this).data('lang'));
  720. rcmail.hide_menu('spellmenu', e);
  721. return false;
  722. }
  723. });
  724. link.appendTo(li);
  725. li.appendTo(ul);
  726. }
  727. ul.appendTo(p.obj);
  728. }
  729. // select current language
  730. $('li', ul).each(function() {
  731. var el = $('a', this);
  732. if (el.data('lang') == lang)
  733. el.addClass('selected').attr('aria-selected', 'true');
  734. else if (el.hasClass('selected'))
  735. el.removeClass('selected').removeAttr('aria-selected');
  736. });
  737. }
  738. /**
  739. *
  740. */
  741. function show_listoptions(p)
  742. {
  743. var $dialog = $('#listoptions');
  744. // close the dialog
  745. if ($dialog.is(':visible')) {
  746. $dialog.dialog('close', p.originalEvent);
  747. return;
  748. }
  749. // set form values
  750. $('input[name="sort_col"][value="'+rcmail.env.sort_col+'"]').prop('checked', true);
  751. $('input[name="sort_ord"][value="DESC"]').prop('checked', rcmail.env.sort_order == 'DESC');
  752. $('input[name="sort_ord"][value="ASC"]').prop('checked', rcmail.env.sort_order != 'DESC');
  753. // set checkboxes
  754. $('input[name="list_col[]"]').each(function() {
  755. $(this).prop('checked', $.inArray(this.value, rcmail.env.listcols) != -1);
  756. });
  757. $dialog.dialog({
  758. modal: true,
  759. resizable: false,
  760. closeOnEscape: true,
  761. title: null,
  762. open: function(e) {
  763. setTimeout(function(){ $dialog.find('a, input:not(:disabled)').not('[aria-disabled=true]').first().focus(); }, 100);
  764. },
  765. close: function(e) {
  766. $dialog.dialog('destroy').hide();
  767. if (e.originalEvent && rcube_event.is_keyboard(e.originalEvent))
  768. $('#listmenulink').focus();
  769. },
  770. minWidth: 500,
  771. width: $dialog.width()+25
  772. }).show();
  773. }
  774. /**
  775. *
  776. */
  777. function save_listoptions(p)
  778. {
  779. $('#listoptions').dialog('close');
  780. if (rcube_event.is_keyboard(p.originalEvent))
  781. $('#listmenulink').focus();
  782. var sort = $('input[name="sort_col"]:checked').val(),
  783. ord = $('input[name="sort_ord"]:checked').val(),
  784. cols = $('input[name="list_col[]"]:checked')
  785. .map(function(){ return this.value; }).get();
  786. rcmail.set_list_options(cols, sort, ord, rcmail.env.threading);
  787. }
  788. /**
  789. *
  790. */
  791. function set_searchmod(elem)
  792. {
  793. var all, m, task = rcmail.env.task,
  794. mods = rcmail.env.search_mods,
  795. mbox = rcmail.env.mailbox,
  796. scope = $('input[name="s_scope"]:checked').val();
  797. if (scope == 'all')
  798. mbox = '*';
  799. if (!mods)
  800. mods = {};
  801. if (task == 'mail') {
  802. if (!mods[mbox])
  803. mods[mbox] = rcube_clone_object(mods['*']);
  804. m = mods[mbox];
  805. all = 'text';
  806. }
  807. else { //addressbook
  808. m = mods;
  809. all = '*';
  810. }
  811. if (!elem.checked)
  812. delete(m[elem.value]);
  813. else
  814. m[elem.value] = 1;
  815. // mark all fields
  816. if (elem.value == all) {
  817. $('input:checkbox[name="s_mods[]"]').map(function() {
  818. if (this == elem)
  819. return;
  820. this.checked = true;
  821. if (elem.checked) {
  822. this.disabled = true;
  823. delete m[this.value];
  824. }
  825. else {
  826. this.disabled = false;
  827. m[this.value] = 1;
  828. }
  829. });
  830. }
  831. rcmail.set_searchmods(m);
  832. }
  833. function set_searchscope(elem)
  834. {
  835. rcmail.set_searchscope(elem.value);
  836. }
  837. function push_contactgroup(p)
  838. {
  839. // lets the contacts list swipe to the left, nice!
  840. var table = $('#contacts-table'),
  841. scroller = table.parent().css('overflow', 'hidden');
  842. table.clone()
  843. .css({ position:'absolute', top:'0', left:'0', width:table.width()+'px', 'z-index':10 })
  844. .appendTo(scroller)
  845. .animate({ left: -(table.width()+5) + 'px' }, 300, 'swing', function(){
  846. $(this).remove();
  847. scroller.css('overflow', 'auto')
  848. });
  849. }
  850. function pop_contactgroup(p)
  851. {
  852. // lets the contacts list swipe to the left, nice!
  853. var table = $('#contacts-table'),
  854. scroller = table.parent().css('overflow', 'hidden'),
  855. clone = table.clone().appendTo(scroller);
  856. table.css({ position:'absolute', top:'0', left:-(table.width()+5) + 'px', width:table.width()+'px', height:table.height()+'px', 'z-index':10 })
  857. .animate({ left:'0' }, 300, 'linear', function(){
  858. clone.remove();
  859. $(this).css({ position:'relative', left:'0', width:'100%', height:'auto', 'z-index':1 });
  860. scroller.css('overflow', 'auto')
  861. });
  862. }
  863. function show_uploadform(e)
  864. {
  865. var $dialog = $('#upload-dialog');
  866. // close the dialog
  867. if ($dialog.is(':visible')) {
  868. $dialog.dialog('close');
  869. return;
  870. }
  871. // do nothing if mailvelope editor is active
  872. if (rcmail.mailvelope_editor)
  873. return;
  874. // add icons to clone file input field
  875. if (rcmail.env.action == 'compose' && !$dialog.data('extended')) {
  876. $('<a>')
  877. .addClass('iconlink add')
  878. .attr('href', '#add')
  879. .html('Add')
  880. .appendTo($('input[type="file"]', $dialog).parent())
  881. .click(add_uploadfile);
  882. $dialog.data('extended', true);
  883. }
  884. $dialog.dialog({
  885. modal: true,
  886. resizable: false,
  887. closeOnEscape: true,
  888. title: $dialog.attr('title'),
  889. open: function(e) {
  890. if (!document.all)
  891. $('input[type=file]', $dialog).first().click();
  892. },
  893. close: function() {
  894. try { $('#upload-dialog form').get(0).reset(); }
  895. catch(e){ } // ignore errors
  896. $dialog.dialog('destroy').hide();
  897. $('div.addline', $dialog).remove();
  898. },
  899. width: 480
  900. }).show();
  901. }
  902. function add_uploadfile(e)
  903. {
  904. var div = $(this).parent();
  905. var clone = div.clone().addClass('addline').insertAfter(div);
  906. clone.children('.iconlink').click(add_uploadfile);
  907. clone.children('input').val('');
  908. if (!document.all)
  909. $('input[type=file]', clone).click();
  910. }
  911. /**
  912. *
  913. */
  914. function show_header_row(which, updated)
  915. {
  916. var row = $('#compose-' + which);
  917. if (row.is(':visible'))
  918. return; // nothing to be done here
  919. if (compose_headers[which] && !updated)
  920. $('#_' + which).val(compose_headers[which]);
  921. row.show();
  922. $('#' + which + '-link').hide();
  923. layout_composeview();
  924. $('input,textarea', row).focus();
  925. return false;
  926. }
  927. /**
  928. *
  929. */
  930. function hide_header_row(which)
  931. {
  932. // copy and clear field value
  933. var field = $('#_' + which);
  934. compose_headers[which] = field.val();
  935. field.val('');
  936. $('#compose-' + which).hide();
  937. $('#' + which + '-link').show();
  938. layout_composeview();
  939. return false;
  940. }
  941. /**
  942. * Fieldsets-to-tabs converter
  943. */
  944. function init_tabs(elem, current)
  945. {
  946. var content = $(elem),
  947. id = content.get(0).id,
  948. fs = content.children('fieldset');
  949. if (!fs.length)
  950. return;
  951. if (!id) {
  952. id = 'rcmtabcontainer';
  953. content.attr('id', id);
  954. }
  955. // create tabs container
  956. var tabs = $('<ul>').addClass('tabsbar').prependTo(content);
  957. // convert fildsets into tabs
  958. fs.each(function(idx) {
  959. var tab, a, elm = $(this),
  960. legend = elm.children('legend'),
  961. tid = id + '-t' + idx;
  962. // create a tab
  963. a = $('<a>').text(legend.text()).attr('href', '#' + tid);
  964. tab = $('<li>').addClass('tablink');
  965. // remove legend
  966. legend.remove();
  967. // link fieldset with tab item
  968. elm.attr('id', tid);
  969. // add the tab to container
  970. tab.append(a).appendTo(tabs);
  971. });
  972. // use jquery UI tabs widget to do the interaction and styling
  973. content.tabs({
  974. active: current || 0,
  975. heightStyle: 'content',
  976. activate: function(e, ui) {resize(); }
  977. });
  978. }
  979. /**
  980. * Show about page as jquery UI dialog
  981. */
  982. function show_about(elem)
  983. {
  984. var frame = $('<iframe>').attr({id: 'aboutframe', src: rcmail.url('settings/about'), frameborder: '0'});
  985. h = Math.floor($(window).height() * 0.75),
  986. buttons = {},
  987. supportln = $('#supportlink');
  988. if (supportln.length && (env.supporturl = supportln.attr('href')))
  989. buttons[supportln.html()] = function(e){ env.supporturl.indexOf('mailto:') < 0 ? window.open(env.supporturl) : location.href = env.supporturl };
  990. frame.dialog({
  991. modal: true,
  992. resizable: false,
  993. closeOnEscape: true,
  994. title: elem ? elem.title || elem.innerHTML : null,
  995. close: function() {
  996. frame.dialog('destroy').remove();
  997. },
  998. buttons: buttons,
  999. width: 640,
  1000. height: h
  1001. }).width(640);
  1002. }
  1003. }
  1004. /**
  1005. * Roundcube Scroller class
  1006. *
  1007. * @deprecated Use treelist widget
  1008. */
  1009. function rcube_scroller(list, top, bottom)
  1010. {
  1011. var ref = this;
  1012. this.list = $(list);
  1013. this.top = $(top);
  1014. this.bottom = $(bottom);
  1015. this.step_size = 6;
  1016. this.step_time = 20;
  1017. this.delay = 500;
  1018. this.top
  1019. .mouseenter(function() { if (rcmail.drag_active) ref.ts = window.setTimeout(function() { ref.scroll('down'); }, ref.delay); })
  1020. .mouseout(function() { if (ref.ts) window.clearTimeout(ref.ts); });
  1021. this.bottom
  1022. .mouseenter(function() { if (rcmail.drag_active) ref.ts = window.setTimeout(function() { ref.scroll('up'); }, ref.delay); })
  1023. .mouseout(function() { if (ref.ts) window.clearTimeout(ref.ts); });
  1024. this.scroll = function(dir)
  1025. {
  1026. var ref = this, size = this.step_size;
  1027. if (!rcmail.drag_active)
  1028. return;
  1029. if (dir == 'down')
  1030. size *= -1;
  1031. this.list.get(0).scrollTop += size;
  1032. this.ts = window.setTimeout(function() { ref.scroll(dir); }, this.step_time);
  1033. };
  1034. };
  1035. /**
  1036. * Roundcube UI splitter class
  1037. *
  1038. * @constructor
  1039. */
  1040. function rcube_splitter(p)
  1041. {
  1042. this.p = p;
  1043. this.id = p.id;
  1044. this.horizontal = (p.orientation == 'horizontal' || p.orientation == 'h');
  1045. this.halfsize = (p.size !== undefined ? p.size : 10) / 2;
  1046. this.pos = p.start || 0;
  1047. this.min = p.min || 20;
  1048. this.offset = p.offset || 0;
  1049. this.relative = p.relative ? true : false;
  1050. this.drag_active = false;
  1051. this.render = p.render;
  1052. this.callback = p.callback;
  1053. var me = this;
  1054. rcube_splitter._instances[this.id] = me;
  1055. this.init = function()
  1056. {
  1057. this.p1 = $(this.p.p1);
  1058. this.p2 = $(this.p.p2);
  1059. this.parent = this.p1.parent();
  1060. // check if referenced elements exist, otherwise abort
  1061. if (!this.p1.length || !this.p2.length)
  1062. return;
  1063. // create and position the handle for this splitter
  1064. this.p1pos = this.relative ? this.p1.position() : this.p1.offset();
  1065. this.p2pos = this.relative ? this.p2.position() : this.p2.offset();
  1066. this.handle = $('<div>')
  1067. .attr('id', this.id)
  1068. .attr('unselectable', 'on')
  1069. .attr('role', 'presentation')
  1070. .addClass('splitter ' + (this.horizontal ? 'splitter-h' : 'splitter-v'))
  1071. .appendTo(this.parent)
  1072. .mousedown(onDragStart);
  1073. if (this.horizontal) {
  1074. var top = this.p1pos.top + this.p1.outerHeight();
  1075. this.handle.css({ left:'0px', top:top+'px' });
  1076. }
  1077. else {
  1078. var left = this.p1pos.left + this.p1.outerWidth();
  1079. this.handle.css({ left:left+'px', top:'0px' });
  1080. }
  1081. // listen to window resize on IE
  1082. if (bw.ie)
  1083. $(window).resize(onResize);
  1084. // read saved position from cookie
  1085. var cookie = this.get_cookie();
  1086. if (cookie && !isNaN(cookie)) {
  1087. this.pos = parseFloat(cookie);
  1088. this.resize();
  1089. }
  1090. else if (this.pos) {
  1091. this.resize();
  1092. this.set_cookie();
  1093. }
  1094. };
  1095. /**
  1096. * Set size and position of all DOM objects
  1097. * according to the saved splitter position
  1098. */
  1099. this.resize = function()
  1100. {
  1101. if (this.horizontal) {
  1102. this.p1.css('height', Math.floor(this.pos - this.p1pos.top - Math.floor(this.halfsize)) + 'px');
  1103. this.p2.css('top', Math.ceil(this.pos + Math.ceil(this.halfsize) + 2) + 'px');
  1104. this.handle.css('top', Math.round(this.pos - this.halfsize + this.offset)+'px');
  1105. if (bw.ie) {
  1106. var new_height = parseInt(this.parent.outerHeight(), 10) - parseInt(this.p2.css('top'), 10) - (bw.ie8 ? 2 : 0);
  1107. this.p2.css('height', (new_height > 0 ? new_height : 0) + 'px');
  1108. }
  1109. }
  1110. else {
  1111. this.p1.css('width', Math.floor(this.pos - this.p1pos.left - Math.floor(this.halfsize)) + 'px');
  1112. this.p2.css('left', Math.ceil(this.pos + Math.ceil(this.halfsize)) + 'px');
  1113. this.handle.css('left', Math.round(this.pos - this.halfsize + this.offset + 3)+'px');
  1114. if (bw.ie) {
  1115. var new_width = parseInt(this.parent.outerWidth(), 10) - parseInt(this.p2.css('left'), 10) ;
  1116. this.p2.css('width', (new_width > 0 ? new_width : 0) + 'px');
  1117. }
  1118. }
  1119. this.p2.resize();
  1120. this.p1.resize();
  1121. // also resize iframe covers
  1122. if (this.drag_active) {
  1123. $('iframe').each(function(i, elem) {
  1124. var pos = $(this).offset();
  1125. $('#iframe-splitter-fix-'+i).css({ top: pos.top+'px', left: pos.left+'px', width:elem.offsetWidth+'px', height: elem.offsetHeight+'px' });
  1126. });
  1127. }
  1128. if (typeof this.render == 'function')
  1129. this.render(this);
  1130. };
  1131. /**
  1132. * Handler for mousedown events
  1133. */
  1134. function onDragStart(e)
  1135. {
  1136. // disable text selection while dragging the splitter
  1137. if (bw.konq || bw.chrome || bw.safari)
  1138. document.body.style.webkitUserSelect = 'none';
  1139. me.p1pos = me.relative ? me.p1.position() : me.p1.offset();
  1140. me.p2pos = me.relative ? me.p2.position() : me.p2.offset();
  1141. me.drag_active = true;
  1142. // start listening to mousemove events
  1143. $(document).on('mousemove.' + this.id, onDrag).on('mouseup.' + this.id, onDragStop);
  1144. // enable dragging above iframes
  1145. $('iframe').each(function(i, elem) {
  1146. $('<div>')
  1147. .attr('id', 'iframe-splitter-fix-'+i)
  1148. .addClass('iframe-splitter-fix')
  1149. .css({ background: '#fff',
  1150. width: elem.offsetWidth+'px', height: elem.offsetHeight+'px',
  1151. position: 'absolute', opacity: '0.001', zIndex: 1000
  1152. })
  1153. .css($(this).offset())
  1154. .appendTo('body');
  1155. });
  1156. };
  1157. /**
  1158. * Handler for mousemove events
  1159. */
  1160. function onDrag(e)
  1161. {
  1162. if (!me.drag_active)
  1163. return false;
  1164. // with timing events dragging action is more responsive
  1165. window.clearTimeout(me.ts);
  1166. me.ts = window.setTimeout(function() { onDragAction(e); }, 1);
  1167. return false;
  1168. };
  1169. /**
  1170. * Dragging action (see onDrag())
  1171. */
  1172. function onDragAction(e)
  1173. {
  1174. var pos = rcube_event.get_mouse_pos(e);
  1175. if (me.relative) {
  1176. var parent = me.parent.offset();
  1177. pos.x -= parent.left;
  1178. pos.y -= parent.top;
  1179. }
  1180. if (me.horizontal) {
  1181. if (((pos.y - me.halfsize) > me.p1pos.top) && ((pos.y + me.halfsize) < (me.p2pos.top + me.p2.outerHeight()))) {
  1182. me.pos = Math.max(me.min, pos.y - Math.max(0, me.offset));
  1183. if (me.pos > me.min)
  1184. me.pos = Math.min(me.pos, me.parent.height() - me.min);
  1185. me.resize();
  1186. }
  1187. }
  1188. else {
  1189. if (((pos.x - me.halfsize) > me.p1pos.left) && ((pos.x + me.halfsize) < (me.p2pos.left + me.p2.outerWidth()))) {
  1190. me.pos = Math.max(me.min, pos.x - Math.max(0, me.offset));
  1191. if (me.pos > me.min)
  1192. me.pos = Math.min(me.pos, me.parent.width() - me.min);
  1193. me.resize();
  1194. }
  1195. }
  1196. me.p1pos = me.relative ? me.p1.position() : me.p1.offset();
  1197. me.p2pos = me.relative ? me.p2.position() : me.p2.offset();
  1198. };
  1199. /**
  1200. * Handler for mouseup events
  1201. */
  1202. function onDragStop(e)
  1203. {
  1204. // resume the ability to highlight text
  1205. if (bw.konq || bw.chrome || bw.safari)
  1206. document.body.style.webkitUserSelect = 'auto';
  1207. // cancel the listening for drag events
  1208. $(document).off('.' + me.id);
  1209. me.drag_active = false;
  1210. // remove temp divs
  1211. $('div.iframe-splitter-fix').remove();
  1212. me.set_cookie();
  1213. if (typeof me.callback == 'function')
  1214. me.callback(me);
  1215. return bw.safari ? true : rcube_event.cancel(e);
  1216. };
  1217. /**
  1218. * Handler for window resize events
  1219. */
  1220. function onResize(e)
  1221. {
  1222. if (me.horizontal) {
  1223. var new_height = parseInt(me.parent.outerHeight(), 10) - parseInt(me.p2[0].style.top, 10) - (bw.ie8 ? 2 : 0);
  1224. me.p2.css('height', (new_height > 0 ? new_height : 0) +'px');
  1225. }
  1226. else {
  1227. var new_width = parseInt(me.parent.outerWidth(), 10) - parseInt(me.p2[0].style.left, 10);
  1228. me.p2.css('width', (new_width > 0 ? new_width : 0) + 'px');
  1229. }
  1230. };
  1231. /**
  1232. * Get saved splitter position from cookie
  1233. */
  1234. this.get_cookie = function()
  1235. {
  1236. return window.UI ? UI.get_pref(this.id) : null;
  1237. };
  1238. /**
  1239. * Saves splitter position in cookie
  1240. */
  1241. this.set_cookie = function()
  1242. {
  1243. if (window.UI)
  1244. UI.save_pref(this.id, this.pos);
  1245. };
  1246. } // end class rcube_splitter
  1247. // static getter for splitter instances
  1248. rcube_splitter._instances = {};
  1249. rcube_splitter.get_instance = function(id)
  1250. {
  1251. return rcube_splitter._instances[id];
  1252. };
  1253. // @license-end