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