fix: resolve Prism syntax highlighting error in peer config modal

The PeerViewModal and InterfaceViewModal components displayed:
  Error: The language "undefined" has no grammar.

Root cause: vue-prism-component bundles its own instance of prismjs
(separate from the one imported elsewhere), so registering the ini
grammar on a different Prism instance had no effect on the one
actually performing the highlighting.

Fix: bypass vue-prism-component entirely. Import a single Prism
instance via a new helper (frontend/src/helpers/prism-setup.js) that
also registers the ini grammar, then render highlighted HTML directly
with <pre><code v-html="highlightedConfig">. This ensures the same
Prism object that owns the ini grammar is the one calling highlight().

Affected files:
- frontend/src/helpers/prism-setup.js (new): imports prismjs, defines
  the ini grammar on the Prism instance, re-exports it
- frontend/src/components/PeerViewModal.vue: replaces <Prism> with
  v-html using the shared Prism instance
- frontend/src/components/InterfaceViewModal.vue: same replacement
- frontend/src/main.js: removes the now-unused prism-setup import

Fixes #726
This commit is contained in:
Rene Luria
2026-07-10 17:49:36 +02:00
parent c800e7dd34
commit 8d1155224d
4 changed files with 59 additions and 6 deletions

View File

@@ -3,8 +3,7 @@ import Modal from "./Modal.vue";
import {computed, ref, watch} from "vue"; import {computed, ref, watch} from "vue";
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import {interfaceStore} from "@/stores/interfaces"; import {interfaceStore} from "@/stores/interfaces";
import Prism from 'vue-prism-component' import Prism from '@/helpers/prism-setup';
import 'prismjs/components/prism-ini'
const { t } = useI18n() const { t } = useI18n()
@@ -17,6 +16,14 @@ const props = defineProps({
const configString = ref("") const configString = ref("")
const highlightedConfig = computed(() => {
const grammar = Prism.languages.ini
if (grammar) {
return Prism.highlight(configString.value, grammar, 'ini')
}
return configString.value
})
const emit = defineEmits(['close']) const emit = defineEmits(['close'])
const selectedInterface = computed(() => { const selectedInterface = computed(() => {
@@ -51,7 +58,7 @@ function close() {
<template> <template>
<Modal :title="title" :visible="visible" @close="close"> <Modal :title="title" :visible="visible" @close="close">
<template #default> <template #default>
<Prism language="ini" :code="configString"></Prism> <pre class="language-ini"><code class="language-ini" v-html="highlightedConfig"></code></pre>
</template> </template>
<template #footer> <template #footer>
<button class="btn btn-primary" type="button" @click.prevent="close">{{ $t('general.close') }}</button> <button class="btn btn-primary" type="button" @click.prevent="close">{{ $t('general.close') }}</button>

View File

@@ -5,7 +5,7 @@ import { interfaceStore } from "@/stores/interfaces";
import { computed, ref, watch } from "vue"; import { computed, ref, watch } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { freshInterface, freshPeer, freshStats } from '@/helpers/models'; import { freshInterface, freshPeer, freshStats } from '@/helpers/models';
import Prism from "vue-prism-component"; import Prism from '@/helpers/prism-setup';
import { notify } from "@kyvg/vue3-notification"; import { notify } from "@kyvg/vue3-notification";
import { settingsStore } from "@/stores/settings"; import { settingsStore } from "@/stores/settings";
import { profileStore } from "@/stores/profile"; import { profileStore } from "@/stores/profile";
@@ -81,6 +81,14 @@ const title = computed(() => {
const configStyle = ref("wgquick") const configStyle = ref("wgquick")
const highlightedConfig = computed(() => {
const grammar = Prism.languages.ini
if (grammar) {
return Prism.highlight(configString.value, grammar, 'ini')
}
return configString.value
})
watch(() => props.visible, async (newValue, oldValue) => { watch(() => props.visible, async (newValue, oldValue) => {
if (oldValue === false && newValue === true) { // if modal is shown if (oldValue === false && newValue === true) { // if modal is shown
await peers.LoadPeerConfig(selectedPeer.value.Identifier, configStyle.value) await peers.LoadPeerConfig(selectedPeer.value.Identifier, configStyle.value)
@@ -217,7 +225,7 @@ function ConfigQrUrl() {
<div id="collapseConfig" class="accordion-collapse collapse" aria-labelledby="headingConfig" <div id="collapseConfig" class="accordion-collapse collapse" aria-labelledby="headingConfig"
data-bs-parent="#peerInformation" style=""> data-bs-parent="#peerInformation" style="">
<div class="accordion-body"> <div class="accordion-body">
<Prism language="ini" :code="configString"></Prism> <pre class="language-ini"><code class="language-ini" v-html="highlightedConfig"></code></pre>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,39 @@
import Prism from 'prismjs'
Prism.languages.ini = {
comment: {
pattern: /(^[ \f\t\v]*)[#;][^\n\r]*/m,
lookbehind: true
},
section: {
pattern: /(^[ \f\t\v]*)\[[^\n\r\]]*\]?/m,
lookbehind: true,
inside: {
'section-name': {
pattern: /(^\[[ \f\t\v]*)[^ \f\t\v\]]+(?:[ \f\t\v]+[^ \f\t\v\]]+)*/,
lookbehind: true,
alias: 'selector'
},
punctuation: /\[|\]/
}
},
key: {
pattern: /(^[ \f\t\v]*)[^ \f\n\r\t\v=]+(?:[ \f\t\v]+[^ \f\n\r\t\v=]+)*(?=[ \f\t\v]*=)/m,
lookbehind: true,
alias: 'attr-name'
},
value: {
pattern: /(=[ \f\t\v]*)[^ \f\n\r\t\v]+(?:[ \f\t\v]+[^ \f\n\r\t\v]+)*/,
lookbehind: true,
alias: 'attr-value',
inside: {
'inner-value': {
pattern: /^("|').+(?=\1$)/,
lookbehind: true
}
}
},
punctuation: /=/
}
export default Prism

View File

@@ -22,7 +22,6 @@ import "@fontsource/nunito-sans/600.css";
import "flag-icons/css/flag-icons.min.css" import "flag-icons/css/flag-icons.min.css"
// Syntax Highlighting // Syntax Highlighting
import 'prismjs'
import 'prismjs/themes/prism-okaidia.css' import 'prismjs/themes/prism-okaidia.css'
const app = createApp(App); const app = createApp(App);