您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

repo-code.js 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import $ from 'jquery';
  2. import {svg} from '../svg.js';
  3. import {invertFileFolding} from './file-fold.js';
  4. import {createTippy} from '../modules/tippy.js';
  5. import {clippie} from 'clippie';
  6. import {toAbsoluteUrl} from '../utils.js';
  7. export const singleAnchorRegex = /^#(L|n)([1-9][0-9]*)$/;
  8. export const rangeAnchorRegex = /^#(L[1-9][0-9]*)-(L[1-9][0-9]*)$/;
  9. function changeHash(hash) {
  10. if (window.history.pushState) {
  11. window.history.pushState(null, null, hash);
  12. } else {
  13. window.location.hash = hash;
  14. }
  15. }
  16. function isBlame() {
  17. return Boolean(document.querySelector('div.blame'));
  18. }
  19. function getLineEls() {
  20. return document.querySelectorAll(`.code-view td.lines-code${isBlame() ? '.blame-code' : ''}`);
  21. }
  22. function selectRange($linesEls, $selectionEndEl, $selectionStartEls) {
  23. $linesEls.closest('tr').removeClass('active');
  24. // add hashchange to permalink
  25. const refInNewIssue = document.querySelector('a.ref-in-new-issue');
  26. const copyPermalink = document.querySelector('a.copy-line-permalink');
  27. const viewGitBlame = document.querySelector('a.view_git_blame');
  28. const updateIssueHref = function (anchor) {
  29. if (!refInNewIssue) return;
  30. const urlIssueNew = refInNewIssue.getAttribute('data-url-issue-new');
  31. const urlParamBodyLink = refInNewIssue.getAttribute('data-url-param-body-link');
  32. const issueContent = `${toAbsoluteUrl(urlParamBodyLink)}#${anchor}`; // the default content for issue body
  33. refInNewIssue.setAttribute('href', `${urlIssueNew}?body=${encodeURIComponent(issueContent)}`);
  34. };
  35. const updateViewGitBlameFragment = function (anchor) {
  36. if (!viewGitBlame) return;
  37. let href = viewGitBlame.getAttribute('href');
  38. href = `${href.replace(/#L\d+$|#L\d+-L\d+$/, '')}`;
  39. if (anchor.length !== 0) {
  40. href = `${href}#${anchor}`;
  41. }
  42. viewGitBlame.setAttribute('href', href);
  43. };
  44. const updateCopyPermalinkUrl = function (anchor) {
  45. if (!copyPermalink) return;
  46. let link = copyPermalink.getAttribute('data-url');
  47. link = `${link.replace(/#L\d+$|#L\d+-L\d+$/, '')}#${anchor}`;
  48. copyPermalink.setAttribute('data-url', link);
  49. };
  50. if ($selectionStartEls) {
  51. let a = parseInt($selectionEndEl[0].getAttribute('rel').slice(1));
  52. let b = parseInt($selectionStartEls[0].getAttribute('rel').slice(1));
  53. let c;
  54. if (a !== b) {
  55. if (a > b) {
  56. c = a;
  57. a = b;
  58. b = c;
  59. }
  60. const classes = [];
  61. for (let i = a; i <= b; i++) {
  62. classes.push(`[rel=L${i}]`);
  63. }
  64. $linesEls.filter(classes.join(',')).each(function () {
  65. $(this).closest('tr').addClass('active');
  66. });
  67. changeHash(`#L${a}-L${b}`);
  68. updateIssueHref(`L${a}-L${b}`);
  69. updateViewGitBlameFragment(`L${a}-L${b}`);
  70. updateCopyPermalinkUrl(`L${a}-L${b}`);
  71. return;
  72. }
  73. }
  74. $selectionEndEl.closest('tr').addClass('active');
  75. changeHash(`#${$selectionEndEl[0].getAttribute('rel')}`);
  76. updateIssueHref($selectionEndEl[0].getAttribute('rel'));
  77. updateViewGitBlameFragment($selectionEndEl[0].getAttribute('rel'));
  78. updateCopyPermalinkUrl($selectionEndEl[0].getAttribute('rel'));
  79. }
  80. function showLineButton() {
  81. const menu = document.querySelector('.code-line-menu');
  82. if (!menu) return;
  83. // remove all other line buttons
  84. for (const el of document.querySelectorAll('.code-line-button')) {
  85. el.remove();
  86. }
  87. // find active row and add button
  88. const tr = document.querySelector('.code-view tr.active');
  89. const td = tr.querySelector('td.lines-num');
  90. const btn = document.createElement('button');
  91. btn.classList.add('code-line-button', 'ui', 'basic', 'button');
  92. btn.innerHTML = svg('octicon-kebab-horizontal');
  93. td.prepend(btn);
  94. // put a copy of the menu back into DOM for the next click
  95. btn.closest('.code-view').append(menu.cloneNode(true));
  96. createTippy(btn, {
  97. trigger: 'click',
  98. hideOnClick: true,
  99. content: menu,
  100. placement: 'right-start',
  101. interactive: true,
  102. onShow: (tippy) => {
  103. tippy.popper.addEventListener('click', () => {
  104. tippy.hide();
  105. }, {once: true});
  106. },
  107. });
  108. }
  109. export function initRepoCodeView() {
  110. if ($('.code-view .lines-num').length > 0) {
  111. $(document).on('click', '.lines-num span', function (e) {
  112. const linesEls = getLineEls();
  113. const selectedEls = Array.from(linesEls).filter((el) => {
  114. return el.matches(`[rel=${this.getAttribute('id')}]`);
  115. });
  116. let from;
  117. if (e.shiftKey) {
  118. from = Array.from(linesEls).filter((el) => {
  119. return el.closest('tr').classList.contains('active');
  120. });
  121. }
  122. selectRange($(linesEls), $(selectedEls), from ? $(from) : null);
  123. if (window.getSelection) {
  124. window.getSelection().removeAllRanges();
  125. } else {
  126. document.selection.empty();
  127. }
  128. showLineButton();
  129. });
  130. $(window).on('hashchange', () => {
  131. let m = window.location.hash.match(rangeAnchorRegex);
  132. const $linesEls = $(getLineEls());
  133. let $first;
  134. if (m) {
  135. $first = $linesEls.filter(`[rel=${m[1]}]`);
  136. if ($first.length) {
  137. selectRange($linesEls, $first, $linesEls.filter(`[rel=${m[2]}]`));
  138. // show code view menu marker (don't show in blame page)
  139. if (!isBlame()) {
  140. showLineButton();
  141. }
  142. $('html, body').scrollTop($first.offset().top - 200);
  143. return;
  144. }
  145. }
  146. m = window.location.hash.match(singleAnchorRegex);
  147. if (m) {
  148. $first = $linesEls.filter(`[rel=L${m[2]}]`);
  149. if ($first.length) {
  150. selectRange($linesEls, $first);
  151. // show code view menu marker (don't show in blame page)
  152. if (!isBlame()) {
  153. showLineButton();
  154. }
  155. $('html, body').scrollTop($first.offset().top - 200);
  156. }
  157. }
  158. }).trigger('hashchange');
  159. }
  160. $(document).on('click', '.fold-file', ({currentTarget}) => {
  161. invertFileFolding(currentTarget.closest('.file-content'), currentTarget);
  162. });
  163. $(document).on('click', '.copy-line-permalink', async ({currentTarget}) => {
  164. await clippie(toAbsoluteUrl(currentTarget.getAttribute('data-url')));
  165. });
  166. }