mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-05 08:10:14 +00:00
Add syntax highlighting (#178)
This commit is contained in:
parent
9a94ad70bf
commit
9edcee5c53
7 changed files with 581 additions and 15 deletions
|
|
@ -64,6 +64,7 @@
|
|||
"react-use": "^17.6.0",
|
||||
"recharts": "^2.15.3",
|
||||
"require-in-the-middle": "^7.5.2",
|
||||
"shiki": "^3.7.0",
|
||||
"sonner": "^2.0.3",
|
||||
"stripe": "^18.2.0",
|
||||
"tailwind-merge": "^3.3.0",
|
||||
|
|
|
|||
|
|
@ -1,30 +1,24 @@
|
|||
'use client';
|
||||
|
||||
import { MermaidDiagram } from './MermaidDiagram';
|
||||
import type { Element } from 'hast';
|
||||
import { SyntaxHighlighter } from './SyntaxHighlighter';
|
||||
|
||||
interface CodeBlockProps extends React.HTMLAttributes<HTMLElement> {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
inline?: boolean;
|
||||
node?: Element;
|
||||
}
|
||||
|
||||
export const CodeBlock = ({
|
||||
children,
|
||||
className,
|
||||
inline,
|
||||
...props
|
||||
}: CodeBlockProps) => {
|
||||
// Extract language from className (format: "language-xxx")
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
const language = match ? match[1] : '';
|
||||
|
||||
// Convert children to string
|
||||
const code = String(children).replace(/\n$/, '');
|
||||
|
||||
// If it's inline code or not mermaid, render as regular code
|
||||
if (inline || language !== 'mermaid') {
|
||||
// No language = inline code (from `backticks`)
|
||||
if (!match) {
|
||||
return (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
|
|
@ -32,15 +26,13 @@ export const CodeBlock = ({
|
|||
);
|
||||
}
|
||||
|
||||
// If it's a mermaid code block, render with MermaidDiagram
|
||||
// Mermaid diagrams
|
||||
if (language === 'mermaid') {
|
||||
return <MermaidDiagram chart={code} className="my-4" />;
|
||||
return <MermaidDiagram chart={code} className="my-2" />;
|
||||
}
|
||||
|
||||
// Fallback to regular code block
|
||||
// Code blocks with syntax highlighting
|
||||
return (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
<SyntaxHighlighter code={code} language={language || ''} className="my-2" />
|
||||
);
|
||||
};
|
||||
|
|
|
|||
170
apps/web/src/components/ui/SyntaxHighlighter.tsx
Normal file
170
apps/web/src/components/ui/SyntaxHighlighter.tsx
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
'use client';
|
||||
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { useTheme } from 'next-themes';
|
||||
import type { BundledLanguage, BundledTheme, HighlighterGeneric } from 'shiki';
|
||||
|
||||
interface SyntaxHighlighterProps {
|
||||
code: string;
|
||||
language: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
// Cache for the highlighter instance
|
||||
let highlighterCache: HighlighterGeneric<BundledLanguage, BundledTheme> | null =
|
||||
null;
|
||||
let highlighterPromise: Promise<
|
||||
HighlighterGeneric<BundledLanguage, BundledTheme>
|
||||
> | null = null;
|
||||
|
||||
// Common languages to preload for better performance
|
||||
const COMMON_LANGUAGES: BundledLanguage[] = [
|
||||
'javascript',
|
||||
'typescript',
|
||||
'jsx',
|
||||
'tsx',
|
||||
'python',
|
||||
'java',
|
||||
'go',
|
||||
'rust',
|
||||
'cpp',
|
||||
'c',
|
||||
'csharp',
|
||||
'php',
|
||||
'ruby',
|
||||
'swift',
|
||||
'kotlin',
|
||||
'scala',
|
||||
'html',
|
||||
'css',
|
||||
'scss',
|
||||
'json',
|
||||
'yaml',
|
||||
'xml',
|
||||
'markdown',
|
||||
'bash',
|
||||
'shell',
|
||||
'sql',
|
||||
'dockerfile',
|
||||
];
|
||||
|
||||
const getHighlighter = async () => {
|
||||
if (highlighterCache) {
|
||||
return highlighterCache;
|
||||
}
|
||||
|
||||
if (highlighterPromise) {
|
||||
return highlighterPromise;
|
||||
}
|
||||
|
||||
highlighterPromise = (async () => {
|
||||
const { createHighlighter } = await import('shiki');
|
||||
|
||||
const highlighter = await createHighlighter({
|
||||
themes: ['github-light', 'github-dark'],
|
||||
langs: COMMON_LANGUAGES,
|
||||
});
|
||||
|
||||
highlighterCache = highlighter;
|
||||
return highlighter;
|
||||
})();
|
||||
|
||||
return highlighterPromise;
|
||||
};
|
||||
|
||||
export const SyntaxHighlighter = ({
|
||||
code,
|
||||
language,
|
||||
className,
|
||||
}: SyntaxHighlighterProps) => {
|
||||
const [highlightedCode, setHighlightedCode] = useState<string>('');
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { theme, systemTheme } = useTheme();
|
||||
|
||||
const currentTheme = theme === 'system' ? systemTheme : theme;
|
||||
const shikiTheme = currentTheme === 'dark' ? 'github-dark' : 'github-light';
|
||||
|
||||
const highlightCode = useCallback(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
const highlighter = await getHighlighter();
|
||||
|
||||
// Check if the language is supported, fallback to 'text' if not
|
||||
const supportedLanguages = highlighter.getLoadedLanguages();
|
||||
const langToUse = supportedLanguages.includes(language as BundledLanguage)
|
||||
? (language as BundledLanguage)
|
||||
: 'text';
|
||||
|
||||
// If the language isn't loaded yet, try to load it
|
||||
if (!supportedLanguages.includes(langToUse) && langToUse !== 'text') {
|
||||
try {
|
||||
await highlighter.loadLanguage(langToUse);
|
||||
} catch {
|
||||
// If loading fails, fall back to text
|
||||
}
|
||||
}
|
||||
|
||||
const html = highlighter.codeToHtml(code, {
|
||||
lang: langToUse,
|
||||
theme: shikiTheme,
|
||||
transformers: [
|
||||
{
|
||||
pre(node) {
|
||||
// Remove default background and padding since we'll handle it with CSS
|
||||
if (node.properties.style) {
|
||||
node.properties.style = (node.properties.style as string)
|
||||
.replace(/background-color:[^;]+;?/g, '')
|
||||
.replace(/padding:[^;]+;?/g, '');
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
setHighlightedCode(html);
|
||||
} catch (err) {
|
||||
console.error('Failed to highlight code:', err);
|
||||
setError('Failed to highlight code');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [code, language, shikiTheme]);
|
||||
|
||||
useEffect(() => {
|
||||
highlightCode();
|
||||
}, [highlightCode]);
|
||||
|
||||
if (error) {
|
||||
// Fallback to plain code if highlighting fails
|
||||
return (
|
||||
<pre
|
||||
className={`bg-muted p-3 rounded overflow-x-auto ${className || ''}`}
|
||||
>
|
||||
<code className="bg-transparent p-0 font-mono text-sm">{code}</code>
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
// Show loading state with plain code
|
||||
return (
|
||||
<pre
|
||||
className={`bg-muted p-3 rounded overflow-x-auto ${className || ''}`}
|
||||
>
|
||||
<code className="bg-transparent p-0 font-mono text-sm opacity-70">
|
||||
{code}
|
||||
</code>
|
||||
</pre>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`bg-muted p-3 rounded overflow-x-auto [&>pre]:!bg-transparent [&>pre]:!p-0 [&>pre]:!m-0 ${className || ''}`}
|
||||
dangerouslySetInnerHTML={{ __html: highlightedCode }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
99
apps/web/src/components/ui/__tests__/CodeBlock.test.tsx
Normal file
99
apps/web/src/components/ui/__tests__/CodeBlock.test.tsx
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import { render, screen } from '@testing-library/react';
|
||||
import { ThemeProvider } from 'next-themes';
|
||||
import { CodeBlock } from '../CodeBlock';
|
||||
|
||||
// Mock the SyntaxHighlighter component
|
||||
vi.mock('../SyntaxHighlighter', () => ({
|
||||
SyntaxHighlighter: ({
|
||||
code,
|
||||
language,
|
||||
className,
|
||||
}: {
|
||||
code: string;
|
||||
language: string;
|
||||
className?: string;
|
||||
}) => (
|
||||
<div
|
||||
data-testid="syntax-highlighter"
|
||||
data-language={language}
|
||||
className={className}
|
||||
>
|
||||
{code || ''}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
// Mock the MermaidDiagram component
|
||||
vi.mock('../MermaidDiagram', () => ({
|
||||
MermaidDiagram: ({
|
||||
chart,
|
||||
className,
|
||||
}: {
|
||||
chart: string;
|
||||
className?: string;
|
||||
}) => (
|
||||
<div data-testid="mermaid-diagram" className={className}>
|
||||
{chart}
|
||||
</div>
|
||||
),
|
||||
}));
|
||||
|
||||
const renderWithTheme = (component: React.ReactElement) => {
|
||||
return render(
|
||||
<ThemeProvider attribute="class" defaultTheme="light">
|
||||
{component}
|
||||
</ThemeProvider>,
|
||||
);
|
||||
};
|
||||
|
||||
describe('CodeBlock', () => {
|
||||
it('renders inline code as regular code element', () => {
|
||||
renderWithTheme(<CodeBlock>const x = 1;</CodeBlock>);
|
||||
|
||||
const codeElement = screen.getByText(/const/).closest('code');
|
||||
expect(codeElement?.tagName).toBe('CODE');
|
||||
});
|
||||
|
||||
it('renders mermaid code blocks using MermaidDiagram component', () => {
|
||||
const mermaidCode = `graph TD
|
||||
A[Start] --> B{Is it working?}`;
|
||||
|
||||
renderWithTheme(
|
||||
<CodeBlock className="language-mermaid">{mermaidCode}</CodeBlock>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId('mermaid-diagram')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders code blocks with language using SyntaxHighlighter', () => {
|
||||
renderWithTheme(
|
||||
<CodeBlock className="language-javascript">
|
||||
console.log('test');
|
||||
</CodeBlock>,
|
||||
);
|
||||
|
||||
const syntaxHighlighter = screen.getByTestId('syntax-highlighter');
|
||||
expect(syntaxHighlighter).toBeInTheDocument();
|
||||
expect(syntaxHighlighter).toHaveAttribute('data-language', 'javascript');
|
||||
});
|
||||
|
||||
it('renders text language using SyntaxHighlighter', () => {
|
||||
renderWithTheme(
|
||||
<CodeBlock className="language-text">This is plain text</CodeBlock>,
|
||||
);
|
||||
|
||||
const syntaxHighlighter = screen.getByTestId('syntax-highlighter');
|
||||
expect(syntaxHighlighter).toBeInTheDocument();
|
||||
expect(syntaxHighlighter).toHaveAttribute('data-language', 'text');
|
||||
});
|
||||
|
||||
it('renders inline code when no language is specified', () => {
|
||||
renderWithTheme(<CodeBlock>Some code without language</CodeBlock>);
|
||||
|
||||
const codeElement = screen
|
||||
.getByText(/Some code without language/)
|
||||
.closest('code');
|
||||
expect(codeElement).toBeInTheDocument();
|
||||
expect(codeElement?.tagName).toBe('CODE');
|
||||
});
|
||||
});
|
||||
163
apps/web/src/components/ui/__tests__/SyntaxHighlighter.test.tsx
Normal file
163
apps/web/src/components/ui/__tests__/SyntaxHighlighter.test.tsx
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { ThemeProvider } from 'next-themes';
|
||||
import { SyntaxHighlighter } from '../SyntaxHighlighter';
|
||||
|
||||
// Mock shiki
|
||||
vi.mock('shiki', () => ({
|
||||
createHighlighter: vi.fn().mockResolvedValue({
|
||||
getLoadedLanguages: vi
|
||||
.fn()
|
||||
.mockReturnValue(['javascript', 'typescript', 'python']),
|
||||
loadLanguage: vi.fn().mockResolvedValue(undefined),
|
||||
codeToHtml: vi.fn().mockImplementation((code, options) => {
|
||||
return `<pre><code class="language-${options.lang}">${code}</code></pre>`;
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
|
||||
const renderWithTheme = (component: React.ReactElement, theme = 'light') => {
|
||||
return render(
|
||||
<ThemeProvider attribute="class" defaultTheme={theme}>
|
||||
{component}
|
||||
</ThemeProvider>,
|
||||
);
|
||||
};
|
||||
|
||||
describe('SyntaxHighlighter', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders loading state initially', () => {
|
||||
renderWithTheme(
|
||||
<SyntaxHighlighter code="console.log('test');" language="javascript" />,
|
||||
);
|
||||
|
||||
// Should show loading state with plain code
|
||||
const codeElement = screen.getByText("console.log('test');");
|
||||
expect(codeElement).toBeInTheDocument();
|
||||
expect(codeElement).toHaveClass('opacity-70'); // Loading state styling
|
||||
});
|
||||
|
||||
it('renders highlighted code after loading', async () => {
|
||||
renderWithTheme(
|
||||
<SyntaxHighlighter code="console.log('test');" language="javascript" />,
|
||||
);
|
||||
|
||||
// Wait for the highlighting to complete
|
||||
await waitFor(() => {
|
||||
const highlightedElement = screen.getByText("console.log('test');");
|
||||
expect(highlightedElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles unsupported languages gracefully', async () => {
|
||||
renderWithTheme(
|
||||
<SyntaxHighlighter code="some code" language="unsupported-lang" />,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const codeElement = screen.getByText('some code');
|
||||
expect(codeElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('applies correct theme based on theme provider', async () => {
|
||||
renderWithTheme(
|
||||
<SyntaxHighlighter code="const x = 1;" language="javascript" />,
|
||||
'dark',
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const codeElement = screen.getByText('const x = 1;');
|
||||
expect(codeElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles empty code', async () => {
|
||||
renderWithTheme(<SyntaxHighlighter code="" language="javascript" />);
|
||||
|
||||
await waitFor(() => {
|
||||
// Should render without errors - check for the container div
|
||||
const container = document.querySelector('.bg-muted');
|
||||
expect(container).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('applies custom className', async () => {
|
||||
renderWithTheme(
|
||||
<SyntaxHighlighter
|
||||
code="test"
|
||||
language="javascript"
|
||||
className="custom-class"
|
||||
/>,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const container = document.querySelector('.custom-class');
|
||||
expect(container).toBeInTheDocument();
|
||||
expect(container).toHaveClass('custom-class');
|
||||
});
|
||||
});
|
||||
|
||||
it('falls back to plain code on error', async () => {
|
||||
// Mock an error in the highlighter
|
||||
const mockCreateHighlighter = vi
|
||||
.fn()
|
||||
.mockRejectedValue(new Error('Highlighting failed'));
|
||||
vi.doMock('shiki', () => ({
|
||||
createHighlighter: mockCreateHighlighter,
|
||||
}));
|
||||
|
||||
renderWithTheme(
|
||||
<SyntaxHighlighter code="error code" language="javascript" />,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const codeElement = screen.getByText('error code');
|
||||
expect(codeElement).toBeInTheDocument();
|
||||
// Should be in a pre/code fallback structure
|
||||
expect(codeElement.closest('pre')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles different programming languages', async () => {
|
||||
const languages = ['python', 'typescript', 'java'];
|
||||
|
||||
for (const lang of languages) {
|
||||
const { unmount } = renderWithTheme(
|
||||
<SyntaxHighlighter code={`// ${lang} code`} language={lang} />,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
const codeElement = screen.getByText(`// ${lang} code`);
|
||||
expect(codeElement).toBeInTheDocument();
|
||||
});
|
||||
|
||||
unmount();
|
||||
}
|
||||
});
|
||||
|
||||
it('caches highlighter instance for performance', async () => {
|
||||
// Render multiple instances
|
||||
const { unmount: unmount1 } = renderWithTheme(
|
||||
<SyntaxHighlighter code="code1" language="javascript" />,
|
||||
);
|
||||
|
||||
const { unmount: unmount2 } = renderWithTheme(
|
||||
<SyntaxHighlighter code="code2" language="python" />,
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('code1')).toBeInTheDocument();
|
||||
expect(screen.getByText('code2')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// Both components should render successfully
|
||||
expect(screen.getByText('code1')).toBeInTheDocument();
|
||||
expect(screen.getByText('code2')).toBeInTheDocument();
|
||||
|
||||
unmount1();
|
||||
unmount2();
|
||||
});
|
||||
});
|
||||
|
|
@ -12,4 +12,19 @@ global.IntersectionObserver = vi.fn().mockImplementation(() => ({
|
|||
disconnect: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock matchMedia for next-themes
|
||||
Object.defineProperty(window, 'matchMedia', {
|
||||
writable: true,
|
||||
value: vi.fn().mockImplementation((query) => ({
|
||||
matches: false,
|
||||
media: query,
|
||||
onchange: null,
|
||||
addListener: vi.fn(), // deprecated
|
||||
removeListener: vi.fn(), // deprecated
|
||||
addEventListener: vi.fn(),
|
||||
removeEventListener: vi.fn(),
|
||||
dispatchEvent: vi.fn(),
|
||||
})),
|
||||
});
|
||||
|
||||
beforeEach(() => vi.clearAllMocks());
|
||||
|
|
|
|||
126
pnpm-lock.yaml
generated
126
pnpm-lock.yaml
generated
|
|
@ -302,6 +302,9 @@ importers:
|
|||
require-in-the-middle:
|
||||
specifier: ^7.5.2
|
||||
version: 7.5.2
|
||||
shiki:
|
||||
specifier: ^3.7.0
|
||||
version: 3.7.0
|
||||
sonner:
|
||||
specifier: ^2.0.3
|
||||
version: 2.0.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
|
|
@ -2451,6 +2454,27 @@ packages:
|
|||
peerDependencies:
|
||||
webpack: '>=4.40.0'
|
||||
|
||||
'@shikijs/core@3.7.0':
|
||||
resolution: {integrity: sha512-yilc0S9HvTPyahHpcum8eonYrQtmGTU0lbtwxhA6jHv4Bm1cAdlPFRCJX4AHebkCm75aKTjjRAW+DezqD1b/cg==}
|
||||
|
||||
'@shikijs/engine-javascript@3.7.0':
|
||||
resolution: {integrity: sha512-0t17s03Cbv+ZcUvv+y33GtX75WBLQELgNdVghnsdhTgU3hVcWcMsoP6Lb0nDTl95ZJfbP1mVMO0p3byVh3uuzA==}
|
||||
|
||||
'@shikijs/engine-oniguruma@3.7.0':
|
||||
resolution: {integrity: sha512-5BxcD6LjVWsGu4xyaBC5bu8LdNgPCVBnAkWTtOCs/CZxcB22L8rcoWfv7Hh/3WooVjBZmFtyxhgvkQFedPGnFw==}
|
||||
|
||||
'@shikijs/langs@3.7.0':
|
||||
resolution: {integrity: sha512-1zYtdfXLr9xDKLTGy5kb7O0zDQsxXiIsw1iIBcNOO8Yi5/Y1qDbJ+0VsFoqTlzdmneO8Ij35g7QKF8kcLyznCQ==}
|
||||
|
||||
'@shikijs/themes@3.7.0':
|
||||
resolution: {integrity: sha512-VJx8497iZPy5zLiiCTSIaOChIcKQwR0FebwE9S3rcN0+J/GTWwQ1v/bqhTbpbY3zybPKeO8wdammqkpXc4NVjQ==}
|
||||
|
||||
'@shikijs/types@3.7.0':
|
||||
resolution: {integrity: sha512-MGaLeaRlSWpnP0XSAum3kP3a8vtcTsITqoEPYdt3lQG3YCdQH4DnEhodkYcNMcU0uW0RffhoD1O3e0vG5eSBBg==}
|
||||
|
||||
'@shikijs/vscode-textmate@10.0.2':
|
||||
resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==}
|
||||
|
||||
'@sindresorhus/merge-streams@4.0.0':
|
||||
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
|
@ -4345,6 +4369,9 @@ packages:
|
|||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
hast-util-to-html@9.0.5:
|
||||
resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==}
|
||||
|
||||
hast-util-to-jsx-runtime@2.3.6:
|
||||
resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
|
||||
|
||||
|
|
@ -4371,6 +4398,9 @@ packages:
|
|||
html-url-attributes@3.0.1:
|
||||
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
|
||||
|
||||
html-void-elements@3.0.0:
|
||||
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
|
||||
|
||||
http-errors@2.0.0:
|
||||
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
|
@ -5359,6 +5389,12 @@ packages:
|
|||
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
oniguruma-parser@0.12.1:
|
||||
resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==}
|
||||
|
||||
oniguruma-to-es@4.3.3:
|
||||
resolution: {integrity: sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==}
|
||||
|
||||
optionator@0.9.4:
|
||||
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
|
@ -5844,6 +5880,15 @@ packages:
|
|||
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
regex-recursion@6.0.2:
|
||||
resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==}
|
||||
|
||||
regex-utilities@2.3.0:
|
||||
resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==}
|
||||
|
||||
regex@6.0.1:
|
||||
resolution: {integrity: sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==}
|
||||
|
||||
regexp.prototype.flags@1.5.4:
|
||||
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
|
@ -6038,6 +6083,9 @@ packages:
|
|||
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
shiki@3.7.0:
|
||||
resolution: {integrity: sha512-ZcI4UT9n6N2pDuM2n3Jbk0sR4Swzq43nLPgS/4h0E3B/NrFn2HKElrDtceSf8Zx/OWYOo7G1SAtBLypCp+YXqg==}
|
||||
|
||||
shimmer@1.2.1:
|
||||
resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==}
|
||||
|
||||
|
|
@ -8809,6 +8857,39 @@ snapshots:
|
|||
- encoding
|
||||
- supports-color
|
||||
|
||||
'@shikijs/core@3.7.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 3.7.0
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
hast-util-to-html: 9.0.5
|
||||
|
||||
'@shikijs/engine-javascript@3.7.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 3.7.0
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
oniguruma-to-es: 4.3.3
|
||||
|
||||
'@shikijs/engine-oniguruma@3.7.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 3.7.0
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
|
||||
'@shikijs/langs@3.7.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 3.7.0
|
||||
|
||||
'@shikijs/themes@3.7.0':
|
||||
dependencies:
|
||||
'@shikijs/types': 3.7.0
|
||||
|
||||
'@shikijs/types@3.7.0':
|
||||
dependencies:
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
'@shikijs/vscode-textmate@10.0.2': {}
|
||||
|
||||
'@sindresorhus/merge-streams@4.0.0': {}
|
||||
|
||||
'@standard-schema/utils@0.3.0': {}
|
||||
|
|
@ -10965,6 +11046,20 @@ snapshots:
|
|||
dependencies:
|
||||
function-bind: 1.1.2
|
||||
|
||||
hast-util-to-html@9.0.5:
|
||||
dependencies:
|
||||
'@types/hast': 3.0.4
|
||||
'@types/unist': 3.0.3
|
||||
ccount: 2.0.1
|
||||
comma-separated-tokens: 2.0.3
|
||||
hast-util-whitespace: 3.0.0
|
||||
html-void-elements: 3.0.0
|
||||
mdast-util-to-hast: 13.2.0
|
||||
property-information: 7.1.0
|
||||
space-separated-tokens: 2.0.2
|
||||
stringify-entities: 4.0.4
|
||||
zwitch: 2.0.4
|
||||
|
||||
hast-util-to-jsx-runtime@2.3.6:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.7
|
||||
|
|
@ -11007,6 +11102,8 @@ snapshots:
|
|||
|
||||
html-url-attributes@3.0.1: {}
|
||||
|
||||
html-void-elements@3.0.0: {}
|
||||
|
||||
http-errors@2.0.0:
|
||||
dependencies:
|
||||
depd: 2.0.0
|
||||
|
|
@ -12150,6 +12247,14 @@ snapshots:
|
|||
dependencies:
|
||||
mimic-function: 5.0.1
|
||||
|
||||
oniguruma-parser@0.12.1: {}
|
||||
|
||||
oniguruma-to-es@4.3.3:
|
||||
dependencies:
|
||||
oniguruma-parser: 0.12.1
|
||||
regex: 6.0.1
|
||||
regex-recursion: 6.0.2
|
||||
|
||||
optionator@0.9.4:
|
||||
dependencies:
|
||||
deep-is: 0.1.4
|
||||
|
|
@ -12676,6 +12781,16 @@ snapshots:
|
|||
get-proto: 1.0.1
|
||||
which-builtin-type: 1.2.1
|
||||
|
||||
regex-recursion@6.0.2:
|
||||
dependencies:
|
||||
regex-utilities: 2.3.0
|
||||
|
||||
regex-utilities@2.3.0: {}
|
||||
|
||||
regex@6.0.1:
|
||||
dependencies:
|
||||
regex-utilities: 2.3.0
|
||||
|
||||
regexp.prototype.flags@1.5.4:
|
||||
dependencies:
|
||||
call-bind: 1.0.8
|
||||
|
|
@ -12976,6 +13091,17 @@ snapshots:
|
|||
|
||||
shebang-regex@3.0.0: {}
|
||||
|
||||
shiki@3.7.0:
|
||||
dependencies:
|
||||
'@shikijs/core': 3.7.0
|
||||
'@shikijs/engine-javascript': 3.7.0
|
||||
'@shikijs/engine-oniguruma': 3.7.0
|
||||
'@shikijs/langs': 3.7.0
|
||||
'@shikijs/themes': 3.7.0
|
||||
'@shikijs/types': 3.7.0
|
||||
'@shikijs/vscode-textmate': 10.0.2
|
||||
'@types/hast': 3.0.4
|
||||
|
||||
shimmer@1.2.1: {}
|
||||
|
||||
side-channel-list@1.0.0:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue