feat(notes): render code files with Shikiji syntax highlighting

Non-markdown files opened as stacked notes are now highlighted using
the existing markdown-it-shikiji pipeline (4-backtick fence wrapping)
with a h1 filename heading. Edit controls are hidden for code files.
Adds alloy language grammar and a fileLanguage utility mapping
extensions to Shikiji language IDs.
This commit is contained in:
Julien Calixte
2026-04-27 19:57:15 +02:00
parent 812f393283
commit 9d6f70546e
4 changed files with 329 additions and 5 deletions

View File

@@ -13,7 +13,8 @@ import { useFile } from "@/hooks/useFile.hook"
import { useGitHubContent } from "@/hooks/useGitHubContent.hook"
import { useImages } from "@/hooks/useImages.hook"
import { useLinks } from "@/hooks/useLinks.hook"
import { runMermaid, useShikiji } from "@/hooks/useMarkdown.hook"
import { renderCodeFile, runMermaid, useShikiji } from "@/hooks/useMarkdown.hook"
import { getFileLanguage, isMarkdownPath } from "@/utils/fileLanguage"
import { useNoteOverlay } from "@/hooks/useNoteOverlay.hook"
import { useRouteQueryStackedNotes } from "@/hooks/useRouteQueryStackedNotes.hook"
import { useTitleNotes } from "@/hooks/useTitleNotes.hook"
@@ -53,6 +54,33 @@ const {
getEditedSha
} = useFile(sha)
const initialRawContent = ref<string | null>(null)
const isMarkdown = computed(() => (path.value ? isMarkdownPath(path.value) : true))
const displayedContent = ref("")
watch(
[rawContent, isMarkdown, path],
async ([raw, isMd, p]) => {
if (!raw) {
displayedContent.value = ""
return
}
if (isMd) {
displayedContent.value = content.value
return
}
const lang = p ? getFileLanguage(p) : null
const filename = p?.split("/").pop()
const result = await renderCodeFile(raw, lang, filename)
if (rawContent.value === raw) {
displayedContent.value = result
}
},
{ immediate: true }
)
watch(content, (c) => {
if (isMarkdown.value) displayedContent.value = c
})
const className = computed(() => `stacked-note-${props.index}`)
const { listenToClick } = useLinks(className.value, sha)
const titleClassName = computed(() => `title-${className.value}`)
@@ -92,7 +120,7 @@ watch([content, mode], () => {
runMermaid(`.note-${sha.value} .mermaid`)
}
if (rawContent.value.includes("```")) {
if (isMarkdown.value && rawContent.value.includes("```")) {
useShikiji()
}
})
@@ -157,6 +185,7 @@ watch(mode, async (newMode) => {
</a>
<section class="text-content">
<button
v-if="isMarkdown"
class="action button is-text is-light"
:class="{ 'is-link': mode === 'edit' }"
:style="mode === 'edit' ? 'color: var(--color-primary)' : ''"
@@ -205,10 +234,10 @@ watch(mode, async (newMode) => {
<path d="M14 4l0 4l-6 0l0 -4" />
</svg>
</button>
<div v-if="mode === 'edit'" class="edit">
<div v-if="mode === 'edit' && isMarkdown" class="edit">
<edit-note v-model="rawContent" />
</div>
<div v-if="mode === 'read'" class="note-content" v-html="content"></div>
<div v-if="mode === 'read'" class="note-content" v-html="displayedContent"></div>
</section>
<linked-notes v-if="hasBacklinks && content" :sha="sha" />
</div>