Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 38 additions & 8 deletions site/app/assets/css/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
margin: 0;
padding: 1rem;
border-radius: 0;
background: transparent !important;
background: transparent;
font-size: 0.875rem;
line-height: 1.625;
}
Expand All @@ -53,15 +53,45 @@
font-size: inherit;
}

/* Shiki dual themes — sync with Nuxt UI color mode (html.dark) */
html:not(.dark) :where(.shiki, .shiki span) {
color: var(--shiki-light) !important;
background-color: var(--shiki-light-bg) !important;
/*
* Shiki dual themes (class-based), same theme names everywhere:
* light/default = light-plus, dark = dark-plus, defaultColor: false
* https://shiki.style/guide/dual-themes#class-based-dark-mode
*/
html .shiki {
background-color: var(--shiki-default-bg, var(--shiki-light-bg));
color: var(--shiki-default, var(--shiki-light));
}

html.dark :where(.shiki, .shiki span) {
color: var(--shiki-dark) !important;
background-color: var(--shiki-dark-bg) !important;
html .shiki span {
color: var(--shiki-default, var(--shiki-light));
font-style: var(--shiki-default-font-style, var(--shiki-light-font-style));
font-weight: var(--shiki-default-font-weight, var(--shiki-light-font-weight));
text-decoration: var(--shiki-default-text-decoration, var(--shiki-light-text-decoration));
}

html.light .shiki {
background-color: var(--shiki-light-bg, var(--shiki-default-bg));
color: var(--shiki-light, var(--shiki-default));
}

html.light .shiki span {
color: var(--shiki-light, var(--shiki-default));
font-style: var(--shiki-light-font-style, var(--shiki-default-font-style));
font-weight: var(--shiki-light-font-weight, var(--shiki-default-font-weight));
text-decoration: var(--shiki-light-text-decoration, var(--shiki-default-text-decoration));
}

html.dark .shiki {
background-color: var(--shiki-dark-bg);
color: var(--shiki-dark);
}

html.dark .shiki span {
color: var(--shiki-dark);
font-style: var(--shiki-dark-font-style);
font-weight: var(--shiki-dark-font-weight);
text-decoration: var(--shiki-dark-text-decoration);
}

.prose-docs :where(code) {
Expand Down
17 changes: 17 additions & 0 deletions site/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ export default defineNuxtConfig({
build: {
markdown: {
highlight: {
// Must set `light` too — @nuxt/ui defaults it to material-theme-lighter.
theme: {
light: 'light-plus',
default: 'light-plus',
dark: 'dark-plus',
},
Expand All @@ -92,10 +94,25 @@ export default defineNuxtConfig({
'json',
'yaml',
'md',
'js',
'ts',
'html',
'css',
'vue',
],
},
},
},
},
// Explicit MDC override (Nuxt UI registers highlight defaults on @nuxtjs/mdc).
mdc: {
highlight: {
theme: {
light: 'light-plus',
default: 'light-plus',
dark: 'dark-plus',
},
},
},
compatibilityDate: '2025-08-01',
})
48 changes: 34 additions & 14 deletions site/server/utils/highlightCode.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,62 @@
import { createHighlighter, type Highlighter } from 'shiki'

const SHIKI_LIGHT_THEME = 'light-plus'
const SHIKI_DARK_THEME = 'dark-plus'

const LANGS = ['csharp', 'json', 'js', 'ts', 'html', 'css', 'vue', 'shell', 'bash', 'xml', 'yaml', 'md', 'text'] as const
/** Same theme keys as Nuxt Content / @nuxtjs/mdc (defaultColor: false). */
export const SHIKI_THEME_LIGHT = 'light-plus'
export const SHIKI_THEME_DARK = 'dark-plus'

export const SHIKI_THEMES = {
// Include `light` so we override Nuxt UI's MDC default (material-theme-lighter).
light: SHIKI_THEME_LIGHT,
default: SHIKI_THEME_LIGHT,
dark: SHIKI_THEME_DARK,
} as const

export const SHIKI_LANGS = [
'csharp',
'json',
'js',
'ts',
'html',
'css',
'vue',
'shell',
'bash',
'xml',
'yaml',
'md',
'text',
] as const

export type ShikiLang = (typeof SHIKI_LANGS)[number]

let highlighterPromise: Promise<Highlighter> | null = null

async function getHighlighter() {
if (!highlighterPromise) {
highlighterPromise = createHighlighter({
themes: [SHIKI_LIGHT_THEME, SHIKI_DARK_THEME],
langs: [...LANGS],
themes: [SHIKI_THEME_LIGHT, SHIKI_THEME_DARK],
langs: [...SHIKI_LANGS],
})
}
return highlighterPromise
}

function normalizeLang(lang: string) {
export function normalizeLang(lang: string): ShikiLang {
const value = lang.trim().toLowerCase()
if (value === 'cs') return 'csharp'
if (value === 'csproj') return 'xml'
return LANGS.includes(value as typeof LANGS[number]) ? value : 'text'
return SHIKI_LANGS.includes(value as ShikiLang) ? (value as ShikiLang) : 'text'
}

export async function getCodeHighlighter() {
return getHighlighter()
}

export { normalizeLang }

export async function highlightCode(code: string, lang = 'text') {
const highlighter = await getHighlighter()
return highlighter.codeToHtml(code.trimEnd(), {
lang: normalizeLang(lang),
themes: {
light: SHIKI_LIGHT_THEME,
dark: SHIKI_DARK_THEME,
},
themes: { ...SHIKI_THEMES },
defaultColor: false,
})
}
11 changes: 3 additions & 8 deletions site/server/utils/renderApiMarkdown.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import MarkdownIt from 'markdown-it'
import { getCodeHighlighter, normalizeLang } from './highlightCode'

const SHIKI_LIGHT_THEME = 'light-plus'
const SHIKI_DARK_THEME = 'dark-plus'
import { getCodeHighlighter, normalizeLang, SHIKI_THEMES } from './highlightCode'

function stripHtml(value: string) {
return value
Expand Down Expand Up @@ -74,10 +71,8 @@ export async function renderApiMarkdown(markdown: string) {
const code = token.content.replace(/\n$/, '')
const html = highlighter.codeToHtml(code, {
lang,
themes: {
light: SHIKI_LIGHT_THEME,
dark: SHIKI_DARK_THEME,
},
themes: { ...SHIKI_THEMES },
defaultColor: false,
})

return `<div class="api-code-block">${html}</div>`
Expand Down