summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue
blob: 28a16cd846a2daab2344ca8e0ae04f72e1d03b21 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
<script>
import { GlIntersectionObserver } from '@gitlab/ui';
import LineHighlighter from '~/blob/line_highlighter';
import ChunkLine from './chunk_line.vue';

/*
 * We only highlight the chunk that is currently visible to the user.
 * By making use of the Intersection Observer API we can determine when a chunk becomes visible and highlight it accordingly.
 *
 * Content that is not visible to the user (i.e. not highlighted) do not need to look nice,
 * so by making text transparent and rendering raw (non-highlighted) text,
 * the browser spends less resources on painting content that is not immediately relevant.
 *
 * Why use transparent text as opposed to hiding content entirely?
 * 1. If content is hidden entirely, native find text (⌘ + F) won't work.
 * 2. When URL contains line numbers, the browser needs to be able to jump to the correct line.
 */
export default {
  components: {
    ChunkLine,
    GlIntersectionObserver,
  },
  props: {
    isFirstChunk: {
      type: Boolean,
      required: false,
      default: false,
    },
    chunkIndex: {
      type: Number,
      required: false,
      default: 0,
    },
    isHighlighted: {
      type: Boolean,
      required: true,
    },
    content: {
      type: String,
      required: true,
    },
    startingFrom: {
      type: Number,
      required: false,
      default: 0,
    },
    totalLines: {
      type: Number,
      required: false,
      default: 0,
    },
    totalChunks: {
      type: Number,
      required: false,
      default: 0,
    },
    language: {
      type: String,
      required: false,
      default: null,
    },
    blamePath: {
      type: String,
      required: true,
    },
  },
  data() {
    return {
      isLoading: true,
    };
  },
  computed: {
    lines() {
      return this.content.split('\n');
    },
  },

  created() {
    if (this.isFirstChunk) {
      this.isLoading = false;
      return;
    }

    window.requestIdleCallback(async () => {
      this.isLoading = false;
      const { hash } = this.$route;
      if (hash && this.totalChunks > 0 && this.totalChunks === this.chunkIndex + 1) {
        // when the last chunk is loaded scroll to the hash
        await this.$nextTick();
        const lineHighlighter = new LineHighlighter({ scrollBehavior: 'auto' });
        lineHighlighter.highlightHash(hash);
      }
    });
  },
  methods: {
    handleChunkAppear() {
      if (!this.isHighlighted) {
        this.$emit('appear', this.chunkIndex);
      }
    },
    calculateLineNumber(index) {
      return this.startingFrom + index + 1;
    },
  },
};
</script>
<template>
  <gl-intersection-observer @appear="handleChunkAppear">
    <div v-if="isHighlighted">
      <chunk-line
        v-for="(line, index) in lines"
        :key="index"
        :number="calculateLineNumber(index)"
        :content="line"
        :language="language"
        :blame-path="blamePath"
      />
    </div>
    <div v-else-if="!isLoading" class="gl-display-flex gl-text-transparent">
      <div class="gl-display-flex gl-flex-direction-column content-visibility-auto">
        <span
          v-for="(n, index) in totalLines"
          v-once
          :id="`L${calculateLineNumber(index)}`"
          :key="index"
          data-testid="line-number"
          v-text="calculateLineNumber(index)"
        ></span>
      </div>
      <div v-once class="gl-white-space-pre-wrap!" data-testid="content">{{ content }}</div>
    </div>
  </gl-intersection-observer>
</template>