diff --git a/modules/home/code-line.tsx b/modules/home/code-line.tsx index 06da7478..62b8dff7 100644 --- a/modules/home/code-line.tsx +++ b/modules/home/code-line.tsx @@ -1,40 +1,62 @@ - "use client"; import React from 'react'; -export const CodeLine = ({ line }: { line: string }) => { - // Basic replacements to simulate syntax highlighting - // Note: This is a very simplistic implementation and should be replaced with a proper library like prismjs or shiki in production - - const highlight = (text: string) => { - // We use a series of replacements. Order matters to avoid replacing inside already replaced spans. - // A better approach for robust highlighting is tokenization. - - const highlighted = text; - - // Comments (simple // for now) - if (highlighted.includes('//')) { - const parts = highlighted.split('//'); - return <>{'//' + parts[1]}; - } +const escapeHtml = (text: string) => { + return text + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +}; - return ; +const highlightCode = (code: string) => { + // Use placeholders for span tags to preserve them during escaping + const placeholderPrefix = "__SPAN_"; + const placeholderSuffix = "__"; + let placeholderIndex = 0; + const placeholders: string[] = []; + + const replaceWithPlaceholder = (match: string) => { + const placeholder = `${placeholderPrefix}${placeholderIndex++}${placeholderSuffix}`; + placeholders.push(match); + return placeholder; }; - const highlightCode = (code: string) => { - return code - .replace(/import|from|export|default|return|const|new/g, '$&') - .replace(/'[^']*'/g, '$&') - .replace(/"[^"]*"/g, '$&') - .replace(/Editron|console|editor/g, '$&'); - } + // Apply syntax highlighting on raw code first + let highlighted = code + .replace(/\b(?:import|from|export|default|return|const|new)\b/g, (match) => replaceWithPlaceholder(`${escapeHtml(match)}`)) + .replace(/'[^']*'/g, (match) => replaceWithPlaceholder(`${escapeHtml(match)}`)) + .replace(/\"[^\"]*\"/g, (match) => replaceWithPlaceholder(`${escapeHtml(match)}`)) + .replace(/\b(?:Editron|console|editor)\b/g, (match) => replaceWithPlaceholder(`${escapeHtml(match)}`)); - const [highlighted, setHighlighted] = React.useState(line); + // Escape HTML entities in the non-span content + highlighted = escapeHtml(highlighted); - React.useEffect(() => { - setHighlighted(highlight(line)); -// eslint-disable-next-line react-hooks/exhaustive-deps - }, [line]); + // Restore the span tags from placeholders + placeholders.forEach((span, index) => { + highlighted = highlighted.replace(`${placeholderPrefix}${index}${placeholderSuffix}`, span); + }); return highlighted; }; + +const highlight = (text: string) => { + if (text.includes('//')) { + const commentStart = text.indexOf('//'); + const codePart = text.slice(0, commentStart); + const commentPart = text.slice(commentStart); + return ( + <> + + {commentPart} + + ); + } + return ; +}; + +export const CodeLine = ({ line }: { line: string }) => { + const highlighted = React.useMemo(() => highlight(line), [line]); + return highlighted; +}; \ No newline at end of file diff --git a/modules/playground/components/playground-editor.tsx b/modules/playground/components/playground-editor.tsx index 1434060a..6d4735b5 100644 --- a/modules/playground/components/playground-editor.tsx +++ b/modules/playground/components/playground-editor.tsx @@ -1,7 +1,7 @@ "use client"; import { EDITOR_CONFIG } from "@/lib/constants/config"; -import { useRef, useEffect, useState } from "react"; +import { useRef, useEffect, useState, useCallback } from "react"; import Editor, { type Monaco } from "@monaco-editor/react"; import type { editor as MonacoEditor } from "monaco-editor"; import { @@ -253,7 +253,7 @@ const PlaygroundEditor = ({ ); }; - const updateEditorLanguage = () => { + const updateEditorLanguage = useCallback(() => { if (!activeFile || !monacoRef.current || !editorRef.current) return; const model = editorRef.current.getModel(); if (!model) return; @@ -264,12 +264,11 @@ const PlaygroundEditor = ({ } catch (error) { console.warn("Failed to set editor language:", error); } - }; + }, [activeFile]); useEffect(() => { updateEditorLanguage(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [activeFile]); + }, [updateEditorLanguage]); // Bind Yjs to Monaco useEffect(() => { diff --git a/modules/webcontainers/components/terminal.tsx b/modules/webcontainers/components/terminal.tsx index 743a32fd..4154e015 100644 --- a/modules/webcontainers/components/terminal.tsx +++ b/modules/webcontainers/components/terminal.tsx @@ -325,7 +325,6 @@ const writePrompt(); return terminal; -// eslint-disable-next-line react-hooks/exhaustive-deps }, [theme, handleTerminalInput, writePrompt]); const connectToWebContainer = useCallback(async () => { @@ -420,7 +419,6 @@ const currentProcess.current.kill(); } if (shellProcess.current) { -// eslint-disable-next-line react-hooks/exhaustive-deps shellProcess.current.kill(); } if (term.current) {