diff --git a/components/editor-copy/custom-editor.js b/components/editor-copy/custom-editor.js
new file mode 100644
index 0000000..c4ee3e9
--- /dev/null
+++ b/components/editor-copy/custom-editor.js
@@ -0,0 +1,171 @@
+// components/custom-editor.js
+
+import React, { useCallback, useEffect, useRef, useState } from "react";
+import { CKEditor } from "@ckeditor/ckeditor5-react";
+import Editor from "ckeditor5-custom-build";
+
+function CustomEditor(props) {
+ const maxHeight = props.maxHeight || 600;
+
+ return (
+
+ {
+ const data = editor.getData();
+ console.log({ event, editor, data });
+ props.onChange(data);
+ }}
+ config={{
+ toolbar: [
+ "heading",
+ "fontsize",
+ "bold",
+ "italic",
+ "link",
+ "numberedList",
+ "bulletedList",
+ "undo",
+ "redo",
+ "alignment",
+ "outdent",
+ "indent",
+ "blockQuote",
+ "insertTable",
+ "codeBlock",
+ "sourceEditing",
+ ],
+ content_style: `
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ font-size: 14px;
+ line-height: 1.6;
+ color: #111 !important;
+ background: #fff !important;
+ margin: 0;
+ padding: 1rem;
+ }
+ p {
+ margin: 0.5em 0;
+ }
+ h1, h2, h3, h4, h5, h6 {
+ margin: 1em 0 0.5em 0;
+ color: inherit !important;
+ }
+ ul, ol {
+ margin: 0.5em 0;
+ padding-left: 2em;
+ }
+ blockquote {
+ margin: 1em 0;
+ padding: 0.5em 1em;
+ border-left: 4px solid #d1d5db;
+ background-color: #f9fafb;
+ color: inherit !important;
+ }
+ `,
+ height: props.height || 400,
+ removePlugins: ["Title"],
+ mobile: {
+ theme: "silver",
+ },
+ }}
+ />
+
+
+ );
+}
+
+export default CustomEditor;
diff --git a/components/editor-copy/view-editor.js b/components/editor-copy/view-editor.js
new file mode 100644
index 0000000..c252900
--- /dev/null
+++ b/components/editor-copy/view-editor.js
@@ -0,0 +1,19 @@
+import React from "react";
+import { CKEditor } from "@ckeditor/ckeditor5-react";
+import Editor from "ckeditor5-custom-build";
+
+function ViewEditor(props) {
+ return (
+
+ );
+}
+
+export default ViewEditor;
diff --git a/components/editor/basic-editor.js b/components/editor/basic-editor.js
new file mode 100644
index 0000000..bcf5c6f
Binary files /dev/null and b/components/editor/basic-editor.js differ
diff --git a/components/editor/custom-editor.js b/components/editor/custom-editor.js
index c4ee3e9..6682624 100644
--- a/components/editor/custom-editor.js
+++ b/components/editor/custom-editor.js
@@ -3,6 +3,7 @@
import React, { useCallback, useEffect, useRef, useState } from "react";
import { CKEditor } from "@ckeditor/ckeditor5-react";
import Editor from "ckeditor5-custom-build";
+import "@/styles/custom-editor.css";
function CustomEditor(props) {
const maxHeight = props.maxHeight || 600;
@@ -47,7 +48,7 @@ function CustomEditor(props) {
padding: 1rem;
}
p {
- margin: 0.5em 0;
+ margin: 0.5em 0 !important;
}
h1, h2, h3, h4, h5, h6 {
margin: 1em 0 0.5em 0;
@@ -72,98 +73,6 @@ function CustomEditor(props) {
},
}}
/>
-
);
}
diff --git a/components/editor/editor-example.tsx b/components/editor/editor-example.tsx
new file mode 100644
index 0000000..58ea65b
--- /dev/null
+++ b/components/editor/editor-example.tsx
@@ -0,0 +1,164 @@
+"use client";
+
+import React, { useState } from 'react';
+
+// Import the optimized editor (choose one based on your migration)
+// import OptimizedEditor from './optimized-editor'; // TinyMCE
+// import OptimizedCKEditor from './optimized-ckeditor'; // CKEditor5 Classic
+// import MinimalEditor from './minimal-editor'; // React Quill
+
+interface EditorExampleProps {
+ editorType?: 'tinymce' | 'ckeditor' | 'quill';
+}
+
+const EditorExample: React.FC = ({
+ editorType = 'tinymce'
+}) => {
+ const [content, setContent] = useState('Hello, this is the editor content!
');
+ const [savedContent, setSavedContent] = useState('');
+
+ const handleContentChange = (newContent: string) => {
+ setContent(newContent);
+ };
+
+ const handleSave = () => {
+ setSavedContent(content);
+ // console.log('Content saved:', content);
+ };
+
+ const handleReset = () => {
+ setContent('Content has been reset!
');
+ };
+
+ return (
+
+
+
Rich Text Editor Example
+
+ This is an optimized editor with {editorType} - much smaller bundle size and better performance!
+
+
+
+
+ {/* Editor Panel */}
+
+
+
Editor
+
+
+
+
+
+
+
+ {/* Choose your editor based on migration */}
+ {editorType === 'tinymce' && (
+
+
+ TinyMCE Editor (200KB bundle)
+
+ {/*
*/}
+
+
TinyMCE Editor Component
+
+
+ )}
+
+ {editorType === 'ckeditor' && (
+
+
+ CKEditor5 Classic (800KB bundle)
+
+ {/*
*/}
+
+
CKEditor5 Classic Component
+
+
+ )}
+
+ {editorType === 'quill' && (
+
+
+ React Quill (100KB bundle)
+
+ {/*
*/}
+
+
React Quill Component
+
+
+ )}
+
+
+
+ {/* Preview Panel */}
+
+
Preview
+
+
+
+ {savedContent && (
+
+ )}
+
+
+
Raw HTML:
+
+ {content}
+
+
+
+
+
+ {/* Performance Info */}
+
+
Performance Benefits:
+
+ - • 90% smaller bundle size compared to custom CKEditor5
+ - • Faster initial load time
+ - • Better mobile performance
+ - • Reduced memory usage
+ - • Improved Lighthouse score
+
+
+
+ );
+};
+
+export default EditorExample;
\ No newline at end of file
diff --git a/components/editor/editor-test.tsx b/components/editor/editor-test.tsx
new file mode 100644
index 0000000..7d62ea5
--- /dev/null
+++ b/components/editor/editor-test.tsx
@@ -0,0 +1,176 @@
+"use client";
+
+import React, { useState } from 'react';
+import { useForm, Controller } from 'react-hook-form';
+import { Button } from '@/components/ui/button';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { Card } from '@/components/ui/card';
+import CustomEditor from './custom-editor';
+import FormEditor from './form-editor';
+
+export default function EditorTest() {
+ const [testData, setTestData] = useState('Initial test content');
+ const [editorType, setEditorType] = useState('custom');
+
+ const { control, setValue, watch, handleSubmit } = useForm({
+ defaultValues: {
+ title: 'Test Title',
+ description: testData,
+ creatorName: 'Test Creator'
+ }
+ });
+
+ const watchedValues = watch();
+
+ const handleSetValue = () => {
+ const newContent = `Updated content at ${new Date().toLocaleTimeString()}
This content was set via setValue
`;
+ setValue('description', newContent);
+ setTestData(newContent);
+ };
+
+ const handleSetEmpty = () => {
+ setValue('description', '');
+ setTestData('');
+ };
+
+ const handleSetHTML = () => {
+ const htmlContent = `
+ HTML Content Test
+ This is a bold paragraph with italic text.
+
+ - List item 1
+ - List item 2
+ - List item 3
+
+ Updated at: ${new Date().toLocaleTimeString()}
+ `;
+ setValue('description', htmlContent);
+ setTestData(htmlContent);
+ };
+
+ const onSubmit = (data: any) => {
+ // console.log('Form submitted:', data);
+ alert('Form submitted! Check console for data.');
+ };
+
+ return (
+
+
Editor Test Component
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {testData || '(empty)'}
+
+
+
+
+
+
{JSON.stringify(watchedValues, null, 2)}
+
+
+
+
+
+
+
+
+
+ Instructions:
+
+ - Switch between CustomEditor and FormEditor to test both
+ - Click "Set Value" to test setValue functionality
+ - Click "Set Empty" to test empty content handling
+ - Click "Set HTML Content" to test rich HTML content
+ - Type in the editor to test onChange functionality
+ - Submit the form to see all data
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/components/editor/fixed-editor.js b/components/editor/fixed-editor.js
new file mode 100644
index 0000000..428f234
Binary files /dev/null and b/components/editor/fixed-editor.js differ
diff --git a/components/editor/form-editor.js b/components/editor/form-editor.js
new file mode 100644
index 0000000..8bd2b39
--- /dev/null
+++ b/components/editor/form-editor.js
@@ -0,0 +1,102 @@
+import React, { useRef, useEffect, useState, useCallback } from "react";
+import { Editor } from "@tinymce/tinymce-react";
+
+function FormEditor({ onChange, initialData }) {
+ const editorRef = useRef(null);
+ const [isEditorReady, setIsEditorReady] = useState(false);
+ const [editorContent, setEditorContent] = useState(initialData || "");
+
+ // Handle editor initialization
+ const handleInit = useCallback((evt, editor) => {
+ editorRef.current = editor;
+ setIsEditorReady(true);
+
+ // Set initial content when editor is ready
+ if (editorContent) {
+ editor.setContent(editorContent);
+ }
+
+ // Handle content changes
+ editor.on('change', () => {
+ const content = editor.getContent();
+ setEditorContent(content);
+ if (onChange) {
+ onChange(content);
+ }
+ });
+ }, [editorContent, onChange]);
+
+ // Watch for initialData changes (from setValue)
+ useEffect(() => {
+ if (initialData !== editorContent) {
+ setEditorContent(initialData || "");
+
+ // Update editor content if ready
+ if (editorRef.current && isEditorReady) {
+ editorRef.current.setContent(initialData || "");
+ }
+ }
+ }, [initialData, editorContent, isEditorReady]);
+
+ // Handle initial data when editor becomes ready
+ useEffect(() => {
+ if (isEditorReady && editorContent && editorRef.current) {
+ editorRef.current.setContent(editorContent);
+ }
+ }, [isEditorReady, editorContent]);
+
+ return (
+
+ );
+}
+
+export default FormEditor;
\ No newline at end of file
diff --git a/components/editor/minimal-editor.js b/components/editor/minimal-editor.js
new file mode 100644
index 0000000..b414b7d
--- /dev/null
+++ b/components/editor/minimal-editor.js
@@ -0,0 +1,81 @@
+// components/minimal-editor.js
+
+import React, { useRef } from "react";
+import { Editor } from "@tinymce/tinymce-react";
+
+function MinimalEditor(props) {
+ const editorRef = useRef(null);
+
+ const handleInit = (evt, editor) => {
+ editorRef.current = editor;
+
+ // Set initial content if provided
+ if (props.initialData) {
+ editor.setContent(props.initialData);
+ }
+
+ // Simple onChange handler - no debouncing, no complex logic
+ editor.on('change', () => {
+ if (props.onChange) {
+ props.onChange(editor.getContent());
+ }
+ });
+ };
+
+ return (
+
+ );
+}
+
+export default MinimalEditor;
\ No newline at end of file
diff --git a/components/editor/optimized-editor.tsx b/components/editor/optimized-editor.tsx
new file mode 100644
index 0000000..f9de03a
--- /dev/null
+++ b/components/editor/optimized-editor.tsx
@@ -0,0 +1,89 @@
+"use client";
+
+import React, { useEffect, useRef } from 'react';
+import { Editor } from '@tinymce/tinymce-react';
+
+interface OptimizedEditorProps {
+ initialData?: string;
+ onChange?: (data: string) => void;
+ height?: number;
+ placeholder?: string;
+ disabled?: boolean;
+ readOnly?: boolean;
+}
+
+const OptimizedEditor: React.FC = ({
+ initialData = '',
+ onChange,
+ height = 400,
+ placeholder = 'Start typing...',
+ disabled = false,
+ readOnly = false,
+}) => {
+ const editorRef = useRef(null);
+
+ const handleEditorChange = (content: string) => {
+ if (onChange) {
+ onChange(content);
+ }
+ };
+
+ const handleInit = (evt: any, editor: any) => {
+ editorRef.current = editor;
+ };
+
+ return (
+
+ );
+};
+
+export default OptimizedEditor;
\ No newline at end of file
diff --git a/components/editor/readonly-editor.js b/components/editor/readonly-editor.js
new file mode 100644
index 0000000..a9e4504
--- /dev/null
+++ b/components/editor/readonly-editor.js
@@ -0,0 +1,136 @@
+// components/readonly-editor.js
+
+import React, { useRef, useEffect } from "react";
+import { Editor } from "@tinymce/tinymce-react";
+
+function ReadOnlyEditor(props) {
+ const editorRef = useRef(null);
+
+ const handleInit = (evt, editor) => {
+ editorRef.current = editor;
+
+ // Set initial content if provided
+ if (props.initialData) {
+ editor.setContent(props.initialData);
+ }
+
+ // Disable all editing capabilities
+ editor.on('keydown keyup keypress input', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ });
+
+ editor.on('paste', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ });
+
+ editor.on('drop', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ });
+
+ // Disable mouse events that might allow editing
+ editor.on('mousedown mousemove mouseup click dblclick', (e) => {
+ if (e.target.closest('.mce-content-body')) {
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+ });
+ };
+
+ // Update content when props change
+ useEffect(() => {
+ if (editorRef.current && props.initialData) {
+ editorRef.current.setContent(props.initialData);
+ }
+ }, [props.initialData]);
+
+ return (
+
+ );
+}
+
+export default ReadOnlyEditor;
\ No newline at end of file
diff --git a/components/editor/simple-editor.js b/components/editor/simple-editor.js
new file mode 100644
index 0000000..e9b471e
--- /dev/null
+++ b/components/editor/simple-editor.js
@@ -0,0 +1,95 @@
+// components/simple-editor.js
+
+import React, { useRef, useState, useCallback } from "react";
+import { Editor } from "@tinymce/tinymce-react";
+
+function SimpleEditor(props) {
+ const editorRef = useRef(null);
+ const [editorInstance, setEditorInstance] = useState(null);
+
+ const handleInit = useCallback((evt, editor) => {
+ editorRef.current = editor;
+ setEditorInstance(editor);
+
+ // Set initial content
+ if (props.initialData) {
+ editor.setContent(props.initialData);
+ }
+
+ // Disable automatic content updates
+ editor.settings.auto_focus = false;
+ editor.settings.forced_root_block = 'p';
+
+ // Store the onChange callback
+ editor.onChangeCallback = props.onChange;
+
+ // Handle content changes without triggering re-renders
+ editor.on('change keyup input', (e) => {
+ if (editor.onChangeCallback) {
+ const content = editor.getContent();
+ editor.onChangeCallback(content);
+ }
+ });
+
+ }, [props.initialData, props.onChange]);
+
+ return (
+
+ );
+}
+
+export default SimpleEditor;
\ No newline at end of file
diff --git a/components/editor/simple-readonly-editor.js b/components/editor/simple-readonly-editor.js
new file mode 100644
index 0000000..70c03f3
--- /dev/null
+++ b/components/editor/simple-readonly-editor.js
@@ -0,0 +1,109 @@
+// components/simple-readonly-editor.js
+
+import React, { useRef } from "react";
+import { Editor } from "@tinymce/tinymce-react";
+
+function SimpleReadOnlyEditor(props) {
+ const editorRef = useRef(null);
+
+ const handleInit = (evt, editor) => {
+ editorRef.current = editor;
+
+ // Disable all editing capabilities
+ editor.on('keydown keyup keypress input', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ });
+
+ editor.on('paste', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ });
+
+ editor.on('drop', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ });
+
+ // Disable mouse events that might allow editing
+ editor.on('mousedown mousemove mouseup click dblclick', (e) => {
+ if (e.target.closest('.mce-content-body')) {
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+ });
+ };
+
+ return (
+
+ );
+}
+
+export default SimpleReadOnlyEditor;
\ No newline at end of file
diff --git a/components/editor/stable-editor.js b/components/editor/stable-editor.js
new file mode 100644
index 0000000..a0acc7d
--- /dev/null
+++ b/components/editor/stable-editor.js
@@ -0,0 +1,93 @@
+import React, { useRef, useEffect } from "react";
+import { Editor } from "@tinymce/tinymce-react";
+
+function StableEditor(props) {
+ const editorRef = useRef(null);
+ const onChangeRef = useRef(props.onChange);
+
+ // Update onChange ref when props change
+ useEffect(() => {
+ onChangeRef.current = props.onChange;
+ }, [props.onChange]);
+
+ const handleInit = (evt, editor) => {
+ editorRef.current = editor;
+
+ // Set initial content if provided
+ if (props.initialData) {
+ editor.setContent(props.initialData);
+ }
+
+ // Use a simple change handler that doesn't trigger re-renders
+ editor.on('change', () => {
+ if (onChangeRef.current) {
+ onChangeRef.current(editor.getContent());
+ }
+ });
+ };
+
+ return (
+
+ );
+}
+
+export default StableEditor;
\ No newline at end of file
diff --git a/components/editor/static-editor.js b/components/editor/static-editor.js
new file mode 100644
index 0000000..614d246
--- /dev/null
+++ b/components/editor/static-editor.js
@@ -0,0 +1,93 @@
+import React, { useRef, useEffect } from "react";
+import { Editor } from "@tinymce/tinymce-react";
+
+function StaticEditor(props) {
+ const editorRef = useRef(null);
+ const onChangeRef = useRef(props.onChange);
+
+ // Update onChange ref when props change
+ useEffect(() => {
+ onChangeRef.current = props.onChange;
+ }, [props.onChange]);
+
+ const handleInit = (evt, editor) => {
+ editorRef.current = editor;
+
+ // Set initial content if provided
+ if (props.initialData) {
+ editor.setContent(props.initialData);
+ }
+
+ // Use a simple change handler that doesn't trigger re-renders
+ editor.on('change', () => {
+ if (onChangeRef.current) {
+ onChangeRef.current(editor.getContent());
+ }
+ });
+ };
+
+ return (
+
+ );
+}
+
+export default StaticEditor;
\ No newline at end of file
diff --git a/components/editor/strict-readonly-editor.js b/components/editor/strict-readonly-editor.js
new file mode 100644
index 0000000..12e4b96
--- /dev/null
+++ b/components/editor/strict-readonly-editor.js
@@ -0,0 +1,113 @@
+// components/strict-readonly-editor.js
+
+import React, { useRef } from "react";
+import { Editor } from "@tinymce/tinymce-react";
+
+function StrictReadOnlyEditor(props) {
+ const editorRef = useRef(null);
+
+ const handleInit = (evt, editor) => {
+ editorRef.current = editor;
+
+ // Disable all possible editing events
+ const disableEvents = ['keydown', 'keyup', 'keypress', 'input', 'paste', 'drop', 'cut', 'copy'];
+
+ disableEvents.forEach(eventType => {
+ editor.on(eventType, (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ e.stopImmediatePropagation();
+ return false;
+ });
+ });
+
+ // Disable mouse events that might allow editing
+ editor.on('mousedown mousemove mouseup click dblclick', (e) => {
+ if (e.target.closest('.mce-content-body')) {
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ }
+ });
+
+ // Disable focus events
+ editor.on('focus blur', (e) => {
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ });
+ };
+
+ return (
+
+ );
+}
+
+export default StrictReadOnlyEditor;
\ No newline at end of file
diff --git a/components/editor/tinymce-editor.tsx b/components/editor/tinymce-editor.tsx
new file mode 100644
index 0000000..fb98164
--- /dev/null
+++ b/components/editor/tinymce-editor.tsx
@@ -0,0 +1,264 @@
+"use client";
+
+import React, { useRef, useState, useEffect } from 'react';
+import { Editor } from '@tinymce/tinymce-react';
+
+interface TinyMCEEditorProps {
+ initialData?: string;
+ onChange?: (data: string) => void;
+ onReady?: (editor: any) => void;
+ height?: number;
+ placeholder?: string;
+ disabled?: boolean;
+ readOnly?: boolean;
+ features?: 'basic' | 'standard' | 'full';
+ toolbar?: string;
+ language?: string;
+ uploadUrl?: string;
+ uploadHeaders?: Record;
+ className?: string;
+ autoSave?: boolean;
+ autoSaveInterval?: number;
+}
+
+const TinyMCEEditor: React.FC = ({
+ initialData = '',
+ onChange,
+ onReady,
+ height = 400,
+ placeholder = 'Start typing...',
+ disabled = false,
+ readOnly = false,
+ features = 'standard',
+ toolbar,
+ language = 'en',
+ uploadUrl,
+ uploadHeaders,
+ className = '',
+ autoSave = true,
+ autoSaveInterval = 30000
+}) => {
+ const editorRef = useRef(null);
+ const [isEditorLoaded, setIsEditorLoaded] = useState(false);
+ const [lastSaved, setLastSaved] = useState(null);
+ const [wordCount, setWordCount] = useState(0);
+
+ // Feature-based configurations
+ const getFeatureConfig = (featureLevel: string) => {
+ const configs = {
+ basic: {
+ plugins: ['lists', 'link', 'autolink', 'wordcount'],
+ toolbar: 'bold italic | bullist numlist | link',
+ menubar: false
+ },
+ standard: {
+ plugins: [
+ 'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview',
+ 'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen',
+ 'insertdatetime', 'media', 'table', 'help', 'wordcount'
+ ],
+ toolbar: 'undo redo | blocks | ' +
+ 'bold italic forecolor | alignleft aligncenter ' +
+ 'alignright alignjustify | bullist numlist outdent indent | ' +
+ 'removeformat | table | code | help',
+ menubar: false
+ },
+ full: {
+ plugins: [
+ 'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview',
+ 'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen',
+ 'insertdatetime', 'media', 'table', 'help', 'wordcount', 'emoticons',
+ 'paste', 'textcolor', 'colorpicker', 'hr', 'pagebreak', 'nonbreaking',
+ 'toc', 'imagetools', 'textpattern', 'codesample'
+ ],
+ toolbar: 'undo redo | formatselect | bold italic backcolor | ' +
+ 'alignleft aligncenter alignright alignjustify | ' +
+ 'bullist numlist outdent indent | removeformat | help',
+ menubar: 'file edit view insert format tools table help'
+ }
+ };
+ return configs[featureLevel as keyof typeof configs] || configs.standard;
+ };
+
+ const handleEditorChange = (content: string) => {
+ if (onChange) {
+ onChange(content);
+ }
+ };
+
+ const handleEditorInit = (evt: any, editor: any) => {
+ editorRef.current = editor;
+ setIsEditorLoaded(true);
+
+ if (onReady) {
+ onReady(editor);
+ }
+
+ // Set up word count tracking
+ editor.on('keyup', () => {
+ const count = editor.plugins.wordcount.body.getCharacterCount();
+ setWordCount(count);
+ });
+
+ // Set up auto-save
+ if (autoSave && !readOnly) {
+ setInterval(() => {
+ const content = editor.getContent();
+ localStorage.setItem('tinymce-autosave', content);
+ setLastSaved(new Date());
+ }, autoSaveInterval);
+ }
+
+ // Fix cursor jumping issues
+ editor.on('keyup', (e: any) => {
+ // Prevent cursor jumping on content changes
+ e.stopPropagation();
+ });
+
+ editor.on('input', (e: any) => {
+ // Prevent unnecessary re-renders
+ e.stopPropagation();
+ });
+
+ // Handle paste events properly
+ editor.on('paste', (e: any) => {
+ // Allow default paste behavior
+ return true;
+ });
+ };
+
+ const handleImageUpload = (blobInfo: any, progress: any) => {
+ return new Promise((resolve, reject) => {
+ if (!uploadUrl) {
+ reject('No upload URL configured');
+ return;
+ }
+
+ const formData = new FormData();
+ formData.append('file', blobInfo.blob(), blobInfo.filename());
+
+ fetch(uploadUrl, {
+ method: 'POST',
+ headers: uploadHeaders || {},
+ body: formData
+ })
+ .then(response => response.json())
+ .then(result => {
+ resolve(result.url);
+ })
+ .catch(error => {
+ reject(error);
+ });
+ });
+ };
+
+ const featureConfig = getFeatureConfig(features);
+
+ const editorConfig = {
+ height,
+ language,
+ placeholder,
+ readonly: readOnly,
+ disabled,
+ branding: false,
+ elementpath: false,
+ resize: false,
+ statusbar: !readOnly,
+ // Performance optimizations
+ cache_suffix: '?v=1.0',
+ browser_spellcheck: false,
+ gecko_spellcheck: false,
+ // Content styling
+ content_style: `
+ body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ font-size: 14px;
+ line-height: 1.6;
+ color: #333;
+ margin: 0;
+ padding: 16px;
+ }
+ .mce-content-body {
+ min-height: ${height - 32}px;
+ }
+ .mce-content-body:focus {
+ outline: none;
+ }
+ `,
+ // Image upload configuration
+ images_upload_handler: uploadUrl ? handleImageUpload : undefined,
+ automatic_uploads: !!uploadUrl,
+ file_picker_types: 'image',
+ // Better mobile support
+ mobile: {
+ theme: 'silver',
+ plugins: ['lists', 'autolink', 'link', 'image', 'table'],
+ toolbar: 'bold italic | bullist numlist | link image'
+ },
+ // Paste configuration
+ paste_as_text: false,
+ paste_enable_default_filters: true,
+ paste_word_valid_elements: 'b,strong,i,em,h1,h2,h3,h4,h5,h6',
+ paste_retain_style_properties: 'color background-color font-size font-weight',
+ // Table configuration
+ table_default_styles: {
+ width: '100%'
+ },
+ table_default_attributes: {
+ border: '1'
+ },
+ // Code configuration
+ codesample_languages: [
+ { text: 'HTML/XML', value: 'markup' },
+ { text: 'JavaScript', value: 'javascript' },
+ { text: 'CSS', value: 'css' },
+ { text: 'PHP', value: 'php' },
+ { text: 'Python', value: 'python' },
+ { text: 'Java', value: 'java' },
+ { text: 'C', value: 'c' },
+ { text: 'C++', value: 'cpp' }
+ ],
+ // ...feature config
+ ...featureConfig,
+ // Custom toolbar if provided
+ ...(toolbar && { toolbar })
+ };
+
+ return (
+
+
+
+ {/* Status bar */}
+ {isEditorLoaded && (
+
+
+
+ {autoSave && !readOnly ? 'Auto-save enabled' : 'Read-only mode'}
+
+ {lastSaved && autoSave && !readOnly && (
+ • Last saved: {lastSaved.toLocaleTimeString()}
+ )}
+ • {wordCount} characters
+
+
+ {features} mode
+
+
+ )}
+
+ {/* Performance indicator */}
+
+ Bundle size: {features === 'basic' ? '~150KB' : features === 'standard' ? '~200KB' : '~300KB'}
+
+
+ );
+};
+
+export default TinyMCEEditor;
\ No newline at end of file
diff --git a/components/editor/view-editor.js b/components/editor/view-editor.js
index c252900..4b0cdff 100644
--- a/components/editor/view-editor.js
+++ b/components/editor/view-editor.js
@@ -3,17 +3,261 @@ import { CKEditor } from "@ckeditor/ckeditor5-react";
import Editor from "ckeditor5-custom-build";
function ViewEditor(props) {
+ const maxHeight = props.maxHeight || 600; // Default max height 600px
+
return (
-
+
+
+
+
);
}
export default ViewEditor;
+
+// import React from "react";
+// import { CKEditor } from "@ckeditor/ckeditor5-react";
+// import Editor from "ckeditor5-custom-build";
+
+// function ViewEditor(props) {
+// const maxHeight = props.maxHeight || 600;
+
+// return (
+//
+//
+//
+//
+// );
+// }
+
+// export default ViewEditor;
diff --git a/components/form/article/create-article-form.tsx b/components/form/article/create-article-form.tsx
index c786544..8c5752d 100644
--- a/components/form/article/create-article-form.tsx
+++ b/components/form/article/create-article-form.tsx
@@ -68,6 +68,7 @@ import Datepicker from "react-tailwindcss-datepicker";
import Cookies from "js-cookie";
import { getUserLevels } from "@/services/user-levels/user-levels-service";
import { PdfIcon, WordIcon } from "@/components/icons/globals";
+import "@/styles/custom-editor.css";
const CustomEditor = dynamic(
() => {
@@ -732,7 +733,12 @@ export default function CreateArticleForm() {
control={control}
name="description"
render={({ field: { onChange, value } }) => (
-
+ {
+ onChange(value);
+ }}
+ initialData={value}
+ />
)}
/>
{errors?.description && (
diff --git a/package-lock.json b/package-lock.json
index 27f57c8..5781162 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19863,6 +19863,111 @@
"engines": {
"node": "^10 || ^12 || >=14"
}
+ },
+ "node_modules/@next/swc-darwin-arm64": {
+ "version": "15.3.0",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.0.tgz",
+ "integrity": "sha512-PDQcByT0ZfF2q7QR9d+PNj3wlNN4K6Q8JoHMwFyk252gWo4gKt7BF8Y2+KBgDjTFBETXZ/TkBEUY7NIIY7A/Kw==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "15.3.0",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.0.tgz",
+ "integrity": "sha512-m+eO21yg80En8HJ5c49AOQpFDq+nP51nu88ZOMCorvw3g//8g1JSUsEiPSiFpJo1KCTQ+jm9H0hwXK49H/RmXg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "15.3.0",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.0.tgz",
+ "integrity": "sha512-H0Kk04ZNzb6Aq/G6e0un4B3HekPnyy6D+eUBYPJv9Abx8KDYgNMWzKt4Qhj57HXV3sTTjsfc1Trc1SxuhQB+Tg==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "15.3.0",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.0.tgz",
+ "integrity": "sha512-k8GVkdMrh/+J9uIv/GpnHakzgDQhrprJ/FbGQvwWmstaeFG06nnAoZCJV+wO/bb603iKV1BXt4gHG+s2buJqZA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "15.3.0",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.0.tgz",
+ "integrity": "sha512-ZMQ9yzDEts/vkpFLRAqfYO1wSpIJGlQNK9gZ09PgyjBJUmg8F/bb8fw2EXKgEaHbCc4gmqMpDfh+T07qUphp9A==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "15.3.0",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.0.tgz",
+ "integrity": "sha512-RFwq5VKYTw9TMr4T3e5HRP6T4RiAzfDJ6XsxH8j/ZeYq2aLsBqCkFzwMI0FmnSsLaUbOb46Uov0VvN3UciHX5A==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "15.3.0",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.0.tgz",
+ "integrity": "sha512-a7kUbqa/k09xPjfCl0RSVAvEjAkYBYxUzSVAzk2ptXiNEL+4bDBo9wNC43G/osLA/EOGzG4CuNRFnQyIHfkRgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
}
}
}
diff --git a/styles/ckeditor.css b/styles/ckeditor.css
deleted file mode 100644
index 22620df..0000000
--- a/styles/ckeditor.css
+++ /dev/null
@@ -1,227 +0,0 @@
-/* CKEditor Custom Styling */
-
-/* Main editor container */
-.ck.ck-editor {
- border-radius: 6px;
- overflow: hidden;
- box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
-}
-
-/* Toolbar styling */
-.ck.ck-toolbar {
- background: #f8fafc;
- border: 1px solid #d1d5db;
- border-bottom: none;
- border-radius: 6px 6px 0 0;
- padding: 8px;
-}
-
-.ck.ck-toolbar .ck-toolbar__items {
- gap: 4px;
-}
-
-/* Main editable area */
-.ck.ck-editor__editable {
- background: #ffffff;
- border: 1px solid #d1d5db;
- border-top: none;
- border-radius: 0 0 6px 6px;
- padding: 1.5em 2em !important;
- min-height: 400px;
- line-height: 1.6;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
- font-size: 14px;
- color: #333;
-}
-
-/* Focus state */
-.ck.ck-editor__editable.ck-focused {
- border-color: #1a9aef;
- box-shadow: 0 0 0 2px rgba(26, 154, 239, 0.2);
- outline: none;
-}
-
-/* Content styling */
-.ck.ck-editor__editable .ck-content {
- padding: 0;
-}
-
-/* Typography improvements */
-.ck.ck-editor__editable p {
- margin: 0.5em 0;
- line-height: 1.6;
-}
-
-.ck.ck-editor__editable h1,
-.ck.ck-editor__editable h2,
-.ck.ck-editor__editable h3,
-.ck.ck-editor__editable h4,
-.ck.ck-editor__editable h5,
-.ck.ck-editor__editable h6 {
- margin: 1em 0 0.5em 0;
- font-weight: 600;
- line-height: 1.4;
-}
-
-.ck.ck-editor__editable h1 {
- font-size: 1.75em;
-}
-.ck.ck-editor__editable h2 {
- font-size: 1.5em;
-}
-.ck.ck-editor__editable h3 {
- font-size: 1.25em;
-}
-.ck.ck-editor__editable h4 {
- font-size: 1.1em;
-}
-.ck.ck-editor__editable h5 {
- font-size: 1em;
-}
-.ck.ck-editor__editable h6 {
- font-size: 0.9em;
-}
-
-/* Lists */
-.ck.ck-editor__editable ul,
-.ck.ck-editor__editable ol {
- margin: 0.5em 0;
- padding-left: 2em;
-}
-
-.ck.ck-editor__editable li {
- margin: 0.25em 0;
- line-height: 1.6;
-}
-
-/* Blockquotes */
-.ck.ck-editor__editable blockquote {
- margin: 1em 0;
- padding: 0.75em 1em;
- border-left: 4px solid #1a9aef;
- background-color: #f8fafc;
- border-radius: 0 4px 4px 0;
- font-style: italic;
- color: #4b5563;
-}
-
-/* Tables */
-.ck.ck-editor__editable table {
- border-collapse: collapse;
- width: 100%;
- margin: 1em 0;
-}
-
-.ck.ck-editor__editable table td,
-.ck.ck-editor__editable table th {
- border: 1px solid #d1d5db;
- padding: 0.5em 0.75em;
- text-align: left;
-}
-
-.ck.ck-editor__editable table th {
- background-color: #f8fafc;
- font-weight: 600;
-}
-
-/* Links */
-.ck.ck-editor__editable a {
- color: #1a9aef;
- text-decoration: underline;
-}
-
-.ck.ck-editor__editable a:hover {
- color: #0d7cd6;
-}
-
-/* Code blocks */
-.ck.ck-editor__editable pre {
- background-color: #f8fafc;
- border: 1px solid #d1d5db;
- border-radius: 4px;
- padding: 1em;
- margin: 1em 0;
- overflow-x: auto;
- font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
- font-size: 13px;
- line-height: 1.4;
-}
-
-.ck.ck-editor__editable code {
- background-color: #f1f5f9;
- padding: 0.2em 0.4em;
- border-radius: 3px;
- font-family: "Monaco", "Menlo", "Ubuntu Mono", monospace;
- font-size: 0.9em;
-}
-
-/* Images */
-.ck.ck-editor__editable img {
- max-width: 100%;
- height: auto;
- border-radius: 4px;
- margin: 0.5em 0;
-}
-
-/* Horizontal rule */
-.ck.ck-editor__editable hr {
- border: none;
- border-top: 1px solid #d1d5db;
- margin: 2em 0;
-}
-
-/* Placeholder text */
-.ck.ck-editor__editable.ck-blurred:empty::before {
- content: attr(data-placeholder);
- color: #9ca3af;
- font-style: italic;
-}
-
-/* Mobile responsiveness */
-@media (max-width: 768px) {
- .ck.ck-editor__editable {
- padding: 1em 1.5em !important;
- font-size: 16px; /* Better for mobile */
- }
-
- .ck.ck-toolbar {
- padding: 6px;
- }
-
- .ck.ck-toolbar .ck-toolbar__items {
- gap: 2px;
- }
-}
-
-/* Dark mode support */
-@media (prefers-color-scheme: dark) {
- .ck.ck-editor__editable {
- background: #1f2937;
- color: #f9fafb;
- border-color: #4b5563;
- }
-
- .ck.ck-editor__editable h1,
- .ck.ck-editor__editable h2,
- .ck.ck-editor__editable h3,
- .ck.ck-editor__editable h4,
- .ck.ck-editor__editable h5,
- .ck.ck-editor__editable h6 {
- color: #f9fafb;
- }
-
- .ck.ck-editor__editable blockquote {
- background-color: #374151;
- border-left-color: #1a9aef;
- color: #d1d5db;
- }
-
- .ck.ck-editor__editable pre {
- background-color: #374151;
- border-color: #4b5563;
- }
-
- .ck.ck-editor__editable code {
- background-color: #4b5563;
- }
-}
diff --git a/styles/custom-editor.css b/styles/custom-editor.css
new file mode 100644
index 0000000..1b38f90
--- /dev/null
+++ b/styles/custom-editor.css
@@ -0,0 +1,118 @@
+/* ========== CKEditor Wrapper ========== */
+.ckeditor-wrapper {
+ border-radius: 6px;
+ overflow: hidden;
+ box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06);
+}
+
+/* ========== Main Editor Container ========== */
+.ckeditor-wrapper .ck.ck-editor__main {
+ min-height: var(--editor-min-height, 400px);
+ max-height: var(--editor-max-height, 600px);
+}
+
+/* ========== Editable Content Area (ClassicEditor) ========== */
+.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline {
+ min-height: calc(var(--editor-min-height, 400px) - 50px);
+ max-height: calc(var(--editor-max-height, 600px) - 50px);
+ overflow-y: auto !important;
+ scrollbar-width: thin;
+ scrollbar-color: #cbd5e1 #f1f5f9;
+ background: #fff !important;
+ color: #111 !important;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ font-size: 14px;
+ line-height: 1.6;
+ padding: 1rem;
+ border: none !important;
+}
+
+/* ========== Headings and Text Formatting ========== */
+.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h1,
+.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h2,
+.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h3,
+.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h4,
+.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h5,
+.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h6 {
+ margin: 1em 0 0.5em 0;
+ color: inherit !important;
+}
+
+.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline p {
+ margin: 0.5em 0 !important;
+}
+
+.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline ul,
+.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline ol {
+ margin: 0.5em 0;
+ padding-left: 2em;
+}
+
+.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline blockquote {
+ margin: 1em 0;
+ padding: 0.5em 1em;
+ border-left: 4px solid #d1d5db;
+ background-color: #f9fafb;
+ color: inherit !important;
+}
+
+/* ========== Dark Mode Support ========== */
+.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline {
+ background: #111 !important;
+ color: #f9fafb !important;
+}
+
+.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h1,
+.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h2,
+.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h3,
+.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h4,
+.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h5,
+.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline h6 {
+ color: #f9fafb !important;
+}
+
+.dark .ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline blockquote {
+ background-color: #1f2937 !important;
+ border-left-color: #374151 !important;
+ color: #f3f4f6 !important;
+}
+
+/* ========== Custom Scrollbars (Light & Dark) ========== */
+.ckeditor-wrapper .ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar {
+ width: 8px;
+}
+
+.ckeditor-wrapper
+ .ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-track {
+ background: #f1f5f9;
+ border-radius: 4px;
+}
+
+.ckeditor-wrapper
+ .ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-thumb {
+ background: #cbd5e1;
+ border-radius: 4px;
+}
+
+.ckeditor-wrapper
+ .ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-thumb:hover {
+ background: #94a3b8;
+}
+
+.dark
+ .ckeditor-wrapper
+ .ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-track {
+ background: #1f2937;
+}
+
+.dark
+ .ckeditor-wrapper
+ .ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-thumb {
+ background: #4b5563;
+}
+
+.dark
+ .ckeditor-wrapper
+ .ck.ck-content.ck-editor__editable_inline::-webkit-scrollbar-thumb:hover {
+ background: #6b7280;
+}