diff --git a/vault/components/code-editor.tsx b/vault/components/code-editor.tsx new file mode 100644 index 0000000..1ed72bc --- /dev/null +++ b/vault/components/code-editor.tsx @@ -0,0 +1,140 @@ +"use client"; + +import dynamic from "next/dynamic"; + +const CodeMirrorEditor = dynamic( + () => import("@uiw/react-codemirror").then((mod) => mod.default), + { ssr: false } +); + +const langExtensions: Record Promise> = { + javascript: () => import("@codemirror/lang-javascript").then((m) => m.javascript()), + typescript: () => import("@codemirror/lang-javascript").then((m) => m.javascript({ jsx: true, typescript: true })), + python: () => import("@codemirror/lang-python").then((m) => m.python()), + html: () => import("@codemirror/lang-html").then((m) => m.html()), + css: () => import("@codemirror/lang-css").then((m) => m.css()), + json: () => import("@codemirror/lang-json").then((m) => m.json()), + markdown: () => import("@codemirror/lang-markdown").then((m) => m.markdown()), + rust: () => import("@codemirror/lang-rust").then((m) => m.rust()), + go: () => import("@codemirror/lang-go").then((m) => m.go()), + java: () => import("@codemirror/lang-java").then((m) => m.java()), + cpp: () => import("@codemirror/lang-cpp").then((m) => m.cpp()), + xml: () => import("@codemirror/lang-xml").then((m) => m.xml()), + php: () => import("@codemirror/lang-php").then((m) => m.php()), +}; + +import { useState, useEffect, useCallback } from "react"; +import { EditorView } from "@codemirror/view"; +import type { Extension } from "@codemirror/state"; + +type ThemeName = "dark" | "light" | "dracula" | "monokai" | "github-dark" | "nord"; + +const THEMES: { value: ThemeName; label: string }[] = [ + { value: "dark", label: "One Dark" }, + { value: "dracula", label: "Dracula" }, + { value: "monokai", label: "Monokai" }, + { value: "nord", label: "Nord" }, + { value: "github-dark", label: "GitHub Dark" }, + { value: "light", label: "Light" }, +]; + +const THEME_STORAGE_KEY = "cvault:editor-theme"; + +async function loadThemeExtension(name: ThemeName): Promise<"dark" | "light" | Extension> { + switch (name) { + case "dark": return "dark"; + case "light": return "light"; + case "dracula": { + const { dracula } = await import("@uiw/codemirror-theme-dracula"); + return dracula; + } + case "monokai": { + const { monokai } = await import("@uiw/codemirror-theme-monokai"); + return monokai; + } + case "github-dark": { + const { githubDark } = await import("@uiw/codemirror-theme-github"); + return githubDark; + } + case "nord": { + const { nord } = await import("@uiw/codemirror-theme-nord"); + return nord; + } + } +} + +interface CodeEditorProps { + value: string; + onChange: (value: string) => void; + language: string; +} + +export function CodeEditor({ value, onChange, language }: CodeEditorProps) { + const [langExtension, setLangExtension] = useState(null); + const [themeName, setThemeName] = useState("dark"); + const [themeExtension, setThemeExtension] = useState<"dark" | "light" | Extension>("dark"); + + useEffect(() => { + const stored = localStorage.getItem(THEME_STORAGE_KEY) as ThemeName | null; + if (stored && THEMES.some((t) => t.value === stored)) setThemeName(stored); + }, []); + + useEffect(() => { + loadThemeExtension(themeName).then(setThemeExtension); + }, [themeName]); + + useEffect(() => { + const loader = langExtensions[language]; + if (loader) { + loader().then((ext) => setLangExtension(ext as Extension)); + } else { + setLangExtension(null); + } + }, [language]); + + const handleThemeChange = useCallback((name: ThemeName) => { + setThemeName(name); + localStorage.setItem(THEME_STORAGE_KEY, name); + }, []); + + const extensions: Extension[] = [ + ...(langExtension ? [langExtension] : []), + EditorView.lineWrapping, + ]; + + return ( +
+
+ + editor +
+ {CodeMirrorEditor && ( + + )} +
+ ); +}