my.full.js 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356
  1. (function($)
  2. {
  3. /* Tab session */
  4. if(!config.tabSession) return;
  5. /** Store current tab id */
  6. var _tid = '';
  7. /**
  8. * Get current tab id
  9. * @returns {string} Tab id
  10. */
  11. function getTid(){return _tid;}
  12. /**
  13. * Convert url with tab id
  14. * @param {string} url
  15. * @param {string} [tid]
  16. * @param {boolean} [force]
  17. * @returns {string} Tab id
  18. */
  19. function convertUrlWithTid(url, tid, force)
  20. {
  21. var link = $.parseLink(url);
  22. if(!link.moduleName) return url;
  23. tid = tid || _tid;
  24. if(!force && link.tid === tid) return url;
  25. link.tid = tid;
  26. return $.createLink(link);
  27. }
  28. /** Init */
  29. function init()
  30. {
  31. /* Check tid */
  32. if(window.parent !== window)
  33. {
  34. if(window.parent.$.tabSession) _tid = window.parent.$.tabSession.getTid();
  35. }
  36. else
  37. {
  38. var isIndexOrLoginPage = (config.currentModule === 'index' && config.currentMethod === 'index') || (config.currentModule === 'user' && config.currentMethod === 'login');
  39. var link = $.parseLink(window.location.href);
  40. _tid = sessionStorage.getItem('TID');
  41. if(!_tid)
  42. {
  43. if(link.tid && isIndexOrLoginPage)
  44. {
  45. _tid = link.tid
  46. }
  47. if(!_tid)
  48. {
  49. _tid = $.zui.uuid();
  50. _tid = _tid.substr(_tid.length - 8);
  51. }
  52. }
  53. sessionStorage.setItem('TID', _tid);
  54. if(isIndexOrLoginPage && !link.tid)
  55. {
  56. window.location.href = convertUrlWithTid(window.location.href, _tid);
  57. }
  58. }
  59. $.tabSession =
  60. {
  61. getTid: getTid,
  62. convertUrlWithTid: convertUrlWithTid,
  63. };
  64. /* Handle all links in page */
  65. $('a').each(function()
  66. {
  67. var $a = $(this);
  68. var url = $a.attr('href');
  69. var urlWithTid = convertUrlWithTid(url);
  70. if(urlWithTid !== url) $a.attr('href', urlWithTid);
  71. });
  72. $('[data-url]').each(function()
  73. {
  74. var $e = $(this);
  75. var url = $e.attr('data-url');
  76. var urlWithTid = convertUrlWithTid(url);
  77. if(urlWithTid !== url) $e.attr('data-url', urlWithTid);
  78. });
  79. if(config.debug > 2)
  80. {
  81. $(function()
  82. {
  83. $('#tid').prepend('<code class="bg-blue">localtid=' + _tid + '</code>');
  84. });
  85. }
  86. }
  87. init();
  88. /* Hide context menu when window is scroll. */
  89. $(window).on('scroll', function()
  90. {
  91. $.zui.ContextMenu.hide();
  92. });
  93. }(jQuery));
  94. /**
  95. * Set the ping url.
  96. *
  97. * @access public
  98. * @return void
  99. */
  100. function setPing()
  101. {
  102. $('#hiddenwin').attr('src', createLink('misc', 'ping'));
  103. }
  104. /**
  105. * Disable the submit button when submit form.
  106. *
  107. * @access public
  108. * @return void
  109. */
  110. function setForm()
  111. {
  112. var formClicked = false;
  113. $('form').submit(function()
  114. {
  115. submitObj = $(this).find(':submit');
  116. if($(submitObj).size() >= 1)
  117. {
  118. var isBtn = submitObj.prop('tagName') == "BUTTON";
  119. submitLabel = isBtn ? $(submitObj).html() : $(submitObj).attr('value');
  120. $(submitObj).attr('disabled', 'disabled');
  121. var submitting = submitObj.attr('data-submitting') || lang.submitting;
  122. if(isBtn) submitObj.text(submitting);
  123. else $(submitObj).attr('value', submitting);
  124. formClicked = true;
  125. }
  126. });
  127. $("body").click(function()
  128. {
  129. if(formClicked)
  130. {
  131. $(submitObj).removeAttr('disabled');
  132. if(submitObj.prop('tagName') == "BUTTON")
  133. {
  134. submitObj.text(submitLabel);
  135. }
  136. else
  137. {
  138. $(submitObj).attr('value', submitLabel);
  139. }
  140. $(submitObj).removeClass('button-d');
  141. }
  142. formClicked = false;
  143. });
  144. }
  145. /**
  146. * Set form action and submit.
  147. *
  148. * @param url $actionLink
  149. * @param string $hiddenwin 'hiddenwin'
  150. * @access public
  151. * @return void
  152. */
  153. function setFormAction(actionLink, hiddenwin, obj)
  154. {
  155. $form = typeof(obj) == 'undefined' ? $('form') : $(obj).closest('form');
  156. if(hiddenwin) $form.attr('target', hiddenwin);
  157. else $form.removeAttr('target');
  158. $form.attr('action', actionLink);
  159. // Check safari for bug #1000, see http://pms.zentao.net/bug-view-1000.html
  160. var userAgent = navigator.userAgent;
  161. var isSafari = userAgent.indexOf('AppleWebKit') > -1 && userAgent.indexOf('Safari') > -1 && userAgent.indexOf('Chrome') < 0;
  162. if(isSafari)
  163. {
  164. var idPreffix = 'checkbox-fix-' + $.zui.uuid();
  165. $form.find('[data-fix-checkbox]').remove();
  166. $form.find('input[type="checkbox"]:not(.rows-selector)').each(function()
  167. {
  168. var $checkbox = $(this);
  169. var checkboxId = idPreffix + $checkbox.val();
  170. $checkbox.clone().attr('data-fix-checkbox', checkboxId).css('display', 'none').after('<div id="' + checkboxId + '"/>').appendTo($form);
  171. });
  172. }
  173. $form.submit();
  174. }
  175. /**
  176. * Set the max with of image.
  177. *
  178. * @access public
  179. * @return void
  180. */
  181. function setImageSize(image, maxWidth, maxHeight)
  182. {
  183. var $image = $(image);
  184. if($image.parent().prop('tagName').toLowerCase() == 'a') return;
  185. /* If not set maxWidth, set it auto. */
  186. if(!maxWidth)
  187. {
  188. bodyWidth = $('body').width();
  189. maxWidth = bodyWidth - 470; // The side bar's width is 336, and add some margins.
  190. }
  191. if(!maxHeight) maxHeight = $(top.window).height();
  192. setTimeout(function()
  193. {
  194. maxHeightStyle = $image.height() > 0 ? 'max-height:' + maxHeight + 'px' : '';
  195. if(!document.getElementsByClassName('xxc-embed').length && $image.width() > 0 && $image.width() > maxWidth) $image.attr('width', maxWidth);
  196. $image.wrap('<a href="' + $image.attr('src') + '" style="display:inline-block;position:relative;overflow:hidden;' + maxHeightStyle + '" target="_blank"></a>');
  197. if($image.height() > 0 && $image.height() > maxHeight) $image.closest('a').append("<a href='###' class='showMoreImage' onclick='showMoreImage(this)'>" + lang.expand + " <i class='icon-angle-down'></i></a>");
  198. }, 50);
  199. }
  200. /**
  201. * Show more image when image is too height.
  202. *
  203. * @param obj $obj
  204. * @access public
  205. * @return void
  206. */
  207. function showMoreImage(obj)
  208. {
  209. $(obj).parents('a').css('max-height', 'none');
  210. $(obj).remove();
  211. }
  212. /**
  213. * add one option of a select to another select.
  214. *
  215. * @param string $SelectID
  216. * @param string $TargetID
  217. * @access public
  218. * @return void
  219. */
  220. function addItem(SelectID,TargetID)
  221. {
  222. ItemList = document.getElementById(SelectID);
  223. Target = document.getElementById(TargetID);
  224. for(var x = 0; x < ItemList.length; x++)
  225. {
  226. var opt = ItemList.options[x];
  227. if (opt.selected)
  228. {
  229. flag = true;
  230. for (var y=0;y<Target.length;y++)
  231. {
  232. var myopt = Target.options[y];
  233. if (myopt.value == opt.value)
  234. {
  235. flag = false;
  236. }
  237. }
  238. if(flag)
  239. {
  240. Target.options[Target.options.length] = new Option(opt.text, opt.value, 0, 0);
  241. }
  242. }
  243. }
  244. }
  245. /**
  246. * Remove one selected option from a select.
  247. *
  248. * @param string $SelectID
  249. * @access public
  250. * @return void
  251. */
  252. function delItem(SelectID)
  253. {
  254. ItemList = document.getElementById(SelectID);
  255. for(var x=ItemList.length-1;x>=0;x--)
  256. {
  257. var opt = ItemList.options[x];
  258. if (opt.selected)
  259. {
  260. ItemList.options[x] = null;
  261. }
  262. }
  263. }
  264. /**
  265. * move one selected option up from a select.
  266. *
  267. * @param string $SelectID
  268. * @access public
  269. * @return void
  270. */
  271. function upItem(SelectID)
  272. {
  273. ItemList = document.getElementById(SelectID);
  274. for(var x=1;x<ItemList.length;x++)
  275. {
  276. var opt = ItemList.options[x];
  277. if(opt.selected)
  278. {
  279. tmpUpValue = ItemList.options[x-1].value;
  280. tmpUpText = ItemList.options[x-1].text;
  281. ItemList.options[x-1].value = opt.value;
  282. ItemList.options[x-1].text = opt.text;
  283. ItemList.options[x].value = tmpUpValue;
  284. ItemList.options[x].text = tmpUpText;
  285. ItemList.options[x-1].selected = true;
  286. ItemList.options[x].selected = false;
  287. break;
  288. }
  289. }
  290. }
  291. /**
  292. * move one selected option down from a select.
  293. *
  294. * @param string $SelectID
  295. * @access public
  296. * @return void
  297. */
  298. function downItem(SelectID)
  299. {
  300. ItemList = document.getElementById(SelectID);
  301. for(var x=0;x<ItemList.length;x++)
  302. {
  303. var opt = ItemList.options[x];
  304. if(opt.selected)
  305. {
  306. tmpUpValue = ItemList.options[x+1].value;
  307. tmpUpText = ItemList.options[x+1].text;
  308. ItemList.options[x+1].value = opt.value;
  309. ItemList.options[x+1].text = opt.text;
  310. ItemList.options[x].value = tmpUpValue;
  311. ItemList.options[x].text = tmpUpText;
  312. ItemList.options[x+1].selected = true;
  313. ItemList.options[x].selected = false;
  314. break;
  315. }
  316. }
  317. }
  318. /**
  319. * select all items of a select.
  320. *
  321. * @param string $SelectID
  322. * @access public
  323. * @return void
  324. */
  325. function selectItem(SelectID)
  326. {
  327. ItemList = document.getElementById(SelectID);
  328. for(var x=ItemList.length-1;x>=0;x--)
  329. {
  330. var opt = ItemList.options[x];
  331. opt.selected = true;
  332. }
  333. }
  334. /**
  335. * Delete item use ajax.
  336. *
  337. * @param string url
  338. * @param string replaceID
  339. * @param string notice
  340. * @access public
  341. * @return void
  342. */
  343. function ajaxDelete(url, replaceID, notice)
  344. {
  345. if(confirm(notice))
  346. {
  347. $.ajax(
  348. {
  349. type: 'GET',
  350. url: url,
  351. dataType: 'json',
  352. success: function(data)
  353. {
  354. if(data.result == 'success')
  355. {
  356. var $table = $('#' + replaceID).closest('[data-ride="table"]');
  357. if($table.length)
  358. {
  359. var table = $table.data('zui.table');
  360. if(table)
  361. {
  362. table.options.replaceId = replaceID;
  363. return table.reload();
  364. }
  365. }
  366. $.get(document.location.href, function(data)
  367. {
  368. if(!($(data).find('#' + replaceID).length)) location.reload();
  369. $('#' + replaceID).html($(data).find('#' + replaceID).html());
  370. if(typeof sortTable == 'function') sortTable();
  371. $('#' + replaceID).find('[data-toggle=modal], a.iframe').modalTrigger();
  372. if($('#' + replaceID).find('table.datatable').length) $('#' + replaceID).find('table.datatable').datatable();
  373. $('.table-footer [data-ride=pager]').pager();
  374. });
  375. }
  376. else if(data.result == 'fail' && typeof(data.message) == 'string')
  377. {
  378. bootbox.alert(data.message);
  379. }
  380. }
  381. });
  382. }
  383. }
  384. /**
  385. * Judge the string is a integer number
  386. *
  387. * @access public
  388. * @return bool
  389. */
  390. function isNum(s)
  391. {
  392. if(s!=null)
  393. {
  394. var r, re;
  395. re = /\d*/i;
  396. r = s.match(re);
  397. return (r == s) ? true : false;
  398. }
  399. return false;
  400. }
  401. /**
  402. * Start cron.
  403. *
  404. * @access public
  405. * @return void
  406. */
  407. function startCron(restart)
  408. {
  409. if(typeof(restart) == 'undefined') restart = 0;
  410. $.ajax({type:"GET", timeout:100, url:createLink('cron', 'ajaxExec', 'restart=' + restart)});
  411. }
  412. function computePasswordStrength(password)
  413. {
  414. if(password.length == 0) return 0;
  415. var strength = 0;
  416. var length = password.length;
  417. var complexity = new Array();
  418. for(i = 0; i < length; i++)
  419. {
  420. letter = password.charAt(i);
  421. var asc = letter.charCodeAt();
  422. if(asc >= 48 && asc <= 57)
  423. {
  424. complexity[0] = 1;
  425. }
  426. else if((asc >= 65 && asc <= 90))
  427. {
  428. complexity[1] = 2;
  429. }
  430. else if(asc >= 97 && asc <= 122)
  431. {
  432. complexity[2] = 4;
  433. }
  434. else
  435. {
  436. complexity[3] = 8;
  437. }
  438. }
  439. var sumComplexity = 0;
  440. for(i in complexity) sumComplexity += complexity[i];
  441. if((sumComplexity == 7 || sumComplexity == 15) && password.length >= 6) strength = 1;
  442. if(sumComplexity == 15 && password.length >= 10) strength = 2;
  443. return strength;
  444. }
  445. /**
  446. * Check onlybody page when it is not open in modal then location to on onlybody page.
  447. *
  448. * @access public
  449. * @return void
  450. */
  451. function checkOnlybodyPage()
  452. {
  453. if(self == parent)
  454. {
  455. href = location.href.replace('?onlybody=yes', '');
  456. location.href = href.replace('&onlybody=yes', '');
  457. }
  458. }
  459. /**
  460. * Fixed table head in list when scrolling.
  461. *
  462. * @param string $tableID
  463. * @access public
  464. * @return void
  465. */
  466. function fixedTheadOfList(tableID)
  467. {
  468. if($(tableID).size() == 0) return false;
  469. if($(tableID).css('display') == 'none') return false;
  470. if($(tableID).find('thead').size() == 0) return false;
  471. fixTheadInit();
  472. $(window).scroll(fixThead);//Fix table head when scrolling.
  473. $('.side-handle').click(function(){setTimeout(fixTheadInit, 300);});//Fix table head if module tree is hidden or displayed.
  474. var tableWidth, theadOffset, fixedThead, $fixedThead;
  475. function fixThead()
  476. {
  477. theadOffset = $(tableID).find('thead').offset().top;
  478. $fixedThead = $(tableID).parent().find('.fixedTheadOfList');
  479. if($fixedThead.size() <= 0 &&theadOffset < $(window).scrollTop())
  480. {
  481. tableWidth = $(tableID).width();
  482. fixedThead = "<table class='fixedTheadOfList'><thead>" + $(tableID).find('thead').html() + '</thead></table>';
  483. $(tableID).before(fixedThead);
  484. $('.fixedTheadOfList').addClass($(tableID).attr('class')).width(tableWidth);
  485. }
  486. if($fixedThead.size() > 0 && theadOffset >= $(window).scrollTop()) $fixedThead.remove();
  487. }
  488. function fixTheadInit()
  489. {
  490. $fixedThead = $(tableID).parent().find('.fixedTheadOfList');
  491. if($fixedThead.size() > 0) $fixedThead.remove();
  492. fixThead();
  493. }
  494. }
  495. /**
  496. * Apply cs style to page
  497. * @return void
  498. */
  499. function applyCssStyle(css, tag)
  500. {
  501. tag = tag || 'default';
  502. var name = 'applyStyle-' + tag;
  503. var $style = $('style#' + name);
  504. if(!$style.length)
  505. {
  506. $style = $('<style id="' + name + '">').appendTo('body');
  507. }
  508. var styleTag = $style.get(0);
  509. if (styleTag.styleSheet) styleTag.styleSheet.cssText = css;
  510. else styleTag.innerHTML = css;
  511. }
  512. /**
  513. * Remove cookie by key
  514. *
  515. * @param cookieKey $cookieKey
  516. * @access public
  517. * @return void
  518. */
  519. function removeCookieByKey(cookieKey)
  520. {
  521. $.cookie(cookieKey, '', {expires:config.cookieLife, path:config.webRoot});
  522. location.href = location.href;
  523. }
  524. /**
  525. * Set homepage.
  526. *
  527. * @param string $module
  528. * @param string $page
  529. * @access public
  530. * @return void
  531. */
  532. function setHomepage(module, page)
  533. {
  534. $.get(createLink('custom', 'ajaxSetHomepage', 'module=' + module + '&page=' + page), function(){location.reload(true)});
  535. }
  536. /**
  537. * Reload page when tutorial mode setted in this session but not load in iframe
  538. *
  539. * @access public
  540. * @return void
  541. */
  542. function checkTutorial()
  543. {
  544. if(config.currentModule != 'tutorial' && window.TUTORIAL && (!frameElement || frameElement.tagName != 'IFRAME'))
  545. {
  546. if(confirm(window.TUTORIAL.tip))
  547. {
  548. $.getJSON(createLink('tutorial', 'ajaxQuit'), function()
  549. {
  550. window.location.reload();
  551. }).error(function(){alert(lang.timeout)});
  552. }
  553. else
  554. {
  555. window.location.href = createLink('tutorial', 'index');
  556. }
  557. }
  558. }
  559. /* Remove 'ditto' in first row when batch create or edit. */
  560. function removeDitto()
  561. {
  562. $firstTr = $('.table-form').find('tbody tr:first');
  563. $firstTr.find('td select').each(function()
  564. {
  565. $(this).find("option[value='ditto']").remove();
  566. $(this).trigger("chosen:updated");
  567. });
  568. }
  569. /**
  570. * Revert module cookie.
  571. *
  572. * @access public
  573. * @return void
  574. */
  575. function revertModuleCookie()
  576. {
  577. if($('#mainmenu .nav li[data-id="project"]').hasClass('active'))
  578. {
  579. $('#modulemenu .nav li[data-id="task"] a').click(function()
  580. {
  581. $.cookie('moduleBrowseParam', 0, {expires:config.cookieLife, path:config.webRoot});
  582. });
  583. }
  584. if($('#mainmenu .nav li[data-id="product"]').hasClass('active'))
  585. {
  586. $('#modulemenu .nav li[data-id="story"] a').click(function()
  587. {
  588. $.cookie('storyModule', 0, {expires:config.cookieLife, path:config.webRoot});
  589. });
  590. }
  591. if($('#mainmenu .nav li[data-id="qa"]').hasClass('active'))
  592. {
  593. $('#modulemenu .nav li[data-id="bug"] a').click(function()
  594. {
  595. $.cookie('bugModule', 0, {expires:config.cookieLife, path:config.webRoot});
  596. });
  597. $('#modulemenu .nav li[data-id="testcase"] a').click(function()
  598. {
  599. $.cookie('caseModule', 0, {expires:config.cookieLife, path:config.webRoot});
  600. });
  601. }
  602. }
  603. /**
  604. * Focus move up or down for input.
  605. *
  606. * @param direction up|down
  607. */
  608. function inputFocusJump(direction, type){
  609. var $input = $('#mainContent table').find(type || 'input').filter(':focus').first();
  610. if(!$input.length) return;
  611. var $row = $input.closest('tr');
  612. var $nextRow = $row[direction === 'up' ? 'prev' : 'next']('tr');
  613. if(!$nextRow.length) $nextRow = $row.parent().children('tr')[direction === 'up' ? 'last' : 'first']();
  614. if(!$nextRow.length) return;
  615. var datetimepicker = $input.data('datetimepicker');
  616. if(datetimepicker) datetimepicker.hide();
  617. return $nextRow.find(':input[name^=' + ($input.attr('name').split('[')[0]) + ']:text:not(:disabled):not([name*="%"])').focus();
  618. }
  619. /**
  620. * Focus move up or down for select.
  621. *
  622. * @param direction
  623. */
  624. function selectFocusJump(direction)
  625. {
  626. return inputFocusJump(direction, 'select');
  627. }
  628. function adjustNoticePosition()
  629. {
  630. var bottom = 25;
  631. $('#noticeBox').find('.alert').each(function()
  632. {
  633. $(this).css('bottom', bottom + 'px');
  634. bottom += $(this).outerHeight(true) - 10;
  635. });
  636. }
  637. function notifyMessage(data)
  638. {
  639. if(window.Notification)
  640. {
  641. var notify = null;
  642. message = data;
  643. if(typeof data.message == 'string') message = data.message;
  644. if(Notification.permission == "granted")
  645. {
  646. notify = new Notification("", {body:message, tag:'zentao', data:data});
  647. }
  648. else if(Notification.permission != "denied")
  649. {
  650. Notification.requestPermission().then(function(permission)
  651. {
  652. notify = new Notification("", {body:message, tag:'zentao', data:data});
  653. });
  654. }
  655. if(notify)
  656. {
  657. notify.onclick = function()
  658. {
  659. window.focus();
  660. if(typeof notify.data.url == 'string' && notify.data.url) window.location.href = notify.data.url;
  661. notify.close();
  662. }
  663. setTimeout(function()
  664. {
  665. notify.close();
  666. }, 3000);
  667. }
  668. }
  669. }
  670. /**
  671. * Get fingerprint.
  672. *
  673. * @access public
  674. * @return void
  675. */
  676. function getFingerprint()
  677. {
  678. if(typeof(Fingerprint) == 'function') return new Fingerprint().get();
  679. fingerprint = '';
  680. $.each(navigator, function(key, value)
  681. {
  682. if(typeof(value) == 'string') fingerprint += value.length;
  683. })
  684. return fingerprint;
  685. }
  686. /**
  687. * Alert message with bootbox.
  688. *
  689. * @param message $message
  690. * @access public
  691. * @return bool
  692. */
  693. function bootAlert(message)
  694. {
  695. bootbox.alert(message);
  696. return false;
  697. }
  698. /**
  699. * Toggle fold or unfold for parent.
  700. *
  701. * @param string $form
  702. * @param array $unfoldIdList
  703. * @param int $objectID
  704. * @param string $objectType
  705. * @access public
  706. * @return void
  707. */
  708. function toggleFold(form, unfoldIdList, objectID, objectType)
  709. {
  710. $form = $(form);
  711. $parentTd = $form.find('td.has-child');
  712. if($parentTd.length == 0) return false;
  713. var toggleClass = ['product', 'requirement', 'story'].indexOf(objectType) !== -1 ? 'story-toggle' : 'task-toggle';
  714. var nameClass = ['product', 'productplan'].indexOf(objectType) !== -1 ? 'c-title' : 'c-name';
  715. if(objectType == 'demand')
  716. {
  717. toggleClass = 'demand-toggle';
  718. nameClass = 'c-title';
  719. }
  720. $form.find('th.' + nameClass).addClass('clearfix').append("<span id='toggleFold' class='collapsed'><i class='icon icon-angle-double-right'></i></span>");
  721. var allUnfold = true;
  722. $parentTd.each(function()
  723. {
  724. var dataID = $(this).closest('tr').attr('data-id');
  725. if(typeof(unfoldIdList[dataID]) != 'undefined') return true;
  726. allUnfold = false;
  727. $form.find('tr.parent-' + dataID).hide();
  728. $(this).find('a.' + toggleClass).addClass('collapsed')
  729. })
  730. $form.find('th.' + nameClass + ' #toggleFold').toggleClass('collapsed', !allUnfold);
  731. $(document).on('click', '#toggleFold', function()
  732. {
  733. var newUnfoldID = [];
  734. var url = '';
  735. var collapsed = $(this).hasClass('collapsed');
  736. $parentTd.each(function()
  737. {
  738. var dataID = $(this).closest('tr').attr('data-id');
  739. $form.find('tr.parent-' + dataID).toggle(collapsed);
  740. $(this).find('a.' + toggleClass).toggleClass('collapsed', !collapsed)
  741. newUnfoldID.push(dataID);
  742. })
  743. $(this).toggleClass('collapsed', !collapsed);
  744. url = createLink('misc', 'ajaxSetUnfoldID', 'objectID=' + objectID + '&objectType=' + objectType + '&action=' + (collapsed ? 'add' : 'delete'));
  745. $.post(url, {'newUnfoldID': JSON.stringify(newUnfoldID)});
  746. });
  747. $parentTd.find('a.' + toggleClass).click(function()
  748. {
  749. var newUnfoldID = [];
  750. var url = '';
  751. var collapsed = $(this).hasClass('collapsed');
  752. var dataID = $(this).closest('tr').attr('data-id');
  753. $form.find('tr.parent-' + dataID).toggle(!collapsed);
  754. newUnfoldID.push(dataID);
  755. url = createLink('misc', 'ajaxSetUnfoldID', 'objectID=' + objectID + '&objectType=' + objectType + '&action=' + (collapsed ? 'add' : 'delete'));
  756. $table = $(this).closest('table');
  757. setTimeout(function()
  758. {
  759. hasCollapsed = $table.find('td.has-child a.' + toggleClass + '.collapsed').length != 0;
  760. $('#toggleFold').toggleClass('collapsed', hasCollapsed);
  761. }, 100);
  762. $.post(url, {'newUnfoldID': JSON.stringify(newUnfoldID)});
  763. });
  764. }
  765. /**
  766. * Adjust menu width.
  767. *
  768. * @access public
  769. * @return void
  770. */
  771. function adjustMenuWidth()
  772. {
  773. if(window.navigator.userAgent.indexOf('xuanxuan') > 0) return;
  774. var $mainHeader = $('#mainHeader .container');
  775. if($mainHeader.length == 0) return false;
  776. var $navbar = $mainHeader.find('#navbar .nav');
  777. var mainHeaderWidth = $mainHeader.width() - 10;
  778. var headingWidth = $mainHeader.find('#heading').width() + 30;
  779. var navbarWidth = $navbar.width();
  780. var toolbarWidth = $mainHeader.find('#toolbar').width() + 20;
  781. if(mainHeaderWidth < headingWidth + navbarWidth + toolbarWidth)
  782. {
  783. var delta = (headingWidth + navbarWidth + toolbarWidth) - mainHeaderWidth;
  784. delta = Math.ceil(delta / $navbar.children('li').length / 2);
  785. var aTagPadding = $navbar.find('a:first').css('padding-left').replace('px', '');
  786. var dividerMargin = $navbar.find('.divider').css('margin-left')?.replace('px', '') || 0;
  787. var newPadding = aTagPadding - delta;
  788. var newMargin = dividerMargin - delta - 1;
  789. if(newPadding < 0) newPadding = 0;
  790. if(newMargin < 0) newMargin = 0;
  791. $navbar.children('li').find('a').css('padding-left', newPadding).css('padding-right', newPadding);
  792. $navbar.find('.divider').css('margin-left', newMargin).css('margin-right', newMargin);
  793. }
  794. }
  795. /**
  796. * Scroll to selected item in drop menu.
  797. *
  798. * @param string $id
  799. * @access public
  800. * @return void
  801. */
  802. function scrollToSelected()
  803. {
  804. setTimeout(function()
  805. {
  806. $selected = $('#dropMenu .selected');
  807. if($selected.length == 0) return;
  808. $id = $selected.closest('.list-group');
  809. $id.mouseout(function(){$(this).find('a.active:not(.not-list-item)').removeClass('active')});
  810. var fixOffset = 160;
  811. offsetTop = $selected.offset().top;
  812. if(offsetTop < fixOffset) return;
  813. $id.scrollTop(offsetTop - fixOffset);
  814. }, 100);
  815. }
  816. /**
  817. * Limit iframe levels up to 3.
  818. *
  819. * @access public
  820. * @return void
  821. */
  822. function limitIframeLevel()
  823. {
  824. /* Fix bug #15325. */
  825. if(window.parent != window.top)
  826. {
  827. $('body').find('a.iframe').each(function()
  828. {
  829. $(this).replaceWith($(this).clone().removeClass('iframe'));
  830. });
  831. }
  832. }
  833. /**
  834. * Remove html tag.
  835. *
  836. * @param str $str
  837. * @access public
  838. * @return void
  839. */
  840. function removeHtmlTag(str)
  841. {
  842. return str.replace(/<[^>]+>/g,"");
  843. }
  844. /* Ping the server every some minutes to keep the session. */
  845. needPing = true;
  846. /* When body's ready, execute these. */
  847. $(document).ready(function()
  848. {
  849. if(needPing) setTimeout('setPing()', 1000 * 60 * 10); // After 10 minutes, begin ping.
  850. checkTutorial();
  851. revertModuleCookie();
  852. $(document).on('click', '#helpMenuItem .close-help-tab', function(){$('#helpMenuItem').prev().remove();$('#helpMenuItem').remove();});
  853. /* Open link in new tab when pressed ctrl key in windows */
  854. if(window.navigator.userAgent.match(/Windows/i))
  855. {
  856. $(document).on('mousedown', 'a', function(e)
  857. {
  858. var $a = $(this);
  859. if(!e.ctrlKey || $a.attr('target')) return;
  860. $a.attr('target', '_blank');
  861. clearTimeout($a.data('ctrlTimer'));
  862. $a.data('ctrlTimer', setTimeout(function(){$a.attr('target', null).data('ctrlTimer', 0)}, 100));
  863. e.preventDefault();
  864. });
  865. }
  866. /* Hide the global create drop-down when hovering over the avatar. */
  867. $('.has-avatar').hover(function()
  868. {
  869. $(this).next().removeClass('open');
  870. $(this).prev().removeClass('open');
  871. });
  872. /* Hide the avatar drop-down when hovering over the global create button. */
  873. $('#globalCreate').hover(function()
  874. {
  875. $(this).next().removeClass('open');
  876. $(this).addClass('dropdown-hover');
  877. });
  878. /* Hide create button when global create menu is clicked. */
  879. $('#globalCreate').click(function()
  880. {
  881. $(this).removeClass('dropdown-hover');
  882. });
  883. });
  884. /**
  885. * Make the selected product non clickable.
  886. *
  887. * @return void
  888. */
  889. function disableSelectedProduct()
  890. {
  891. $("select[id^='products'] option[disabled='disabled']").removeAttr('disabled');
  892. var selectedVal = [];
  893. $("select[id^='products']").each(function()
  894. {
  895. var selectedProduct = $(this).val();
  896. if(selectedProduct != 0 && $.inArray(selectedProduct, selectedVal) < 0) selectedVal.push(selectedProduct);
  897. })
  898. $("select[id^='products']").each(function()
  899. {
  900. var selectedProduct = $(this).val();
  901. $(this).find('option').each(function()
  902. {
  903. var optionVal = $(this).attr('value');
  904. if(optionVal != selectedProduct && $.inArray(optionVal, selectedVal) >= 0) $(this).attr('disabled', 'disabled');
  905. })
  906. })
  907. $("select[id^=products]").trigger('chosen:updated');
  908. }
  909. /**
  910. * Make the selected branch non clickable.
  911. *
  912. * @return void
  913. */
  914. function disableSelectedBranch()
  915. {
  916. var relatedProduct = $(this).siblings("select[id^='products']").val();
  917. /* Get the products control of the same value and their branch control. */
  918. var sameProductControl = [];
  919. var sameProductBranchControl = [];
  920. $("select[id^='products']").each(function()
  921. {
  922. if($(this).val() == relatedProduct)
  923. {
  924. $(this).siblings("select[id^='branch']").find("option[disabled='disabled']").removeAttr('disabled');
  925. sameProductControl.push(this);
  926. sameProductBranchControl.push($(this).siblings("select[id^='branch']"));
  927. }
  928. });
  929. /* Get the selected branch of the related product. */
  930. var preSelectedVal = [];
  931. $.each(sameProductControl, function()
  932. {
  933. var selectedBranch = $(this).siblings("select[id^='branch']").val();
  934. if($.inArray(selectedBranch, preSelectedVal) < 0) preSelectedVal.push(selectedBranch);
  935. });
  936. var selectedVal = [];
  937. $.each(sameProductControl, function()
  938. {
  939. var selectedBranch = $(this).siblings("select[id^='branch']").val();
  940. if($.inArray(selectedBranch, selectedVal) >= 0)
  941. {
  942. $(this).siblings("select[id^='branch']").find('option').removeAttr('selected');
  943. for(i in preSelectedVal) $(this).siblings("select[id^='branch']").find('option[value=' + preSelectedVal[i] + ']').attr('disabled', 'disabled');
  944. $(this).siblings("select[id^='branch']").find('option').not('[disabled=disabled]').eq(0).attr('selected', 'selected');
  945. var selectedBranch = $(this).siblings("select[id^='branch']").val();
  946. }
  947. if($.inArray(selectedBranch, selectedVal) < 0) selectedVal.push(selectedBranch);
  948. });
  949. /* Make the selected value disabled. */
  950. $.each(sameProductBranchControl, function()
  951. {
  952. var selectedBranch = $(this).val();
  953. $(this).find('option').each(function()
  954. {
  955. var optionVal = $(this).attr('value');
  956. if(optionVal != selectedBranch && $.inArray(optionVal, selectedVal) >= 0) $(this).attr('disabled', 'disabled');
  957. })
  958. })
  959. $("select[id^=branch]").trigger('chosen:updated');
  960. }
  961. /**
  962. * Determine whether multi-branch products should be disabled.
  963. *
  964. * @param object product
  965. * @return bool
  966. */
  967. function checkMultiProducts(product)
  968. {
  969. var disabledBranchList = [];
  970. var optionLength = $(product).siblings("select[id^='branch']").find('option').length;
  971. $(product).siblings("select[id^='branch']").find("option[disabled='disabled']").each(function()
  972. {
  973. disabledBranchList.push($(this).attr('value'));
  974. });
  975. if(optionLength - disabledBranchList.length == 1) return true;
  976. return false;
  977. }
  978. /**
  979. * Add row.
  980. *
  981. * @param object $obj
  982. * @access public
  983. * @return void
  984. */
  985. function addRow(obj)
  986. {
  987. var row = $('#addRow').html().replace(/%i%/g, rowIndex + 1);
  988. $('<tr class="addedRow">' + row + '</tr>').insertAfter($(obj).closest('tr'));
  989. var $row = $(obj).closest('tr').next();
  990. $row.find(".form-date").datepicker();
  991. $row.find("input[name^=color]").colorPicker();
  992. $row.find('div[id$=_chosen]').remove();
  993. $row.find('.picker').remove();
  994. $row.find('.chosen').chosen();
  995. $row.find('.picker-select').picker();
  996. rowIndex ++;
  997. }
  998. /**
  999. * Delete row.
  1000. *
  1001. * @param object $obj
  1002. * @access public
  1003. * @return void
  1004. */
  1005. function deleteRow(obj)
  1006. {
  1007. $(obj).closest('tr').remove();
  1008. }
  1009. /**
  1010. * Show checked fields.
  1011. *
  1012. * @param string fields
  1013. * @access public
  1014. * @return void
  1015. */
  1016. function showCheckedFields(fields)
  1017. {
  1018. var fieldList = ',' + fields + ',';
  1019. $('#formSettingForm > .checkboxes > .checkbox-primary > input').each(function()
  1020. {
  1021. var field = ',' + $(this).val() + ',';
  1022. var $field = config.currentMethod == 'create' ? $('#' + $(this).val()) : $('[name^=' + $(this).val() + ']');
  1023. var $fieldBox = $('.' + $(this).val() + 'Box' );
  1024. var required = '';
  1025. if(typeof requiredFields != 'undefined') var required = ',' + requiredFields + ',';
  1026. if(fieldList.indexOf(field) >= 0 || (required && required.indexOf(field) >= 0))
  1027. {
  1028. $fieldBox.removeClass('hidden');
  1029. $field.removeAttr('disabled');
  1030. }
  1031. else if(!$fieldBox.hasClass('hidden'))
  1032. {
  1033. $fieldBox.addClass('hidden');
  1034. if($(this).val() != 'branch') $field.attr('disabled', true);
  1035. }
  1036. if(config.currentModule == 'story' && $(this).val() == 'source')
  1037. {
  1038. var $sourceNote = config.currentMethod == 'create' ? $('#sourceNote') : $('[name^=sourceNote]');
  1039. $sourceNote.attr('disabled', $fieldBox.hasClass('hidden'));
  1040. }
  1041. });
  1042. if(config.currentModule == 'task' && config.currentMethod == 'create');
  1043. {
  1044. if(fieldList.indexOf(',estStarted,') >= 0 && fieldList.indexOf(',deadline,') >= 0)
  1045. {
  1046. $('.borderBox').removeClass('hidden');
  1047. }
  1048. else if(fieldList.indexOf(',estStarted,') >= 0 || fieldList.indexOf(',deadline,') >= 0)
  1049. {
  1050. $('.datePlanBox').removeClass('hidden');
  1051. if(!$('.borderBox').hasClass('hidden')) $('.borderBox').addClass('hidden');
  1052. }
  1053. else
  1054. {
  1055. if(!$('.borderBox').hasClass('hidden')) $('.borderBox').addClass('hidden');
  1056. if(!$('.datePlanBox').hasClass('hidden')) $('.datePlanBox').addClass('hidden');
  1057. }
  1058. if(typeof lifetime != 'undefined' && lifetime == 'ops') $('.storyBox').addClass('hidden');
  1059. }
  1060. }
  1061. /**
  1062. * Hidden require field.
  1063. *
  1064. * @access public
  1065. * @return void
  1066. */
  1067. function hiddenRequireFields()
  1068. {
  1069. $('#formSettingForm > .checkboxes > .checkbox-primary > input').each(function()
  1070. {
  1071. var field = ',' + $(this).val() + ',';
  1072. var required = ',' + requiredFields + ',';
  1073. if(required.indexOf(field) >= 0) $(this).closest('div').addClass('hidden');
  1074. });
  1075. }
  1076. /**
  1077. * Save custom fields.
  1078. *
  1079. * @param stirng $key
  1080. * @param int $maxFieldCount
  1081. * @param object $name
  1082. * @param int $nameMinWidth
  1083. * @access public
  1084. * @return void
  1085. */
  1086. function saveCustomFields(key, maxFieldCount, $name, nameMinWidth)
  1087. {
  1088. var fields = '';
  1089. $('#formSettingForm > .checkboxes > .checkbox-primary > input:checked').each(function()
  1090. {
  1091. fields += ',' + $(this).val();
  1092. });
  1093. var module = config.currentModule;
  1094. var link = createLink('custom', 'ajaxSaveCustomFields', 'module=' + module + '&section=custom&key=' + key);
  1095. $.post(link, {'fields' : fields}, function()
  1096. {
  1097. showFields = fields;
  1098. showCheckedFields(fields);
  1099. $('#formSetting').parent().removeClass('open');
  1100. if(key == 'batchCreateFields') setCustomFieldsStyle(maxFieldCount, $name, nameMinWidth);
  1101. });
  1102. }
  1103. /**
  1104. * Set custom fields style.
  1105. *
  1106. * @param int $maxFieldCount
  1107. * @param object $name
  1108. * @param int $nameMinWidth
  1109. * @access public
  1110. * @return void
  1111. */
  1112. function setCustomFieldsStyle(maxFieldCount, $name, nameMinWidth)
  1113. {
  1114. var fieldCount = $('#batchCreateForm .table thead>tr>th:visible').length;
  1115. $('.form-actions').attr('colspan', fieldCount);
  1116. var $table = $('#batchCreateForm > .table-responsive');
  1117. if(fieldCount > maxFieldCount)
  1118. {
  1119. $table.removeClass('scroll-none');
  1120. $table.css('overflow', 'auto');
  1121. }
  1122. else
  1123. {
  1124. $table.addClass('scroll-none');
  1125. $table.css('overflow', 'visible');
  1126. }
  1127. if($name.width() < nameMinWidth) $name.width(200);
  1128. }
  1129. /**
  1130. * Refresh budget units of the project.
  1131. *
  1132. * @param object $data
  1133. * @access public
  1134. * @return void
  1135. */
  1136. function refreshBudgetUnit(data)
  1137. {
  1138. $('#budgetUnit').val(data.budgetUnit).trigger('chosen:updated');
  1139. if(typeof(data.availableBudget) == 'undefined')
  1140. {
  1141. $('#budget').removeAttr('placeholder').attr('disabled', true);
  1142. $('#future').prop('checked', true);
  1143. }
  1144. else
  1145. {
  1146. $('#budget').removeAttr('disabled');
  1147. $('#future').prop('checked', false);
  1148. }
  1149. }
  1150. /**
  1151. * Handle radio logic of Kanban column width setting.
  1152. *
  1153. * @access public
  1154. * @return void
  1155. */
  1156. function handleKanbanWidthAttr ()
  1157. {
  1158. $('#colWidth, #minColWidth, #maxColWidth').attr('onkeyup', 'value=value.match(/^\\d+$/) ? value : ""');
  1159. $('#colWidth, #minColWidth, #maxColWidth').attr('maxlength', '3');
  1160. var fluidBoard = $("#mainContent input[name='fluidBoard'][checked='checked']").val() || 0;
  1161. var addAttrEle = fluidBoard == 0 ? '#colWidth' : '#minColWidth, #maxColWidth';
  1162. var $fixedTip = $('#colWidth + .fixedTip');
  1163. var $autoTip = $('#maxColWidth + .autoTip');
  1164. $(addAttrEle).closest('.width-radio-row').addClass('required');
  1165. $('#colWidth').attr('disabled',fluidBoard == 1);
  1166. $('#minColWidth, #maxColWidth').attr('disabled',fluidBoard == 0);
  1167. $("#minColWidth, #maxColWidth").on('input', function()
  1168. {
  1169. $('#minColWidthLabel, #maxColWidthLabel').remove();
  1170. $('#minColWidth, #maxColWidth').removeClass('has-error');
  1171. });
  1172. if(fluidBoard == 1)
  1173. {
  1174. $fixedTip.addClass('hidden');
  1175. $autoTip.removeClass('hidden');
  1176. }
  1177. else
  1178. {
  1179. $fixedTip.removeClass('hidden');
  1180. $autoTip.addClass('hidden');
  1181. }
  1182. $(document).on('change', "#mainContent input[name='fluidBoard']", function(e)
  1183. {
  1184. $('#colWidth').attr('disabled', e.target.value == 1);
  1185. $('#minColWidth, #maxColWidth').attr('disabled', e.target.value == 0);
  1186. if(e.target.value == 0 && $('#minColWidthLabel, #maxColWidthLabel'))
  1187. {
  1188. $('#colWidth').closest('.width-radio-row').addClass('required');
  1189. $('#minColWidth, #maxColWidth').closest('.width-radio-row').removeClass('required');
  1190. $('#minColWidthLabel, #maxColWidthLabel').remove();
  1191. $('#minColWidth, #maxColWidth').removeClass('has-error');
  1192. $fixedTip.removeClass('hidden');
  1193. $autoTip.addClass('hidden');
  1194. }
  1195. else if(e.target.value == 1 && $('#colWidthLabel'))
  1196. {
  1197. $('#minColWidth, #maxColWidth').closest('.width-radio-row').addClass('required');
  1198. $('#colWidth').closest('.width-radio-row').removeClass('required');
  1199. $('#colWidthLabel').remove();
  1200. $('#colWidth').removeClass('has-error');
  1201. $fixedTip.addClass('hidden');
  1202. $autoTip.removeClass('hidden');
  1203. }
  1204. });
  1205. }
  1206. function fetchMessage(force, fetchUrl)
  1207. {
  1208. let $this = $('#messageBar');
  1209. let $dropmenu = $("#dropdownMessageMenu");
  1210. let isOpen = $this.hasClass('open');
  1211. if(typeof(force) === 'undefined' || typeof(force) === 'object') force = false;
  1212. if(typeof(fetchUrl) === 'undefined') fetchUrl = $this.attr('data-fetcher');
  1213. if(!force)
  1214. {
  1215. $('#messageDropdown #messageBar').toggleClass('open', !isOpen);
  1216. $('#messageDropdown .messageDropdownBox').toggleClass('show', !isOpen);
  1217. }
  1218. if(isOpen && !force) return;
  1219. $dropmenu.load(fetchUrl);
  1220. }