gitea源码

repo-issue-content.ts 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import {svg} from '../svg.ts';
  2. import {showErrorToast} from '../modules/toast.ts';
  3. import {GET, POST} from '../modules/fetch.ts';
  4. import {createElementFromHTML, showElem} from '../utils/dom.ts';
  5. import {parseIssuePageInfo} from '../utils.ts';
  6. import {fomanticQuery} from '../modules/fomantic/base.ts';
  7. let i18nTextEdited: string;
  8. let i18nTextOptions: string;
  9. let i18nTextDeleteFromHistory: string;
  10. let i18nTextDeleteFromHistoryConfirm: string;
  11. function showContentHistoryDetail(issueBaseUrl: string, commentId: string, historyId: string, itemTitleHtml: string) {
  12. const elDetailDialog = createElementFromHTML(`
  13. <div class="ui modal content-history-detail-dialog">
  14. ${svg('octicon-x', 16, 'close icon inside')}
  15. <div class="header tw-flex tw-items-center tw-justify-between">
  16. <div>${itemTitleHtml}</div>
  17. <div class="ui dropdown dialog-header-options tw-mr-8 tw-hidden">
  18. ${i18nTextOptions}
  19. ${svg('octicon-triangle-down', 14, 'dropdown icon')}
  20. <div class="menu">
  21. <div class="item red text" data-option-item="delete">${i18nTextDeleteFromHistory}</div>
  22. </div>
  23. </div>
  24. </div>
  25. <div class="comment-diff-data is-loading"></div>
  26. </div>`);
  27. document.body.append(elDetailDialog);
  28. const elOptionsDropdown = elDetailDialog.querySelector('.ui.dropdown.dialog-header-options');
  29. const $fomanticDialog = fomanticQuery(elDetailDialog);
  30. const $fomanticDropdownOptions = fomanticQuery(elOptionsDropdown);
  31. $fomanticDropdownOptions.dropdown({
  32. showOnFocus: false,
  33. allowReselection: true,
  34. async onChange(_value: string, _text: string, $item: any) {
  35. const optionItem = $item.data('option-item');
  36. if (optionItem === 'delete') {
  37. if (window.confirm(i18nTextDeleteFromHistoryConfirm)) {
  38. try {
  39. const params = new URLSearchParams();
  40. params.append('comment_id', commentId);
  41. params.append('history_id', historyId);
  42. const response = await POST(`${issueBaseUrl}/content-history/soft-delete?${params.toString()}`);
  43. const resp = await response.json();
  44. if (resp.ok) {
  45. $fomanticDialog.modal('hide');
  46. } else {
  47. showErrorToast(resp.message);
  48. }
  49. } catch (error) {
  50. console.error('Error:', error);
  51. showErrorToast('An error occurred while deleting the history.');
  52. }
  53. }
  54. } else { // required by eslint
  55. showErrorToast(`unknown option item: ${optionItem}`);
  56. }
  57. },
  58. onHide() {
  59. $fomanticDropdownOptions.dropdown('clear', true);
  60. },
  61. });
  62. $fomanticDialog.modal({
  63. async onShow() {
  64. try {
  65. const params = new URLSearchParams();
  66. params.append('comment_id', commentId);
  67. params.append('history_id', historyId);
  68. const url = `${issueBaseUrl}/content-history/detail?${params.toString()}`;
  69. const response = await GET(url);
  70. const resp = await response.json();
  71. const commentDiffData = elDetailDialog.querySelector('.comment-diff-data');
  72. commentDiffData.classList.remove('is-loading');
  73. commentDiffData.innerHTML = resp.diffHtml;
  74. // there is only one option "item[data-option-item=delete]", so the dropdown can be entirely shown/hidden.
  75. if (resp.canSoftDelete) {
  76. showElem(elOptionsDropdown);
  77. }
  78. } catch (error) {
  79. console.error('Error:', error);
  80. }
  81. },
  82. onHidden() {
  83. $fomanticDialog.remove();
  84. },
  85. }).modal('show');
  86. }
  87. function showContentHistoryMenu(issueBaseUrl: string, elCommentItem: Element, commentId: string) {
  88. const elHeaderLeft = elCommentItem.querySelector('.comment-header-left');
  89. const menuHtml = `
  90. <div class="ui dropdown interact-fg content-history-menu" data-comment-id="${commentId}">
  91. &bull; ${i18nTextEdited}${svg('octicon-triangle-down', 14, 'dropdown icon')}
  92. <div class="menu">
  93. </div>
  94. </div>`;
  95. elHeaderLeft.querySelector(`.ui.dropdown.content-history-menu`)?.remove(); // remove the old one if exists
  96. elHeaderLeft.append(createElementFromHTML(menuHtml));
  97. const elDropdown = elHeaderLeft.querySelector('.ui.dropdown.content-history-menu');
  98. const $fomanticDropdown = fomanticQuery(elDropdown);
  99. $fomanticDropdown.dropdown({
  100. action: 'hide',
  101. apiSettings: {
  102. cache: false,
  103. url: `${issueBaseUrl}/content-history/list?comment_id=${commentId}`,
  104. },
  105. saveRemoteData: false,
  106. onHide() {
  107. $fomanticDropdown.dropdown('change values', null);
  108. },
  109. onChange(value: string, itemHtml: string, $item: any) {
  110. if (value && !$item.find('[data-history-is-deleted=1]').length) {
  111. showContentHistoryDetail(issueBaseUrl, commentId, value, itemHtml);
  112. }
  113. },
  114. });
  115. }
  116. export async function initRepoIssueContentHistory() {
  117. const issuePageInfo = parseIssuePageInfo();
  118. if (!issuePageInfo.issueNumber) return;
  119. const elIssueDescription = document.querySelector('.repository.issue .timeline-item.comment.first'); // issue(PR) main content
  120. const elComments = document.querySelectorAll('.repository.issue .comment-list .comment'); // includes: issue(PR) comments, review comments, code comments
  121. if (!elIssueDescription && !elComments.length) return;
  122. const issueBaseUrl = `${issuePageInfo.repoLink}/issues/${issuePageInfo.issueNumber}`;
  123. try {
  124. const response = await GET(`${issueBaseUrl}/content-history/overview`);
  125. const resp = await response.json();
  126. i18nTextEdited = resp.i18n.textEdited;
  127. i18nTextDeleteFromHistory = resp.i18n.textDeleteFromHistory;
  128. i18nTextDeleteFromHistoryConfirm = resp.i18n.textDeleteFromHistoryConfirm;
  129. i18nTextOptions = resp.i18n.textOptions;
  130. if (resp.editedHistoryCountMap[0] && elIssueDescription) {
  131. showContentHistoryMenu(issueBaseUrl, elIssueDescription, '0');
  132. }
  133. for (const [commentId, _editedCount] of Object.entries(resp.editedHistoryCountMap)) {
  134. if (commentId === '0') continue;
  135. const elIssueComment = document.querySelector(`#issuecomment-${commentId}`);
  136. if (elIssueComment) showContentHistoryMenu(issueBaseUrl, elIssueComment, commentId);
  137. }
  138. } catch (error) {
  139. console.error('Error:', error);
  140. }
  141. }