Repository: go-gitea/gitea
File: web_src/js/components/RepoActionView.vue (Vue)

Code before:
      const logTimeSeconds = createElementFromAttrs('span', {class: 'log-time-seconds'},
        `${seconds}s`, // for "Show seconds"
      );

      toggleElem(logTimeStamp, this.timeVisible['log-time-stamp']);
      toggleElem(logTimeSeconds, this.timeVisible['log-time-seconds']);

      return createElementFromAttrs('div', {id: `jobstep-${stepIndex}-${line.index}`, class: 'job-log-line'},
        lineNum, logTimeStamp, logMsg, logTimeSeconds,
      );
    },

    shouldAutoScroll(stepIndex: number): boolean {
      if (!this.optionAlwaysAutoScroll) return false;
      const el = this.getJobStepLogsContainer(stepIndex);
      // if the logs container is empty, then auto-scroll if the step is expanded
      if (!el.lastChild) return this.currentJobStepsStates[stepIndex].expanded;
      // use extraViewPortHeight to tolerate some extra "virtual view port" height (for example: the last line is partially visible)
      return isLogElementInViewport(el.lastChild as Element, {extraViewPortHeight: 5});
    },

    appendLogs(stepIndex: number, startTime: number, logLines: LogLine[]) {
      for (const line of logLines) {
        if (shouldHideLine(line)) {
          continue;
        }
        const el = this.getActiveLogsContainer(stepIndex);
        const cmd = parseLineCommand(line);
        if (cmd?.name === 'group') {
          this.beginLogGroup(stepIndex, startTime, line, cmd);
          continue;
        } else if (cmd?.name === 'endgroup') {
          this.endLogGroup(stepIndex, startTime, line, cmd);
          continue;
        }
        el.append(this.createLogLine(stepIndex, startTime, line));
      }
    },

    async deleteArtifact(name: string) {
      if (!window.confirm(this.locale.confirmDeleteArtifact.replace('%s', name))) return;
      // TODO: should escape the "name"?
      await DELETE(`${this.run.link}/artifacts/${name}`);
      await this.loadJobForce();
    },

    async fetchJobData(abortController: AbortController) {
      const logCursors = this.currentJobStepsStates.map((it, idx) => {
        // cursor is used to indicate the last position of the logs
        // it's only used by backend, frontend just reads it and passes it back, it and can be any type.
        // for example: make cursor=null means the first time to fetch logs, cursor=eof means no more logs, etc

Review comment (suggestion):
```suggestion
        if (shouldHideLine(line)) continue;
```

Code after:
    endLogGroup(stepIndex: number, startTime: number, line: LogLine, cmd: LogLineCommand) {
      const el = (this.$refs.logs as any)[stepIndex];
      el._stepLogsActiveContainer = null;
      el.append(this.createLogLine(stepIndex, startTime, {
        index: line.index,
        timestamp: line.timestamp,
        message: line.message.substring(cmd.prefix.length),
      }));
    },

    // show/hide the step logs for a step
    toggleStepLogs(idx: number) {
      this.currentJobStepsStates[idx].expanded = !this.currentJobStepsStates[idx].expanded;
      if (this.currentJobStepsStates[idx].expanded) {
        this.loadJobForce(); // try to load the data immediately instead of waiting for next timer interval
      } else if (this.currentJob.steps[idx].status === 'running') {
        this.currentJobStepsStates[idx].manuallyCollapsed = true;
      }
    },
    // cancel a run
    cancelRun() {
      POST(`${this.run.link}/cancel`);
    },
    // approve a run
    approveRun() {
      POST(`${this.run.link}/approve`);
    },

    createLogLine(stepIndex: number, startTime: number, line: LogLine) {
      const lineNum = createElementFromAttrs('a', {class: 'line-num muted', href: `#jobstep-${stepIndex}-${line.index}`},
        String(line.index),
      );

      const logTimeStamp = createElementFromAttrs('span', {class: 'log-time-stamp'},
        formatDatetime(new Date(line.timestamp * 1000)), // for "Show timestamps"
      );

      const logMsg = createElementFromAttrs('span', {class: 'log-msg'});
      logMsg.innerHTML = renderAnsi(line.message);

      const seconds = Math.floor(line.timestamp - startTime);
      const logTimeSeconds = createElementFromAttrs('span', {class: 'log-time-seconds'},
        `${seconds}s`, // for "Show seconds"
      );

      toggleElem(logTimeStamp, this.timeVisible['log-time-stamp']);
      toggleElem(logTimeSeconds, this.timeVisible['log-time-seconds']);

      return createElementFromAttrs('div', {id: `jobstep-${stepIndex}-${line.index}`, class: 'job-log-line'},
        lineNum, logTimeStamp, logMsg, logTimeSeconds,
      );