Enhance Richtext editor
This commit is contained in:
@@ -5,7 +5,8 @@
|
|||||||
"sup", "sub", "small", "big", "mark", "pre", "code", "kbd", "samp", "var",
|
"sup", "sub", "small", "big", "mark", "pre", "code", "kbd", "samp", "var",
|
||||||
"ul", "ol", "li", "dl", "dt", "dd",
|
"ul", "ol", "li", "dl", "dt", "dd",
|
||||||
"a", "table", "thead", "tbody", "tfoot", "tr", "th", "td", "caption", "colgroup", "col",
|
"a", "table", "thead", "tbody", "tfoot", "tr", "th", "td", "caption", "colgroup", "col",
|
||||||
"blockquote", "cite", "q", "hr", "details", "summary"
|
"blockquote", "cite", "q", "hr", "details", "summary",
|
||||||
|
"section", "article", "font", "center", "abbr", "dfn", "tt"
|
||||||
],
|
],
|
||||||
"allowedAttributes": {
|
"allowedAttributes": {
|
||||||
"p": ["class", "style"],
|
"p": ["class", "style"],
|
||||||
@@ -32,12 +33,44 @@
|
|||||||
"pre": ["class", "style"],
|
"pre": ["class", "style"],
|
||||||
"code": ["class", "style"],
|
"code": ["class", "style"],
|
||||||
"details": ["class", "style"],
|
"details": ["class", "style"],
|
||||||
"summary": ["class", "style"]
|
"summary": ["class", "style"],
|
||||||
|
"section": ["class", "style"],
|
||||||
|
"article": ["class", "style"],
|
||||||
|
"font": ["class", "style", "color", "size", "face"],
|
||||||
|
"center": ["class", "style"],
|
||||||
|
"abbr": ["class", "style", "title"],
|
||||||
|
"dfn": ["class", "style"],
|
||||||
|
"tt": ["class", "style"],
|
||||||
|
"b": ["class", "style"],
|
||||||
|
"strong": ["class", "style"],
|
||||||
|
"i": ["class", "style"],
|
||||||
|
"em": ["class", "style"],
|
||||||
|
"u": ["class", "style"],
|
||||||
|
"s": ["class", "style"],
|
||||||
|
"small": ["class", "style"],
|
||||||
|
"big": ["class", "style"],
|
||||||
|
"mark": ["class", "style"],
|
||||||
|
"sup": ["class", "style"],
|
||||||
|
"sub": ["class", "style"],
|
||||||
|
"del": ["class", "style"],
|
||||||
|
"ins": ["class", "style"],
|
||||||
|
"strike": ["class", "style"],
|
||||||
|
"kbd": ["class", "style"],
|
||||||
|
"samp": ["class", "style"],
|
||||||
|
"var": ["class", "style"]
|
||||||
},
|
},
|
||||||
"allowedCssProperties": [
|
"allowedCssProperties": [
|
||||||
"color", "background-color", "font-size", "font-weight",
|
"color", "background-color", "font-size", "font-weight",
|
||||||
"font-style", "text-align", "text-decoration", "margin",
|
"font-style", "text-align", "text-decoration", "margin",
|
||||||
"padding", "text-indent", "line-height"
|
"padding", "text-indent", "line-height",
|
||||||
|
"border", "border-color", "border-width", "border-style",
|
||||||
|
"font-family", "font-variant", "font-variant-ligatures",
|
||||||
|
"font-variant-caps", "font-variant-numeric", "font-variant-east-asian",
|
||||||
|
"font-variant-alternates", "font-variant-position", "font-variant-emoji",
|
||||||
|
"font-stretch", "letter-spacing", "word-spacing",
|
||||||
|
"text-transform", "text-shadow", "white-space",
|
||||||
|
"vertical-align", "display", "float", "clear",
|
||||||
|
"width", "height", "min-width", "min-height", "max-width", "max-height"
|
||||||
],
|
],
|
||||||
"removedAttributes": {
|
"removedAttributes": {
|
||||||
"a": ["href", "target"]
|
"a": ["href", "target"]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import { useState, useRef, useEffect } from 'react';
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import { Textarea } from '../ui/Input';
|
import { Textarea } from '../ui/Input';
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
import { sanitizeHtmlSync, preloadSanitizationConfig } from '../../lib/sanitization';
|
import { sanitizeHtmlSync } from '../../lib/sanitization';
|
||||||
|
|
||||||
interface RichTextEditorProps {
|
interface RichTextEditorProps {
|
||||||
value: string;
|
value: string;
|
||||||
@@ -25,7 +25,11 @@ export default function RichTextEditor({
|
|||||||
|
|
||||||
// Preload sanitization config
|
// Preload sanitization config
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
preloadSanitizationConfig().catch(console.error);
|
// Clear cache and reload config to get latest sanitization rules
|
||||||
|
import('../../lib/sanitization').then(({ clearSanitizationCache, preloadSanitizationConfig }) => {
|
||||||
|
clearSanitizationCache();
|
||||||
|
preloadSanitizationConfig().catch(console.error);
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleVisualChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const handleVisualChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
@@ -109,7 +113,15 @@ export default function RichTextEditor({
|
|||||||
console.log('Raw HTML:', htmlContent.substring(0, 500));
|
console.log('Raw HTML:', htmlContent.substring(0, 500));
|
||||||
|
|
||||||
const sanitizedHtml = sanitizeHtmlSync(htmlContent);
|
const sanitizedHtml = sanitizeHtmlSync(htmlContent);
|
||||||
console.log('Sanitized HTML:', sanitizedHtml.substring(0, 500));
|
console.log('Sanitized HTML length:', sanitizedHtml.length);
|
||||||
|
console.log('Sanitized HTML preview:', sanitizedHtml.substring(0, 500));
|
||||||
|
|
||||||
|
// Check if sanitization removed too much content
|
||||||
|
const ratio = sanitizedHtml.length / htmlContent.length;
|
||||||
|
console.log('Sanitization ratio (kept/original):', ratio.toFixed(3));
|
||||||
|
if (ratio < 0.1) {
|
||||||
|
console.warn('Sanitization removed >90% of content - this might be too aggressive');
|
||||||
|
}
|
||||||
|
|
||||||
// Insert at cursor position instead of just appending
|
// Insert at cursor position instead of just appending
|
||||||
const textarea = visualTextareaRef.current;
|
const textarea = visualTextareaRef.current;
|
||||||
|
|||||||
Reference in New Issue
Block a user