Thanks to visit codestin.com
Credit goes to github.com

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
26 changes: 0 additions & 26 deletions app/src/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
28 changes: 28 additions & 0 deletions app/src/components/editor/MarkdownPreview.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(
<MarkdownPreview
content={content}
handleFileSelect={mockHandleFileSelect}
/>
);

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)';

Expand Down
19 changes: 5 additions & 14 deletions app/src/components/editor/MarkdownPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,12 +29,6 @@ interface MarkdownLinkProps {
[key: string]: unknown;
}

interface MarkdownCodeProps {
children: ReactNode;
className?: string;
[key: string]: unknown;
}

const MarkdownPreview: React.FC<MarkdownPreviewProps> = ({
content,
handleFileSelect,
Expand All @@ -42,7 +37,10 @@ const MarkdownPreview: React.FC<MarkdownPreviewProps> = ({
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 = (
Expand Down Expand Up @@ -107,13 +105,6 @@ const MarkdownPreview: React.FC<MarkdownPreviewProps> = ({
{children}
</a>
),
code: ({ children, className, ...props }: MarkdownCodeProps) => {
return (
<pre className={className}>
<code {...props}>{children}</code>
</pre>
);
},
},
} as Options);
}, [currentWorkspace?.name, baseUrl, handleFileSelect]);
Expand Down
36 changes: 36 additions & 0 deletions app/src/hooks/useHighlightTheme.ts
Original file line number Diff line number Diff line change
@@ -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;

Check failure on line 21 in app/src/hooks/useHighlightTheme.ts

View workflow job for this annotation

GitHub Actions / TypeScript Type Check

This assertion is unnecessary since it does not change the type of the expression
} else {
style.textContent = atomOneLightTheme as string;

Check failure on line 23 in app/src/hooks/useHighlightTheme.ts

View workflow job for this annotation

GitHub Actions / TypeScript Type Check

This assertion is unnecessary since it does not change the type of the expression
}

document.head.appendChild(style);

return () => {
// Cleanup on unmount
const stylesheet = document.querySelector('style[data-highlight-theme]');
if (stylesheet) {
stylesheet.remove();
}
};
}, [colorScheme]);
};
5 changes: 5 additions & 0 deletions app/src/types/css-inline.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Type declarations for CSS imports with ?inline modifier
declare module '*.css?inline' {
const content: string;
export default content;
}
Loading
Loading