diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index 7c65d5a131..c247967161 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -204,15 +204,19 @@ const sfc = { }; }, - mounted() { + async mounted() { // load job data and then auto-reload periodically - this.loadJob(); + // need to await first loadJob so this.currentJobStepsStates is initialized and can be used in hashChangeListener + await this.loadJob(); this.intervalID = setInterval(this.loadJob, 1000); document.body.addEventListener('click', this.closeDropdown); + this.hashChangeListener(); + window.addEventListener('hashchange', this.hashChangeListener); }, beforeUnmount() { document.body.removeEventListener('click', this.closeDropdown); + window.removeEventListener('hashchange', this.hashChangeListener); }, unmounted() { @@ -280,14 +284,16 @@ const sfc = { this.fetchPost(`${this.run.link}/approve`); }, - createLogLine(line, startTime) { + createLogLine(line, startTime, stepIndex) { const div = document.createElement('div'); div.classList.add('job-log-line'); + div.setAttribute('id', `jobstep-${stepIndex}-${line.index}`); div._jobLogTime = line.timestamp; - const lineNumber = document.createElement('div'); - lineNumber.className = 'line-num'; + const lineNumber = document.createElement('a'); + lineNumber.classList.add('line-num', 'muted'); lineNumber.textContent = line.index; + lineNumber.setAttribute('href', `#jobstep-${stepIndex}-${line.index}`); div.append(lineNumber); // for "Show timestamps" @@ -318,7 +324,7 @@ const sfc = { for (const line of logLines) { // TODO: group support: ##[group]GroupTitle , ##[endgroup] const el = this.getLogsContainer(stepIndex); - el.append(this.createLogLine(line, startTime)); + el.append(this.createLogLine(line, startTime, stepIndex)); } }, @@ -429,6 +435,21 @@ const sfc = { } else { actionBodyEl.append(fullScreenEl); } + }, + async hashChangeListener() { + const selectedLogStep = window.location.hash; + if (!selectedLogStep) return; + const [_, step, _line] = selectedLogStep.split('-'); + if (!this.currentJobStepsStates[step]) return; + if (!this.currentJobStepsStates[step].expanded && this.currentJobStepsStates[step].cursor === null) { + this.currentJobStepsStates[step].expanded = true; + // need to await for load job if the step log is loaded for the first time + // so logline can be selected by querySelector + await this.loadJob(); + } + const logLine = this.$refs.steps.querySelector(selectedLogStep); + if (!logLine) return; + logLine.querySelector('.line-num').click(); } }, }; @@ -802,10 +823,15 @@ export function initRepositoryActionView() { display: flex; } -.job-step-section .job-step-logs .job-log-line:hover { +.job-log-line:hover, +.job-log-line:target { background-color: var(--color-console-hover-bg); } +.job-log-line:target { + scroll-margin-top: 95px; +} + /* class names 'log-time-seconds' and 'log-time-stamp' are used in the method toggleTimeDisplay */ .job-log-line .line-num, .log-time-seconds { width: 48px; @@ -814,6 +840,11 @@ export function initRepositoryActionView() { user-select: none; } +.job-log-line:target > .line-num { + color: var(--color-primary); + text-decoration: underline; +} + .log-time-seconds { padding-right: 2px; }