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");
const line = lines.length; // Current line number (1-based)
const column = lines[lines.length - 1].length + 1; // Current column (1-based)
setCursor({ line, column }); setCursor({ line, column });
}; };
const onSelect = (event: React.ChangeEvent<HTMLTextAreaElement>) => { const execCommand = (command: string) => {
const { selectionStart, selectionEnd } = event.currentTarget;
if (selectionStart === selectionEnd) return;
setSelection({ start: selectionStart, end: selectionEnd });
};
useEffect(() => {
setSelection(null);
}, [text]);
const moveCursor = (newPos: number) => {
if (!ref.current) return;
ref.current.selectionEnd = newPos;
ref.current.focus();
};
const execCommand = (command: Command, symetry: boolean = true) => {
if (selection) {
const selectedText = text.substring(selection.start, selection.end);
const pre = text.slice(0, selection.start);
const post = text.slice(selection.end);
const newSelectedText = `${command}${selectedText}${symetry ? command : ""}`;
setText(pre + newSelectedText + post);
return;
}
const pre = text.slice(0, cursor.column); const pre = text.slice(0, cursor.column);
const post = text.slice(cursor.column); const post = text.slice(cursor.column);
if (!symetry) setText(pre + command + post); setText(pre + command + post);
else { getCursorPosition(pre + command + post); // Update cursor position after change
const t = pre + command + command + post;
setText(t);
}
console.log(pre.length + command.length);
moveCursor(cursor.column + 2);
}; };
const sanitized = DOMPurify.sanitize(marked(text, { async: false })); // Sanitize the MDX text before rendering
const sanitized = DOMPurify.sanitize(text);
return ( return (
<section className="flex"> <section className="flex m-5">
<div> <div className="flex-1">
<div className="flex gap-2 mb-2 border-b pb-2"> <div className="flex gap-2 mb-2 border-b pb-2">
<Button <Button variant="outline" size="icon" onClick={() => execCommand("**")}>
variant="outline"
size="icon"
onClick={() => execCommand(Command.Bold)}
>
<Bold size={16} /> <Bold size={16} />
</Button> </Button>
<Button <Button variant="outline" size="icon" onClick={() => execCommand("*")}>
variant="outline"
size="icon"
onClick={() => execCommand(Command.Italic)}
>
<Italic size={16} /> <Italic size={16} />
</Button> </Button>
<Button <Button variant="outline" size="icon" onClick={() => execCommand("__")}>
variant="outline"
size="icon"
onClick={() => execCommand(Command.Underline)}
>
<Underline size={16} /> <Underline size={16} />
</Button> </Button>
<Button <Button variant="outline" size="icon" onClick={() => execCommand("~~")}>
variant="outline"
size="icon"
onClick={() => execCommand(Command.Strikethrough)}
>
<Strikethrough size={16} /> <Strikethrough size={16} />
</Button> </Button>
{/*<Button variant="outline" size="icon" onClick={handleLink}>
<Link size={16} />
</Button> */}
</div> </div>
<Textarea
ref={ref} {/* Using MDXEditor instead of a basic Textarea */}
value={text} <MDXEditor
onSelect={(e: React.ChangeEvent<HTMLTextAreaElement>) => { onChange={setText}
getCursorPosition(e); onBlur={() => getCursorPosition(text)}
onSelect(e); markdown="ok"
}}
onChange={(e) => setText(e.currentTarget.value)}
/> />
<div> <div>
Line: {cursor.line}; Column: {cursor.column} Line: {cursor.line}; Column: {cursor.column}
<br />
Selection: Start {selection?.start} End {selection?.end}
</div> </div>
</div> </div>
<div <div
className="mt-4 p-2 bg-gray-100 border rounded-md text-sm text-black" className="mt-4 p-2 bg-gray-100 border rounded-md text-sm text-black"
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
// @ts-ignore
__html: sanitized, __html: sanitized,
}} }}
></div> />
</section> </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",