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.

ui.js 46KB

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