SyntaxHighlightedCode.svelte 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. <script lang="ts">
  2. import hljs from 'highlight.js';
  3. import { browser } from '$app/environment';
  4. import { mode } from 'mode-watcher';
  5. import githubDarkCss from 'highlight.js/styles/github-dark.css?inline';
  6. import githubLightCss from 'highlight.js/styles/github.css?inline';
  7. interface Props {
  8. code: string;
  9. language?: string;
  10. class?: string;
  11. maxHeight?: string;
  12. maxWidth?: string;
  13. }
  14. let {
  15. code,
  16. language = 'text',
  17. class: className = '',
  18. maxHeight = '60vh',
  19. maxWidth = ''
  20. }: Props = $props();
  21. let highlightedHtml = $state('');
  22. function loadHighlightTheme(isDark: boolean) {
  23. if (!browser) return;
  24. const existingThemes = document.querySelectorAll('style[data-highlight-theme-preview]');
  25. existingThemes.forEach((style) => style.remove());
  26. const style = document.createElement('style');
  27. style.setAttribute('data-highlight-theme-preview', 'true');
  28. style.textContent = isDark ? githubDarkCss : githubLightCss;
  29. document.head.appendChild(style);
  30. }
  31. $effect(() => {
  32. const currentMode = mode.current;
  33. const isDark = currentMode === 'dark';
  34. loadHighlightTheme(isDark);
  35. });
  36. $effect(() => {
  37. if (!code) {
  38. highlightedHtml = '';
  39. return;
  40. }
  41. try {
  42. // Check if the language is supported
  43. const lang = language.toLowerCase();
  44. const isSupported = hljs.getLanguage(lang);
  45. if (isSupported) {
  46. const result = hljs.highlight(code, { language: lang });
  47. highlightedHtml = result.value;
  48. } else {
  49. // Try auto-detection or fallback to plain text
  50. const result = hljs.highlightAuto(code);
  51. highlightedHtml = result.value;
  52. }
  53. } catch {
  54. // Fallback to escaped plain text
  55. highlightedHtml = code.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  56. }
  57. });
  58. </script>
  59. <div
  60. class="code-preview-wrapper overflow-auto rounded-lg border border-border bg-muted {className}"
  61. style="max-height: {maxHeight}; max-width: {maxWidth};"
  62. >
  63. <!-- Needs to be formatted as single line for proper rendering -->
  64. <pre class="m-0 overflow-x-auto p-4"><code class="hljs text-sm leading-relaxed"
  65. >{@html highlightedHtml}</code
  66. ></pre>
  67. </div>
  68. <style>
  69. .code-preview-wrapper {
  70. font-family:
  71. ui-monospace, SFMono-Regular, 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas,
  72. 'Liberation Mono', Menlo, monospace;
  73. }
  74. .code-preview-wrapper pre {
  75. background: transparent;
  76. }
  77. .code-preview-wrapper code {
  78. background: transparent;
  79. }
  80. </style>