1- import $ from 'jquery' ;
21import { svg } from '../svg.ts' ;
3- import { invertFileFolding } from './file-fold.ts' ;
42import { createTippy } from '../modules/tippy.ts' ;
53import { clippie } from 'clippie' ;
64import { toAbsoluteUrl } from '../utils.ts' ;
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 ] * ) $ / ;
5+ import { addDelegatedEventListener } from '../utils/dom.ts' ;
106
117function changeHash ( hash : string ) {
128 if ( window . history . pushState ) {
@@ -16,20 +12,11 @@ function changeHash(hash: string) {
1612 }
1713}
1814
19- function isBlame ( ) {
20- return Boolean ( document . querySelector ( 'div.blame' ) ) ;
21- }
15+ // it selects the code lines defined by range: `L1-L3` (3 lines) or `L2` (singe line)
16+ function selectRange ( range : string ) : Element {
17+ for ( const el of document . querySelectorAll ( '.code-view tr.active' ) ) el . classList . remove ( 'active' ) ;
18+ const elLineNums = document . querySelectorAll ( `.code-view td.lines-num span[data-line-number]` ) ;
2219
23- function getLineEls ( ) {
24- return document . querySelectorAll ( `.code-view td.lines-code${ isBlame ( ) ? '.blame-code' : '' } ` ) ;
25- }
26-
27- function selectRange ( $linesEls , $selectionEndEl , $selectionStartEls ?) {
28- for ( const el of $linesEls ) {
29- el . closest ( 'tr' ) . classList . remove ( 'active' ) ;
30- }
31-
32- // add hashchange to permalink
3320 const refInNewIssue = document . querySelector ( 'a.ref-in-new-issue' ) ;
3421 const copyPermalink = document . querySelector ( 'a.copy-line-permalink' ) ;
3522 const viewGitBlame = document . querySelector ( 'a.view_git_blame' ) ;
@@ -59,37 +46,30 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls?) {
5946 copyPermalink . setAttribute ( 'data-url' , link ) ;
6047 } ;
6148
62- if ( $selectionStartEls ) {
63- let a = parseInt ( $selectionEndEl [ 0 ] . getAttribute ( 'rel' ) . slice ( 1 ) ) ;
64- let b = parseInt ( $selectionStartEls [ 0 ] . getAttribute ( 'rel' ) . slice ( 1 ) ) ;
65- let c ;
66- if ( a !== b ) {
67- if ( a > b ) {
68- c = a ;
69- a = b ;
70- b = c ;
71- }
72- const classes = [ ] ;
73- for ( let i = a ; i <= b ; i ++ ) {
74- classes . push ( `[rel=L${ i } ]` ) ;
75- }
76- $linesEls . filter ( classes . join ( ',' ) ) . each ( function ( ) {
77- this . closest ( 'tr' ) . classList . add ( 'active' ) ;
78- } ) ;
79- changeHash ( `#L${ a } -L${ b } ` ) ;
80-
81- updateIssueHref ( `L${ a } -L${ b } ` ) ;
82- updateViewGitBlameFragment ( `L${ a } -L${ b } ` ) ;
83- updateCopyPermalinkUrl ( `L${ a } -L${ b } ` ) ;
84- return ;
85- }
49+ const rangeFields = range ? range . split ( '-' ) : [ ] ;
50+ const start = rangeFields [ 0 ] ?? '' ;
51+ if ( ! start ) return null ;
52+ const stop = rangeFields [ 1 ] || start ;
53+
54+ // format is i.e. 'L14-L26'
55+ let startLineNum = parseInt ( start . substring ( 1 ) ) ;
56+ let stopLineNum = parseInt ( stop . substring ( 1 ) ) ;
57+ if ( startLineNum > stopLineNum ) {
58+ const tmp = startLineNum ;
59+ startLineNum = stopLineNum ;
60+ stopLineNum = tmp ;
61+ range = `${ stop } -${ start } ` ;
8662 }
87- $selectionEndEl [ 0 ] . closest ( 'tr' ) . classList . add ( 'active' ) ;
88- changeHash ( `#${ $selectionEndEl [ 0 ] . getAttribute ( 'rel' ) } ` ) ;
8963
90- updateIssueHref ( $selectionEndEl [ 0 ] . getAttribute ( 'rel' ) ) ;
91- updateViewGitBlameFragment ( $selectionEndEl [ 0 ] . getAttribute ( 'rel' ) ) ;
92- updateCopyPermalinkUrl ( $selectionEndEl [ 0 ] . getAttribute ( 'rel' ) ) ;
64+ const first = elLineNums [ startLineNum - 1 ] ?? null ;
65+ for ( let i = startLineNum - 1 ; i <= stopLineNum - 1 && i < elLineNums . length ; i ++ ) {
66+ elLineNums [ i ] . closest ( 'tr' ) . classList . add ( 'active' ) ;
67+ }
68+ changeHash ( `#${ range } ` ) ;
69+ updateIssueHref ( range ) ;
70+ updateViewGitBlameFragment ( range ) ;
71+ updateCopyPermalinkUrl ( range ) ;
72+ return first ;
9373}
9474
9575function showLineButton ( ) {
@@ -103,6 +83,8 @@ function showLineButton() {
10383
10484 // find active row and add button
10585 const tr = document . querySelector ( '.code-view tr.active' ) ;
86+ if ( ! tr ) return ;
87+
10688 const td = tr . querySelector ( 'td.lines-num' ) ;
10789 const btn = document . createElement ( 'button' ) ;
10890 btn . classList . add ( 'code-line-button' , 'ui' , 'basic' , 'button' ) ;
@@ -128,62 +110,36 @@ function showLineButton() {
128110}
129111
130112export function initRepoCodeView ( ) {
131- if ( $ ( '.code-view .lines-num' ) . length > 0 ) {
132- $ ( document ) . on ( 'click' , '.lines-num span' , function ( e ) {
133- const linesEls = getLineEls ( ) ;
134- const selectedEls = Array . from ( linesEls ) . filter ( ( el ) => {
135- return el . matches ( `[rel=${ this . getAttribute ( 'id' ) } ]` ) ;
136- } ) ;
137-
138- let from ;
139- if ( e . shiftKey ) {
140- from = Array . from ( linesEls ) . filter ( ( el ) => {
141- return el . closest ( 'tr' ) . classList . contains ( 'active' ) ;
142- } ) ;
143- }
144- selectRange ( $ ( linesEls ) , $ ( selectedEls ) , from ? $ ( from ) : null ) ;
145- window . getSelection ( ) . removeAllRanges ( ) ;
146- showLineButton ( ) ;
147- } ) ;
148-
149- $ ( window ) . on ( 'hashchange' , ( ) => {
150- let m = rangeAnchorRegex . exec ( window . location . hash ) ;
151- const $linesEls = $ ( getLineEls ( ) ) ;
152- let $first ;
153- if ( m ) {
154- $first = $linesEls . filter ( `[rel=${ m [ 1 ] } ]` ) ;
155- if ( $first . length ) {
156- selectRange ( $linesEls , $first , $linesEls . filter ( `[rel=${ m [ 2 ] } ]` ) ) ;
157-
158- // show code view menu marker (don't show in blame page)
159- if ( ! isBlame ( ) ) {
160- showLineButton ( ) ;
161- }
162-
163- $ ( 'html, body' ) . scrollTop ( $first . offset ( ) . top - 200 ) ;
164- return ;
165- }
166- }
167- m = singleAnchorRegex . exec ( window . location . hash ) ;
168- if ( m ) {
169- $first = $linesEls . filter ( `[rel=L${ m [ 2 ] } ]` ) ;
170- if ( $first . length ) {
171- selectRange ( $linesEls , $first ) ;
172-
173- // show code view menu marker (don't show in blame page)
174- if ( ! isBlame ( ) ) {
175- showLineButton ( ) ;
176- }
177-
178- $ ( 'html, body' ) . scrollTop ( $first . offset ( ) . top - 200 ) ;
179- }
180- }
181- } ) . trigger ( 'hashchange' ) ;
182- }
183- $ ( document ) . on ( 'click' , '.fold-file' , ( { currentTarget} ) => {
184- invertFileFolding ( currentTarget . closest ( '.file-content' ) , currentTarget ) ;
113+ if ( ! document . querySelector ( '.code-view .lines-num' ) ) return ;
114+
115+ let selRangeStart : string ;
116+ addDelegatedEventListener ( document , 'click' , '.lines-num span' , ( el : HTMLElement , e : KeyboardEvent ) => {
117+ if ( ! selRangeStart || ! e . shiftKey ) {
118+ selRangeStart = el . getAttribute ( 'id' ) ;
119+ selectRange ( selRangeStart ) ;
120+ } else {
121+ const selRangeStop = el . getAttribute ( 'id' ) ;
122+ selectRange ( `${ selRangeStart } -${ selRangeStop } ` ) ;
123+ }
124+ window . getSelection ( ) . removeAllRanges ( ) ;
125+ showLineButton ( ) ;
185126 } ) ;
186- $ ( document ) . on ( 'click' , '.copy-line-permalink' , async ( { currentTarget} ) => {
187- await clippie ( toAbsoluteUrl ( currentTarget . getAttribute ( 'data-url' ) ) ) ;
127+
128+ const onHashChange = ( ) => {
129+ if ( ! window . location . hash ) return ;
130+ const range = window . location . hash . substring ( 1 ) ;
131+ const first = selectRange ( range ) ;
132+ if ( first ) {
133+ // set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
134+ if ( window . history . scrollRestoration !== 'manual' ) window . history . scrollRestoration = 'manual' ;
135+ first . scrollIntoView ( { block : 'start' } ) ;
136+ showLineButton ( ) ;
137+ }
138+ } ;
139+ onHashChange ( ) ;
140+ window . addEventListener ( 'hashchange' , onHashChange ) ;
141+
142+ addDelegatedEventListener ( document , 'click' , '.copy-line-permalink' , ( el ) => {
143+ clippie ( toAbsoluteUrl ( el . getAttribute ( 'data-url' ) ) ) ;
188144 } ) ;
189145}
0 commit comments