Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

repo-issue.js 23KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. import $ from 'jquery';
  2. import {htmlEscape} from 'escape-goat';
  3. import {showTemporaryTooltip, createTippy} from '../modules/tippy.js';
  4. import {hideElem, showElem, toggleElem} from '../utils/dom.js';
  5. import {setFileFolding} from './file-fold.js';
  6. import {getComboMarkdownEditor, initComboMarkdownEditor} from './comp/ComboMarkdownEditor.js';
  7. const {appSubUrl, csrfToken} = window.config;
  8. export function initRepoIssueTimeTracking() {
  9. $(document).on('click', '.issue-add-time', () => {
  10. $('.issue-start-time-modal').modal({
  11. duration: 200,
  12. onApprove() {
  13. $('#add_time_manual_form').trigger('submit');
  14. },
  15. }).modal('show');
  16. $('.issue-start-time-modal input').on('keydown', (e) => {
  17. if ((e.keyCode || e.key) === 13) {
  18. $('#add_time_manual_form').trigger('submit');
  19. }
  20. });
  21. });
  22. $(document).on('click', '.issue-start-time, .issue-stop-time', () => {
  23. $('#toggle_stopwatch_form').trigger('submit');
  24. });
  25. $(document).on('click', '.issue-cancel-time', () => {
  26. $('#cancel_stopwatch_form').trigger('submit');
  27. });
  28. $(document).on('click', 'button.issue-delete-time', function () {
  29. const sel = `.issue-delete-time-modal[data-id="${$(this).data('id')}"]`;
  30. $(sel).modal({
  31. duration: 200,
  32. onApprove() {
  33. $(`${sel} form`).trigger('submit');
  34. },
  35. }).modal('show');
  36. });
  37. }
  38. function updateDeadline(deadlineString) {
  39. hideElem($('#deadline-err-invalid-date'));
  40. $('#deadline-loader').addClass('loading');
  41. let realDeadline = null;
  42. if (deadlineString !== '') {
  43. const newDate = Date.parse(deadlineString);
  44. if (Number.isNaN(newDate)) {
  45. $('#deadline-loader').removeClass('loading');
  46. showElem($('#deadline-err-invalid-date'));
  47. return false;
  48. }
  49. realDeadline = new Date(newDate);
  50. }
  51. $.ajax(`${$('#update-issue-deadline-form').attr('action')}`, {
  52. data: JSON.stringify({
  53. due_date: realDeadline,
  54. }),
  55. headers: {
  56. 'X-Csrf-Token': csrfToken,
  57. },
  58. contentType: 'application/json',
  59. type: 'POST',
  60. success() {
  61. window.location.reload();
  62. },
  63. error() {
  64. $('#deadline-loader').removeClass('loading');
  65. showElem($('#deadline-err-invalid-date'));
  66. },
  67. });
  68. }
  69. export function initRepoIssueDue() {
  70. $(document).on('click', '.issue-due-edit', () => {
  71. toggleElem('#deadlineForm');
  72. });
  73. $(document).on('click', '.issue-due-remove', () => {
  74. updateDeadline('');
  75. });
  76. $(document).on('submit', '.issue-due-form', () => {
  77. updateDeadline($('#deadlineDate').val());
  78. return false;
  79. });
  80. }
  81. export function initRepoIssueSidebarList() {
  82. const repolink = $('#repolink').val();
  83. const repoId = $('#repoId').val();
  84. const crossRepoSearch = $('#crossRepoSearch').val();
  85. const tp = $('#type').val();
  86. let issueSearchUrl = `${appSubUrl}/${repolink}/issues/search?q={query}&type=${tp}`;
  87. if (crossRepoSearch === 'true') {
  88. issueSearchUrl = `${appSubUrl}/issues/search?q={query}&priority_repo_id=${repoId}&type=${tp}`;
  89. }
  90. $('#new-dependency-drop-list')
  91. .dropdown({
  92. apiSettings: {
  93. url: issueSearchUrl,
  94. onResponse(response) {
  95. const filteredResponse = {success: true, results: []};
  96. const currIssueId = $('#new-dependency-drop-list').data('issue-id');
  97. // Parse the response from the api to work with our dropdown
  98. $.each(response, (_i, issue) => {
  99. // Don't list current issue in the dependency list.
  100. if (issue.id === currIssueId) {
  101. return;
  102. }
  103. filteredResponse.results.push({
  104. name: `#${issue.number} ${htmlEscape(issue.title)
  105. }<div class="text small gt-word-break">${htmlEscape(issue.repository.full_name)}</div>`,
  106. value: issue.id,
  107. });
  108. });
  109. return filteredResponse;
  110. },
  111. cache: false,
  112. },
  113. fullTextSearch: true,
  114. });
  115. function excludeLabel(item) {
  116. const href = $(item).attr('href');
  117. const id = $(item).data('label-id');
  118. const regStr = `labels=((?:-?[0-9]+%2c)*)(${id})((?:%2c-?[0-9]+)*)&`;
  119. const newStr = 'labels=$1-$2$3&';
  120. window.location = href.replace(new RegExp(regStr), newStr);
  121. }
  122. $('.menu a.label-filter-item').each(function () {
  123. $(this).on('click', function (e) {
  124. if (e.altKey) {
  125. e.preventDefault();
  126. excludeLabel(this);
  127. }
  128. });
  129. });
  130. $('.menu .ui.dropdown.label-filter').on('keydown', (e) => {
  131. if (e.altKey && e.keyCode === 13) {
  132. const selectedItems = $('.menu .ui.dropdown.label-filter .menu .item.selected');
  133. if (selectedItems.length > 0) {
  134. excludeLabel($(selectedItems[0]));
  135. }
  136. }
  137. });
  138. $('.ui.dropdown.label-filter, .ui.dropdown.select-label').dropdown('setting', {'hideDividers': 'empty'}).dropdown('refreshItems');
  139. }
  140. export function initRepoIssueCommentDelete() {
  141. // Delete comment
  142. $(document).on('click', '.delete-comment', function () {
  143. const $this = $(this);
  144. if (window.confirm($this.data('locale'))) {
  145. $.post($this.data('url'), {
  146. _csrf: csrfToken,
  147. }).done(() => {
  148. const $conversationHolder = $this.closest('.conversation-holder');
  149. // Check if this was a pending comment.
  150. if ($conversationHolder.find('.pending-label').length) {
  151. const $counter = $('#review-box .review-comments-counter');
  152. let num = parseInt($counter.attr('data-pending-comment-number')) - 1 || 0;
  153. num = Math.max(num, 0);
  154. $counter.attr('data-pending-comment-number', num);
  155. $counter.text(num);
  156. }
  157. $(`#${$this.data('comment-id')}`).remove();
  158. if ($conversationHolder.length && !$conversationHolder.find('.comment').length) {
  159. const path = $conversationHolder.data('path');
  160. const side = $conversationHolder.data('side');
  161. const idx = $conversationHolder.data('idx');
  162. const lineType = $conversationHolder.closest('tr').data('line-type');
  163. if (lineType === 'same') {
  164. $(`[data-path="${path}"] .add-code-comment[data-idx="${idx}"]`).removeClass('gt-invisible');
  165. } else {
  166. $(`[data-path="${path}"] .add-code-comment[data-side="${side}"][data-idx="${idx}"]`).removeClass('gt-invisible');
  167. }
  168. $conversationHolder.remove();
  169. }
  170. });
  171. }
  172. return false;
  173. });
  174. }
  175. export function initRepoIssueDependencyDelete() {
  176. // Delete Issue dependency
  177. $(document).on('click', '.delete-dependency-button', (e) => {
  178. const id = e.currentTarget.getAttribute('data-id');
  179. const type = e.currentTarget.getAttribute('data-type');
  180. $('.remove-dependency').modal({
  181. closable: false,
  182. duration: 200,
  183. onApprove: () => {
  184. $('#removeDependencyID').val(id);
  185. $('#dependencyType').val(type);
  186. $('#removeDependencyForm').trigger('submit');
  187. },
  188. }).modal('show');
  189. });
  190. }
  191. export function initRepoIssueCodeCommentCancel() {
  192. // Cancel inline code comment
  193. $(document).on('click', '.cancel-code-comment', (e) => {
  194. const form = $(e.currentTarget).closest('form');
  195. if (form.length > 0 && form.hasClass('comment-form')) {
  196. form.addClass('gt-hidden');
  197. showElem(form.closest('.comment-code-cloud').find('button.comment-form-reply'));
  198. } else {
  199. form.closest('.comment-code-cloud').remove();
  200. }
  201. });
  202. }
  203. export function initRepoPullRequestUpdate() {
  204. // Pull Request update button
  205. const $pullUpdateButton = $('.update-button > button');
  206. $pullUpdateButton.on('click', function (e) {
  207. e.preventDefault();
  208. const $this = $(this);
  209. const redirect = $this.data('redirect');
  210. $this.addClass('loading');
  211. $.post($this.data('do'), {
  212. _csrf: csrfToken
  213. }).done((data) => {
  214. if (data.redirect) {
  215. window.location.href = data.redirect;
  216. } else if (redirect) {
  217. window.location.href = redirect;
  218. } else {
  219. window.location.reload();
  220. }
  221. });
  222. });
  223. $('.update-button > .dropdown').dropdown({
  224. onChange(_text, _value, $choice) {
  225. const $url = $choice.data('do');
  226. if ($url) {
  227. $pullUpdateButton.find('.button-text').text($choice.text());
  228. $pullUpdateButton.data('do', $url);
  229. }
  230. }
  231. });
  232. }
  233. export function initRepoPullRequestMergeInstruction() {
  234. $('.show-instruction').on('click', () => {
  235. toggleElem($('.instruct-content'));
  236. });
  237. }
  238. export function initRepoPullRequestAllowMaintainerEdit() {
  239. const $checkbox = $('#allow-edits-from-maintainers');
  240. if (!$checkbox.length) return;
  241. const promptError = $checkbox.attr('data-prompt-error');
  242. $checkbox.checkbox({
  243. 'onChange': () => {
  244. const checked = $checkbox.checkbox('is checked');
  245. let url = $checkbox.attr('data-url');
  246. url += '/set_allow_maintainer_edit';
  247. $checkbox.checkbox('set disabled');
  248. $.ajax({url, type: 'POST',
  249. data: {_csrf: csrfToken, allow_maintainer_edit: checked},
  250. error: () => {
  251. showTemporaryTooltip($checkbox[0], promptError);
  252. },
  253. complete: () => {
  254. $checkbox.checkbox('set enabled');
  255. },
  256. });
  257. },
  258. });
  259. }
  260. export function initRepoIssueReferenceRepositorySearch() {
  261. $('.issue_reference_repository_search')
  262. .dropdown({
  263. apiSettings: {
  264. url: `${appSubUrl}/repo/search?q={query}&limit=20`,
  265. onResponse(response) {
  266. const filteredResponse = {success: true, results: []};
  267. $.each(response.data, (_r, repo) => {
  268. filteredResponse.results.push({
  269. name: htmlEscape(repo.repository.full_name),
  270. value: repo.repository.full_name
  271. });
  272. });
  273. return filteredResponse;
  274. },
  275. cache: false,
  276. },
  277. onChange(_value, _text, $choice) {
  278. const $form = $choice.closest('form');
  279. $form.attr('action', `${appSubUrl}/${_text}/issues/new`);
  280. },
  281. fullTextSearch: true
  282. });
  283. }
  284. export function initRepoIssueWipTitle() {
  285. $('.title_wip_desc > a').on('click', (e) => {
  286. e.preventDefault();
  287. const $issueTitle = $('#issue_title');
  288. $issueTitle.trigger('focus');
  289. const value = $issueTitle.val().trim().toUpperCase();
  290. const wipPrefixes = $('.title_wip_desc').data('wip-prefixes');
  291. for (const prefix of wipPrefixes) {
  292. if (value.startsWith(prefix.toUpperCase())) {
  293. return;
  294. }
  295. }
  296. $issueTitle.val(`${wipPrefixes[0]} ${$issueTitle.val()}`);
  297. });
  298. }
  299. export async function updateIssuesMeta(url, action, issueIds, elementId) {
  300. return $.ajax({
  301. type: 'POST',
  302. url,
  303. data: {
  304. _csrf: csrfToken,
  305. action,
  306. issue_ids: issueIds,
  307. id: elementId,
  308. },
  309. });
  310. }
  311. export function initRepoIssueComments() {
  312. if ($('.repository.view.issue .timeline').length === 0) return;
  313. $('.re-request-review').on('click', function (e) {
  314. e.preventDefault();
  315. const url = $(this).data('update-url');
  316. const issueId = $(this).data('issue-id');
  317. const id = $(this).data('id');
  318. const isChecked = $(this).hasClass('checked');
  319. updateIssuesMeta(
  320. url,
  321. isChecked ? 'detach' : 'attach',
  322. issueId,
  323. id,
  324. ).then(() => window.location.reload());
  325. });
  326. $(document).on('click', (event) => {
  327. const urlTarget = $(':target');
  328. if (urlTarget.length === 0) return;
  329. const urlTargetId = urlTarget.attr('id');
  330. if (!urlTargetId) return;
  331. if (!/^(issue|pull)(comment)?-\d+$/.test(urlTargetId)) return;
  332. const $target = $(event.target);
  333. if ($target.closest(`#${urlTargetId}`).length === 0) {
  334. const scrollPosition = $(window).scrollTop();
  335. window.location.hash = '';
  336. $(window).scrollTop(scrollPosition);
  337. window.history.pushState(null, null, ' ');
  338. }
  339. });
  340. }
  341. export async function handleReply($el) {
  342. hideElem($el);
  343. const form = $el.closest('.comment-code-cloud').find('.comment-form');
  344. form.removeClass('gt-hidden');
  345. const $textarea = form.find('textarea');
  346. let editor = getComboMarkdownEditor($textarea);
  347. if (!editor) {
  348. editor = await initComboMarkdownEditor(form.find('.combo-markdown-editor'));
  349. }
  350. editor.focus();
  351. return editor;
  352. }
  353. export function initRepoPullRequestReview() {
  354. if (window.location.hash && window.location.hash.startsWith('#issuecomment-')) {
  355. // set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
  356. if (window.history.scrollRestoration !== 'manual') {
  357. window.history.scrollRestoration = 'manual';
  358. }
  359. const commentDiv = $(window.location.hash);
  360. if (commentDiv) {
  361. // get the name of the parent id
  362. const groupID = commentDiv.closest('div[id^="code-comments-"]').attr('id');
  363. if (groupID && groupID.startsWith('code-comments-')) {
  364. const id = groupID.slice(14);
  365. const ancestorDiffBox = commentDiv.closest('.diff-file-box');
  366. // on pages like conversation, there is no diff header
  367. const diffHeader = ancestorDiffBox.find('.diff-file-header');
  368. // offset is for scrolling
  369. let offset = 30;
  370. if (diffHeader[0]) {
  371. offset += $('.diff-detail-box').outerHeight() + diffHeader.outerHeight();
  372. }
  373. $(`#show-outdated-${id}`).addClass('gt-hidden');
  374. $(`#code-comments-${id}`).removeClass('gt-hidden');
  375. $(`#code-preview-${id}`).removeClass('gt-hidden');
  376. $(`#hide-outdated-${id}`).removeClass('gt-hidden');
  377. // if the comment box is folded, expand it
  378. if (ancestorDiffBox.attr('data-folded') && ancestorDiffBox.attr('data-folded') === 'true') {
  379. setFileFolding(ancestorDiffBox[0], ancestorDiffBox.find('.fold-file')[0], false);
  380. }
  381. window.scrollTo({
  382. top: commentDiv.offset().top - offset,
  383. behavior: 'instant'
  384. });
  385. }
  386. }
  387. }
  388. $(document).on('click', '.show-outdated', function (e) {
  389. e.preventDefault();
  390. const id = $(this).data('comment');
  391. $(this).addClass('gt-hidden');
  392. $(`#code-comments-${id}`).removeClass('gt-hidden');
  393. $(`#code-preview-${id}`).removeClass('gt-hidden');
  394. $(`#hide-outdated-${id}`).removeClass('gt-hidden');
  395. });
  396. $(document).on('click', '.hide-outdated', function (e) {
  397. e.preventDefault();
  398. const id = $(this).data('comment');
  399. $(this).addClass('gt-hidden');
  400. $(`#code-comments-${id}`).addClass('gt-hidden');
  401. $(`#code-preview-${id}`).addClass('gt-hidden');
  402. $(`#show-outdated-${id}`).removeClass('gt-hidden');
  403. });
  404. $(document).on('click', 'button.comment-form-reply', async function (e) {
  405. e.preventDefault();
  406. await handleReply($(this));
  407. });
  408. const $reviewBox = $('.review-box-panel');
  409. if ($reviewBox.length === 1) {
  410. const _promise = initComboMarkdownEditor($reviewBox.find('.combo-markdown-editor'));
  411. }
  412. // The following part is only for diff views
  413. if ($('.repository.pull.diff').length === 0) {
  414. return;
  415. }
  416. const $reviewBtn = $('.js-btn-review');
  417. const $panel = $reviewBtn.parent().find('.review-box-panel');
  418. const $closeBtn = $panel.find('.close');
  419. if ($reviewBtn.length && $panel.length) {
  420. const tippy = createTippy($reviewBtn[0], {
  421. content: $panel[0],
  422. placement: 'bottom',
  423. trigger: 'click',
  424. maxWidth: 'none',
  425. interactive: true,
  426. hideOnClick: true,
  427. });
  428. $closeBtn.on('click', (e) => {
  429. e.preventDefault();
  430. tippy.hide();
  431. });
  432. }
  433. $(document).on('click', '.add-code-comment', async function (e) {
  434. if ($(e.target).hasClass('btn-add-single')) return; // https://github.com/go-gitea/gitea/issues/4745
  435. e.preventDefault();
  436. const isSplit = $(this).closest('.code-diff').hasClass('code-diff-split');
  437. const side = $(this).data('side');
  438. const idx = $(this).data('idx');
  439. const path = $(this).closest('[data-path]').data('path');
  440. const tr = $(this).closest('tr');
  441. const lineType = tr.data('line-type');
  442. let ntr = tr.next();
  443. if (!ntr.hasClass('add-comment')) {
  444. ntr = $(`
  445. <tr class="add-comment" data-line-type="${lineType}">
  446. ${isSplit ? `
  447. <td class="add-comment-left" colspan="4"></td>
  448. <td class="add-comment-right" colspan="4"></td>
  449. ` : `
  450. <td class="add-comment-left add-comment-right" colspan="5"></td>
  451. `}
  452. </tr>`);
  453. tr.after(ntr);
  454. }
  455. const td = ntr.find(`.add-comment-${side}`);
  456. const commentCloud = td.find('.comment-code-cloud');
  457. if (commentCloud.length === 0 && !ntr.find('button[name="pending_review"]').length) {
  458. const html = await $.get($(this).closest('[data-new-comment-url]').attr('data-new-comment-url'));
  459. td.html(html);
  460. td.find("input[name='line']").val(idx);
  461. td.find("input[name='side']").val(side === 'left' ? 'previous' : 'proposed');
  462. td.find("input[name='path']").val(path);
  463. const editor = await initComboMarkdownEditor(td.find('.combo-markdown-editor'));
  464. editor.focus();
  465. }
  466. });
  467. }
  468. export function initRepoIssueReferenceIssue() {
  469. // Reference issue
  470. $(document).on('click', '.reference-issue', function (event) {
  471. const $this = $(this);
  472. const content = $(`#${$this.data('target')}`).text();
  473. const poster = $this.data('poster-username');
  474. const reference = $this.data('reference');
  475. const $modal = $($this.data('modal'));
  476. $modal.find('textarea[name="content"]').val(`${content}\n\n_Originally posted by @${poster} in ${reference}_`);
  477. $modal.modal('show');
  478. event.preventDefault();
  479. });
  480. }
  481. export function initRepoIssueWipToggle() {
  482. // Toggle WIP
  483. $('.toggle-wip a, .toggle-wip button').on('click', async (e) => {
  484. e.preventDefault();
  485. const toggleWip = e.currentTarget.closest('.toggle-wip');
  486. const title = toggleWip.getAttribute('data-title');
  487. const wipPrefix = toggleWip.getAttribute('data-wip-prefix');
  488. const updateUrl = toggleWip.getAttribute('data-update-url');
  489. await $.post(updateUrl, {
  490. _csrf: csrfToken,
  491. title: title?.startsWith(wipPrefix) ? title.slice(wipPrefix.length).trim() : `${wipPrefix.trim()} ${title}`,
  492. });
  493. window.location.reload();
  494. });
  495. }
  496. export function initRepoIssueTitleEdit() {
  497. // Edit issue title
  498. const $issueTitle = $('#issue-title');
  499. const $editInput = $('#edit-title-input input');
  500. const editTitleToggle = function () {
  501. toggleElem($issueTitle);
  502. toggleElem($('.not-in-edit'));
  503. toggleElem($('#edit-title-input'));
  504. toggleElem($('#pull-desc'));
  505. toggleElem($('#pull-desc-edit'));
  506. toggleElem($('.in-edit'));
  507. toggleElem($('.new-issue-button'));
  508. $('#issue-title-wrapper').toggleClass('edit-active');
  509. $editInput[0].focus();
  510. $editInput[0].select();
  511. return false;
  512. };
  513. $('#edit-title').on('click', editTitleToggle);
  514. $('#cancel-edit-title').on('click', editTitleToggle);
  515. $('#save-edit-title').on('click', editTitleToggle).on('click', function () {
  516. const pullrequest_targetbranch_change = function (update_url) {
  517. const targetBranch = $('#pull-target-branch').data('branch');
  518. const $branchTarget = $('#branch_target');
  519. if (targetBranch === $branchTarget.text()) {
  520. window.location.reload();
  521. return false;
  522. }
  523. $.post(update_url, {
  524. _csrf: csrfToken,
  525. target_branch: targetBranch
  526. }).always(() => {
  527. window.location.reload();
  528. });
  529. };
  530. const pullrequest_target_update_url = $(this).attr('data-target-update-url');
  531. if ($editInput.val().length === 0 || $editInput.val() === $issueTitle.text()) {
  532. $editInput.val($issueTitle.text());
  533. pullrequest_targetbranch_change(pullrequest_target_update_url);
  534. } else {
  535. $.post($(this).attr('data-update-url'), {
  536. _csrf: csrfToken,
  537. title: $editInput.val()
  538. }, (data) => {
  539. $editInput.val(data.title);
  540. $issueTitle.text(data.title);
  541. if (pullrequest_target_update_url) {
  542. pullrequest_targetbranch_change(pullrequest_target_update_url); // it will reload the window
  543. } else {
  544. window.location.reload();
  545. }
  546. });
  547. }
  548. return false;
  549. });
  550. }
  551. export function initRepoIssueBranchSelect() {
  552. const changeBranchSelect = function () {
  553. const selectionTextField = $('#pull-target-branch');
  554. const baseName = selectionTextField.data('basename');
  555. const branchNameNew = $(this).data('branch');
  556. const branchNameOld = selectionTextField.data('branch');
  557. // Replace branch name to keep translation from HTML template
  558. selectionTextField.html(selectionTextField.html().replace(
  559. `${baseName}:${branchNameOld}`,
  560. `${baseName}:${branchNameNew}`
  561. ));
  562. selectionTextField.data('branch', branchNameNew); // update branch name in setting
  563. };
  564. $('#branch-select > .item').on('click', changeBranchSelect);
  565. }
  566. export function initSingleCommentEditor($commentForm) {
  567. // pages:
  568. // * normal new issue/pr page, no status-button
  569. // * issue/pr view page, with comment form, has status-button
  570. const opts = {};
  571. const $statusButton = $('#status-button');
  572. if ($statusButton.length) {
  573. opts.onContentChanged = (editor) => {
  574. $statusButton.text($statusButton.attr(editor.value().trim() ? 'data-status-and-comment' : 'data-status'));
  575. };
  576. }
  577. initComboMarkdownEditor($commentForm.find('.combo-markdown-editor'), opts);
  578. }
  579. export function initIssueTemplateCommentEditors($commentForm) {
  580. // pages:
  581. // * new issue with issue template
  582. const $comboFields = $commentForm.find('.combo-editor-dropzone');
  583. const initCombo = async ($combo) => {
  584. const $dropzoneContainer = $combo.find('.form-field-dropzone');
  585. const $formField = $combo.find('.form-field-real');
  586. const $markdownEditor = $combo.find('.combo-markdown-editor');
  587. const editor = await initComboMarkdownEditor($markdownEditor, {
  588. onContentChanged: (editor) => {
  589. $formField.val(editor.value());
  590. }
  591. });
  592. $formField.on('focus', async () => {
  593. // deactivate all markdown editors
  594. showElem($commentForm.find('.combo-editor-dropzone .form-field-real'));
  595. hideElem($commentForm.find('.combo-editor-dropzone .combo-markdown-editor'));
  596. hideElem($commentForm.find('.combo-editor-dropzone .form-field-dropzone'));
  597. // activate this markdown editor
  598. hideElem($formField);
  599. showElem($markdownEditor);
  600. showElem($dropzoneContainer);
  601. await editor.switchToUserPreference();
  602. editor.focus();
  603. });
  604. };
  605. for (const el of $comboFields) {
  606. initCombo($(el));
  607. }
  608. }
  609. // This function used to show and hide archived label on issue/pr
  610. // page in the sidebar where we select the labels
  611. // If we have any archived label tagged to issue and pr. We will show that
  612. // archived label with checked classed otherwise we will hide it
  613. // with the help of this function.
  614. // This function runs globally.
  615. export function initArchivedLabelHandler() {
  616. if (!document.querySelector('.archived-label-hint')) return;
  617. for (const label of document.querySelectorAll('[data-is-archived]')) {
  618. toggleElem(label, label.classList.contains('checked'));
  619. }
  620. }