debian-mirror-gitlab/app/assets/javascripts/repository/mixins/highlight_mixin.js
2023-04-23 21:23:45 +05:30

106 lines
4 KiB
JavaScript

import { nextTick } from 'vue';
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 }) {
if (simpleViewer?.fileType !== TEXT_FILE_TYPE) 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);
},
handleWorkerMessage({ data }) {
this.chunks = data;
this.highlightHash(); // highlight the line if a line number hash is present in the URL
},
instructWorker(content, language) {
this.highlightWorker.postMessage({ content, language });
},
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 nextTick();
const lineHighlighter = new LineHighlighter({ scrollBehavior: 'auto' });
lineHighlighter.highlightHash(hash);
},
},
};