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
|
<script>
import { GlSafeHtmlDirective, GlLoadingIcon } from '@gitlab/ui';
import LineNumbers from '~/vue_shared/components/line_numbers.vue';
import { sanitize } from '~/lib/dompurify';
import { ROUGE_TO_HLJS_LANGUAGE_MAP } from './constants';
import { wrapLines } from './utils';
const LINE_SELECT_CLASS_NAME = 'hll';
export default {
components: {
LineNumbers,
GlLoadingIcon,
},
directives: {
SafeHtml: GlSafeHtmlDirective,
},
props: {
blob: {
type: Object,
required: true,
},
},
data() {
return {
languageDefinition: null,
content: this.blob.rawTextBlob,
language: ROUGE_TO_HLJS_LANGUAGE_MAP[this.blob.language],
hljs: null,
};
},
computed: {
lineNumbers() {
return this.content.split('\n').length;
},
highlightedContent() {
let highlightedContent;
if (this.hljs) {
if (!this.language) {
highlightedContent = this.hljs.highlightAuto(this.content).value;
} else if (this.languageDefinition) {
highlightedContent = this.hljs.highlight(this.content, { language: this.language }).value;
}
}
return wrapLines(highlightedContent);
},
},
watch: {
highlightedContent() {
this.$nextTick(() => this.selectLine());
},
$route() {
this.selectLine();
},
},
async mounted() {
this.hljs = await this.loadHighlightJS();
if (this.language) {
this.languageDefinition = await this.loadLanguage();
}
},
methods: {
loadHighlightJS() {
// If no language can be mapped to highlight.js we load all common languages else we load only the core (smallest footprint)
return !this.language ? import('highlight.js/lib/common') : import('highlight.js/lib/core');
},
async loadLanguage() {
let languageDefinition;
try {
languageDefinition = await import(`highlight.js/lib/languages/${this.language}`);
this.hljs.registerLanguage(this.language, languageDefinition.default);
} catch (message) {
this.$emit('error', message);
}
return languageDefinition;
},
selectLine() {
const hash = sanitize(this.$route.hash);
const lineToSelect = hash && this.$el.querySelector(hash);
if (!lineToSelect) {
return;
}
if (this.$options.currentlySelectedLine) {
this.$options.currentlySelectedLine.classList.remove(LINE_SELECT_CLASS_NAME);
}
lineToSelect.classList.add(LINE_SELECT_CLASS_NAME);
this.$options.currentlySelectedLine = lineToSelect;
lineToSelect.scrollIntoView({ behavior: 'smooth', block: 'center' });
},
},
userColorScheme: window.gon.user_color_scheme,
currentlySelectedLine: null,
};
</script>
<template>
<gl-loading-icon v-if="!highlightedContent" size="sm" class="gl-my-5" />
<div
v-else
class="file-content code js-syntax-highlight blob-content gl-display-flex"
:class="$options.userColorScheme"
data-type="simple"
data-qa-selector="blob_viewer_file_content"
>
<line-numbers :lines="lineNumbers" />
<pre class="code gl-pb-0!"><code v-safe-html="highlightedContent"></code>
</pre>
</div>
</template>
|