gitea源码

repo-code.ts 5.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import {svg} from '../svg.ts';
  2. import {createTippy} from '../modules/tippy.ts';
  3. import {toAbsoluteUrl} from '../utils.ts';
  4. import {addDelegatedEventListener} from '../utils/dom.ts';
  5. function changeHash(hash: string) {
  6. if (window.history.pushState) {
  7. window.history.pushState(null, null, hash);
  8. } else {
  9. window.location.hash = hash;
  10. }
  11. }
  12. // it selects the code lines defined by range: `L1-L3` (3 lines) or `L2` (singe line)
  13. function selectRange(range: string): Element {
  14. for (const el of document.querySelectorAll('.code-view tr.active')) el.classList.remove('active');
  15. const elLineNums = document.querySelectorAll(`.code-view td.lines-num span[data-line-number]`);
  16. const refInNewIssue = document.querySelector('a.ref-in-new-issue');
  17. const copyPermalink = document.querySelector('a.copy-line-permalink');
  18. const viewGitBlame = document.querySelector('a.view_git_blame');
  19. const updateIssueHref = function (anchor: string) {
  20. if (!refInNewIssue) return;
  21. const urlIssueNew = refInNewIssue.getAttribute('data-url-issue-new');
  22. const urlParamBodyLink = refInNewIssue.getAttribute('data-url-param-body-link');
  23. const issueContent = `${toAbsoluteUrl(urlParamBodyLink)}#${anchor}`; // the default content for issue body
  24. refInNewIssue.setAttribute('href', `${urlIssueNew}?body=${encodeURIComponent(issueContent)}`);
  25. };
  26. const updateViewGitBlameFragment = function (anchor: string) {
  27. if (!viewGitBlame) return;
  28. let href = viewGitBlame.getAttribute('href');
  29. href = `${href.replace(/#L\d+$|#L\d+-L\d+$/, '')}`;
  30. if (anchor.length !== 0) {
  31. href = `${href}#${anchor}`;
  32. }
  33. viewGitBlame.setAttribute('href', href);
  34. };
  35. const updateCopyPermalinkUrl = function (anchor: string) {
  36. if (!copyPermalink) return;
  37. let link = copyPermalink.getAttribute('data-url');
  38. link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`;
  39. copyPermalink.setAttribute('data-clipboard-text', link);
  40. copyPermalink.setAttribute('data-clipboard-text-type', 'url');
  41. };
  42. const rangeFields = range ? range.split('-') : [];
  43. const start = rangeFields[0] ?? '';
  44. if (!start) return null;
  45. const stop = rangeFields[1] || start;
  46. // format is i.e. 'L14-L26'
  47. let startLineNum = parseInt(start.substring(1));
  48. let stopLineNum = parseInt(stop.substring(1));
  49. if (startLineNum > stopLineNum) {
  50. const tmp = startLineNum;
  51. startLineNum = stopLineNum;
  52. stopLineNum = tmp;
  53. range = `${stop}-${start}`;
  54. }
  55. const first = elLineNums[startLineNum - 1] ?? null;
  56. for (let i = startLineNum - 1; i <= stopLineNum - 1 && i < elLineNums.length; i++) {
  57. elLineNums[i].closest('tr').classList.add('active');
  58. }
  59. changeHash(`#${range}`);
  60. updateIssueHref(range);
  61. updateViewGitBlameFragment(range);
  62. updateCopyPermalinkUrl(range);
  63. return first;
  64. }
  65. function showLineButton() {
  66. const menu = document.querySelector('.code-line-menu');
  67. if (!menu) return;
  68. // remove all other line buttons
  69. for (const el of document.querySelectorAll('.code-line-button')) {
  70. el.remove();
  71. }
  72. // find active row and add button
  73. const tr = document.querySelector('.code-view tr.active');
  74. if (!tr) return;
  75. const td = tr.querySelector('td.lines-num');
  76. const btn = document.createElement('button');
  77. btn.classList.add('code-line-button', 'ui', 'basic', 'button');
  78. btn.innerHTML = svg('octicon-kebab-horizontal');
  79. td.prepend(btn);
  80. // put a copy of the menu back into DOM for the next click
  81. btn.closest('.code-view').append(menu.cloneNode(true));
  82. createTippy(btn, {
  83. theme: 'menu',
  84. trigger: 'click',
  85. hideOnClick: true,
  86. content: menu,
  87. placement: 'right-start',
  88. interactive: true,
  89. onShow: (tippy) => {
  90. tippy.popper.addEventListener('click', () => {
  91. tippy.hide();
  92. }, {once: true});
  93. },
  94. });
  95. }
  96. export function initRepoCodeView() {
  97. // When viewing a file or blame, there is always a ".file-view" element,
  98. // but the ".code-view" class is only present when viewing the "code" of a file; it is not present when viewing a PDF file.
  99. // Since the ".file-view" will be dynamically reloaded when navigating via the left file tree (eg: view a PDF file, then view a source code file, etc.)
  100. // the "code-view" related event listeners should always be added when the current page contains ".file-view" element.
  101. if (!document.querySelector('.repo-view-container .file-view')) return;
  102. // "file code view" and "blame" pages need this "line number button" feature
  103. let selRangeStart: string;
  104. addDelegatedEventListener(document, 'click', '.code-view .lines-num span', (el: HTMLElement, e: KeyboardEvent) => {
  105. if (!selRangeStart || !e.shiftKey) {
  106. selRangeStart = el.getAttribute('id');
  107. selectRange(selRangeStart);
  108. } else {
  109. const selRangeStop = el.getAttribute('id');
  110. selectRange(`${selRangeStart}-${selRangeStop}`);
  111. }
  112. window.getSelection().removeAllRanges();
  113. showLineButton();
  114. });
  115. // apply the selected range from the URL hash
  116. const onHashChange = () => {
  117. if (!window.location.hash) return;
  118. if (!document.querySelector('.code-view .lines-num')) return;
  119. const range = window.location.hash.substring(1);
  120. const first = selectRange(range);
  121. if (first) {
  122. // set scrollRestoration to 'manual' when there is a hash in the URL, so that the scroll position will not be remembered after refreshing
  123. if (window.history.scrollRestoration !== 'manual') window.history.scrollRestoration = 'manual';
  124. first.scrollIntoView({block: 'start'});
  125. showLineButton();
  126. }
  127. };
  128. onHashChange();
  129. window.addEventListener('hashchange', onHashChange);
  130. }