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.

repo-issue.js 23KB

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