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-code.js 5.7KB

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