diff --git a/app/src/App.scss b/app/src/App.scss index 4f55b6a..eb90e3c 100644 --- a/app/src/App.scss +++ b/app/src/App.scss @@ -108,29 +108,3 @@ $navbar-height: 64px; .tree { padding-top: $padding; } - -// Syntax highlighting themes -@import 'highlight.js/styles/github.css' layer(light-theme); -@import 'highlight.js/styles/github-dark.css' layer(dark-theme); - -// Show light theme by default -@layer light-theme { - [data-mantine-color-scheme='light'] .markdown-preview { - pre code.hljs { - display: block; - overflow-x: auto; - padding: 1em; - } - } -} - -// Show dark theme in dark mode -@layer dark-theme { - [data-mantine-color-scheme='dark'] .markdown-preview { - pre code.hljs { - display: block; - overflow-x: auto; - padding: 1em; - } - } -} diff --git a/app/src/components/editor/MarkdownPreview.test.tsx b/app/src/components/editor/MarkdownPreview.test.tsx index 444ee3a..268d5b5 100644 --- a/app/src/components/editor/MarkdownPreview.test.tsx +++ b/app/src/components/editor/MarkdownPreview.test.tsx @@ -120,6 +120,34 @@ describe('MarkdownPreview', () => { }); }); + it('renders code blocks with correct structure for theme switching', async () => { + const content = '```javascript\nconst hello = "world";\n```'; + + render( + + ); + + await waitFor(() => { + // Check that rehype-highlight generates the correct structure + const preElement = screen + .getByRole('code', { hidden: true }) + .closest('pre'); + const codeElement = preElement?.querySelector('code'); + + expect(preElement).toBeInTheDocument(); + expect(codeElement).toBeInTheDocument(); + + // The code element should have hljs class for theme switching to work + expect(codeElement).toHaveClass('hljs'); + + // Should also have language class + expect(codeElement).toHaveClass('language-javascript'); + }); + }); + it('handles image loading errors gracefully', async () => { const content = '![Test Image](invalid-image.jpg)'; diff --git a/app/src/components/editor/MarkdownPreview.tsx b/app/src/components/editor/MarkdownPreview.tsx index 74ab924..9e72472 100644 --- a/app/src/components/editor/MarkdownPreview.tsx +++ b/app/src/components/editor/MarkdownPreview.tsx @@ -10,6 +10,7 @@ import * as prod from 'react/jsx-runtime'; import { notifications } from '@mantine/notifications'; import { remarkWikiLinks } from '../../utils/remarkWikiLinks'; import { useWorkspace } from '../../hooks/useWorkspace'; +import { useHighlightTheme } from '../../hooks/useHighlightTheme'; interface MarkdownPreviewProps { content: string; @@ -28,12 +29,6 @@ interface MarkdownLinkProps { [key: string]: unknown; } -interface MarkdownCodeProps { - children: ReactNode; - className?: string; - [key: string]: unknown; -} - const MarkdownPreview: React.FC = ({ content, handleFileSelect, @@ -42,7 +37,10 @@ const MarkdownPreview: React.FC = ({ null ); const baseUrl = window.API_BASE_URL; - const { currentWorkspace } = useWorkspace(); + const { currentWorkspace, colorScheme } = useWorkspace(); + + // Use the highlight theme hook + useHighlightTheme(colorScheme === 'auto' ? 'light' : colorScheme); const processor = useMemo(() => { const handleLinkClick = ( @@ -107,13 +105,6 @@ const MarkdownPreview: React.FC = ({ {children} ), - code: ({ children, className, ...props }: MarkdownCodeProps) => { - return ( -
-                {children}
-              
- ); - }, }, } as Options); }, [currentWorkspace?.name, baseUrl, handleFileSelect]); diff --git a/app/src/hooks/useHighlightTheme.ts b/app/src/hooks/useHighlightTheme.ts new file mode 100644 index 0000000..c814ee7 --- /dev/null +++ b/app/src/hooks/useHighlightTheme.ts @@ -0,0 +1,36 @@ +import { useEffect } from 'react'; +// Import theme CSS as text that will be bundled +import atomOneLightTheme from 'highlight.js/styles/atom-one-light.css?inline'; +import atomOneDarkTheme from 'highlight.js/styles/atom-one-dark.css?inline'; + +export const useHighlightTheme = (colorScheme: 'light' | 'dark') => { + useEffect(() => { + // Remove existing highlight theme + const existingStylesheet = document.querySelector( + 'style[data-highlight-theme]' + ); + if (existingStylesheet) { + existingStylesheet.remove(); + } + + // Add new theme stylesheet using bundled CSS + const style = document.createElement('style'); + style.setAttribute('data-highlight-theme', 'true'); + + if (colorScheme === 'dark') { + style.textContent = atomOneDarkTheme as string; + } else { + style.textContent = atomOneLightTheme as string; + } + + document.head.appendChild(style); + + return () => { + // Cleanup on unmount + const stylesheet = document.querySelector('style[data-highlight-theme]'); + if (stylesheet) { + stylesheet.remove(); + } + }; + }, [colorScheme]); +};