RoundCube Webmail
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.

667 lines
18 KiB

20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
20 years ago
15 years ago
  1. /*
  2. +-----------------------------------------------------------------------+
  3. | Roundcube common js library |
  4. | |
  5. | This file is part of the Roundcube web development suite |
  6. | Copyright (C) 2005-2007, Roundcube Dev, - Switzerland |
  7. | Licensed under the GNU GPL |
  8. | |
  9. +-----------------------------------------------------------------------+
  10. | Author: Thomas Bruederli <roundcube@gmail.com> |
  11. +-----------------------------------------------------------------------+
  12. $Id$
  13. */
  14. // Constants
  15. var CONTROL_KEY = 1;
  16. var SHIFT_KEY = 2;
  17. var CONTROL_SHIFT_KEY = 3;
  18. /**
  19. * Default browser check class
  20. * @constructor
  21. */
  22. function roundcube_browser()
  23. {
  24. var n = navigator;
  25. this.ver = parseFloat(n.appVersion);
  26. this.appver = n.appVersion;
  27. this.agent = n.userAgent;
  28. this.agent_lc = n.userAgent.toLowerCase();
  29. this.name = n.appName;
  30. this.vendor = n.vendor ? n.vendor : '';
  31. this.vendver = n.vendorSub ? parseFloat(n.vendorSub) : 0;
  32. this.product = n.product ? n.product : '';
  33. this.platform = String(n.platform).toLowerCase();
  34. this.lang = (n.language) ? n.language.substring(0,2) :
  35. (n.browserLanguage) ? n.browserLanguage.substring(0,2) :
  36. (n.systemLanguage) ? n.systemLanguage.substring(0,2) : 'en';
  37. this.win = (this.platform.indexOf('win') >= 0);
  38. this.mac = (this.platform.indexOf('mac') >= 0);
  39. this.linux = (this.platform.indexOf('linux') >= 0);
  40. this.unix = (this.platform.indexOf('unix') >= 0);
  41. this.dom = document.getElementById ? true : false;
  42. this.dom2 = (document.addEventListener && document.removeEventListener);
  43. this.ie = (document.all && !window.opera);
  44. this.ie4 = (this.ie && !this.dom);
  45. this.ie5 = (this.dom && this.appver.indexOf('MSIE 5')>0);
  46. this.ie8 = (this.dom && this.appver.indexOf('MSIE 8')>0);
  47. this.ie7 = (this.dom && this.appver.indexOf('MSIE 7')>0);
  48. this.ie6 = (this.dom && !this.ie8 && !this.ie7 && this.appver.indexOf('MSIE 6')>0);
  49. this.mz = (this.dom && this.ver >= 5); // (this.dom && this.product=='Gecko')
  50. this.ns = ((this.ver < 5 && this.name == 'Netscape') || (this.ver >= 5 && this.vendor.indexOf('Netscape') >= 0));
  51. this.ns6 = (this.ns && parseInt(this.vendver) == 6); // (this.mz && this.ns) ? true : false;
  52. this.ns7 = (this.ns && parseInt(this.vendver) == 7); // this.agent.indexOf('Netscape/7')>0);
  53. this.chrome = (this.agent_lc.indexOf('chrome') > 0);
  54. this.safari = (!this.chrome && (this.agent_lc.indexOf('safari') > 0 || this.agent_lc.indexOf('applewebkit') > 0));
  55. this.konq = (this.agent_lc.indexOf('konqueror') > 0);
  56. this.iphone = (this.safari && this.agent_lc.indexOf('iphone') > 0);
  57. this.ipad = (this.safari && this.agent_lc.indexOf('ipad') > 0);
  58. this.opera = window.opera ? true : false;
  59. if (this.opera && window.RegExp)
  60. this.vendver = (/opera(\s|\/)([0-9\.]+)/.test(this.agent_lc)) ? parseFloat(RegExp.$2) : -1;
  61. else if (this.chrome && window.RegExp)
  62. this.vendver = (/chrome\/([0-9\.]+)/.test(this.agent_lc)) ? parseFloat(RegExp.$1) : 0;
  63. else if (!this.vendver && this.safari)
  64. this.vendver = (/(safari|applewebkit)\/([0-9]+)/.test(this.agent_lc)) ? parseInt(RegExp.$2) : 0;
  65. else if ((!this.vendver && this.mz) || this.agent.indexOf('Camino')>0)
  66. this.vendver = (/rv:([0-9\.]+)/.test(this.agent)) ? parseFloat(RegExp.$1) : 0;
  67. else if (this.ie && window.RegExp)
  68. this.vendver = (/msie\s+([0-9\.]+)/.test(this.agent_lc)) ? parseFloat(RegExp.$1) : 0;
  69. else if (this.konq && window.RegExp)
  70. this.vendver = (/khtml\/([0-9\.]+)/.test(this.agent_lc)) ? parseFloat(RegExp.$1) : 0;
  71. // get real language out of safari's user agent
  72. if(this.safari && (/;\s+([a-z]{2})-[a-z]{2}\)/.test(this.agent_lc)))
  73. this.lang = RegExp.$1;
  74. this.dhtml = ((this.ie4 && this.win) || this.ie5 || this.ie6 || this.ns4 || this.mz);
  75. this.vml = (this.win && this.ie && this.dom && !this.opera);
  76. this.pngalpha = (this.mz || (this.opera && this.vendver >= 6) || (this.ie && this.mac && this.vendver >= 5) ||
  77. (this.ie && this.win && this.vendver >= 5.5) || this.safari);
  78. this.opacity = (this.mz || (this.ie && this.vendver >= 5.5 && !this.opera) || (this.safari && this.vendver >= 100));
  79. this.cookies = n.cookieEnabled;
  80. // test for XMLHTTP support
  81. this.xmlhttp_test = function()
  82. {
  83. var activeX_test = new Function("try{var o=new ActiveXObject('Microsoft.XMLHTTP');return true;}catch(err){return false;}");
  84. this.xmlhttp = (window.XMLHttpRequest || (window.ActiveXObject && activeX_test()));
  85. return this.xmlhttp;
  86. };
  87. // set class names to html tag according to the current user agent detection
  88. // this allows browser-specific css selectors like "html.chrome .someclass"
  89. this.set_html_class = function()
  90. {
  91. var classname = ' js';
  92. if (this.ie) {
  93. classname += ' ie';
  94. if (this.ie5)
  95. classname += ' ie5';
  96. else if (this.ie6)
  97. classname += ' ie6';
  98. else if (this.ie7)
  99. classname += ' ie7';
  100. else if (this.ie8)
  101. classname += ' ie8';
  102. }
  103. else if (this.opera)
  104. classname += ' opera';
  105. else if (this.konq)
  106. classname += ' konqueror';
  107. else if (this.safari)
  108. classname += ' safari';
  109. if (this.chrome)
  110. classname += ' chrome';
  111. else if (this.iphone)
  112. classname += ' iphone';
  113. else if (this.ipad)
  114. classname += ' ipad';
  115. else if (this.ns6)
  116. classname += ' netscape6';
  117. else if (this.ns7)
  118. classname += ' netscape7';
  119. if (document.documentElement)
  120. document.documentElement.className += classname;
  121. };
  122. };
  123. // static functions for DOM event handling
  124. var rcube_event = {
  125. /**
  126. * returns the event target element
  127. */
  128. get_target: function(e)
  129. {
  130. e = e || window.event;
  131. return e && e.target ? e.target : e.srcElement;
  132. },
  133. /**
  134. * returns the event key code
  135. */
  136. get_keycode: function(e)
  137. {
  138. e = e || window.event;
  139. return e && e.keyCode ? e.keyCode : (e && e.which ? e.which : 0);
  140. },
  141. /**
  142. * returns the event key code
  143. */
  144. get_button: function(e)
  145. {
  146. e = e || window.event;
  147. return e && (typeof e.button != 'undefined') ? e.button : (e && e.which ? e.which : 0);
  148. },
  149. /**
  150. * returns modifier key (constants defined at top of file)
  151. */
  152. get_modifier: function(e)
  153. {
  154. var opcode = 0;
  155. e = e || window.event;
  156. if (bw.mac && e) {
  157. opcode += (e.metaKey && CONTROL_KEY) + (e.shiftKey && SHIFT_KEY);
  158. return opcode;
  159. }
  160. if (e) {
  161. opcode += (e.ctrlKey && CONTROL_KEY) + (e.shiftKey && SHIFT_KEY);
  162. return opcode;
  163. }
  164. },
  165. /**
  166. * Return absolute mouse position of an event
  167. */
  168. get_mouse_pos: function(e)
  169. {
  170. if (!e) e = window.event;
  171. var mX = (e.pageX) ? e.pageX : e.clientX,
  172. mY = (e.pageY) ? e.pageY : e.clientY;
  173. if (document.body && document.all) {
  174. mX += document.body.scrollLeft;
  175. mY += document.body.scrollTop;
  176. }
  177. if (e._offset) {
  178. mX += e._offset.left;
  179. mY += e._offset.top;
  180. }
  181. return { x:mX, y:mY };
  182. },
  183. /**
  184. * Add an object method as event listener to a certain element
  185. */
  186. add_listener: function(p)
  187. {
  188. if (!p.object || !p.method) // not enough arguments
  189. return;
  190. if (!p.element)
  191. p.element = document;
  192. if (!p.object._rc_events)
  193. p.object._rc_events = [];
  194. var key = p.event + '*' + p.method;
  195. if (!p.object._rc_events[key])
  196. p.object._rc_events[key] = function(e){ return p.object[p.method](e); };
  197. if (p.element.addEventListener)
  198. p.element.addEventListener(p.event, p.object._rc_events[key], false);
  199. else if (p.element.attachEvent) {
  200. // IE allows multiple events with the same function to be applied to the same object
  201. // forcibly detach the event, then attach
  202. p.element.detachEvent('on'+p.event, p.object._rc_events[key]);
  203. p.element.attachEvent('on'+p.event, p.object._rc_events[key]);
  204. }
  205. else
  206. p.element['on'+p.event] = p.object._rc_events[key];
  207. },
  208. /**
  209. * Remove event listener
  210. */
  211. remove_listener: function(p)
  212. {
  213. if (!p.element)
  214. p.element = document;
  215. var key = p.event + '*' + p.method;
  216. if (p.object && p.object._rc_events && p.object._rc_events[key]) {
  217. if (p.element.removeEventListener)
  218. p.element.removeEventListener(p.event, p.object._rc_events[key], false);
  219. else if (p.element.detachEvent)
  220. p.element.detachEvent('on'+p.event, p.object._rc_events[key]);
  221. else
  222. p.element['on'+p.event] = null;
  223. }
  224. },
  225. /**
  226. * Prevent event propagation and bubbeling
  227. */
  228. cancel: function(evt)
  229. {
  230. var e = evt ? evt : window.event;
  231. if (e.preventDefault)
  232. e.preventDefault();
  233. if (e.stopPropagation)
  234. e.stopPropagation();
  235. e.cancelBubble = true;
  236. e.returnValue = false;
  237. return false;
  238. },
  239. touchevent: function(e)
  240. {
  241. return { pageX:e.pageX, pageY:e.pageY, offsetX:e.pageX - e.target.offsetLeft, offsetY:e.pageY - e.target.offsetTop, target:e.target, istouch:true };
  242. }
  243. };
  244. /**
  245. * rcmail objects event interface
  246. */
  247. function rcube_event_engine()
  248. {
  249. this._events = {};
  250. };
  251. rcube_event_engine.prototype = {
  252. /**
  253. * Setter for object event handlers
  254. *
  255. * @param {String} Event name
  256. * @param {Function} Handler function
  257. * @return Listener ID (used to remove this handler later on)
  258. */
  259. addEventListener: function(evt, func, obj)
  260. {
  261. if (!this._events)
  262. this._events = {};
  263. if (!this._events[evt])
  264. this._events[evt] = [];
  265. var e = {func:func, obj:obj ? obj : window};
  266. this._events[evt][this._events[evt].length] = e;
  267. },
  268. /**
  269. * Removes a specific event listener
  270. *
  271. * @param {String} Event name
  272. * @param {Int} Listener ID to remove
  273. */
  274. removeEventListener: function(evt, func, obj)
  275. {
  276. if (typeof obj == 'undefined')
  277. obj = window;
  278. for (var h,i=0; this._events && this._events[evt] && i < this._events[evt].length; i++)
  279. if ((h = this._events[evt][i]) && h.func == func && h.obj == obj)
  280. this._events[evt][i] = null;
  281. },
  282. /**
  283. * This will execute all registered event handlers
  284. *
  285. * @param {String} Event to trigger
  286. * @param {Object} Event object/arguments
  287. */
  288. triggerEvent: function(evt, e)
  289. {
  290. var ret, h;
  291. if (typeof e == 'undefined')
  292. e = this;
  293. else if (typeof e == 'object')
  294. e.event = evt;
  295. if (this._events && this._events[evt] && !this._event_exec) {
  296. this._event_exec = true;
  297. for (var i=0; i < this._events[evt].length; i++) {
  298. if ((h = this._events[evt][i])) {
  299. if (typeof h.func == 'function')
  300. ret = h.func.call ? h.func.call(h.obj, e) : h.func(e);
  301. else if (typeof h.obj[h.func] == 'function')
  302. ret = h.obj[h.func](e);
  303. // cancel event execution
  304. if (typeof ret != 'undefined' && !ret)
  305. break;
  306. }
  307. }
  308. }
  309. this._event_exec = false;
  310. return ret;
  311. }
  312. }; // end rcube_event_engine.prototype
  313. /**
  314. * Roundcube generic layer (floating box) class
  315. *
  316. * @constructor
  317. */
  318. function rcube_layer(id, attributes)
  319. {
  320. this.name = id;
  321. // create a new layer in the current document
  322. this.create = function(arg)
  323. {
  324. var l = (arg.x) ? arg.x : 0,
  325. t = (arg.y) ? arg.y : 0,
  326. w = arg.width,
  327. h = arg.height,
  328. z = arg.zindex,
  329. vis = arg.vis,
  330. parent = arg.parent,
  331. obj = document.createElement('DIV');
  332. with (obj) {
  333. id = this.name;
  334. with (style) {
  335. position = 'absolute';
  336. visibility = (vis) ? (vis==2) ? 'inherit' : 'visible' : 'hidden';
  337. left = l+'px';
  338. top = t+'px';
  339. if (w)
  340. width = w.toString().match(/\%$/) ? w : w+'px';
  341. if (h)
  342. height = h.toString().match(/\%$/) ? h : h+'px';
  343. if (z)
  344. zIndex = z;
  345. }
  346. }
  347. if (parent)
  348. parent.appendChild(obj);
  349. else
  350. document.body.appendChild(obj);
  351. this.elm = obj;
  352. };
  353. // create new layer
  354. if (attributes != null) {
  355. this.create(attributes);
  356. this.name = this.elm.id;
  357. }
  358. else // just refer to the object
  359. this.elm = document.getElementById(id);
  360. if (!this.elm)
  361. return false;
  362. // ********* layer object properties *********
  363. this.css = this.elm.style;
  364. this.event = this.elm;
  365. this.width = this.elm.offsetWidth;
  366. this.height = this.elm.offsetHeight;
  367. this.x = parseInt(this.elm.offsetLeft);
  368. this.y = parseInt(this.elm.offsetTop);
  369. this.visible = (this.css.visibility=='visible' || this.css.visibility=='show' || this.css.visibility=='inherit') ? true : false;
  370. // ********* layer object methods *********
  371. // move the layer to a specific position
  372. this.move = function(x, y)
  373. {
  374. this.x = x;
  375. this.y = y;
  376. this.css.left = Math.round(this.x)+'px';
  377. this.css.top = Math.round(this.y)+'px';
  378. };
  379. // change the layers width and height
  380. this.resize = function(w,h)
  381. {
  382. this.css.width = w+'px';
  383. this.css.height = h+'px';
  384. this.width = w;
  385. this.height = h;
  386. };
  387. // show or hide the layer
  388. this.show = function(a)
  389. {
  390. if(a == 1) {
  391. this.css.visibility = 'visible';
  392. this.visible = true;
  393. }
  394. else if(a == 2) {
  395. this.css.visibility = 'inherit';
  396. this.visible = true;
  397. }
  398. else {
  399. this.css.visibility = 'hidden';
  400. this.visible = false;
  401. }
  402. };
  403. // write new content into a Layer
  404. this.write = function(cont)
  405. {
  406. this.elm.innerHTML = cont;
  407. };
  408. };
  409. // check if input is a valid email address
  410. // By Cal Henderson <cal@iamcal.com>
  411. // http://code.iamcal.com/php/rfc822/
  412. function rcube_check_email(input, inline)
  413. {
  414. if (input && window.RegExp) {
  415. var qtext = '[^\\x0d\\x22\\x5c\\x80-\\xff]',
  416. dtext = '[^\\x0d\\x5b-\\x5d\\x80-\\xff]',
  417. atom = '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+',
  418. quoted_pair = '\\x5c[\\x00-\\x7f]',
  419. quoted_string = '\\x22('+qtext+'|'+quoted_pair+')*\\x22',
  420. // Use simplified domain matching, because we need to allow Unicode characters here
  421. // So, e-mail address should be validated also on server side after idn_to_ascii() use
  422. //domain_literal = '\\x5b('+dtext+'|'+quoted_pair+')*\\x5d',
  423. //sub_domain = '('+atom+'|'+domain_literal+')',
  424. domain = '([^@\\x2e]+\\x2e)+[a-z]{2,}',
  425. word = '('+atom+'|'+quoted_string+')',
  426. delim = '[,;\s\n]',
  427. local_part = word+'(\\x2e'+word+')*',
  428. addr_spec = local_part+'\\x40'+domain,
  429. reg1 = inline ? new RegExp('(^|<|'+delim+')'+addr_spec+'($|>|'+delim+')', 'i') : new RegExp('^'+addr_spec+'$', 'i');
  430. return reg1.test(input) ? true : false;
  431. }
  432. return false;
  433. };
  434. // recursively copy an object
  435. function rcube_clone_object(obj)
  436. {
  437. var out = {};
  438. for (var key in obj) {
  439. if (obj[key] && typeof obj[key] == 'object')
  440. out[key] = clone_object(obj[key]);
  441. else
  442. out[key] = obj[key];
  443. }
  444. return out;
  445. };
  446. // make a string URL safe
  447. function urlencode(str)
  448. {
  449. return window.encodeURIComponent ? encodeURIComponent(str) : escape(str);
  450. };
  451. // get any type of html objects by id/name
  452. function rcube_find_object(id, d)
  453. {
  454. var n, f, obj, e;
  455. if(!d) d = document;
  456. if(d.getElementsByName && (e = d.getElementsByName(id)))
  457. obj = e[0];
  458. if(!obj && d.getElementById)
  459. obj = d.getElementById(id);
  460. if(!obj && d.all)
  461. obj = d.all[id];
  462. if(!obj && d.images.length)
  463. obj = d.images[id];
  464. if (!obj && d.forms.length) {
  465. for (f=0; f<d.forms.length; f++) {
  466. if(d.forms[f].name == id)
  467. obj = d.forms[f];
  468. else if(d.forms[f].elements[id])
  469. obj = d.forms[f].elements[id];
  470. }
  471. }
  472. if (!obj && d.layers) {
  473. if (d.layers[id]) obj = d.layers[id];
  474. for (n=0; !obj && n<d.layers.length; n++)
  475. obj = rcube_find_object(id, d.layers[n].document);
  476. }
  477. return obj;
  478. };
  479. // determine whether the mouse is over the given object or not
  480. function rcube_mouse_is_over(ev, obj)
  481. {
  482. var mouse = rcube_event.get_mouse_pos(ev);
  483. var pos = $(obj).offset();
  484. return ((mouse.x >= pos.left) && (mouse.x < (pos.left + obj.offsetWidth)) &&
  485. (mouse.y >= pos.top) && (mouse.y < (pos.top + obj.offsetHeight)));
  486. };
  487. // cookie functions by GoogieSpell
  488. function setCookie(name, value, expires, path, domain, secure)
  489. {
  490. var curCookie = name + "=" + escape(value) +
  491. (expires ? "; expires=" + expires.toGMTString() : "") +
  492. (path ? "; path=" + path : "") +
  493. (domain ? "; domain=" + domain : "") +
  494. (secure ? "; secure" : "");
  495. document.cookie = curCookie;
  496. };
  497. function getCookie(name)
  498. {
  499. var dc = document.cookie;
  500. var prefix = name + "=";
  501. var begin = dc.indexOf("; " + prefix);
  502. if (begin == -1) {
  503. begin = dc.indexOf(prefix);
  504. if (begin != 0) return null;
  505. }
  506. else
  507. begin += 2;
  508. var end = document.cookie.indexOf(";", begin);
  509. if (end == -1)
  510. end = dc.length;
  511. return unescape(dc.substring(begin + prefix.length, end));
  512. };
  513. roundcube_browser.prototype.set_cookie = setCookie;
  514. roundcube_browser.prototype.get_cookie = getCookie;
  515. // tiny replacement for Firebox functionality
  516. function rcube_console()
  517. {
  518. this.log = function(msg)
  519. {
  520. var box = rcube_find_object('dbgconsole');
  521. if (box) {
  522. if (msg.charAt(msg.length-1)=='\n')
  523. msg += '--------------------------------------\n';
  524. else
  525. msg += '\n--------------------------------------\n';
  526. // Konqueror doesn't allows to just change value of hidden element
  527. if (bw.konq) {
  528. box.innerText += msg;
  529. box.value = box.innerText;
  530. } else
  531. box.value += msg;
  532. }
  533. };
  534. this.reset = function()
  535. {
  536. var box = rcube_find_object('dbgconsole');
  537. if (box)
  538. box.innerText = box.value = '';
  539. };
  540. };
  541. var bw = new roundcube_browser();
  542. bw.set_html_class();
  543. if (!window.console)
  544. console = new rcube_console();
  545. // Add escape() method to RegExp object
  546. // http://dev.rubyonrails.org/changeset/7271
  547. RegExp.escape = function(str)
  548. {
  549. return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
  550. };
  551. // Make getElementById() case-sensitive on IE
  552. if (bw.ie)
  553. {
  554. document._getElementById = document.getElementById;
  555. document.getElementById = function(id)
  556. {
  557. var i = 0, obj = document._getElementById(id);
  558. if (obj && obj.id != id)
  559. while ((obj = document.all[i]) && obj.id != id)
  560. i++;
  561. return obj;
  562. }
  563. }