···11-import { useEditor, EditorContent } from '@tiptap/react';
11+import { useEditor, EditorContent, type Editor } from '@tiptap/react';
22+import { useEffect } from 'react';
23import StarterKit from '@tiptap/starter-kit';
34import Typography from '@tiptap/extension-typography';
45import Placeholder from '@tiptap/extension-placeholder';
56import Link from '@tiptap/extension-link';
67import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
78import { common, createLowlight } from 'lowlight';
99+import { Markdown, type MarkdownStorage } from 'tiptap-markdown';
810import { MenuBar } from './MenuBar';
99-import { htmlToMarkdown } from '../../lib/utils/markdown';
10111112const lowlight = createLowlight(common);
1213···1718 className?: string;
1819}
19202121+// Helper to get markdown from editor
2222+function getMarkdownFromEditor(editor: Editor): string {
2323+ const storage = editor.storage as any;
2424+ return storage.markdown?.getMarkdown() || editor.getText();
2525+}
2626+2027export function TipTapEditor({
2128 content,
2229 onChange,
···4451 class: 'bg-gray-900 text-gray-100 rounded-lg p-4 font-mono text-sm overflow-x-auto',
4552 },
4653 }),
5454+ Markdown.configure({
5555+ html: true,
5656+ tightLists: true,
5757+ tightListClass: 'tight',
5858+ bulletListMarker: '-',
5959+ linkify: true,
6060+ breaks: true,
6161+ transformPastedText: true,
6262+ transformCopiedText: true,
6363+ }),
4764 ],
4865 content,
4966 editorProps: {
···5269 },
5370 },
5471 onUpdate: ({ editor }) => {
5555- // Convert HTML to markdown before calling onChange
5656- const html = editor.getHTML();
5757- const markdown = htmlToMarkdown(html);
7272+ // Get markdown directly from the editor using tiptap-markdown
7373+ const markdown = getMarkdownFromEditor(editor);
5874 onChange(markdown);
5975 },
6076 });
7777+7878+ // Update editor content when the content prop changes (e.g., when switching files)
7979+ useEffect(() => {
8080+ if (editor && content) {
8181+ const currentMarkdown = getMarkdownFromEditor(editor);
8282+ if (content !== currentMarkdown) {
8383+ editor.commands.setContent(content);
8484+ }
8585+ }
8686+ }, [editor, content]);
61876288 return (
6389 <div className={`border-2 border-gray-900 bg-white ${className}`}>
+17-5
frontend/src/lib/utils/markdown.ts
···11import TurndownService from 'turndown';
22+import { unified } from 'unified';
33+import remarkParse from 'remark-parse';
44+import remarkHtml from 'remark-html';
2536// Initialize Turndown for HTML to Markdown conversion
47const turndownService = new TurndownService({
···1417 return turndownService.turndown(html);
1518}
16191717-// For markdown to HTML, we'll use the browser's built-in markdown rendering
1818-// via TipTap's setContent which accepts markdown
1919-export function markdownToHtml(markdown: string): string {
2020- // This is a simple conversion - TipTap will handle the actual rendering
2121- // We just need to preserve the markdown structure
2020+// Convert markdown to HTML using remark
2121+export async function markdownToHtml(markdown: string): Promise<string> {
2222+ const file = await unified()
2323+ .use(remarkParse)
2424+ .use(remarkHtml, { sanitize: false })
2525+ .process(markdown);
2626+2727+ return String(file);
2828+}
2929+3030+// Synchronous version for TipTap initialization
3131+export function markdownToHtmlSync(markdown: string): string {
3232+ // For now, return the markdown as-is and let TipTap handle it
3333+ // This will be processed asynchronously by the editor
2234 return markdown;
2335}