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.

application.js 32KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110
  1. /* Redmine - project management software
  2. Copyright (C) 2006-2020 Jean-Philippe Lang */
  3. /* Fix for CVE-2015-9251, to be removed with JQuery >= 3.0 */
  4. $.ajaxPrefilter(function (s) {
  5. if (s.crossDomain) {
  6. s.contents.script = false;
  7. }
  8. });
  9. function checkAll(id, checked) {
  10. $('#'+id).find('input[type=checkbox]:enabled').prop('checked', checked);
  11. }
  12. function toggleCheckboxesBySelector(selector) {
  13. var all_checked = true;
  14. $(selector).each(function(index) {
  15. if (!$(this).is(':checked')) { all_checked = false; }
  16. });
  17. $(selector).prop('checked', !all_checked).trigger('change');
  18. }
  19. function showAndScrollTo(id, focus) {
  20. $('#'+id).show();
  21. if (focus !== null) {
  22. $('#'+focus).focus();
  23. }
  24. $('html, body').animate({scrollTop: $('#'+id).offset().top}, 100);
  25. }
  26. function toggleRowGroup(el) {
  27. var tr = $(el).parents('tr').first();
  28. var n = tr.next();
  29. tr.toggleClass('open');
  30. $(el).toggleClass('icon-expended icon-collapsed');
  31. while (n.length && !n.hasClass('group')) {
  32. n.toggle();
  33. n = n.next('tr');
  34. }
  35. }
  36. function collapseAllRowGroups(el) {
  37. var tbody = $(el).parents('tbody').first();
  38. tbody.children('tr').each(function(index) {
  39. if ($(this).hasClass('group')) {
  40. $(this).removeClass('open');
  41. $(this).find('.expander').switchClass('icon-expended', 'icon-collapsed');
  42. } else {
  43. $(this).hide();
  44. }
  45. });
  46. }
  47. function expandAllRowGroups(el) {
  48. var tbody = $(el).parents('tbody').first();
  49. tbody.children('tr').each(function(index) {
  50. if ($(this).hasClass('group')) {
  51. $(this).addClass('open');
  52. $(this).find('.expander').switchClass('icon-collapsed', 'icon-expended');
  53. } else {
  54. $(this).show();
  55. }
  56. });
  57. }
  58. function toggleAllRowGroups(el) {
  59. var tr = $(el).parents('tr').first();
  60. if (tr.hasClass('open')) {
  61. collapseAllRowGroups(el);
  62. } else {
  63. expandAllRowGroups(el);
  64. }
  65. }
  66. function toggleFieldset(el) {
  67. var fieldset = $(el).parents('fieldset').first();
  68. fieldset.toggleClass('collapsed');
  69. fieldset.children('legend').toggleClass('icon-expended icon-collapsed');
  70. fieldset.children('div').toggle();
  71. }
  72. function hideFieldset(el) {
  73. var fieldset = $(el).parents('fieldset').first();
  74. fieldset.toggleClass('collapsed');
  75. fieldset.children('div').hide();
  76. }
  77. // columns selection
  78. function moveOptions(theSelFrom, theSelTo) {
  79. $(theSelFrom).find('option:selected').detach().prop("selected", false).appendTo($(theSelTo));
  80. }
  81. function moveOptionUp(theSel) {
  82. $(theSel).find('option:selected').each(function(){
  83. $(this).prev(':not(:selected)').detach().insertAfter($(this));
  84. });
  85. }
  86. function moveOptionTop(theSel) {
  87. $(theSel).find('option:selected').detach().prependTo($(theSel));
  88. }
  89. function moveOptionDown(theSel) {
  90. $($(theSel).find('option:selected').get().reverse()).each(function(){
  91. $(this).next(':not(:selected)').detach().insertBefore($(this));
  92. });
  93. }
  94. function moveOptionBottom(theSel) {
  95. $(theSel).find('option:selected').detach().appendTo($(theSel));
  96. }
  97. function initFilters() {
  98. $('#add_filter_select').change(function() {
  99. addFilter($(this).val(), '', []);
  100. });
  101. $('#filters-table td.field input[type=checkbox]').each(function() {
  102. toggleFilter($(this).val());
  103. });
  104. $('#filters-table').on('click', 'td.field input[type=checkbox]', function() {
  105. toggleFilter($(this).val());
  106. });
  107. $('#filters-table').on('keypress', 'input[type=text]', function(e) {
  108. if (e.keyCode == 13) $(this).closest('form').submit();
  109. });
  110. }
  111. function addFilter(field, operator, values) {
  112. var fieldId = field.replace('.', '_');
  113. var tr = $('#tr_'+fieldId);
  114. var filterOptions = availableFilters[field];
  115. if (!filterOptions) return;
  116. if (filterOptions['remote'] && filterOptions['values'] == null) {
  117. $.getJSON(filtersUrl, {'name': field}).done(function(data) {
  118. filterOptions['values'] = data;
  119. addFilter(field, operator, values) ;
  120. });
  121. return;
  122. }
  123. if (tr.length > 0) {
  124. tr.show();
  125. } else {
  126. buildFilterRow(field, operator, values);
  127. }
  128. $('#cb_'+fieldId).prop('checked', true);
  129. toggleFilter(field);
  130. toggleMultiSelectIconInit();
  131. $('#add_filter_select').val('').find('option').each(function() {
  132. if ($(this).attr('value') == field) {
  133. $(this).attr('disabled', true);
  134. }
  135. });
  136. }
  137. function buildFilterRow(field, operator, values) {
  138. var fieldId = field.replace('.', '_');
  139. var filterTable = $("#filters-table");
  140. var filterOptions = availableFilters[field];
  141. if (!filterOptions) return;
  142. var operators = operatorByType[filterOptions['type']];
  143. var filterValues = filterOptions['values'];
  144. var i, select;
  145. var tr = $('<tr class="filter">').attr('id', 'tr_'+fieldId).html(
  146. '<td class="field"><input checked="checked" id="cb_'+fieldId+'" name="f[]" value="'+field+'" type="checkbox"><label for="cb_'+fieldId+'"> '+filterOptions['name']+'</label></td>' +
  147. '<td class="operator"><select id="operators_'+fieldId+'" name="op['+field+']"></td>' +
  148. '<td class="values"></td>'
  149. );
  150. filterTable.append(tr);
  151. select = tr.find('td.operator select');
  152. for (i = 0; i < operators.length; i++) {
  153. var option = $('<option>').val(operators[i]).text(operatorLabels[operators[i]]);
  154. if (operators[i] == operator) { option.prop('selected', true); }
  155. select.append(option);
  156. }
  157. select.change(function(){ toggleOperator(field); });
  158. switch (filterOptions['type']) {
  159. case "list":
  160. case "list_optional":
  161. case "list_status":
  162. case "list_subprojects":
  163. tr.find('td.values').append(
  164. '<span style="display:none;"><select class="value" id="values_'+fieldId+'_1" name="v['+field+'][]"></select>' +
  165. ' <span class="toggle-multiselect icon-only '+(values.length > 1 ? 'icon-toggle-minus' : 'icon-toggle-plus')+'">&nbsp;</span></span>'
  166. );
  167. select = tr.find('td.values select');
  168. if (values.length > 1) { select.attr('multiple', true); }
  169. for (i = 0; i < filterValues.length; i++) {
  170. var filterValue = filterValues[i];
  171. var option = $('<option>');
  172. if ($.isArray(filterValue)) {
  173. option.val(filterValue[1]).text(filterValue[0]);
  174. if ($.inArray(filterValue[1], values) > -1) {option.prop('selected', true);}
  175. if (filterValue.length == 3) {
  176. var optgroup = select.find('optgroup').filter(function(){return $(this).attr('label') == filterValue[2]});
  177. if (!optgroup.length) {optgroup = $('<optgroup>').attr('label', filterValue[2]);}
  178. option = optgroup.append(option);
  179. }
  180. } else {
  181. option.val(filterValue).text(filterValue);
  182. if ($.inArray(filterValue, values) > -1) {option.prop('selected', true);}
  183. }
  184. select.append(option);
  185. }
  186. break;
  187. case "date":
  188. case "date_past":
  189. tr.find('td.values').append(
  190. '<span style="display:none;"><input type="date" name="v['+field+'][]" id="values_'+fieldId+'_1" size="10" class="value date_value" /></span>' +
  191. ' <span style="display:none;"><input type="date" name="v['+field+'][]" id="values_'+fieldId+'_2" size="10" class="value date_value" /></span>' +
  192. ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="3" class="value" /> '+labelDayPlural+'</span>'
  193. );
  194. $('#values_'+fieldId+'_1').val(values[0]).datepickerFallback(datepickerOptions);
  195. $('#values_'+fieldId+'_2').val(values[1]).datepickerFallback(datepickerOptions);
  196. $('#values_'+fieldId).val(values[0]);
  197. break;
  198. case "string":
  199. case "text":
  200. tr.find('td.values').append(
  201. '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="30" class="value" /></span>'
  202. );
  203. $('#values_'+fieldId).val(values[0]);
  204. break;
  205. case "relation":
  206. tr.find('td.values').append(
  207. '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'" size="6" class="value" /></span>' +
  208. '<span style="display:none;"><select class="value" name="v['+field+'][]" id="values_'+fieldId+'_1"></select></span>'
  209. );
  210. $('#values_'+fieldId).val(values[0]);
  211. select = tr.find('td.values select');
  212. for (i = 0; i < filterValues.length; i++) {
  213. var filterValue = filterValues[i];
  214. var option = $('<option>');
  215. option.val(filterValue[1]).text(filterValue[0]);
  216. if (values[0] == filterValue[1]) { option.prop('selected', true); }
  217. select.append(option);
  218. }
  219. break;
  220. case "integer":
  221. case "float":
  222. case "tree":
  223. tr.find('td.values').append(
  224. '<span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_1" size="14" class="value" /></span>' +
  225. ' <span style="display:none;"><input type="text" name="v['+field+'][]" id="values_'+fieldId+'_2" size="14" class="value" /></span>'
  226. );
  227. $('#values_'+fieldId+'_1').val(values[0]);
  228. $('#values_'+fieldId+'_2').val(values[1]);
  229. break;
  230. }
  231. }
  232. function toggleFilter(field) {
  233. var fieldId = field.replace('.', '_');
  234. if ($('#cb_' + fieldId).is(':checked')) {
  235. $("#operators_" + fieldId).show().removeAttr('disabled');
  236. toggleOperator(field);
  237. } else {
  238. $("#operators_" + fieldId).hide().attr('disabled', true);
  239. enableValues(field, []);
  240. }
  241. }
  242. function enableValues(field, indexes) {
  243. var fieldId = field.replace('.', '_');
  244. $('#tr_'+fieldId+' td.values .value').each(function(index) {
  245. if ($.inArray(index, indexes) >= 0) {
  246. $(this).removeAttr('disabled');
  247. $(this).parents('span').first().show();
  248. } else {
  249. $(this).val('');
  250. $(this).attr('disabled', true);
  251. $(this).parents('span').first().hide();
  252. }
  253. if ($(this).hasClass('group')) {
  254. $(this).addClass('open');
  255. } else {
  256. $(this).show();
  257. }
  258. });
  259. }
  260. function toggleOperator(field) {
  261. var fieldId = field.replace('.', '_');
  262. var operator = $("#operators_" + fieldId);
  263. switch (operator.val()) {
  264. case "!*":
  265. case "*":
  266. case "nd":
  267. case "t":
  268. case "ld":
  269. case "nw":
  270. case "w":
  271. case "lw":
  272. case "l2w":
  273. case "nm":
  274. case "m":
  275. case "lm":
  276. case "y":
  277. case "o":
  278. case "c":
  279. case "*o":
  280. case "!o":
  281. enableValues(field, []);
  282. break;
  283. case "><":
  284. enableValues(field, [0,1]);
  285. break;
  286. case "<t+":
  287. case ">t+":
  288. case "><t+":
  289. case "t+":
  290. case ">t-":
  291. case "<t-":
  292. case "><t-":
  293. case "t-":
  294. enableValues(field, [2]);
  295. break;
  296. case "=p":
  297. case "=!p":
  298. case "!p":
  299. enableValues(field, [1]);
  300. break;
  301. default:
  302. enableValues(field, [0]);
  303. break;
  304. }
  305. }
  306. function toggleMultiSelect(el) {
  307. var isWorkflow = el.closest('.controller-workflows');
  308. if (el.attr('multiple')) {
  309. el.removeAttr('multiple');
  310. if (isWorkflow) { el.find("option[value=all]").show(); }
  311. el.attr('size', 1);
  312. } else {
  313. el.attr('multiple', true);
  314. if (isWorkflow) { el.find("option[value=all]").attr("selected", false).hide(); }
  315. if (el.children().length > 10)
  316. el.attr('size', 10);
  317. else
  318. el.attr('size', 4);
  319. }
  320. }
  321. function showTab(name, url) {
  322. $('#tab-content-' + name).parent().find('.tab-content').hide();
  323. $('#tab-content-' + name).show();
  324. $('#tab-' + name).closest('.tabs').find('a').removeClass('selected');
  325. $('#tab-' + name).addClass('selected');
  326. replaceInHistory(url)
  327. return false;
  328. }
  329. function showIssueHistory(journal, url) {
  330. tab_content = $('#tab-content-history');
  331. tab_content.parent().find('.tab-content').hide();
  332. tab_content.show();
  333. tab_content.parent().children('div.tabs').find('a').removeClass('selected');
  334. $('#tab-' + journal).addClass('selected');
  335. replaceInHistory(url)
  336. switch(journal) {
  337. case 'notes':
  338. tab_content.find('.journal:not(.has-notes)').hide();
  339. tab_content.find('.journal.has-notes').show();
  340. break;
  341. case 'properties':
  342. tab_content.find('.journal.has-notes').hide();
  343. tab_content.find('.journal:not(.has-notes)').show();
  344. break;
  345. default:
  346. tab_content.find('.journal').show();
  347. }
  348. return false;
  349. }
  350. function getRemoteTab(name, remote_url, url, load_always) {
  351. load_always = load_always || false;
  352. var tab_content = $('#tab-content-' + name);
  353. tab_content.parent().find('.tab-content').hide();
  354. tab_content.parent().children('div.tabs').find('a').removeClass('selected');
  355. $('#tab-' + name).addClass('selected');
  356. replaceInHistory(url);
  357. if (tab_content.children().length == 0 && load_always == false) {
  358. $.ajax({
  359. url: remote_url,
  360. type: 'get',
  361. success: function(data){
  362. tab_content.html(data)
  363. }
  364. });
  365. }
  366. tab_content.show();
  367. return false;
  368. }
  369. //replaces current URL with the "href" attribute of the current link
  370. //(only triggered if supported by browser)
  371. function replaceInHistory(url) {
  372. if ("replaceState" in window.history && url !== undefined) {
  373. window.history.replaceState(null, document.title, url);
  374. }
  375. }
  376. function moveTabRight(el) {
  377. var lis = $(el).parents('div.tabs').first().find('ul').children();
  378. var bw = $(el).parents('div.tabs-buttons').outerWidth(true);
  379. var tabsWidth = 0;
  380. var i = 0;
  381. lis.each(function() {
  382. if ($(this).is(':visible')) {
  383. tabsWidth += $(this).outerWidth(true);
  384. }
  385. });
  386. if (tabsWidth < $(el).parents('div.tabs').first().width() - bw) { return; }
  387. $(el).siblings('.tab-left').removeClass('disabled');
  388. while (i<lis.length && !lis.eq(i).is(':visible')) { i++; }
  389. var w = lis.eq(i).width();
  390. lis.eq(i).hide();
  391. if (tabsWidth - w < $(el).parents('div.tabs').first().width() - bw) {
  392. $(el).addClass('disabled');
  393. }
  394. }
  395. function moveTabLeft(el) {
  396. var lis = $(el).parents('div.tabs').first().find('ul').children();
  397. var i = 0;
  398. while (i < lis.length && !lis.eq(i).is(':visible')) { i++; }
  399. if (i > 0) {
  400. lis.eq(i-1).show();
  401. $(el).siblings('.tab-right').removeClass('disabled');
  402. }
  403. if (i <= 1) {
  404. $(el).addClass('disabled');
  405. }
  406. }
  407. function displayTabsButtons() {
  408. var lis;
  409. var tabsWidth;
  410. var el;
  411. var numHidden;
  412. $('div.tabs').each(function() {
  413. el = $(this);
  414. lis = el.find('ul').children();
  415. tabsWidth = 0;
  416. numHidden = 0;
  417. lis.each(function(){
  418. if ($(this).is(':visible')) {
  419. tabsWidth += $(this).outerWidth(true);
  420. } else {
  421. numHidden++;
  422. }
  423. });
  424. var bw = $(el).find('div.tabs-buttons').outerWidth(true);
  425. if ((tabsWidth < el.width() - bw) && (lis.length === 0 || lis.first().is(':visible'))) {
  426. el.find('div.tabs-buttons').hide();
  427. } else {
  428. el.find('div.tabs-buttons').show().children('button.tab-left').toggleClass('disabled', numHidden == 0);
  429. }
  430. });
  431. }
  432. function setPredecessorFieldsVisibility() {
  433. var relationType = $('#relation_relation_type');
  434. if (relationType.val() == "precedes" || relationType.val() == "follows") {
  435. $('#predecessor_fields').show();
  436. } else {
  437. $('#predecessor_fields').hide();
  438. }
  439. }
  440. function showModal(id, width, title) {
  441. var el = $('#'+id).first();
  442. if (el.length === 0 || el.is(':visible')) {return;}
  443. if (!title) title = el.find('h3.title').text();
  444. // moves existing modals behind the transparent background
  445. $(".modal").css('zIndex',99);
  446. el.dialog({
  447. width: width,
  448. modal: true,
  449. resizable: false,
  450. dialogClass: 'modal',
  451. title: title
  452. }).on('dialogclose', function(){
  453. $(".modal").css('zIndex',101);
  454. });
  455. el.find("input[type=text], input[type=submit]").first().focus();
  456. }
  457. function hideModal(el) {
  458. var modal;
  459. if (el) {
  460. modal = $(el).parents('.ui-dialog-content');
  461. } else {
  462. modal = $('#ajax-modal');
  463. }
  464. modal.dialog("close");
  465. }
  466. function collapseScmEntry(id) {
  467. $('.'+id).each(function() {
  468. if ($(this).hasClass('open')) {
  469. collapseScmEntry($(this).attr('id'));
  470. }
  471. $(this).hide();
  472. });
  473. $('#'+id).removeClass('open');
  474. }
  475. function expandScmEntry(id) {
  476. $('.'+id).each(function() {
  477. $(this).show();
  478. if ($(this).hasClass('loaded') && !$(this).hasClass('collapsed')) {
  479. expandScmEntry($(this).attr('id'));
  480. }
  481. });
  482. $('#'+id).addClass('open');
  483. }
  484. function scmEntryClick(id, url) {
  485. var el = $('#'+id);
  486. if (el.hasClass('open')) {
  487. collapseScmEntry(id);
  488. el.find('.expander').switchClass('icon-expended', 'icon-collapsed');
  489. el.addClass('collapsed');
  490. return false;
  491. } else if (el.hasClass('loaded')) {
  492. expandScmEntry(id);
  493. el.find('.expander').switchClass('icon-collapsed', 'icon-expended');
  494. el.removeClass('collapsed');
  495. return false;
  496. }
  497. if (el.hasClass('loading')) {
  498. return false;
  499. }
  500. el.addClass('loading');
  501. $.ajax({
  502. url: url,
  503. success: function(data) {
  504. el.after(data);
  505. el.addClass('open').addClass('loaded').removeClass('loading');
  506. el.find('.expander').switchClass('icon-collapsed', 'icon-expended');
  507. }
  508. });
  509. return true;
  510. }
  511. function randomKey(size) {
  512. var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
  513. var key = '';
  514. for (var i = 0; i < size; i++) {
  515. key += chars.charAt(Math.floor(Math.random() * chars.length));
  516. }
  517. return key;
  518. }
  519. function updateIssueFrom(url, el) {
  520. $('#all_attributes input, #all_attributes textarea, #all_attributes select').each(function(){
  521. $(this).data('valuebeforeupdate', $(this).val());
  522. });
  523. if (el) {
  524. $("#form_update_triggered_by").val($(el).attr('id'));
  525. }
  526. return $.ajax({
  527. url: url,
  528. type: 'post',
  529. data: $('#issue-form').serialize()
  530. });
  531. }
  532. function replaceIssueFormWith(html){
  533. var replacement = $(html);
  534. $('#all_attributes input, #all_attributes textarea, #all_attributes select').each(function(){
  535. var object_id = $(this).attr('id');
  536. if (object_id && $(this).data('valuebeforeupdate')!=$(this).val()) {
  537. replacement.find('#'+object_id).val($(this).val());
  538. }
  539. });
  540. $('#all_attributes').empty();
  541. $('#all_attributes').prepend(replacement);
  542. }
  543. function updateBulkEditFrom(url) {
  544. $.ajax({
  545. url: url,
  546. type: 'post',
  547. data: $('#bulk_edit_form').serialize()
  548. });
  549. }
  550. function observeAutocompleteField(fieldId, url, options) {
  551. $(document).ready(function() {
  552. $('#'+fieldId).autocomplete($.extend({
  553. source: url,
  554. minLength: 2,
  555. position: {collision: "flipfit"},
  556. search: function(){$('#'+fieldId).addClass('ajax-loading');},
  557. response: function(){$('#'+fieldId).removeClass('ajax-loading');}
  558. }, options));
  559. $('#'+fieldId).addClass('autocomplete');
  560. });
  561. }
  562. function observeSearchfield(fieldId, targetId, url) {
  563. $('#'+fieldId).each(function() {
  564. var $this = $(this);
  565. $this.addClass('autocomplete');
  566. $this.attr('data-value-was', $this.val());
  567. var check = function() {
  568. var val = $this.val();
  569. if ($this.attr('data-value-was') != val){
  570. $this.attr('data-value-was', val);
  571. $.ajax({
  572. url: url,
  573. type: 'get',
  574. data: {q: $this.val()},
  575. success: function(data){ if(targetId) $('#'+targetId).html(data); },
  576. beforeSend: function(){ $this.addClass('ajax-loading'); },
  577. complete: function(){ $this.removeClass('ajax-loading'); }
  578. });
  579. }
  580. };
  581. var reset = function() {
  582. if (timer) {
  583. clearInterval(timer);
  584. timer = setInterval(check, 300);
  585. }
  586. };
  587. var timer = setInterval(check, 300);
  588. $this.bind('keyup click mousemove', reset);
  589. });
  590. }
  591. $(document).ready(function(){
  592. $(".drdn .autocomplete").val('');
  593. // This variable is used to focus selected project
  594. var selected;
  595. $(".drdn-trigger").click(function(e){
  596. var drdn = $(this).closest(".drdn");
  597. if (drdn.hasClass("expanded")) {
  598. drdn.removeClass("expanded");
  599. } else {
  600. $(".drdn").removeClass("expanded");
  601. drdn.addClass("expanded");
  602. selected = $('.drdn-items a.selected'); // Store selected project
  603. selected.focus(); // Calling focus to scroll to selected project
  604. if (!isMobile()) {
  605. drdn.find(".autocomplete").focus();
  606. }
  607. e.stopPropagation();
  608. }
  609. });
  610. $(document).click(function(e){
  611. if ($(e.target).closest(".drdn").length < 1) {
  612. $(".drdn.expanded").removeClass("expanded");
  613. }
  614. });
  615. observeSearchfield('projects-quick-search', null, $('#projects-quick-search').data('automcomplete-url'));
  616. $(".drdn-content").keydown(function(event){
  617. var items = $(this).find(".drdn-items");
  618. // If a project is selected set focused to selected only once
  619. if (selected && selected.length > 0) {
  620. var focused = selected;
  621. selected = undefined;
  622. }
  623. else {
  624. var focused = items.find("a:focus");
  625. }
  626. switch (event.which) {
  627. case 40: //down
  628. if (focused.length > 0) {
  629. focused.nextAll("a").first().focus();;
  630. } else {
  631. items.find("a").first().focus();;
  632. }
  633. event.preventDefault();
  634. break;
  635. case 38: //up
  636. if (focused.length > 0) {
  637. var prev = focused.prevAll("a");
  638. if (prev.length > 0) {
  639. prev.first().focus();
  640. } else {
  641. $(this).find(".autocomplete").focus();
  642. }
  643. event.preventDefault();
  644. }
  645. break;
  646. case 35: //end
  647. if (focused.length > 0) {
  648. focused.nextAll("a").last().focus();
  649. event.preventDefault();
  650. }
  651. break;
  652. case 36: //home
  653. if (focused.length > 0) {
  654. focused.prevAll("a").last().focus();
  655. event.preventDefault();
  656. }
  657. break;
  658. }
  659. });
  660. });
  661. function beforeShowDatePicker(input, inst) {
  662. var default_date = null;
  663. switch ($(input).attr("id")) {
  664. case "issue_start_date" :
  665. if ($("#issue_due_date").length > 0) {
  666. default_date = $("#issue_due_date").val();
  667. }
  668. break;
  669. case "issue_due_date" :
  670. if ($("#issue_start_date").length > 0) {
  671. var start_date = $("#issue_start_date").val();
  672. if (start_date != "") {
  673. start_date = new Date(Date.parse(start_date));
  674. if (start_date > new Date()) {
  675. default_date = $("#issue_start_date").val();
  676. }
  677. }
  678. }
  679. break;
  680. }
  681. $(input).datepickerFallback("option", "defaultDate", default_date);
  682. }
  683. (function($){
  684. $.fn.positionedItems = function(sortableOptions, options){
  685. var settings = $.extend({
  686. firstPosition: 1
  687. }, options );
  688. return this.sortable($.extend({
  689. axis: 'y',
  690. handle: ".sort-handle",
  691. helper: function(event, ui){
  692. ui.children('td').each(function(){
  693. $(this).width($(this).width());
  694. });
  695. return ui;
  696. },
  697. update: function(event, ui) {
  698. var sortable = $(this);
  699. var handle = ui.item.find(".sort-handle").addClass("ajax-loading");
  700. var url = handle.data("reorder-url");
  701. var param = handle.data("reorder-param");
  702. var data = {};
  703. data[param] = {position: ui.item.index() + settings['firstPosition']};
  704. $.ajax({
  705. url: url,
  706. type: 'put',
  707. dataType: 'script',
  708. data: data,
  709. error: function(jqXHR, textStatus, errorThrown){
  710. alert(jqXHR.status);
  711. sortable.sortable("cancel");
  712. },
  713. complete: function(jqXHR, textStatus, errorThrown){
  714. handle.removeClass("ajax-loading");
  715. }
  716. });
  717. },
  718. }, sortableOptions));
  719. }
  720. }( jQuery ));
  721. var warnLeavingUnsavedMessage;
  722. function warnLeavingUnsaved(message) {
  723. warnLeavingUnsavedMessage = message;
  724. $(document).on('submit', 'form', function(){
  725. $('textarea').removeData('changed');
  726. });
  727. $(document).on('change', 'textarea', function(){
  728. $(this).data('changed', 'changed');
  729. });
  730. window.onbeforeunload = function(){
  731. var warn = false;
  732. $('textarea').blur().each(function(){
  733. if ($(this).data('changed')) {
  734. warn = true;
  735. }
  736. });
  737. if (warn) {return warnLeavingUnsavedMessage;}
  738. };
  739. }
  740. function setupAjaxIndicator() {
  741. $(document).bind('ajaxSend', function(event, xhr, settings) {
  742. if ($('.ajax-loading').length === 0 && settings.contentType != 'application/octet-stream') {
  743. $('#ajax-indicator').show();
  744. }
  745. });
  746. $(document).bind('ajaxStop', function() {
  747. $('#ajax-indicator').hide();
  748. });
  749. }
  750. function setupTabs() {
  751. if($('.tabs').length > 0) {
  752. displayTabsButtons();
  753. $(window).resize(displayTabsButtons);
  754. }
  755. }
  756. function setupFilePreviewNavigation() {
  757. // only bind arrow keys when preview navigation is present
  758. const element = $('.pagination.filepreview').first();
  759. if (element) {
  760. const handleArrowKey = function(selector, e){
  761. const href = $(element).find(selector).attr('href');
  762. if (href) {
  763. window.location = href;
  764. e.preventDefault();
  765. }
  766. };
  767. $(document).keydown(function(e) {
  768. if(e.shiftKey || e.metaKey || e.ctrlKey || e.altKey) return;
  769. switch(e.key) {
  770. case 'ArrowLeft':
  771. handleArrowKey('.previous a', e);
  772. break;
  773. case 'ArrowRight':
  774. handleArrowKey('.next a', e);
  775. break;
  776. }
  777. });
  778. }
  779. }
  780. $(document).on('keydown', 'form textarea', function(e) {
  781. // Submit the form with Ctrl + Enter or Command + Return
  782. var targetForm = $(e.target).closest('form');
  783. if(e.keyCode == 13 && ((e.ctrlKey && !e.metaKey) || (!e.ctrlKey && e.metaKey) && targetForm.length)) {
  784. $(e.target).closest('form').find('textarea').blur().removeData('changed');
  785. targetForm.submit();
  786. }
  787. });
  788. function hideOnLoad() {
  789. $('.hol').hide();
  790. }
  791. function addFormObserversForDoubleSubmit() {
  792. $('form[method=post]').each(function() {
  793. if (!$(this).hasClass('multiple-submit')) {
  794. $(this).submit(function(form_submission) {
  795. if ($(form_submission.target).attr('data-submitted')) {
  796. form_submission.preventDefault();
  797. } else {
  798. $(form_submission.target).attr('data-submitted', true);
  799. }
  800. });
  801. }
  802. });
  803. }
  804. function defaultFocus(){
  805. if (($('#content :focus').length == 0) && (window.location.hash == '')) {
  806. $('#content input[type=text], #content textarea').first().focus();
  807. }
  808. }
  809. function blockEventPropagation(event) {
  810. event.stopPropagation();
  811. event.preventDefault();
  812. }
  813. function toggleDisabledOnChange() {
  814. var checked = $(this).is(':checked');
  815. $($(this).data('disables')).attr('disabled', checked);
  816. $($(this).data('enables')).attr('disabled', !checked);
  817. $($(this).data('shows')).toggle(checked);
  818. }
  819. function toggleDisabledInit() {
  820. $('input[data-disables], input[data-enables], input[data-shows]').each(toggleDisabledOnChange);
  821. }
  822. function toggleMultiSelectIconInit() {
  823. $('.toggle-multiselect:not(.icon-toggle-minus), .toggle-multiselect:not(.icon-toggle-plus)').each(function(){
  824. if ($(this).siblings('select').find('option:selected').length > 1){
  825. $(this).addClass('icon-toggle-minus');
  826. } else {
  827. $(this).addClass('icon-toggle-plus');
  828. }
  829. });
  830. }
  831. function toggleNewObjectDropdown() {
  832. var dropdown = $('#new-object + ul.menu-children');
  833. if(dropdown.hasClass('visible')){
  834. dropdown.removeClass('visible');
  835. }else{
  836. dropdown.addClass('visible');
  837. }
  838. }
  839. (function ( $ ) {
  840. // detect if native date input is supported
  841. var nativeDateInputSupported = true;
  842. var input = document.createElement('input');
  843. input.setAttribute('type','date');
  844. if (input.type === 'text') {
  845. nativeDateInputSupported = false;
  846. }
  847. var notADateValue = 'not-a-date';
  848. input.setAttribute('value', notADateValue);
  849. if (input.value === notADateValue) {
  850. nativeDateInputSupported = false;
  851. }
  852. $.fn.datepickerFallback = function( options ) {
  853. if (nativeDateInputSupported) {
  854. return this;
  855. } else {
  856. return this.datepicker( options );
  857. }
  858. };
  859. }( jQuery ));
  860. $(document).ready(function(){
  861. $('#content').on('change', 'input[data-disables], input[data-enables], input[data-shows]', toggleDisabledOnChange);
  862. toggleDisabledInit();
  863. $('#content').on('click', '.toggle-multiselect', function() {
  864. toggleMultiSelect($(this).siblings('select'));
  865. $(this).toggleClass('icon-toggle-plus icon-toggle-minus');
  866. });
  867. toggleMultiSelectIconInit();
  868. $('#history .tabs').on('click', 'a', function(e){
  869. var tab = $(e.target).attr('id').replace('tab-','');
  870. document.cookie = 'history_last_tab=' + tab
  871. });
  872. });
  873. $(document).ready(function(){
  874. $('#content').on('click', 'div.jstTabs a.tab-preview', function(event){
  875. var tab = $(event.target);
  876. var url = tab.data('url');
  877. var form = tab.parents('form');
  878. var jstBlock = tab.parents('.jstBlock');
  879. var element = encodeURIComponent(jstBlock.find('.wiki-edit').val());
  880. var attachments = form.find('.attachments_fields input').serialize();
  881. $.ajax({
  882. url: url,
  883. type: 'post',
  884. data: "text=" + element + '&' + attachments,
  885. success: function(data){
  886. jstBlock.find('.wiki-preview').html(data);
  887. setupWikiTableSortableHeader();
  888. }
  889. });
  890. });
  891. });
  892. function keepAnchorOnSignIn(form){
  893. var hash = decodeURIComponent(self.document.location.hash);
  894. if (hash) {
  895. if (hash.indexOf("#") === -1) {
  896. hash = "#" + hash;
  897. }
  898. form.action = form.action + hash;
  899. }
  900. return true;
  901. }
  902. $(function ($) {
  903. $('#auth_source_ldap_mode').change(function () {
  904. $('.ldaps_warning').toggle($(this).val() != 'ldaps_verify_peer');
  905. }).change();
  906. });
  907. function setFilecontentContainerHeight() {
  908. var $filecontainer = $('.filecontent-container');
  909. var fileTypeSelectors = ['.image', 'video'];
  910. if($filecontainer.length > 0 && $filecontainer.find(fileTypeSelectors.join(',')).length === 1) {
  911. var containerOffsetTop = $filecontainer.offset().top;
  912. var containerMarginBottom = parseInt($filecontainer.css('marginBottom'));
  913. var paginationHeight = $filecontainer.next('.pagination').height();
  914. var diff = containerOffsetTop + containerMarginBottom + paginationHeight;
  915. $filecontainer.css('height', 'calc(100vh - ' + diff + 'px)')
  916. }
  917. }
  918. function setupAttachmentDetail() {
  919. setFilecontentContainerHeight();
  920. $(window).resize(setFilecontentContainerHeight);
  921. }
  922. function setupWikiTableSortableHeader() {
  923. $('div.wiki table').each(function(i, table){
  924. if (table.rows.length < 3) return true;
  925. var tr = $(table.rows).first();
  926. if (tr.find("TH").length > 0) {
  927. tr.attr('data-sort-method', 'none');
  928. tr.find("TD").attr('data-sort-method', 'none');
  929. new Tablesort(table);
  930. }
  931. });
  932. }
  933. $(function () {
  934. $('[title]').tooltip({
  935. show: {
  936. delay: 400
  937. },
  938. position: {
  939. my: "center bottom-5",
  940. at: "center top"
  941. }
  942. });
  943. });
  944. function inlineAutoComplete(element) {
  945. 'use strict';
  946. // do not attach if Tribute is already initialized
  947. if (element.dataset.tribute === 'true') {return};
  948. const getDataSource = function(entity) {
  949. const dataSources = JSON.parse(rm.AutoComplete.dataSources);
  950. return dataSources[entity];
  951. }
  952. const remoteSearch = function(url, cb) {
  953. const xhr = new XMLHttpRequest();
  954. xhr.onreadystatechange = function ()
  955. {
  956. if (xhr.readyState === 4) {
  957. if (xhr.status === 200) {
  958. var data = JSON.parse(xhr.responseText);
  959. cb(data);
  960. } else if (xhr.status === 403) {
  961. cb([]);
  962. }
  963. }
  964. };
  965. xhr.open("GET", url, true);
  966. xhr.send();
  967. };
  968. const tribute = new Tribute({
  969. trigger: '#',
  970. values: function (text, cb) {
  971. if (event.target.type === 'text' && $(element).attr('autocomplete') != 'off') {
  972. $(element).attr('autocomplete', 'off');
  973. }
  974. remoteSearch(getDataSource('issues') + text, function (issues) {
  975. return cb(issues);
  976. });
  977. },
  978. lookup: 'label',
  979. fillAttr: 'label',
  980. requireLeadingSpace: true,
  981. selectTemplate: function (issue) {
  982. return '#' + issue.original.id;
  983. },
  984. noMatchTemplate: function () {
  985. return '<span style:"visibility: hidden;"></span>';
  986. }
  987. });
  988. tribute.attach(element);
  989. }
  990. $(document).ready(setupAjaxIndicator);
  991. $(document).ready(hideOnLoad);
  992. $(document).ready(addFormObserversForDoubleSubmit);
  993. $(document).ready(defaultFocus);
  994. $(document).ready(setupAttachmentDetail);
  995. $(document).ready(setupTabs);
  996. $(document).ready(setupFilePreviewNavigation);
  997. $(document).ready(setupWikiTableSortableHeader);
  998. $(document).on('focus', '[data-auto-complete=true]', function(event) {
  999. inlineAutoComplete(event.target);
  1000. });