import type { HighlightJS, HighlightTheme } from './CodeTypes' import { appendStyle, appendScript, awaitGlobal } from '@bagelink/vue' import { ref } from 'vue' // Extend the Window interface interface CustomWindow extends Window { hljs: HighlightJS } declare const window: CustomWindow export function useHighlight(theme: HighlightTheme = 'dark') { const hljs = ref(null) const loaded = ref(false) const currentTheme = ref(theme) const normalizeTheme = (t: HighlightTheme): HighlightTheme => { if (t === 'dark') { return 'atom-one-dark' } if (t === 'light') { return 'atom-one-light' } return t } const getThemeCssUrl = (t: HighlightTheme) => `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/styles/${t}.min.css` // Remove highlight.js theme links EXCEPT the one we're about to keep. // Removing a that is still loading prevents its onload from ever // firing, which would leave `appendStyle` hanging forever. When multiple // editors mount at once this caused one editor to get stuck on `loaded=false`. const removeOtherThemeLinks = (keepUrl: string) => { document.querySelectorAll('link[rel="stylesheet"]').forEach((link) => { if (link instanceof HTMLLinkElement && link.href.includes('/styles/') && link.href.includes('highlight.js')) { if (link.href !== keepUrl) { link.parentElement?.removeChild(link) } } }) } const loadTheme = async (theme: HighlightTheme) => { const next = normalizeTheme(theme) try { const url = getThemeCssUrl(next) // Append first (idempotent: appendStyle resolves immediately if the // link already exists), then remove only the *other* theme links. await appendStyle(url) removeOtherThemeLinks(url) currentTheme.value = next } catch (error) { console.error('Failed to apply theme. Falling back to atom-one-dark:', error) currentTheme.value = 'atom-one-dark' const url = getThemeCssUrl(currentTheme.value) await appendStyle(url) removeOtherThemeLinks(url) } } const setTheme = async (theme: HighlightTheme) => { const next = normalizeTheme(theme) if (next === currentTheme.value) { return } await loadTheme(next) } const loadHighlight = async () => { if (loaded.value) { return } try { // Load highlight.js await appendScript('https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.0/highlight.min.js', { id: 'hljs-cdn' }) // `appendScript` may resolve before the script has finished executing when // multiple editors mount at once (a duplicate