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,10 @@ function changeHash(hash: string) {
1612 }
1713}
1814
19- function isBlame ( ) {
20- return Boolean ( document . querySelector ( 'div.blame' ) ) ;
21- }
15+ function selectRange ( range : string ) : Element {
16+ for ( const el of document . querySelectorAll ( '.code-view tr.active' ) ) el . classList . remove ( 'active' ) ;
17+ const elLineNums = document . querySelectorAll ( `.code-view td.lines-num span[data-line-number]` ) ;
2218
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
3319 const refInNewIssue = document . querySelector ( 'a.ref-in-new-issue' ) ;
3420 const copyPermalink = document . querySelector ( 'a.copy-line-permalink' ) ;
3521 const viewGitBlame = document . querySelector ( 'a.view_git_blame' ) ;
@@ -59,37 +45,29 @@ function selectRange($linesEls, $selectionEndEl, $selectionStartEls?) {
5945 copyPermalink . setAttribute ( 'data-url' , link ) ;
6046 } ;
6147
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- }
48+ const rangeFields = range ? range . split ( '-' ) : [ ] ;
49+ const start = rangeFields [ 0 ] ?? '' ;
50+ if ( ! start ) return null ;
51+ const stop = rangeFields [ 1 ] || start ;
52+
53+ let startLineNum = parseInt ( start . substring ( 1 ) ) ;
54+ let stopLineNum = parseInt ( stop . substring ( 1 ) ) ;
55+ if ( startLineNum > stopLineNum ) {
56+ const tmp = startLineNum ;
57+ startLineNum = stopLineNum ;
58+ stopLineNum = tmp ;
59+ range = `${ stop } -${ start } ` ;
8660 }
87- $selectionEndEl [ 0 ] . closest ( 'tr' ) . classList . add ( 'active' ) ;
88- changeHash ( `#${ $selectionEndEl [ 0 ] . getAttribute ( 'rel' ) } ` ) ;
8961
90- updateIssueHref ( $selectionEndEl [ 0 ] . getAttribute ( 'rel' ) ) ;
91- updateViewGitBlameFragment ( $selectionEndEl [ 0 ] . getAttribute ( 'rel' ) ) ;
92- updateCopyPermalinkUrl ( $selectionEndEl [ 0 ] . getAttribute ( 'rel' ) ) ;
62+ const first = elLineNums [ startLineNum - 1 ] ?? null ;
63+ for ( let i = startLineNum - 1 ; i <= stopLineNum - 1 && i < elLineNums . length ; i ++ ) {
64+ elLineNums [ i ] . closest ( 'tr' ) . classList . add ( 'active' ) ;
65+ }
66+ changeHash ( `#${ range } ` ) ;
67+ updateIssueHref ( range ) ;
68+ updateViewGitBlameFragment ( range ) ;
69+ updateCopyPermalinkUrl ( range ) ;
70+ return first ;
9371}
9472
9573function showLineButton ( ) {
@@ -103,6 +81,8 @@ function showLineButton() {
10381
10482 // find active row and add button
10583 const tr = document . querySelector ( '.code-view tr.active' ) ;
84+ if ( ! tr ) return ;
85+
10686 const td = tr . querySelector ( 'td.lines-num' ) ;
10787 const btn = document . createElement ( 'button' ) ;
10888 btn . classList . add ( 'code-line-button' , 'ui' , 'basic' , 'button' ) ;
@@ -128,62 +108,36 @@ function showLineButton() {
128108}
129109
130110export 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 ) ;
111+ if ( ! document . querySelector ( '.code-view .lines-num' ) ) return ;
112+
113+ let selRangeStart : string ;
114+ addDelegatedEventListener ( document , 'click' , '.lines-num span' , ( el : HTMLElement , e : KeyboardEvent ) => {
115+ if ( ! selRangeStart || ! e . shiftKey ) {
116+ selRangeStart = el . getAttribute ( 'id' ) ;
117+ selectRange ( selRangeStart ) ;
118+ } else {
119+ const selRangeStop = el . getAttribute ( 'id' ) ;
120+ selectRange ( `${ selRangeStart } -${ selRangeStop } ` ) ;
121+ }
122+ window . getSelection ( ) . removeAllRanges ( ) ;
123+ showLineButton ( ) ;
185124 } ) ;
186- $ ( document ) . on ( 'click' , '.copy-line-permalink' , async ( { currentTarget} ) => {
187- await clippie ( toAbsoluteUrl ( currentTarget . getAttribute ( 'data-url' ) ) ) ;
125+
126+ const onHashChange = ( ) => {
127+ if ( ! window . location . hash ) return ;
128+ const range = window . location . hash . substring ( 1 ) ;
129+ const first = selectRange ( range ) ;
130+ if ( first ) {
131+ // set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
132+ if ( window . history . scrollRestoration !== 'manual' ) window . history . scrollRestoration = 'manual' ;
133+ first . scrollIntoView ( { block : 'start' } ) ;
134+ showLineButton ( ) ;
135+ }
136+ } ;
137+ onHashChange ( ) ;
138+ window . addEventListener ( 'hashchange' , onHashChange ) ;
139+
140+ addDelegatedEventListener ( document , 'click' , '.copy-line-permalink' , ( el ) => {
141+ clippie ( toAbsoluteUrl ( el . getAttribute ( 'data-url' ) ) ) ;
188142 } ) ;
189143}
0 commit comments