This commit is contained in:
Stefan Hardegger
2025-07-24 13:07:36 +02:00
parent 90428894b4
commit 131e2e8c25
3 changed files with 134 additions and 32 deletions

View File

@@ -47,18 +47,35 @@ export default function RichTextEditor({
e.preventDefault();
try {
// Try to get HTML content from clipboard
const items = e.clipboardData?.items;
// Try multiple approaches to get clipboard data
const clipboardData = e.clipboardData;
let htmlContent = '';
let plainText = '';
if (items) {
for (const item of Array.from(items)) {
if (item.type === 'text/html') {
// Method 1: Try direct getData calls first (more reliable)
try {
htmlContent = clipboardData.getData('text/html');
plainText = clipboardData.getData('text/plain');
console.log('Paste debug - Direct method:');
console.log('HTML length:', htmlContent.length);
console.log('HTML preview:', htmlContent.substring(0, 200));
console.log('Plain text length:', plainText.length);
} catch (e) {
console.log('Direct getData failed:', e);
}
// Method 2: If direct method didn't work, try items approach
if (!htmlContent && clipboardData?.items) {
console.log('Trying items approach...');
const items = Array.from(clipboardData.items);
console.log('Available clipboard types:', items.map(item => item.type));
for (const item of items) {
if (item.type === 'text/html' && !htmlContent) {
htmlContent = await new Promise<string>((resolve) => {
item.getAsString(resolve);
});
} else if (item.type === 'text/plain') {
} else if (item.type === 'text/plain' && !plainText) {
plainText = await new Promise<string>((resolve) => {
item.getAsString(resolve);
});
@@ -66,33 +83,98 @@ export default function RichTextEditor({
}
}
// If we have HTML content, sanitize it and merge with current content
if (htmlContent) {
console.log('Final clipboard data:');
console.log('HTML content length:', htmlContent.length);
console.log('Plain text length:', plainText.length);
// Additional debugging for clipboard types and content
if (clipboardData?.types) {
console.log('Clipboard types available:', clipboardData.types);
for (const type of clipboardData.types) {
try {
const data = clipboardData.getData(type);
console.log(`Type "${type}" content length:`, data.length);
if (data.length > 0 && data.length < 1000) {
console.log(`Type "${type}" content:`, data);
}
} catch (e) {
console.log(`Failed to get data for type "${type}":`, e);
}
}
}
// Process HTML content if available
if (htmlContent && htmlContent.trim().length > 0) {
console.log('Processing HTML content...');
console.log('Raw HTML:', htmlContent.substring(0, 500));
const sanitizedHtml = sanitizeHtmlSync(htmlContent);
console.log('Sanitized HTML:', sanitizedHtml.substring(0, 500));
// 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 => `<p>${paragraph.replace(/\n/g, '<br>')}</p>`)
.join('\n');
const newHtmlValue = value + textAsHtml;
onChange(newHtmlValue);
setHtmlValue(newHtmlValue);
// Insert at cursor position instead of just appending
const textarea = visualTextareaRef.current;
if (textarea) {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const currentPlainText = getPlainText(value);
// Split current content at cursor position
const beforeCursor = currentPlainText.substring(0, start);
const afterCursor = currentPlainText.substring(end);
// Convert plain text parts back to HTML
const beforeHtml = beforeCursor ? beforeCursor.split('\n\n').filter(p => p.trim()).map(p => `<p>${p.replace(/\n/g, '<br>')}</p>`).join('\n') : '';
const afterHtml = afterCursor ? afterCursor.split('\n\n').filter(p => p.trim()).map(p => `<p>${p.replace(/\n/g, '<br>')}</p>`).join('\n') : '';
const newHtmlValue = beforeHtml + (beforeHtml ? '\n' : '') + sanitizedHtml + (afterHtml ? '\n' : '') + afterHtml;
onChange(newHtmlValue);
setHtmlValue(newHtmlValue);
} else {
// Fallback: just append
const newHtmlValue = value + sanitizedHtml;
onChange(newHtmlValue);
setHtmlValue(newHtmlValue);
}
} else if (plainText && plainText.trim().length > 0) {
console.log('Processing plain text content...');
// For plain text, convert to paragraphs and insert at cursor
const textarea = visualTextareaRef.current;
if (textarea) {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const currentPlainText = getPlainText(value);
const beforeCursor = currentPlainText.substring(0, start);
const afterCursor = currentPlainText.substring(end);
const newPlainText = beforeCursor + plainText + afterCursor;
// Convert to HTML
const textAsHtml = newPlainText
.split('\n\n')
.filter(paragraph => paragraph.trim())
.map(paragraph => `<p>${paragraph.replace(/\n/g, '<br>')}</p>`)
.join('\n');
onChange(textAsHtml);
setHtmlValue(textAsHtml);
}
} else {
console.log('No usable clipboard content found');
}
} 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<HTMLTextAreaElement>);
if (plainText) {
const textAsHtml = plainText
.split('\n\n')
.filter(paragraph => paragraph.trim())
.map(paragraph => `<p>${paragraph.replace(/\n/g, '<br>')}</p>`)
.join('\n');
onChange(value + textAsHtml);
setHtmlValue(value + textAsHtml);
}
}
};