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 36KB

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