'use client'; import { LexicalComposer } from '@lexical/react/LexicalComposer'; import { RichTextPlugin } from '@lexical/react/LexicalRichTextPlugin'; import { ContentEditable } from '@lexical/react/LexicalContentEditable'; import { HistoryPlugin } from '@lexical/react/LexicalHistoryPlugin'; import { LexicalErrorBoundary } from '@lexical/react/LexicalErrorBoundary'; import { ListPlugin } from '@lexical/react/LexicalListPlugin'; import { LinkPlugin } from '@lexical/react/LexicalLinkPlugin'; import { OnChangePlugin } from '@lexical/react/LexicalOnChangePlugin'; import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'; import { $generateHtmlFromNodes, $generateNodesFromDOM } from '@lexical/html'; import { $getRoot, $insertNodes, $getSelection, $isRangeSelection, FORMAT_TEXT_COMMAND } from 'lexical'; import { ListNode, ListItemNode } from '@lexical/list'; import { LinkNode, AutoLinkNode, $createLinkNode, TOGGLE_LINK_COMMAND } from '@lexical/link'; import { HeadingNode, QuoteNode, $createHeadingNode } from '@lexical/rich-text'; import { $setBlocksType } from '@lexical/selection'; import { useEffect, useState } from 'react'; // Toolbar Component function ToolbarPlugin() { const [editor] = useLexicalComposerContext(); const [showLinkModal, setShowLinkModal] = useState(false); const [linkUrl, setLinkUrl] = useState(''); const [linkText, setLinkText] = useState(''); const formatBold = () => { editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'bold'); }; const formatItalic = () => { editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'italic'); }; const formatUnderline = () => { editor.dispatchCommand(FORMAT_TEXT_COMMAND, 'underline'); }; const formatHeading = (level: 'h2' | 'h3') => { editor.update(() => { const selection = $getSelection(); if ($isRangeSelection(selection)) { $setBlocksType(selection, () => $createHeadingNode(level)); } }); }; const formatBulletList = () => { editor.dispatchCommand({ type: 'INSERT_UNORDERED_LIST_COMMAND' } as any, undefined); }; const formatNumberedList = () => { editor.dispatchCommand({ type: 'INSERT_ORDERED_LIST_COMMAND' } as any, undefined); }; const openLinkModal = () => { // Get selected text if any editor.getEditorState().read(() => { const selection = $getSelection(); if ($isRangeSelection(selection)) { const text = selection.getTextContent(); setLinkText(text); } }); setLinkUrl(''); setShowLinkModal(true); }; const insertLink = () => { if (!linkUrl) return; editor.update(() => { const selection = $getSelection(); if ($isRangeSelection(selection)) { // If there's selected text, convert it to a link if (selection.getTextContent()) { editor.dispatchCommand(TOGGLE_LINK_COMMAND, linkUrl); } else if (linkText) { // If no selection but we have link text, insert new link const linkNode = $createLinkNode(linkUrl); linkNode.append($getRoot().getFirstChild() as any); selection.insertNodes([linkNode]); } } }); setShowLinkModal(false); setLinkUrl(''); setLinkText(''); }; return ( <>
{/* Link Modal */} {showLinkModal && (
setShowLinkModal(false)}>
e.stopPropagation()}>

Insert Link

setLinkText(e.target.value)} placeholder="Click here" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white" />
setLinkUrl(e.target.value)} placeholder="https://example.com" className="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded focus:outline-none focus:ring-2 focus:ring-indigo-500 dark:bg-gray-700 dark:text-white" autoFocus />
)} ); } // Plugin to load initial HTML content function InitialContentPlugin({ html }: { html?: string }) { const [editor] = useLexicalComposerContext(); useEffect(() => { if (html) { editor.update(() => { const parser = new DOMParser(); const dom = parser.parseFromString(html, 'text/html'); const nodes = $generateNodesFromDOM(editor, dom); $getRoot().clear(); $getRoot().select(); $insertNodes(nodes); }); } }, []); // Only run once on mount return null; } interface RichTextEditorProps { value?: string; onChange: (html: string) => void; placeholder?: string; } export default function RichTextEditor({ value, onChange, placeholder = 'Enter text...' }: RichTextEditorProps) { const initialConfig = { namespace: 'WishlistPreferences', theme: { paragraph: 'mb-2', heading: { h2: 'text-2xl font-bold mb-3 mt-4 text-gray-900 dark:text-white', h3: 'text-xl font-semibold mb-2 mt-3 text-gray-900 dark:text-white', }, list: { ul: 'list-disc list-inside mb-2', ol: 'list-decimal list-inside mb-2', }, link: 'text-indigo-600 dark:text-indigo-400 hover:underline', text: { bold: 'font-bold', italic: 'italic', underline: 'underline', }, }, onError: (error: Error) => { console.error(error); }, nodes: [ HeadingNode, ListNode, ListItemNode, QuoteNode, LinkNode, AutoLinkNode, ], }; const handleChange = (editorState: any, editor: any) => { editor.read(() => { const html = $generateHtmlFromNodes(editor); onChange(html); }); }; return (
} placeholder={
{placeholder}
} ErrorBoundary={LexicalErrorBoundary} /> {value && }
); }