MDXEditor setup

This commit is contained in:
gom-by
2025-02-19 17:53:38 +01:00
parent 70cce97c6a
commit 965f107921
3 changed files with 3253 additions and 147 deletions

View File

@@ -1,144 +1,77 @@
"use client"; "use client";
import { Textarea } from "@/components/ui/textarea"; import { useEffect, useState, useRef } from "react";
import { useEffect, useRef, useState } from "react";
import { marked } from "marked";
import DOMPurify from "isomorphic-dompurify";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Bold, Italic, Strikethrough, Underline } from "lucide-react"; import { Bold, Italic, Strikethrough, Underline } from "lucide-react";
import { MDXEditor } from "@mdxeditor/editor"; // Assuming react-mdx-editor or similar
enum Command { import DOMPurify from "isomorphic-dompurify";
Italic = "*",
Bold = "**",
Strikethrough = "~~",
Underline = "__",
}
export default function NewBlog() { export default function NewBlog() {
const ref = useRef<HTMLTextAreaElement>(null); const [text, setText] = useState("");
const [text, setText] = useState(""); const [cursor, setCursor] = useState<{ line: number; column: number }>({
const [cursor, setCursor] = useState<{ line: number; column: number }>({ line: 0,
line: 0, column: 0,
column: 0, });
});
const [selection, setSelection] = useState<{
start: number;
end: number;
} | null>(null);
const getCursorPosition = ( // Function to update the cursor position
event: React.ChangeEvent<HTMLTextAreaElement>, const getCursorPosition = (newText: string) => {
) => { const cursorPos = newText.length;
const textarea = event.target; const lines = newText.substring(0, cursorPos).split("\n");
const text = textarea.value; const line = lines.length; // Current line number
const cursorPos = textarea.selectionStart; const column = lines[lines.length - 1].length + 1; // Current column number
const lines = text.substring(0, cursorPos).split("\n"); setCursor({ line, column });
const line = lines.length; // Current line number (1-based) };
const column = lines[lines.length - 1].length + 1; // Current column (1-based)
setCursor({ line, column }); const execCommand = (command: string) => {
}; const pre = text.slice(0, cursor.column);
const post = text.slice(cursor.column);
const onSelect = (event: React.ChangeEvent<HTMLTextAreaElement>) => { setText(pre + command + post);
const { selectionStart, selectionEnd } = event.currentTarget; getCursorPosition(pre + command + post); // Update cursor position after change
if (selectionStart === selectionEnd) return; };
setSelection({ start: selectionStart, end: selectionEnd }); // Sanitize the MDX text before rendering
}; const sanitized = DOMPurify.sanitize(text);
useEffect(() => { return (
setSelection(null); <section className="flex m-5">
}, [text]); <div className="flex-1">
<div className="flex gap-2 mb-2 border-b pb-2">
const moveCursor = (newPos: number) => { <Button variant="outline" size="icon" onClick={() => execCommand("**")}>
if (!ref.current) return; <Bold size={16} />
ref.current.selectionEnd = newPos; </Button>
ref.current.focus(); <Button variant="outline" size="icon" onClick={() => execCommand("*")}>
}; <Italic size={16} />
</Button>
const execCommand = (command: Command, symetry: boolean = true) => { <Button variant="outline" size="icon" onClick={() => execCommand("__")}>
if (selection) { <Underline size={16} />
const selectedText = text.substring(selection.start, selection.end); </Button>
const pre = text.slice(0, selection.start); <Button variant="outline" size="icon" onClick={() => execCommand("~~")}>
const post = text.slice(selection.end); <Strikethrough size={16} />
const newSelectedText = `${command}${selectedText}${symetry ? command : ""}`; </Button>
setText(pre + newSelectedText + post); </div>
return;
} {/* Using MDXEditor instead of a basic Textarea */}
<MDXEditor
const pre = text.slice(0, cursor.column); onChange={setText}
const post = text.slice(cursor.column); onBlur={() => getCursorPosition(text)}
markdown="ok"
if (!symetry) setText(pre + command + post); />
else {
const t = pre + command + command + post; <div>
setText(t); Line: {cursor.line}; Column: {cursor.column}
} </div>
console.log(pre.length + command.length); </div>
moveCursor(cursor.column + 2);
}; <div
className="mt-4 p-2 bg-gray-100 border rounded-md text-sm text-black"
const sanitized = DOMPurify.sanitize(marked(text, { async: false })); dangerouslySetInnerHTML={{
__html: sanitized,
return ( }}
<section className="flex"> />
<div> </section>
<div className="flex gap-2 mb-2 border-b pb-2"> );
<Button
variant="outline"
size="icon"
onClick={() => execCommand(Command.Bold)}
>
<Bold size={16} />
</Button>
<Button
variant="outline"
size="icon"
onClick={() => execCommand(Command.Italic)}
>
<Italic size={16} />
</Button>
<Button
variant="outline"
size="icon"
onClick={() => execCommand(Command.Underline)}
>
<Underline size={16} />
</Button>
<Button
variant="outline"
size="icon"
onClick={() => execCommand(Command.Strikethrough)}
>
<Strikethrough size={16} />
</Button>
{/*<Button variant="outline" size="icon" onClick={handleLink}>
<Link size={16} />
</Button> */}
</div>
<Textarea
ref={ref}
value={text}
onSelect={(e: React.ChangeEvent<HTMLTextAreaElement>) => {
getCursorPosition(e);
onSelect(e);
}}
onChange={(e) => setText(e.currentTarget.value)}
/>
<div>
Line: {cursor.line}; Column: {cursor.column}
<br />
Selection: Start {selection?.start} End {selection?.end}
</div>
</div>
<div
className="mt-4 p-2 bg-gray-100 border rounded-md text-sm text-black"
dangerouslySetInnerHTML={{
// @ts-ignore
__html: sanitized,
}}
></div>
</section>
);
} }

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@
}, },
"dependencies": { "dependencies": {
"@hookform/resolvers": "^3.10.0", "@hookform/resolvers": "^3.10.0",
"@mdxeditor/editor": "^3.23.2",
"@radix-ui/react-accordion": "^1.2.2", "@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-avatar": "^1.1.2",
"@radix-ui/react-checkbox": "^1.1.3", "@radix-ui/react-checkbox": "^1.1.3",