Welcome to mirror list, hosted at ThFree Co, Russian Federation.

highlight_mixin.js « mixins « repository « javascripts « assets « app - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 822a8b4ee38a2c468a3aa00aeab5e80600c697d8 (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
import {
  LEGACY_FALLBACKS,
  EVENT_ACTION,
  EVENT_LABEL_FALLBACK,
  LINES_PER_CHUNK,
} from '~/vue_shared/components/source_viewer/constants';
import { splitIntoChunks } from '~/vue_shared/components/source_viewer/workers/highlight_utils';
import LineHighlighter from '~/blob/line_highlighter';
import languageLoader from '~/content_editor/services/highlight_js_language_loader';
import Tracking from '~/tracking';
import { TEXT_FILE_TYPE } from '../constants';

/*
 * This mixin is intended to be used as an interface between our highlight worker and Vue components
 */
export default {
  mixins: [Tracking.mixin()],
  inject: {
    highlightWorker: { default: null },
  },
  data() {
    return {
      chunks: [],
    };
  },
  methods: {
    trackEvent(label, language) {
      this.track(EVENT_ACTION, { label, property: language });
    },
    isUnsupportedLanguage(language) {
      const supportedLanguages = Object.keys(languageLoader);
      const isUnsupportedLanguage = !supportedLanguages.includes(language);

      return LEGACY_FALLBACKS.includes(language) || isUnsupportedLanguage;
    },
    handleUnsupportedLanguage(language) {
      this.trackEvent(EVENT_LABEL_FALLBACK, language);
      this?.onError();
    },
    initHighlightWorker({ rawTextBlob, language, simpleViewer, fileType }) {
      if (simpleViewer?.fileType !== TEXT_FILE_TYPE || !this.glFeatures.highlightJsWorker) return;

      if (this.isUnsupportedLanguage(language)) {
        this.handleUnsupportedLanguage(language);
        return;
      }

      /*
       * We want to start rendering content as soon as possible, but highlighting large amounts of
       * content can take long, so we render the content in phases:
       *
       * 1. `splitIntoChunks` with the first 70 lines of raw text.
       *     This ensures that we start rendering raw content in the DOM as soon as we can so that
       *     the user can see content as fast as possible (improves perceived performance and LCP).
       * 2. `instructWorker` to start highlighting the first 70 lines.
       *     This ensures that we display highlighted** content to the user as fast as possible
       *     (improves perceived performance and makes the first 70 lines look nice).
       * 3. `instructWorker` to start highlighting all the content.
       *     This is the longest task. It ensures that we highlight all content, since the first 70
       *     lines are already rendered, this can happen in the background.
       */

      // Render the first 70 lines (raw text) ASAP, this improves perceived performance and LCP.
      const firstSeventyLines = rawTextBlob.split(/\r?\n/).slice(0, LINES_PER_CHUNK).join('\n');

      this.chunks = splitIntoChunks(language, firstSeventyLines);

      this.highlightWorker.onmessage = this.handleWorkerMessage;

      // Instruct the worker to highlight the first 70 lines ASAP, this improves perceived performance.
      this.instructWorker(firstSeventyLines, language);

      // Instruct the worker to start highlighting all lines in the background.
      this.instructWorker(rawTextBlob, language, fileType);
    },
    handleWorkerMessage({ data }) {
      this.chunks = data;
      this.highlightHash(); // highlight the line if a line number hash is present in the URL
    },
    instructWorker(content, language, fileType) {
      this.highlightWorker.postMessage({ content, language, fileType });
    },
    async highlightHash() {
      const { hash } = this.$route;
      if (!hash) return;

      // Make the chunk containing the line number visible
      const lineNumber = hash.substring(hash.indexOf('L') + 1).split('-')[0];
      const chunkToHighlight = this.chunks.find(
        (chunk) =>
          chunk.startingFrom <= lineNumber && chunk.startingFrom + chunk.totalLines >= lineNumber,
      );

      if (chunkToHighlight) {
        chunkToHighlight.isHighlighted = true;
      }

      // Line numbers in the DOM needs to update first based on changes made to `chunks`.
      await this.$nextTick();

      const lineHighlighter = new LineHighlighter({ scrollBehavior: 'auto' });
      lineHighlighter.highlightHash(hash);
    },
  },
};