diff --git a/frontend/src/components/stories/RichTextEditor.tsx b/frontend/src/components/stories/RichTextEditor.tsx index 152ad2f..13dd396 100644 --- a/frontend/src/components/stories/RichTextEditor.tsx +++ b/frontend/src/components/stories/RichTextEditor.tsx @@ -1,8 +1,9 @@ 'use client'; -import { useState, useRef } from 'react'; +import { useState, useRef, useEffect } from 'react'; import { Textarea } from '../ui/Input'; import Button from '../ui/Button'; +import { sanitizeHtmlSync, preloadSanitizationConfig } from '../../lib/sanitization'; interface RichTextEditorProps { value: string; @@ -20,6 +21,12 @@ export default function RichTextEditor({ const [viewMode, setViewMode] = useState<'visual' | 'html'>('visual'); const [htmlValue, setHtmlValue] = useState(value); const previewRef = useRef(null); + const visualTextareaRef = useRef(null); + + // Preload sanitization config + useEffect(() => { + preloadSanitizationConfig().catch(console.error); + }, []); const handleVisualChange = (e: React.ChangeEvent) => { const plainText = e.target.value; @@ -34,6 +41,61 @@ export default function RichTextEditor({ setHtmlValue(htmlContent); }; + const handlePaste = async (e: React.ClipboardEvent) => { + if (viewMode !== 'visual') return; + + e.preventDefault(); + + try { + // Try to get HTML content from clipboard + const items = e.clipboardData?.items; + let htmlContent = ''; + let plainText = ''; + + if (items) { + for (const item of Array.from(items)) { + if (item.type === 'text/html') { + htmlContent = await new Promise((resolve) => { + item.getAsString(resolve); + }); + } else if (item.type === 'text/plain') { + plainText = await new Promise((resolve) => { + item.getAsString(resolve); + }); + } + } + } + + // If we have HTML content, sanitize it and merge with current content + if (htmlContent) { + const sanitizedHtml = sanitizeHtmlSync(htmlContent); + + // Simply append the sanitized HTML to current content + // This approach maintains the HTML formatting while being simpler + const newHtmlValue = value + sanitizedHtml; + + onChange(newHtmlValue); + setHtmlValue(newHtmlValue); + } else if (plainText) { + // For plain text, convert to paragraphs and append + const textAsHtml = plainText + .split('\n\n') + .filter(paragraph => paragraph.trim()) + .map(paragraph => `

${paragraph.replace(/\n/g, '
')}

`) + .join('\n'); + + const newHtmlValue = value + textAsHtml; + onChange(newHtmlValue); + setHtmlValue(newHtmlValue); + } + } catch (error) { + console.error('Error handling paste:', error); + // Fallback to default paste behavior + const plainText = e.clipboardData.getData('text/plain'); + handleVisualChange({ target: { value: plainText } } as React.ChangeEvent); + } + }; + const handleHtmlChange = (e: React.ChangeEvent) => { const html = e.target.value; setHtmlValue(html); @@ -137,8 +199,10 @@ export default function RichTextEditor({
{viewMode === 'visual' ? (