2022-01-28 22:00:11 +01:00
import $ from 'jquery' ;
2021-10-16 19:28:04 +02:00
import { htmlEscape } from 'escape-goat' ;
2022-12-23 17:03:11 +01:00
import { attachTribute } from './tribute.js' ;
2022-01-05 13:17:25 +01:00
import { createCommentEasyMDE , getAttachedEasyMDE } from './comp/EasyMDE.js' ;
2022-06-28 19:52:58 +02:00
import { initEasyMDEImagePaste } from './comp/ImagePaste.js' ;
2021-10-16 19:28:04 +02:00
import { initCompMarkupContentPreviewTab } from './comp/MarkupContentPreview.js' ;
Use a general approach to show tooltip, fix temporary tooltip bug (#23574)
## TLDR
* Improve performance: lazy creating the tippy instances.
* Transparently support all "tooltip" elements, no need to call
`initTooltip` again and again.
* Fix a temporary tooltip re-entrance bug, which causes showing temp
content forever.
* Upgrade vue3-calendar-heatmap to 2.0.2 with lazy tippy init
(initHeatmap time decreases from 100ms to 50ms)
## Details
### The performance
Creating a lot of tippy tooltip instances is expensive. This PR doesn't
create all tippy tooltip instances, instead, it only adds "mouseover"
event listener to necessary elements, and then switches to the tippy
tooltip
### The general approach for all tooltips
Before, dynamically generated tooltips need to be called with
`initTooltip`.
After, use MutationObserver to:
* Attach the event listeners to newly created tooltip elements, work for
Vue (easier than before)
* Catch changed attributes and update the tooltip content (better than
before)
It does help a lot, eg:
https://github.com/go-gitea/gitea/blob/1a4efa0ee9a49d48549be7479a46be133b9bc260/web_src/js/components/PullRequestMergeForm.vue#L33-L36
### Temporary tooltip re-entrance bug
To reproduce, on try.gitea.io, click the "copy clone url" quickly, then
the tooltip will be "Copied!" forever.
After this PR, with the help of `attachTippyTooltip`, the tooltip
content could be reset to the default correctly.
### Other changes
* `data-tooltip-content` is preferred from now on, the old
`data-content` may cause conflicts with other modules.
* `data-placement` was only used for tooltip, so it's renamed to
`data-tooltip-placement`, and removed from `createTippy`.
2023-03-23 10:56:15 +01:00
import { showTemporaryTooltip , createTippy } from '../modules/tippy.js' ;
2023-02-19 05:06:14 +01:00
import { hideElem , showElem , toggleElem } from '../utils/dom.js' ;
Imrove scroll behavior to hash issuecomment(scroll position, auto expand if file is folded, and on refreshing) (#23513)
Close #23466
Right now on pull request "files Changed" tab, if a file is viewed, when
the comments' links are visited, the comment will not be shown as the
file is folded after viewed. This PR is to improve the behavior, to make
the comment seen even the related file is folded, like on github.
And right now scroll position will be remembered and hence it won’t
scroll to hashed comment after refreshing, this PR also adjust the
scroll position remembering behavior: When there is hash comment in url,
do not remember the scroll position.
Before:
https://user-images.githubusercontent.com/17645053/225512079-6cf79581-9346-44cf-95d6-06919642e6a8.mov
After:
https://user-images.githubusercontent.com/17645053/225523753-3f6728f2-977b-4ed0-a65c-63dcef2ace80.mov
Update - long comment's behavior after using `scrollTop ` (Comment div
scroll to the position which is 30px below the diff header, or 30px
below top on conversation tab):
https://user-images.githubusercontent.com/17645053/225614460-0602c1a6-229c-41f4-84d2-334e78251486.mov
2023-03-17 11:24:18 +01:00
import { setFileFolding } from './file-fold.js' ;
2021-10-16 19:28:04 +02:00
2021-10-21 09:37:43 +02:00
const { appSubUrl , csrfToken } = window . config ;
2021-10-16 19:28:04 +02:00
export function initRepoIssueTimeTracking ( ) {
$ ( document ) . on ( 'click' , '.issue-add-time' , ( ) => {
$ ( '.issue-start-time-modal' ) . modal ( {
duration : 200 ,
onApprove ( ) {
$ ( '#add_time_manual_form' ) . trigger ( 'submit' ) ;
} ,
} ) . modal ( 'show' ) ;
$ ( '.issue-start-time-modal input' ) . on ( 'keydown' , ( e ) => {
if ( ( e . keyCode || e . key ) === 13 ) {
$ ( '#add_time_manual_form' ) . trigger ( 'submit' ) ;
}
} ) ;
} ) ;
$ ( document ) . on ( 'click' , '.issue-start-time, .issue-stop-time' , ( ) => {
$ ( '#toggle_stopwatch_form' ) . trigger ( 'submit' ) ;
} ) ;
$ ( document ) . on ( 'click' , '.issue-cancel-time' , ( ) => {
$ ( '#cancel_stopwatch_form' ) . trigger ( 'submit' ) ;
} ) ;
$ ( document ) . on ( 'click' , 'button.issue-delete-time' , function ( ) {
const sel = ` .issue-delete-time-modal[data-id=" ${ $ ( this ) . data ( 'id' ) } "] ` ;
2022-01-16 12:19:26 +01:00
$ ( sel ) . modal ( {
2021-10-16 19:28:04 +02:00
duration : 200 ,
onApprove ( ) {
$ ( ` ${ sel } form ` ) . trigger ( 'submit' ) ;
} ,
} ) . modal ( 'show' ) ;
} ) ;
}
function updateDeadline ( deadlineString ) {
2023-02-19 05:06:14 +01:00
hideElem ( $ ( '#deadline-err-invalid-date' ) ) ;
2021-10-16 19:28:04 +02:00
$ ( '#deadline-loader' ) . addClass ( 'loading' ) ;
let realDeadline = null ;
if ( deadlineString !== '' ) {
const newDate = Date . parse ( deadlineString ) ;
if ( Number . isNaN ( newDate ) ) {
$ ( '#deadline-loader' ) . removeClass ( 'loading' ) ;
2023-02-19 05:06:14 +01:00
showElem ( $ ( '#deadline-err-invalid-date' ) ) ;
2021-10-16 19:28:04 +02:00
return false ;
}
realDeadline = new Date ( newDate ) ;
}
2022-04-07 20:59:56 +02:00
$ . ajax ( ` ${ $ ( '#update-issue-deadline-form' ) . attr ( 'action' ) } ` , {
2021-10-16 19:28:04 +02:00
data : JSON . stringify ( {
due _date : realDeadline ,
} ) ,
headers : {
2021-10-21 09:37:43 +02:00
'X-Csrf-Token' : csrfToken ,
2021-10-16 19:28:04 +02:00
} ,
contentType : 'application/json' ,
type : 'POST' ,
success ( ) {
window . location . reload ( ) ;
} ,
error ( ) {
$ ( '#deadline-loader' ) . removeClass ( 'loading' ) ;
2023-02-19 05:06:14 +01:00
showElem ( $ ( '#deadline-err-invalid-date' ) ) ;
2021-10-16 19:28:04 +02:00
} ,
} ) ;
}
export function initRepoIssueDue ( ) {
$ ( document ) . on ( 'click' , '.issue-due-edit' , ( ) => {
$ ( '#deadlineForm' ) . fadeToggle ( 150 ) ;
} ) ;
$ ( document ) . on ( 'click' , '.issue-due-remove' , ( ) => {
updateDeadline ( '' ) ;
} ) ;
$ ( document ) . on ( 'submit' , '.issue-due-form' , ( ) => {
updateDeadline ( $ ( '#deadlineDate' ) . val ( ) ) ;
return false ;
} ) ;
}
export function initRepoIssueList ( ) {
const repolink = $ ( '#repolink' ) . val ( ) ;
const repoId = $ ( '#repoId' ) . val ( ) ;
const crossRepoSearch = $ ( '#crossRepoSearch' ) . val ( ) ;
const tp = $ ( '#type' ) . val ( ) ;
2022-04-07 20:59:56 +02:00
let issueSearchUrl = ` ${ appSubUrl } / ${ repolink } /issues/search?q={query}&type= ${ tp } ` ;
2021-10-16 19:28:04 +02:00
if ( crossRepoSearch === 'true' ) {
2022-04-07 20:59:56 +02:00
issueSearchUrl = ` ${ appSubUrl } /issues/search?q={query}&priority_repo_id= ${ repoId } &type= ${ tp } ` ;
2021-10-16 19:28:04 +02:00
}
$ ( '#new-dependency-drop-list' )
. dropdown ( {
apiSettings : {
url : issueSearchUrl ,
onResponse ( response ) {
const filteredResponse = { success : true , results : [ ] } ;
const currIssueId = $ ( '#new-dependency-drop-list' ) . data ( 'issue-id' ) ;
// Parse the response from the api to work with our dropdown
$ . each ( response , ( _i , issue ) => {
// Don't list current issue in the dependency list.
if ( issue . id === currIssueId ) {
return ;
}
filteredResponse . results . push ( {
name : ` # ${ issue . number } ${ htmlEscape ( issue . title )
} < div class = "text small dont-break-out" > $ { htmlEscape ( issue . repository . full _name ) } < / d i v > ` ,
value : issue . id ,
} ) ;
} ) ;
return filteredResponse ;
} ,
cache : false ,
} ,
fullTextSearch : true ,
} ) ;
function excludeLabel ( item ) {
const href = $ ( item ) . attr ( 'href' ) ;
const id = $ ( item ) . data ( 'label-id' ) ;
const regStr = ` labels=((?:-?[0-9]+%2c)*)( ${ id } )((?:%2c-?[0-9]+)*)& ` ;
const newStr = 'labels=$1-$2$3&' ;
window . location = href . replace ( new RegExp ( regStr ) , newStr ) ;
}
$ ( '.menu a.label-filter-item' ) . each ( function ( ) {
$ ( this ) . on ( 'click' , function ( e ) {
if ( e . altKey ) {
e . preventDefault ( ) ;
excludeLabel ( this ) ;
}
} ) ;
} ) ;
$ ( '.menu .ui.dropdown.label-filter' ) . on ( 'keydown' , ( e ) => {
if ( e . altKey && e . keyCode === 13 ) {
const selectedItems = $ ( '.menu .ui.dropdown.label-filter .menu .item.selected' ) ;
if ( selectedItems . length > 0 ) {
excludeLabel ( $ ( selectedItems [ 0 ] ) ) ;
}
}
} ) ;
}
export function initRepoIssueCommentDelete ( ) {
// Delete comment
$ ( document ) . on ( 'click' , '.delete-comment' , function ( ) {
const $this = $ ( this ) ;
if ( window . confirm ( $this . data ( 'locale' ) ) ) {
$ . post ( $this . data ( 'url' ) , {
2021-10-21 09:37:43 +02:00
_csrf : csrfToken ,
2021-10-16 19:28:04 +02:00
} ) . done ( ( ) => {
const $conversationHolder = $this . closest ( '.conversation-holder' ) ;
2022-05-07 07:35:12 +02:00
// Check if this was a pending comment.
if ( $conversationHolder . find ( '.pending-label' ) . length ) {
const $counter = $ ( '#review-box .review-comments-counter' ) ;
let num = parseInt ( $counter . attr ( 'data-pending-comment-number' ) ) - 1 || 0 ;
num = Math . max ( num , 0 ) ;
$counter . attr ( 'data-pending-comment-number' , num ) ;
$counter . text ( num ) ;
}
2021-10-16 19:28:04 +02:00
$ ( ` # ${ $this . data ( 'comment-id' ) } ` ) . remove ( ) ;
if ( $conversationHolder . length && ! $conversationHolder . find ( '.comment' ) . length ) {
const path = $conversationHolder . data ( 'path' ) ;
const side = $conversationHolder . data ( 'side' ) ;
const idx = $conversationHolder . data ( 'idx' ) ;
const lineType = $conversationHolder . closest ( 'tr' ) . data ( 'line-type' ) ;
if ( lineType === 'same' ) {
2021-11-19 03:28:27 +01:00
$ ( ` [data-path=" ${ path } "] a.add-code-comment[data-idx=" ${ idx } "] ` ) . removeClass ( 'invisible' ) ;
2021-10-16 19:28:04 +02:00
} else {
2021-11-19 03:28:27 +01:00
$ ( ` [data-path=" ${ path } "] a.add-code-comment[data-side=" ${ side } "][data-idx=" ${ idx } "] ` ) . removeClass ( 'invisible' ) ;
2021-10-16 19:28:04 +02:00
}
$conversationHolder . remove ( ) ;
}
} ) ;
}
return false ;
} ) ;
}
export function initRepoIssueDependencyDelete ( ) {
// Delete Issue dependency
$ ( document ) . on ( 'click' , '.delete-dependency-button' , ( e ) => {
2021-11-22 09:19:01 +01:00
const id = e . currentTarget . getAttribute ( 'data-id' ) ;
const type = e . currentTarget . getAttribute ( 'data-type' ) ;
2021-10-16 19:28:04 +02:00
$ ( '.remove-dependency' ) . modal ( {
closable : false ,
duration : 200 ,
onApprove : ( ) => {
$ ( '#removeDependencyID' ) . val ( id ) ;
$ ( '#dependencyType' ) . val ( type ) ;
$ ( '#removeDependencyForm' ) . trigger ( 'submit' ) ;
} ,
} ) . modal ( 'show' ) ;
} ) ;
}
export function initRepoIssueCodeCommentCancel ( ) {
// Cancel inline code comment
$ ( document ) . on ( 'click' , '.cancel-code-comment' , ( e ) => {
const form = $ ( e . currentTarget ) . closest ( 'form' ) ;
if ( form . length > 0 && form . hasClass ( 'comment-form' ) ) {
2023-02-19 05:06:14 +01:00
form . addClass ( 'gt-hidden' ) ;
showElem ( form . closest ( '.comment-code-cloud' ) . find ( 'button.comment-form-reply' ) ) ;
2021-10-16 19:28:04 +02:00
} else {
form . closest ( '.comment-code-cloud' ) . remove ( ) ;
}
} ) ;
}
export function initRepoIssueStatusButton ( ) {
// Change status
const $statusButton = $ ( '#status-button' ) ;
$ ( '#comment-form textarea' ) . on ( 'keyup' , function ( ) {
2021-12-10 03:51:27 +01:00
const easyMDE = getAttachedEasyMDE ( this ) ;
const value = easyMDE ? . value ( ) || $ ( this ) . val ( ) ;
2021-10-16 19:28:04 +02:00
$statusButton . text ( $statusButton . data ( value . length === 0 ? 'status' : 'status-and-comment' ) ) ;
} ) ;
2023-03-14 04:34:09 +01:00
$statusButton . on ( 'click' , ( e ) => {
e . preventDefault ( ) ;
2021-10-16 19:28:04 +02:00
$ ( '#status' ) . val ( $statusButton . data ( 'status-val' ) ) ;
$ ( '#comment-form' ) . trigger ( 'submit' ) ;
} ) ;
}
export function initRepoPullRequestUpdate ( ) {
// Pull Request update button
const $pullUpdateButton = $ ( '.update-button > button' ) ;
$pullUpdateButton . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
const $this = $ ( this ) ;
const redirect = $this . data ( 'redirect' ) ;
$this . addClass ( 'loading' ) ;
$ . post ( $this . data ( 'do' ) , {
2021-10-21 09:37:43 +02:00
_csrf : csrfToken
2021-10-16 19:28:04 +02:00
} ) . done ( ( data ) => {
if ( data . redirect ) {
window . location . href = data . redirect ;
} else if ( redirect ) {
window . location . href = redirect ;
} else {
window . location . reload ( ) ;
}
} ) ;
} ) ;
$ ( '.update-button > .dropdown' ) . dropdown ( {
onChange ( _text , _value , $choice ) {
const $url = $choice . data ( 'do' ) ;
if ( $url ) {
$pullUpdateButton . find ( '.button-text' ) . text ( $choice . text ( ) ) ;
$pullUpdateButton . data ( 'do' , $url ) ;
}
}
} ) ;
}
export function initRepoPullRequestMergeInstruction ( ) {
$ ( '.show-instruction' ) . on ( 'click' , ( ) => {
2023-02-19 05:06:14 +01:00
toggleElem ( $ ( '.instruct-content' ) ) ;
2021-10-16 19:28:04 +02:00
} ) ;
}
2022-04-28 17:45:33 +02:00
export function initRepoPullRequestAllowMaintainerEdit ( ) {
const $checkbox = $ ( '#allow-edits-from-maintainers' ) ;
if ( ! $checkbox . length ) return ;
const promptError = $checkbox . attr ( 'data-prompt-error' ) ;
$checkbox . checkbox ( {
'onChange' : ( ) => {
const checked = $checkbox . checkbox ( 'is checked' ) ;
let url = $checkbox . attr ( 'data-url' ) ;
url += '/set_allow_maintainer_edit' ;
$checkbox . checkbox ( 'set disabled' ) ;
$ . ajax ( { url , type : 'POST' ,
data : { _csrf : csrfToken , allow _maintainer _edit : checked } ,
error : ( ) => {
2022-08-09 14:37:34 +02:00
showTemporaryTooltip ( $checkbox [ 0 ] , promptError ) ;
2022-04-28 17:45:33 +02:00
} ,
complete : ( ) => {
$checkbox . checkbox ( 'set enabled' ) ;
} ,
} ) ;
} ,
} ) ;
}
2021-10-16 19:28:04 +02:00
export function initRepoIssueReferenceRepositorySearch ( ) {
$ ( '.issue_reference_repository_search' )
. dropdown ( {
apiSettings : {
2022-04-07 20:59:56 +02:00
url : ` ${ appSubUrl } /repo/search?q={query}&limit=20 ` ,
2021-10-16 19:28:04 +02:00
onResponse ( response ) {
const filteredResponse = { success : true , results : [ ] } ;
$ . each ( response . data , ( _r , repo ) => {
filteredResponse . results . push ( {
name : htmlEscape ( repo . full _name ) ,
value : repo . full _name
} ) ;
} ) ;
return filteredResponse ;
} ,
cache : false ,
} ,
onChange ( _value , _text , $choice ) {
const $form = $choice . closest ( 'form' ) ;
2021-10-21 09:37:43 +02:00
$form . attr ( 'action' , ` ${ appSubUrl } / ${ _text } /issues/new ` ) ;
2021-10-16 19:28:04 +02:00
} ,
fullTextSearch : true
} ) ;
}
export function initRepoIssueWipTitle ( ) {
$ ( '.title_wip_desc > a' ) . on ( 'click' , ( e ) => {
e . preventDefault ( ) ;
const $issueTitle = $ ( '#issue_title' ) ;
$issueTitle . focus ( ) ;
const value = $issueTitle . val ( ) . trim ( ) . toUpperCase ( ) ;
const wipPrefixes = $ ( '.title_wip_desc' ) . data ( 'wip-prefixes' ) ;
for ( const prefix of wipPrefixes ) {
if ( value . startsWith ( prefix . toUpperCase ( ) ) ) {
return ;
}
}
$issueTitle . val ( ` ${ wipPrefixes [ 0 ] } ${ $issueTitle . val ( ) } ` ) ;
} ) ;
}
2021-11-16 03:21:13 +01:00
export async function updateIssuesMeta ( url , action , issueIds , elementId ) {
return $ . ajax ( {
type : 'POST' ,
url ,
data : {
_csrf : csrfToken ,
action ,
issue _ids : issueIds ,
id : elementId ,
} ,
2021-10-16 19:28:04 +02:00
} ) ;
}
export function initRepoIssueComments ( ) {
if ( $ ( '.repository.view.issue .timeline' ) . length === 0 ) return ;
2021-11-22 09:19:01 +01:00
$ ( '.re-request-review' ) . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
2021-10-16 19:28:04 +02:00
const url = $ ( this ) . data ( 'update-url' ) ;
const issueId = $ ( this ) . data ( 'issue-id' ) ;
const id = $ ( this ) . data ( 'id' ) ;
const isChecked = $ ( this ) . hasClass ( 'checked' ) ;
updateIssuesMeta (
url ,
isChecked ? 'detach' : 'attach' ,
issueId ,
id ,
2021-12-04 07:43:14 +01:00
) . then ( ( ) => window . location . reload ( ) ) ;
2021-10-16 19:28:04 +02:00
} ) ;
$ ( '.dismiss-review-btn' ) . on ( 'click' , function ( e ) {
e . preventDefault ( ) ;
const $this = $ ( this ) ;
const $dismissReviewModal = $this . next ( ) ;
$dismissReviewModal . modal ( 'show' ) ;
} ) ;
$ ( document ) . on ( 'click' , ( event ) => {
const urlTarget = $ ( ':target' ) ;
if ( urlTarget . length === 0 ) return ;
const urlTargetId = urlTarget . attr ( 'id' ) ;
if ( ! urlTargetId ) return ;
if ( ! /^(issue|pull)(comment)?-\d+$/ . test ( urlTargetId ) ) return ;
const $target = $ ( event . target ) ;
if ( $target . closest ( ` # ${ urlTargetId } ` ) . length === 0 ) {
const scrollPosition = $ ( window ) . scrollTop ( ) ;
window . location . hash = '' ;
$ ( window ) . scrollTop ( scrollPosition ) ;
window . history . pushState ( null , null , ' ' ) ;
}
} ) ;
}
function assignMenuAttributes ( menu ) {
const id = Math . floor ( Math . random ( ) * Math . floor ( 1000000 ) ) ;
menu . attr ( 'data-write' , menu . attr ( 'data-write' ) + id ) ;
menu . attr ( 'data-preview' , menu . attr ( 'data-preview' ) + id ) ;
menu . find ( '.item' ) . each ( function ( ) {
const tab = $ ( this ) . attr ( 'data-tab' ) + id ;
$ ( this ) . attr ( 'data-tab' , tab ) ;
} ) ;
menu . parent ( ) . find ( "*[data-tab='write']" ) . attr ( 'data-tab' , ` write ${ id } ` ) ;
menu . parent ( ) . find ( "*[data-tab='preview']" ) . attr ( 'data-tab' , ` preview ${ id } ` ) ;
initCompMarkupContentPreviewTab ( menu . parent ( '.form' ) ) ;
return id ;
}
2023-03-02 20:53:22 +01:00
export async function handleReply ( $el ) {
hideElem ( $el ) ;
const form = $el . closest ( '.comment-code-cloud' ) . find ( '.comment-form' ) ;
form . removeClass ( 'gt-hidden' ) ;
const $textarea = form . find ( 'textarea' ) ;
let easyMDE = getAttachedEasyMDE ( $textarea ) ;
if ( ! easyMDE ) {
await attachTribute ( $textarea . get ( ) , { mentions : true , emoji : true } ) ;
easyMDE = await createCommentEasyMDE ( $textarea ) ;
}
$textarea . focus ( ) ;
easyMDE . codemirror . focus ( ) ;
assignMenuAttributes ( form . find ( '.menu' ) ) ;
return easyMDE ;
}
2021-10-16 19:28:04 +02:00
export function initRepoPullRequestReview ( ) {
if ( window . location . hash && window . location . hash . startsWith ( '#issuecomment-' ) ) {
Imrove scroll behavior to hash issuecomment(scroll position, auto expand if file is folded, and on refreshing) (#23513)
Close #23466
Right now on pull request "files Changed" tab, if a file is viewed, when
the comments' links are visited, the comment will not be shown as the
file is folded after viewed. This PR is to improve the behavior, to make
the comment seen even the related file is folded, like on github.
And right now scroll position will be remembered and hence it won’t
scroll to hashed comment after refreshing, this PR also adjust the
scroll position remembering behavior: When there is hash comment in url,
do not remember the scroll position.
Before:
https://user-images.githubusercontent.com/17645053/225512079-6cf79581-9346-44cf-95d6-06919642e6a8.mov
After:
https://user-images.githubusercontent.com/17645053/225523753-3f6728f2-977b-4ed0-a65c-63dcef2ace80.mov
Update - long comment's behavior after using `scrollTop ` (Comment div
scroll to the position which is 30px below the diff header, or 30px
below top on conversation tab):
https://user-images.githubusercontent.com/17645053/225614460-0602c1a6-229c-41f4-84d2-334e78251486.mov
2023-03-17 11:24:18 +01:00
// set scrollRestoration to 'manual' when there is a hash in url, so that the scroll position will not be remembered after refreshing
if ( window . history . scrollRestoration !== 'manual' ) {
window . history . scrollRestoration = 'manual' ;
}
2021-10-16 19:28:04 +02:00
const commentDiv = $ ( window . location . hash ) ;
if ( commentDiv ) {
// get the name of the parent id
const groupID = commentDiv . closest ( 'div[id^="code-comments-"]' ) . attr ( 'id' ) ;
if ( groupID && groupID . startsWith ( 'code-comments-' ) ) {
2022-02-18 07:50:36 +01:00
const id = groupID . slice ( 14 ) ;
Imrove scroll behavior to hash issuecomment(scroll position, auto expand if file is folded, and on refreshing) (#23513)
Close #23466
Right now on pull request "files Changed" tab, if a file is viewed, when
the comments' links are visited, the comment will not be shown as the
file is folded after viewed. This PR is to improve the behavior, to make
the comment seen even the related file is folded, like on github.
And right now scroll position will be remembered and hence it won’t
scroll to hashed comment after refreshing, this PR also adjust the
scroll position remembering behavior: When there is hash comment in url,
do not remember the scroll position.
Before:
https://user-images.githubusercontent.com/17645053/225512079-6cf79581-9346-44cf-95d6-06919642e6a8.mov
After:
https://user-images.githubusercontent.com/17645053/225523753-3f6728f2-977b-4ed0-a65c-63dcef2ace80.mov
Update - long comment's behavior after using `scrollTop ` (Comment div
scroll to the position which is 30px below the diff header, or 30px
below top on conversation tab):
https://user-images.githubusercontent.com/17645053/225614460-0602c1a6-229c-41f4-84d2-334e78251486.mov
2023-03-17 11:24:18 +01:00
const ancestorDiffBox = commentDiv . closest ( '.diff-file-box' ) ;
// on pages like conversation, there is no diff header
const diffHeader = ancestorDiffBox . find ( '.diff-file-header' ) ;
// offset is for scrolling
let offset = 30 ;
if ( diffHeader [ 0 ] ) {
offset += $ ( '.diff-detail-box' ) . outerHeight ( ) + diffHeader . outerHeight ( ) ;
}
2023-02-16 13:07:21 +01:00
$ ( ` #show-outdated- ${ id } ` ) . addClass ( 'gt-hidden' ) ;
$ ( ` #code-comments- ${ id } ` ) . removeClass ( 'gt-hidden' ) ;
$ ( ` #code-preview- ${ id } ` ) . removeClass ( 'gt-hidden' ) ;
$ ( ` #hide-outdated- ${ id } ` ) . removeClass ( 'gt-hidden' ) ;
Imrove scroll behavior to hash issuecomment(scroll position, auto expand if file is folded, and on refreshing) (#23513)
Close #23466
Right now on pull request "files Changed" tab, if a file is viewed, when
the comments' links are visited, the comment will not be shown as the
file is folded after viewed. This PR is to improve the behavior, to make
the comment seen even the related file is folded, like on github.
And right now scroll position will be remembered and hence it won’t
scroll to hashed comment after refreshing, this PR also adjust the
scroll position remembering behavior: When there is hash comment in url,
do not remember the scroll position.
Before:
https://user-images.githubusercontent.com/17645053/225512079-6cf79581-9346-44cf-95d6-06919642e6a8.mov
After:
https://user-images.githubusercontent.com/17645053/225523753-3f6728f2-977b-4ed0-a65c-63dcef2ace80.mov
Update - long comment's behavior after using `scrollTop ` (Comment div
scroll to the position which is 30px below the diff header, or 30px
below top on conversation tab):
https://user-images.githubusercontent.com/17645053/225614460-0602c1a6-229c-41f4-84d2-334e78251486.mov
2023-03-17 11:24:18 +01:00
// if the comment box is folded, expand it
if ( ancestorDiffBox . attr ( 'data-folded' ) && ancestorDiffBox . attr ( 'data-folded' ) === 'true' ) {
setFileFolding ( ancestorDiffBox [ 0 ] , ancestorDiffBox . find ( '.fold-file' ) [ 0 ] , false ) ;
}
window . scrollTo ( {
top : commentDiv . offset ( ) . top - offset ,
behavior : 'instant'
} ) ;
2021-10-16 19:28:04 +02:00
}
}
}
$ ( document ) . on ( 'click' , '.show-outdated' , function ( e ) {
e . preventDefault ( ) ;
const id = $ ( this ) . data ( 'comment' ) ;
2023-02-16 13:07:21 +01:00
$ ( this ) . addClass ( 'gt-hidden' ) ;
$ ( ` #code-comments- ${ id } ` ) . removeClass ( 'gt-hidden' ) ;
$ ( ` #code-preview- ${ id } ` ) . removeClass ( 'gt-hidden' ) ;
$ ( ` #hide-outdated- ${ id } ` ) . removeClass ( 'gt-hidden' ) ;
2021-10-16 19:28:04 +02:00
} ) ;
$ ( document ) . on ( 'click' , '.hide-outdated' , function ( e ) {
e . preventDefault ( ) ;
const id = $ ( this ) . data ( 'comment' ) ;
2023-02-16 13:07:21 +01:00
$ ( this ) . addClass ( 'gt-hidden' ) ;
$ ( ` #code-comments- ${ id } ` ) . addClass ( 'gt-hidden' ) ;
$ ( ` #code-preview- ${ id } ` ) . addClass ( 'gt-hidden' ) ;
$ ( ` #show-outdated- ${ id } ` ) . removeClass ( 'gt-hidden' ) ;
2021-10-16 19:28:04 +02:00
} ) ;
2022-01-05 13:17:25 +01:00
$ ( document ) . on ( 'click' , 'button.comment-form-reply' , async function ( e ) {
2021-10-16 19:28:04 +02:00
e . preventDefault ( ) ;
2023-03-02 20:53:22 +01:00
await handleReply ( $ ( this ) ) ;
2021-10-16 19:28:04 +02:00
} ) ;
2023-02-21 14:36:53 +01:00
const $reviewBox = $ ( '.review-box-panel' ) ;
2021-10-16 19:28:04 +02:00
if ( $reviewBox . length === 1 ) {
2022-01-08 14:33:32 +01:00
( async ( ) => {
2022-01-18 17:57:57 +01:00
// the editor's height is too large in some cases, and the panel cannot be scrolled with page now because there is `.repository .diff-detail-box.sticky { position: sticky; }`
// the temporary solution is to make the editor's height smaller (about 4 lines). GitHub also only show 4 lines for default. We can improve the UI (including Dropzone area) in future
2022-03-04 21:57:26 +01:00
// EasyMDE's options can not handle minHeight & maxHeight together correctly, we have to set max-height for .CodeMirror-scroll in CSS.
2022-06-28 19:52:58 +02:00
const $reviewTextarea = $reviewBox . find ( 'textarea' ) ;
const easyMDE = await createCommentEasyMDE ( $reviewTextarea , { minHeight : '80px' } ) ;
initEasyMDEImagePaste ( easyMDE , $reviewBox . find ( '.dropzone' ) ) ;
2022-01-08 14:33:32 +01:00
} ) ( ) ;
2021-10-16 19:28:04 +02:00
}
// The following part is only for diff views
if ( $ ( '.repository.pull.diff' ) . length === 0 ) {
return ;
}
2023-03-17 18:24:00 +01:00
const $reviewBtn = $ ( '.js-btn-review' ) ;
const $panel = $reviewBtn . parent ( ) . find ( '.review-box-panel' ) ;
const $closeBtn = $panel . find ( '.close' ) ;
2023-03-18 22:08:38 +01:00
if ( $reviewBtn . length && $panel . length ) {
const tippy = createTippy ( $reviewBtn [ 0 ] , {
content : $panel [ 0 ] ,
placement : 'bottom' ,
trigger : 'click' ,
role : 'menu' ,
maxWidth : 'none' ,
interactive : true ,
hideOnClick : true ,
} ) ;
2023-03-17 18:24:00 +01:00
2023-03-18 22:08:38 +01:00
$closeBtn . on ( 'click' , ( e ) => {
e . preventDefault ( ) ;
tippy . hide ( ) ;
} ) ;
}
2021-10-16 19:28:04 +02:00
2021-11-20 13:12:59 +01:00
$ ( document ) . on ( 'click' , 'a.add-code-comment' , async function ( e ) {
2021-10-16 19:28:04 +02:00
if ( $ ( e . target ) . hasClass ( 'btn-add-single' ) ) return ; // https://github.com/go-gitea/gitea/issues/4745
e . preventDefault ( ) ;
const isSplit = $ ( this ) . closest ( '.code-diff' ) . hasClass ( 'code-diff-split' ) ;
const side = $ ( this ) . data ( 'side' ) ;
const idx = $ ( this ) . data ( 'idx' ) ;
2021-11-19 03:28:27 +01:00
const path = $ ( this ) . closest ( '[data-path]' ) . data ( 'path' ) ;
2021-10-16 19:28:04 +02:00
const tr = $ ( this ) . closest ( 'tr' ) ;
const lineType = tr . data ( 'line-type' ) ;
let ntr = tr . next ( ) ;
if ( ! ntr . hasClass ( 'add-comment' ) ) {
ntr = $ ( `
< tr class = "add-comment" data - line - type = "${lineType}" >
$ { isSplit ? `
< td class = "lines-num" > < / t d >
2022-01-25 13:18:30 +01:00
< td class = "lines-escape" > < / t d >
2021-10-16 19:28:04 +02:00
< td class = "lines-type-marker" > < / t d >
2022-10-25 13:11:49 +02:00
< td class = "add-comment-left" colspan = "4" > < / t d >
2021-10-16 19:28:04 +02:00
< td class = "lines-num" > < / t d >
2022-01-25 13:18:30 +01:00
< td class = "lines-escape" > < / t d >
2021-10-16 19:28:04 +02:00
< td class = "lines-type-marker" > < / t d >
2022-10-25 13:11:49 +02:00
< td class = "add-comment-right" colspan = "4" > < / t d >
2021-10-16 19:28:04 +02:00
` : `
2022-02-01 20:08:33 +01:00
< td class = "lines-num" > < / t d >
< td class = "lines-num" > < / t d >
< td class = "lines-escape" > < / t d >
2022-10-25 13:11:49 +02:00
< td class = "add-comment-left add-comment-right" colspan = "5" > < / t d >
2021-10-16 19:28:04 +02:00
` }
< / t r > ` ) ;
tr . after ( ntr ) ;
}
const td = ntr . find ( ` .add-comment- ${ side } ` ) ;
let commentCloud = td . find ( '.comment-code-cloud' ) ;
2023-03-04 08:13:37 +01:00
if ( commentCloud . length === 0 && ! ntr . find ( 'button[name="pending_review"]' ) . length ) {
2021-11-14 06:28:57 +01:00
const data = await $ . get ( $ ( this ) . closest ( '[data-new-comment-url]' ) . data ( 'new-comment-url' ) ) ;
2021-10-16 19:28:04 +02:00
td . html ( data ) ;
commentCloud = td . find ( '.comment-code-cloud' ) ;
assignMenuAttributes ( commentCloud . find ( '.menu' ) ) ;
td . find ( "input[name='line']" ) . val ( idx ) ;
td . find ( "input[name='side']" ) . val ( side === 'left' ? 'previous' : 'proposed' ) ;
td . find ( "input[name='path']" ) . val ( path ) ;
const $textarea = commentCloud . find ( 'textarea' ) ;
2022-01-05 13:17:25 +01:00
await attachTribute ( $textarea . get ( ) , { mentions : true , emoji : true } ) ;
const easyMDE = await createCommentEasyMDE ( $textarea ) ;
2021-10-16 19:28:04 +02:00
$textarea . focus ( ) ;
2021-12-10 03:51:27 +01:00
easyMDE . codemirror . focus ( ) ;
2021-10-16 19:28:04 +02:00
}
} ) ;
}
export function initRepoIssueReferenceIssue ( ) {
// Reference issue
$ ( document ) . on ( 'click' , '.reference-issue' , function ( event ) {
const $this = $ ( this ) ;
2022-12-09 17:25:32 +01:00
const content = $ ( ` # ${ $this . data ( 'target' ) } ` ) . text ( ) ;
2021-10-16 19:28:04 +02:00
const poster = $this . data ( 'poster-username' ) ;
const reference = $this . data ( 'reference' ) ;
2022-01-16 12:19:26 +01:00
const $modal = $ ( $this . data ( 'modal' ) ) ;
2021-10-16 19:28:04 +02:00
$modal . find ( 'textarea[name="content"]' ) . val ( ` ${ content } \n \n _Originally posted by @ ${ poster } in ${ reference } _ ` ) ;
$modal . modal ( 'show' ) ;
event . preventDefault ( ) ;
} ) ;
}
export function initRepoIssueWipToggle ( ) {
// Toggle WIP
$ ( '.toggle-wip a, .toggle-wip button' ) . on ( 'click' , async ( e ) => {
e . preventDefault ( ) ;
2021-11-22 09:19:01 +01:00
const toggleWip = e . currentTarget . closest ( '.toggle-wip' ) ;
const title = toggleWip . getAttribute ( 'data-title' ) ;
const wipPrefix = toggleWip . getAttribute ( 'data-wip-prefix' ) ;
const updateUrl = toggleWip . getAttribute ( 'data-update-url' ) ;
2021-10-16 19:28:04 +02:00
await $ . post ( updateUrl , {
2021-10-21 09:37:43 +02:00
_csrf : csrfToken ,
2022-02-18 07:50:36 +01:00
title : title ? . startsWith ( wipPrefix ) ? title . slice ( wipPrefix . length ) . trim ( ) : ` ${ wipPrefix . trim ( ) } ${ title } ` ,
2021-10-16 19:28:04 +02:00
} ) ;
window . location . reload ( ) ;
} ) ;
}
export function initRepoIssueTitleEdit ( ) {
// Edit issue title
const $issueTitle = $ ( '#issue-title' ) ;
const $editInput = $ ( '#edit-title-input input' ) ;
const editTitleToggle = function ( ) {
2023-02-19 05:06:14 +01:00
toggleElem ( $issueTitle ) ;
toggleElem ( $ ( '.not-in-edit' ) ) ;
toggleElem ( $ ( '#edit-title-input' ) ) ;
toggleElem ( $ ( '#pull-desc' ) ) ;
toggleElem ( $ ( '#pull-desc-edit' ) ) ;
toggleElem ( $ ( '.in-edit' ) ) ;
2021-10-16 19:28:04 +02:00
$ ( '#issue-title-wrapper' ) . toggleClass ( 'edit-active' ) ;
$editInput . focus ( ) ;
return false ;
} ;
$ ( '#edit-title' ) . on ( 'click' , editTitleToggle ) ;
$ ( '#cancel-edit-title' ) . on ( 'click' , editTitleToggle ) ;
$ ( '#save-edit-title' ) . on ( 'click' , editTitleToggle ) . on ( 'click' , function ( ) {
const pullrequest _targetbranch _change = function ( update _url ) {
const targetBranch = $ ( '#pull-target-branch' ) . data ( 'branch' ) ;
const $branchTarget = $ ( '#branch_target' ) ;
if ( targetBranch === $branchTarget . text ( ) ) {
2023-02-09 18:11:16 +01:00
window . location . reload ( ) ;
2021-10-16 19:28:04 +02:00
return false ;
}
$ . post ( update _url , {
2021-10-21 09:37:43 +02:00
_csrf : csrfToken ,
2021-10-16 19:28:04 +02:00
target _branch : targetBranch
} ) . done ( ( data ) => {
$branchTarget . text ( data . base _branch ) ;
} ) . always ( ( ) => {
window . location . reload ( ) ;
} ) ;
} ;
2023-02-09 18:11:16 +01:00
const pullrequest _target _update _url = $ ( this ) . attr ( 'data-target-update-url' ) ;
2021-10-16 19:28:04 +02:00
if ( $editInput . val ( ) . length === 0 || $editInput . val ( ) === $issueTitle . text ( ) ) {
$editInput . val ( $issueTitle . text ( ) ) ;
pullrequest _targetbranch _change ( pullrequest _target _update _url ) ;
} else {
2023-02-09 18:11:16 +01:00
$ . post ( $ ( this ) . attr ( 'data-update-url' ) , {
2021-10-21 09:37:43 +02:00
_csrf : csrfToken ,
2021-10-16 19:28:04 +02:00
title : $editInput . val ( )
} , ( data ) => {
$editInput . val ( data . title ) ;
$issueTitle . text ( data . title ) ;
2023-02-09 18:11:16 +01:00
if ( pullrequest _target _update _url ) {
pullrequest _targetbranch _change ( pullrequest _target _update _url ) ; // it will reload the window
} else {
window . location . reload ( ) ;
}
2021-10-16 19:28:04 +02:00
} ) ;
}
return false ;
} ) ;
}
export function initRepoIssueBranchSelect ( ) {
const changeBranchSelect = function ( ) {
const selectionTextField = $ ( '#pull-target-branch' ) ;
const baseName = selectionTextField . data ( 'basename' ) ;
const branchNameNew = $ ( this ) . data ( 'branch' ) ;
const branchNameOld = selectionTextField . data ( 'branch' ) ;
// Replace branch name to keep translation from HTML template
selectionTextField . html ( selectionTextField . html ( ) . replace (
` ${ baseName } : ${ branchNameOld } ` ,
` ${ baseName } : ${ branchNameNew } `
) ) ;
selectionTextField . data ( 'branch' , branchNameNew ) ; // update branch name in setting
} ;
$ ( '#branch-select > .item' ) . on ( 'click' , changeBranchSelect ) ;
}