2023-07-28 22:18:12 +03:00
< script >
import { SvgIcon } from '../svg.js' ;
2023-09-19 03:50:30 +03:00
import { GET } from '../modules/fetch.js' ;
2023-07-28 22:18:12 +03:00
export default {
components : { SvgIcon } ,
data : ( ) => {
2023-08-14 05:16:40 +03:00
const el = document . getElementById ( 'diff-commit-select' ) ;
2023-07-28 22:18:12 +03:00
return {
menuVisible : false ,
isLoading : false ,
2023-08-14 05:16:40 +03:00
locale : {
filter _changes _by _commit : el . getAttribute ( 'data-filter_changes_by_commit' ) ,
} ,
2023-07-28 22:18:12 +03:00
commits : [ ] ,
hoverActivated : false ,
lastReviewCommitSha : null
} ;
} ,
computed : {
commitsSinceLastReview ( ) {
if ( this . lastReviewCommitSha ) {
return this . commits . length - this . commits . findIndex ( ( x ) => x . id === this . lastReviewCommitSha ) - 1 ;
}
return 0 ;
} ,
queryParams ( ) {
return this . $el . parentNode . getAttribute ( 'data-queryparams' ) ;
} ,
issueLink ( ) {
return this . $el . parentNode . getAttribute ( 'data-issuelink' ) ;
}
} ,
mounted ( ) {
document . body . addEventListener ( 'click' , this . onBodyClick ) ;
this . $el . addEventListener ( 'keydown' , this . onKeyDown ) ;
this . $el . addEventListener ( 'keyup' , this . onKeyUp ) ;
} ,
unmounted ( ) {
document . body . removeEventListener ( 'click' , this . onBodyClick ) ;
this . $el . removeEventListener ( 'keydown' , this . onKeyDown ) ;
this . $el . removeEventListener ( 'keyup' , this . onKeyUp ) ;
} ,
methods : {
onBodyClick ( event ) {
// close this menu on click outside of this element when the dropdown is currently visible opened
if ( this . $el . contains ( event . target ) ) return ;
if ( this . menuVisible ) {
this . toggleMenu ( ) ;
}
} ,
onKeyDown ( event ) {
if ( ! this . menuVisible ) return ;
const item = document . activeElement ;
if ( ! this . $el . contains ( item ) ) return ;
switch ( event . key ) {
case 'ArrowDown' : // select next element
event . preventDefault ( ) ;
this . focusElem ( item . nextElementSibling , item ) ;
break ;
case 'ArrowUp' : // select previous element
event . preventDefault ( ) ;
this . focusElem ( item . previousElementSibling , item ) ;
break ;
case 'Escape' : // close menu
event . preventDefault ( ) ;
item . tabIndex = - 1 ;
this . toggleMenu ( ) ;
break ;
}
} ,
onKeyUp ( event ) {
if ( ! this . menuVisible ) return ;
const item = document . activeElement ;
if ( ! this . $el . contains ( item ) ) return ;
if ( event . key === 'Shift' && this . hoverActivated ) {
// shift is not pressed anymore -> deactivate hovering and reset hovered and selected
this . hoverActivated = false ;
for ( const commit of this . commits ) {
commit . hovered = false ;
commit . selected = false ;
}
}
} ,
highlight ( commit ) {
if ( ! this . hoverActivated ) return ;
const indexSelected = this . commits . findIndex ( ( x ) => x . selected ) ;
const indexCurrentElem = this . commits . findIndex ( ( x ) => x . id === commit . id ) ;
for ( const [ idx , commit ] of this . commits . entries ( ) ) {
commit . hovered = Math . min ( indexSelected , indexCurrentElem ) <= idx && idx <= Math . max ( indexSelected , indexCurrentElem ) ;
}
} ,
/** Focus given element */
focusElem ( elem , prevElem ) {
if ( elem ) {
elem . tabIndex = 0 ;
2023-11-06 05:05:24 +03:00
if ( prevElem ) prevElem . tabIndex = - 1 ;
2023-07-28 22:18:12 +03:00
elem . focus ( ) ;
}
} ,
/** Opens our menu, loads commits before opening */
async toggleMenu ( ) {
this . menuVisible = ! this . menuVisible ;
// load our commits when the menu is not yet visible (it'll be toggled after loading)
// and we got no commits
if ( this . commits . length === 0 && this . menuVisible && ! this . isLoading ) {
this . isLoading = true ;
try {
await this . fetchCommits ( ) ;
} finally {
this . isLoading = false ;
}
}
// set correct tabindex to allow easier navigation
this . $nextTick ( ( ) => {
const expandBtn = this . $el . querySelector ( '#diff-commit-list-expand' ) ;
const showAllChanges = this . $el . querySelector ( '#diff-commit-list-show-all' ) ;
if ( this . menuVisible ) {
this . focusElem ( showAllChanges , expandBtn ) ;
} else {
this . focusElem ( expandBtn , showAllChanges ) ;
}
} ) ;
} ,
/** Load the commits to show in this dropdown */
async fetchCommits ( ) {
2023-09-19 03:50:30 +03:00
const resp = await GET ( ` ${ this . issueLink } /commits/list ` ) ;
2023-07-28 22:18:12 +03:00
const results = await resp . json ( ) ;
this . commits . push ( ... results . commits . map ( ( x ) => {
x . hovered = false ;
return x ;
} ) ) ;
this . commits . reverse ( ) ;
this . lastReviewCommitSha = results . last _review _commit _sha || null ;
if ( this . lastReviewCommitSha && this . commits . findIndex ( ( x ) => x . id === this . lastReviewCommitSha ) === - 1 ) {
// the lastReviewCommit is not available (probably due to a force push)
// reset the last review commit sha
this . lastReviewCommitSha = null ;
}
Object . assign ( this . locale , results . locale ) ;
} ,
showAllChanges ( ) {
window . location = ` ${ this . issueLink } /files ${ this . queryParams } ` ;
} ,
/** Called when user clicks on since last review */
changesSinceLastReviewClick ( ) {
window . location = ` ${ this . issueLink } /files/ ${ this . lastReviewCommitSha } .. ${ this . commits . at ( - 1 ) . id } ${ this . queryParams } ` ;
} ,
/** Clicking on a single commit opens this specific commit */
commitClicked ( commitId , newWindow = false ) {
const url = ` ${ this . issueLink } /commits/ ${ commitId } ${ this . queryParams } ` ;
if ( newWindow ) {
window . open ( url ) ;
} else {
window . location = url ;
}
} ,
/ * *
* When a commit is clicked with shift this enables the range
* selection . Second click ( with shift ) defines the end of the
* range . This opens the diff of this range
* Exception : first commit is the first commit of this PR . Then
* the diff from beginning of PR up to the second clicked commit is
* opened
* /
commitClickedShift ( commit ) {
this . hoverActivated = ! this . hoverActivated ;
commit . selected = true ;
// Second click -> determine our range and open links accordingly
if ( ! this . hoverActivated ) {
// find all selected commits and generate a link
if ( this . commits [ 0 ] . selected ) {
// first commit is selected - generate a short url with only target sha
const lastCommitIdx = this . commits . findLastIndex ( ( x ) => x . selected ) ;
if ( lastCommitIdx === this . commits . length - 1 ) {
// user selected all commits - just show the normal diff page
window . location = ` ${ this . issueLink } /files ${ this . queryParams } ` ;
} else {
window . location = ` ${ this . issueLink } /files/ ${ this . commits [ lastCommitIdx ] . id } ${ this . queryParams } ` ;
}
} else {
const start = this . commits [ this . commits . findIndex ( ( x ) => x . selected ) - 1 ] . id ;
const end = this . commits . findLast ( ( x ) => x . selected ) . id ;
window . location = ` ${ this . issueLink } /files/ ${ start } .. ${ end } ${ this . queryParams } ` ;
}
}
} ,
}
} ;
< / script >
2023-09-02 17:59:07 +03:00
< template >
< div class = "ui scrolling dropdown custom" >
< button
class = "ui basic button"
id = "diff-commit-list-expand"
@ click . stop = "toggleMenu()"
: data - tooltip - content = "locale.filter_changes_by_commit"
aria - haspopup = "true"
aria - controls = "diff-commit-selector-menu"
: aria - label = "locale.filter_changes_by_commit"
aria - activedescendant = "diff-commit-list-show-all"
>
< svg -icon name = "octicon-git-commit" / >
< / button >
< div class = "menu left transition" id = "diff-commit-selector-menu" : class = "{visible: menuVisible}" v-show ="menuVisible" v-cloak :aria-expanded="menuVisible ? 'true': 'false'" >
< div class = "loading-indicator is-loading" v -if = " isLoading " / >
< div v-if ="!isLoading" class="vertical item gt-df gt-fc gt-gap-2" id="diff-commit-list-show-all" role="menuitem" @keydown.enter="showAllChanges()" @click="showAllChanges()" >
< div class = "gt-ellipsis" >
{ { locale . show _all _commits } }
< / div >
< div class = "gt-ellipsis text light-2 gt-mb-0" >
{ { locale . stats _num _commits } }
< / div >
< / div >
<!-- only show the show changes since last review if there is a review AND we are commits ahead of the last review -- >
< div
v - if = "lastReviewCommitSha != null" role = "menuitem"
class = "vertical item gt-df gt-fc gt-gap-2 gt-border-secondary-top"
: class = "{disabled: commitsSinceLastReview === 0}"
@ keydown . enter = "changesSinceLastReviewClick()"
@ click = "changesSinceLastReviewClick()"
>
< div class = "gt-ellipsis" >
{ { locale . show _changes _since _your _last _review } }
< / div >
< div class = "gt-ellipsis text light-2" >
{ { commitsSinceLastReview } } commits
< / div >
< / div >
< span v-if ="!isLoading" class="info gt-border-secondary-top text light-2" > {{ locale.select_commit_hold_shift_for_range }} < / span >
< template v-for ="commit in commits" :key="commit.id" >
< div
class = "vertical item gt-df gt-gap-2 gt-border-secondary-top" role = "menuitem"
: class = "{selection: commit.selected, hovered: commit.hovered}"
@ keydown . enter . exact = "commitClicked(commit.id)"
@ keydown . enter . shift . exact = "commitClickedShift(commit)"
@ mouseover . shift = "highlight(commit)"
@ click . exact = "commitClicked(commit.id)"
@ click . ctrl . exact = "commitClicked(commit.id, true)"
@ click . meta . exact = "commitClicked(commit.id, true)"
@ click . shift . exact . stop . prevent = "commitClickedShift(commit)"
>
< div class = "gt-f1 gt-df gt-fc gt-gap-2" >
< div class = "gt-ellipsis commit-list-summary" >
{ { commit . summary } }
< / div >
< div class = "gt-ellipsis text light-2" >
{ { commit . committer _or _author _name } }
< span class = "text right" >
2024-01-02 04:25:30 +03:00
<!-- TODO : make this respect the PreferredTimestampTense setting -- >
2023-09-02 17:59:07 +03:00
< relative -time class = "time-since" prefix = "" :datetime ="commit.time" data -tooltip -content data -tooltip -interactive = " true " > { { commit . time } } < / r e l a t i v e - t i m e >
< / span >
< / div >
< / div >
< div class = "gt-mono" >
{ { commit . short _sha } }
< / div >
< / div >
< / template >
< / div >
< / div >
< / template >
2023-07-28 22:18:12 +03:00
< style scoped >
. hovered : not ( . selection ) {
background - color : var ( -- color - small - accent ) ! important ;
}
. selection {
background - color : var ( -- color - accent ) ! important ;
}
. info {
display : inline - block ;
padding : 7 px 14 px ! important ;
line - height : 1.4 ;
width : 100 % ;
}
# diff - commit - selector - menu {
overflow - x : hidden ;
max - height : 450 px ;
}
# diff - commit - selector - menu . loading - indicator {
height : 200 px ;
width : 350 px ;
}
# diff - commit - selector - menu . item {
flex - direction : row ;
line - height : 1.4 ;
padding : 7 px 14 px ! important ;
}
# diff - commit - selector - menu . item : focus {
color : var ( -- color - text ) ;
background : var ( -- color - hover ) ;
}
# diff - commit - selector - menu . commit - list - summary {
max - width : min ( 380 px , 96 vw ) ;
}
< / style >