22import {SvgIcon } from ' ../svg.ts' ;
33import ActionRunStatus from ' ./ActionRunStatus.vue' ;
44import {defineComponent , type PropType } from ' vue' ;
5- import {createElementFromAttrs , toggleElem } from ' ../utils/dom.ts' ;
5+ import {addDelegatedEventListener , createElementFromAttrs , toggleElem } from ' ../utils/dom.ts' ;
66import {formatDatetime } from ' ../utils/time.ts' ;
77import {renderAnsi } from ' ../render/ansi.ts' ;
88import {POST , DELETE } from ' ../modules/fetch.ts' ;
@@ -188,6 +188,19 @@ export default defineComponent({
188188 // load job data and then auto-reload periodically
189189 // need to await first loadJob so this.currentJobStepsStates is initialized and can be used in hashChangeListener
190190 await this .loadJob ();
191+
192+ // auto-scroll to the bottom of the log group when it is opened
193+ // "toggle" event doesn't bubble, so we need to use 'click' event delegation to handle it
194+ addDelegatedEventListener (this .elStepsContainer (), ' click' , ' summary.job-log-group-summary' , (el , _ ) => {
195+ if (! this .optionAlwaysAutoScroll ) return ;
196+ const elJobLogGroup = el .closest (' details.job-log-group' ) as HTMLDetailsElement ;
197+ setTimeout (() => {
198+ if (elJobLogGroup .open && ! isLogElementInViewport (elJobLogGroup )) {
199+ elJobLogGroup .scrollIntoView ({behavior: ' smooth' , block: ' end' });
200+ }
201+ }, 0 );
202+ });
203+
191204 this .intervalID = setInterval (() => this .loadJob (), 1000 );
192205 document .body .addEventListener (' click' , this .closeDropdown );
193206 this .hashChangeListener ();
@@ -424,9 +437,13 @@ export default defineComponent({
424437 if (this .menuVisible ) this .menuVisible = false ;
425438 },
426439
440+ elStepsContainer(): HTMLElement {
441+ return this .$refs .stepsContainer as HTMLElement ;
442+ },
443+
427444 toggleTimeDisplay(type : ' seconds' | ' stamp' ) {
428445 this .timeVisible [` log-time-${type } ` ] = ! this .timeVisible [` log-time-${type } ` ];
429- for (const el of ( this .$refs . steps as HTMLElement ).querySelectorAll (` .log-time-${type } ` )) {
446+ for (const el of this .elStepsContainer ( ).querySelectorAll (` .log-time-${type } ` )) {
430447 toggleElem (el , this .timeVisible [` log-time-${type } ` ]);
431448 }
432449 },
@@ -448,7 +465,7 @@ export default defineComponent({
448465 // so logline can be selected by querySelector
449466 await this .loadJob ();
450467 }
451- const logLine = ( this .$refs . steps as HTMLElement ).querySelector (selectedLogStep );
468+ const logLine = this .elStepsContainer ( ).querySelector (selectedLogStep );
452469 if (! logLine ) return ;
453470 logLine .querySelector <HTMLAnchorElement >(' .line-num' ).click ();
454471 },
@@ -583,7 +600,7 @@ export default defineComponent({
583600 </div >
584601 </div >
585602 </div >
586- <div class =" job-step-container" ref =" steps " v-if =" currentJob.steps.length" >
603+ <div class =" job-step-container" ref =" stepsContainer " v-if =" currentJob.steps.length" >
587604 <div class =" job-step-section" v-for =" (jobStep, i) in currentJob.steps" :key =" i" >
588605 <div class =" job-step-summary" @click.stop =" isExpandable(jobStep.status) && toggleStepLogs(i)" :class =" [currentJobStepsStates[i].expanded ? 'selected' : '', isExpandable(jobStep.status) && 'step-expandable']" >
589606 <!-- If the job is done and the job step log is loaded for the first time, show the loading icon
0 commit comments