MDXEditor setup
This commit is contained in:
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
3204
frontend/package-lock.json
generated
3204
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user