{"version":3,"file":"cisstech-nge-markdown-shiki.mjs","sources":["../../../projects/nge/markdown/shiki/shiki.ts","../../../projects/nge/markdown/shiki/cisstech-nge-markdown-shiki.ts"],"sourcesContent":["import { isPlatformServer } from '@angular/common'\nimport { DOCUMENT, PLATFORM_ID, inject, provideAppInitializer } from '@angular/core'\nimport {\n  NGE_MARKDOWN_CONFIG,\n  NGE_MARKDOWN_CONTRIBUTION,\n  NGE_MARKDOWN_HIGHLIGHTER_SERVICE,\n  NgeMarkdownFeature,\n  NgeMarkdownHighlighter,\n  NgeMarkdownHighlighterService,\n} from '@cisstech/nge/markdown'\n\n/** Options of the shiki highlighter (`withShiki`). */\nexport interface NgeMarkdownShikiOptions {\n  /**\n   * Shiki theme per color scheme. Both palettes are emitted as CSS variables,\n   * so switching schemes never re-highlights. Default:\n   * `{ light: 'github-light', dark: 'github-dark' }`.\n   */\n  themes?: { light: string; dark: string }\n  /**\n   * Languages preloaded on the server before the first render. Server\n   * rendering snapshots the page as soon as the app is stable, and a grammar\n   * loading mid-render is not tracked as pending work: a language outside this\n   * list may render unhighlighted in the prerendered HTML (the client pass\n   * still highlights it). Defaults to the common web stack.\n   */\n  langs?: string[]\n}\n\n/** Languages warmed up for server rendering when `langs` is not customized. */\nexport const NGE_MARKDOWN_SHIKI_DEFAULT_LANGS = [\n  'bash',\n  'css',\n  'html',\n  'javascript',\n  'json',\n  'markdown',\n  'scss',\n  'shell',\n  'typescript',\n  'yaml',\n]\n\n/**\n * Loads shiki and the configured grammars ahead of the first render, so every\n * `codeToHtml` call during server rendering resolves without async grammar\n * fetches. Called by `withShiki` through an app initializer on the server.\n */\nexport async function preloadShiki(options: NgeMarkdownShikiOptions = {}): Promise<void> {\n  const themes = options.themes ?? { light: 'github-light', dark: 'github-dark' }\n  const langs = options.langs ?? NGE_MARKDOWN_SHIKI_DEFAULT_LANGS\n  const { codeToHtml } = await import('shiki')\n  await Promise.all(langs.map((lang) => codeToHtml('', { lang, themes, defaultColor: false }).catch(() => undefined)))\n}\n\nconst THEME_STYLE_ID = 'nge-markdown-shiki'\n\n/**\n * Highlighter service backed by [shiki](https://shiki.style). Unlike the monaco\n * colorizer it also runs during server rendering, so prerendered pages ship\n * highlighted HTML. Shiki is an optional peer dependency, imported lazily on\n * the first code block.\n */\nexport function shikiHighlighterService(options: NgeMarkdownShikiOptions = {}): NgeMarkdownHighlighterService {\n  const themes = options.themes ?? { light: 'github-light', dark: 'github-dark' }\n  return {\n    ssr: true,\n    highligtht: async (injector, highlight) => {\n      const code = highlight.element\n      // domino (the server DOM) lacks parentElement; parentNode is universal.\n      const pre = (code.parentElement ?? code.parentNode) as HTMLElement | null\n      if (!pre) {\n        return\n      }\n\n      const { codeToHtml } = await import('shiki')\n      let html: string\n      try {\n        html = await codeToHtml(code.textContent ?? '', {\n          lang: highlight.language || 'plaintext',\n          themes,\n          defaultColor: false,\n        })\n      } catch {\n        // Unknown language: leave the block as plain text.\n        return\n      }\n\n      // Graft shiki's output onto the existing block: its pre carries the theme\n      // variables, its code the highlighted lines.\n      const document = injector.get(DOCUMENT)\n      const holder = document.createElement('div')\n      holder.innerHTML = html\n      const shikiPre = holder.querySelector('pre')\n      const shikiCode = holder.querySelector('pre > code')\n      if (!shikiPre || !shikiCode) {\n        return\n      }\n      pre.setAttribute('class', shikiPre.getAttribute('class') ?? 'shiki')\n      pre.setAttribute('style', shikiPre.getAttribute('style') ?? '')\n      code.innerHTML = shikiCode.innerHTML\n\n      decorateLines(pre, code, highlight.lines, highlight.highlights)\n      ensureThemeStyle(document, injector.get(NGE_MARKDOWN_CONFIG, null)?.darkThemeClassName)\n    },\n  }\n}\n\n/**\n * Highlight fenced code blocks with [shiki](https://shiki.style). Unlike\n * `withHighlighter`, it also runs during server rendering: prerendered pages\n * ship highlighted HTML, and both color schemes are emitted as CSS variables so\n * theme switches never re-highlight.\n *\n * Import from `@cisstech/nge/markdown/shiki`: shiki lives behind this entry\n * point, so only apps that opt into it pull the SDK and need the `shiki`\n * dependency installed.\n */\nexport function withShiki(options?: NgeMarkdownShikiOptions): NgeMarkdownFeature {\n  return {\n    providers: [\n      { provide: NGE_MARKDOWN_CONTRIBUTION, multi: true, useClass: NgeMarkdownHighlighter },\n      { provide: NGE_MARKDOWN_HIGHLIGHTER_SERVICE, useValue: shikiHighlighterService(options) },\n      // On the server, grammars must be loaded before the first render: the page\n      // is serialized at stability, and a grammar fetched mid-render is not\n      // tracked as pending work. The browser keeps lazy loading.\n      provideAppInitializer(() => {\n        if (isPlatformServer(inject(PLATFORM_ID))) {\n          return preloadShiki(options)\n        }\n        return undefined\n      }),\n    ],\n  }\n}\n\n/**\n * Applies the fence options shared with the monaco colorizer: `highlights`\n * marks lines, `lines` shows line numbers (a single value numbers every line\n * from it, a list only the listed ones, always with their real line number).\n */\nfunction decorateLines(pre: HTMLElement, code: HTMLElement, lines?: string, highlights?: string): void {\n  const spans = Array.from(code.querySelectorAll('.line'))\n\n  for (const n of parseLineNumbers(highlights ?? '')) {\n    const span = spans[n - 1]\n    span?.setAttribute('class', `${span.getAttribute('class') ?? ''} nge-highlighted`.trim())\n  }\n\n  const numbered = parseLineNumbers(lines ?? '')\n  if (!numbered.length) {\n    return\n  }\n  if (numbered.length === 1) {\n    for (let n = numbered[0] + 1; n <= spans.length; n++) {\n      numbered.push(n)\n    }\n  }\n  pre.setAttribute('class', `${pre.getAttribute('class') ?? ''} nge-numbered`.trim())\n  for (const n of numbered) {\n    spans[n - 1]?.setAttribute('data-line', `${n}`)\n  }\n}\n\n/** `\"2 4-7 9\"` -> `[2, 4, 5, 6, 7, 9]`. */\nfunction parseLineNumbers(spec: string): number[] {\n  const numbers: number[] = []\n  for (const part of spec.split(' ').filter(Boolean)) {\n    const range = part.match(/^(\\d+)-(\\d+)$/)\n    if (range) {\n      for (let n = Number(range[1]); n <= Number(range[2]); n++) {\n        numbers.push(n)\n      }\n    } else if (/^\\d+$/.test(part)) {\n      numbers.push(Number(part))\n    }\n  }\n  return [...new Set(numbers)]\n}\n\n/**\n * Activates the light palette by default and the dark one under the configured\n * dark class(es), by pointing the shiki CSS variables at the current scheme.\n */\nfunction ensureThemeStyle(document: Document, darkThemeClassName?: string | string[]): void {\n  if (document.getElementById(THEME_STYLE_ID)) {\n    return\n  }\n  const darkClasses = darkThemeClassName ? [darkThemeClassName].flat() : ['dark-theme']\n  const dark = darkClasses\n    .map(\n      (name) =>\n        `.${name} .shiki, .${name} .shiki span { color: var(--shiki-dark); background-color: var(--shiki-dark-bg); }`\n    )\n    .join('\\n')\n  const style = document.createElement('style')\n  style.id = THEME_STYLE_ID\n  style.textContent = [\n    '.shiki, .shiki span { color: var(--shiki-light); background-color: var(--shiki-light-bg); }',\n    dark,\n    '.shiki { padding: 0.75em 1em; border-radius: 6px; overflow: auto; }',\n    '.shiki .nge-highlighted { display: inline-block; width: 100%; background-color: color-mix(in srgb, currentColor 12%, transparent); }',\n    '.shiki.nge-numbered .line::before { content: attr(data-line); display: inline-block; min-width: 2.5ch; margin-right: 1.25em; text-align: right; opacity: 0.45; }',\n  ].join('\\n')\n  document.head.appendChild(style)\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}