summaryrefslogtreecommitdiff
path: root/app/assets/javascripts/vue_shared/components/source_viewer/components/chunk.vue
blob: 092e8ba6c1578ff4c798d96c66d6f77596b9f4af (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 glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import SafeHtml from '~/vue_shared/directives/safe_html';
import { getPageParamValue, getPageSearchString } from '~/blob/utils';

/*
 * 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) does not need to look nice,
 * so by rendering raw (non-highlighted) text, the browser spends less resources on painting
 * content that is not immediately relevant.
 * Why use plaintext as opposed to hiding content entirely?
 * If content is hidden entirely, native find text (⌘ + F) won't work.
 */
export default {
  components: {
    GlIntersectionObserver,
  },
  directives: {
    SafeHtml,
  },
  mixins: [glFeatureFlagMixin()],
  props: {
    isHighlighted: {
      type: Boolean,
      required: true,
    },
    chunkIndex: {
      type: Number,
      required: false,
      default: 0,
    },
    rawContent: {
      type: String,
      required: true,
    },
    highlightedContent: {
      type: String,
      required: true,
    },
    totalLines: {
      type: Number,
      required: false,
      default: 0,
    },
    startingFrom: {
      type: Number,
      required: false,
      default: 0,
    },
    blamePath: {
      type: String,
      required: true,
    },
  },
  data() {
    return {
      hasAppeared: false,
      isLoading: true,
    };
  },
  computed: {
    shouldHighlight() {
      return Boolean(this.highlightedContent) && (this.hasAppeared || this.isHighlighted);
    },
    lines() {
      return this.content.split('\n');
    },
    pageSearchString() {
      if (!this.glFeatures.fileLineBlame) return '';
      const page = getPageParamValue(this.number);
      return getPageSearchString(this.blamePath, page);
    },
  },
  created() {
    if (this.chunkIndex === 0) {
      // Display first chunk ASAP in order to improve perceived performance
      this.isLoading = false;
      return;
    }

    window.requestIdleCallback(() => {
      this.isLoading = false;
    });
  },
  methods: {
    handleChunkAppear() {
      this.hasAppeared = true;
    },
    calculateLineNumber(index) {
      return this.startingFrom + index + 1;
    },
  },
};
</script>
<template>
  <gl-intersection-observer @appear="handleChunkAppear">
    <div class="gl-display-flex">
      <div v-if="shouldHighlight" class="gl-display-flex gl-flex-direction-column">
        <div
          v-for="(n, index) in totalLines"
          :key="index"
          data-testid="line-numbers"
          class="gl-p-0! gl-z-index-3 diff-line-num gl-border-r gl-display-flex line-links line-numbers"
        >
          <a
            v-if="glFeatures.fileLineBlame"
            class="gl-user-select-none gl-shadow-none! file-line-blame"
            :href="`${blamePath}${pageSearchString}#L${calculateLineNumber(index)}`"
          ></a>
          <a
            :id="`L${calculateLineNumber(index)}`"
            class="gl-user-select-none gl-shadow-none! file-line-num"
            :href="`#L${calculateLineNumber(index)}`"
            :data-line-number="calculateLineNumber(index)"
          >
            {{ calculateLineNumber(index) }}
          </a>
        </div>
      </div>

      <div v-else-if="!isLoading" class="line-numbers gl-p-0! gl-mr-3 gl-text-transparent">
        <!-- Placeholder for line numbers while content is not highlighted -->
      </div>

      <pre
        class="gl-m-0 gl-p-0! gl-w-full gl-overflow-visible! gl-border-none! code highlight gl-line-height-0"
      ><code v-if="shouldHighlight" v-once v-safe-html="highlightedContent" data-testid="content"></code><code v-else-if="!isLoading" v-once class="line gl-white-space-pre-wrap! gl-ml-1" data-testid="content" v-text="rawContent"></code></pre>
    </div>
  </gl-intersection-observer>
</template>