diff --git a/components/editor/optimized-editor.tsx b/components/editor/optimized-editor.tsx index f9de03a..82a5508 100644 --- a/components/editor/optimized-editor.tsx +++ b/components/editor/optimized-editor.tsx @@ -1,7 +1,7 @@ "use client"; -import React, { useEffect, useRef } from 'react'; -import { Editor } from '@tinymce/tinymce-react'; +import React, { useEffect, useRef } from "react"; +import { Editor } from "@tinymce/tinymce-react"; interface OptimizedEditorProps { initialData?: string; @@ -13,10 +13,10 @@ interface OptimizedEditorProps { } const OptimizedEditor: React.FC = ({ - initialData = '', + initialData = "", onChange, height = 400, - placeholder = 'Start typing...', + placeholder = "Start typing...", disabled = false, readOnly = false, }) => { @@ -42,14 +42,30 @@ const OptimizedEditor: React.FC = ({ height, menubar: false, plugins: [ - 'advlist', 'autolink', 'lists', 'link', 'image', 'charmap', 'preview', - 'anchor', 'searchreplace', 'visualblocks', 'code', 'fullscreen', - 'insertdatetime', 'media', 'table', 'code', 'help', 'wordcount' + "advlist", + "autolink", + "lists", + "link", + "image", + "charmap", + "preview", + "anchor", + "searchreplace", + "visualblocks", + "code", + "fullscreen", + "insertdatetime", + "media", + "table", + "code", + "help", + "wordcount", ], - toolbar: 'undo redo | blocks | ' + - 'bold italic forecolor | alignleft aligncenter ' + - 'alignright alignjustify | bullist numlist outdent indent | ' + - 'removeformat | table | code | help', + toolbar: + "undo redo | blocks | " + + "bold italic forecolor | alignleft aligncenter " + + "alignright alignjustify | bullist numlist outdent indent | " + + "removeformat | table | code | help", content_style: ` body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; @@ -63,27 +79,27 @@ const OptimizedEditor: React.FC = ({ } `, placeholder, - readonly: readOnly, + // readonly: readOnly, branding: false, elementpath: false, resize: false, statusbar: false, // Performance optimizations - cache_suffix: '?v=1.0', + cache_suffix: "?v=1.0", browser_spellcheck: false, gecko_spellcheck: false, // Auto-save feature auto_save: true, - auto_save_interval: '30s', + auto_save_interval: "30s", // Better mobile support mobile: { - theme: 'silver', - plugins: ['lists', 'autolink', 'link', 'image', 'table'], - toolbar: 'bold italic | bullist numlist | link image' - } + theme: "silver", + plugins: ["lists", "autolink", "link", "image", "table"], + toolbar: "bold italic | bullist numlist | link image", + }, }} /> ); }; -export default OptimizedEditor; \ No newline at end of file +export default OptimizedEditor; diff --git a/components/editor/tinymce-editor.tsx b/components/editor/tinymce-editor.tsx index fb98164..b8d8e57 100644 --- a/components/editor/tinymce-editor.tsx +++ b/components/editor/tinymce-editor.tsx @@ -1,7 +1,7 @@ "use client"; -import React, { useRef, useState, useEffect } from 'react'; -import { Editor } from '@tinymce/tinymce-react'; +import React, { useRef, useState, useEffect } from "react"; +import { Editor } from "@tinymce/tinymce-react"; interface TinyMCEEditorProps { initialData?: string; @@ -11,7 +11,7 @@ interface TinyMCEEditorProps { placeholder?: string; disabled?: boolean; readOnly?: boolean; - features?: 'basic' | 'standard' | 'full'; + features?: "basic" | "standard" | "full"; toolbar?: string; language?: string; uploadUrl?: string; @@ -22,21 +22,21 @@ interface TinyMCEEditorProps { } const TinyMCEEditor: React.FC = ({ - initialData = '', + initialData = "", onChange, onReady, height = 400, - placeholder = 'Start typing...', + placeholder = "Start typing...", disabled = false, readOnly = false, - features = 'standard', + features = "standard", toolbar, - language = 'en', + language = "en", uploadUrl, uploadHeaders, - className = '', + className = "", autoSave = true, - autoSaveInterval = 30000 + autoSaveInterval = 30000, }) => { const editorRef = useRef(null); const [isEditorLoaded, setIsEditorLoaded] = useState(false); @@ -47,35 +47,74 @@ const TinyMCEEditor: React.FC = ({ const getFeatureConfig = (featureLevel: string) => { const configs = { basic: { - plugins: ['lists', 'link', 'autolink', 'wordcount'], - toolbar: 'bold italic | bullist numlist | link', - menubar: false + 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' + "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 + 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' + "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' - } + 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; }; @@ -89,13 +128,13 @@ const TinyMCEEditor: React.FC = ({ const handleEditorInit = (evt: any, editor: any) => { editorRef.current = editor; setIsEditorLoaded(true); - + if (onReady) { onReady(editor); } // Set up word count tracking - editor.on('keyup', () => { + editor.on("keyup", () => { const count = editor.plugins.wordcount.body.getCharacterCount(); setWordCount(count); }); @@ -104,24 +143,24 @@ const TinyMCEEditor: React.FC = ({ if (autoSave && !readOnly) { setInterval(() => { const content = editor.getContent(); - localStorage.setItem('tinymce-autosave', content); + localStorage.setItem("tinymce-autosave", content); setLastSaved(new Date()); }, autoSaveInterval); } // Fix cursor jumping issues - editor.on('keyup', (e: any) => { + editor.on("keyup", (e: any) => { // Prevent cursor jumping on content changes e.stopPropagation(); }); - editor.on('input', (e: any) => { + editor.on("input", (e: any) => { // Prevent unnecessary re-renders e.stopPropagation(); }); // Handle paste events properly - editor.on('paste', (e: any) => { + editor.on("paste", (e: any) => { // Allow default paste behavior return true; }); @@ -130,23 +169,23 @@ const TinyMCEEditor: React.FC = ({ const handleImageUpload = (blobInfo: any, progress: any) => { return new Promise((resolve, reject) => { if (!uploadUrl) { - reject('No upload URL configured'); + reject("No upload URL configured"); return; } const formData = new FormData(); - formData.append('file', blobInfo.blob(), blobInfo.filename()); + formData.append("file", blobInfo.blob(), blobInfo.filename()); fetch(uploadUrl, { - method: 'POST', + method: "POST", headers: uploadHeaders || {}, - body: formData + body: formData, }) - .then(response => response.json()) - .then(result => { + .then((response) => response.json()) + .then((result) => { resolve(result.url); }) - .catch(error => { + .catch((error) => { reject(error); }); }); @@ -165,7 +204,7 @@ const TinyMCEEditor: React.FC = ({ resize: false, statusbar: !readOnly, // Performance optimizations - cache_suffix: '?v=1.0', + cache_suffix: "?v=1.0", browser_spellcheck: false, gecko_spellcheck: false, // Content styling @@ -188,40 +227,41 @@ const TinyMCEEditor: React.FC = ({ // Image upload configuration images_upload_handler: uploadUrl ? handleImageUpload : undefined, automatic_uploads: !!uploadUrl, - file_picker_types: 'image', + file_picker_types: "image", // Better mobile support mobile: { - theme: 'silver', - plugins: ['lists', 'autolink', 'link', 'image', 'table'], - toolbar: 'bold italic | bullist numlist | link image' + 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', + 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%' + width: "100%", }, table_default_attributes: { - border: '1' + 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' } + { 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 }) + ...(toolbar && { toolbar }), }; return ( @@ -232,15 +272,15 @@ const TinyMCEEditor: React.FC = ({ onEditorChange={handleEditorChange} disabled={disabled} apiKey={process.env.NEXT_PUBLIC_TINYMCE_API_KEY} - init={editorConfig} + // init={editorConfig} /> - + {/* Status bar */} {isEditorLoaded && (
- {autoSave && !readOnly ? 'Auto-save enabled' : 'Read-only mode'} + {autoSave && !readOnly ? "Auto-save enabled" : "Read-only mode"} {lastSaved && autoSave && !readOnly && ( • Last saved: {lastSaved.toLocaleTimeString()} @@ -255,10 +295,15 @@ const TinyMCEEditor: React.FC = ({ {/* Performance indicator */}
- Bundle size: {features === 'basic' ? '~150KB' : features === 'standard' ? '~200KB' : '~300KB'} + Bundle size:{" "} + {features === "basic" + ? "~150KB" + : features === "standard" + ? "~200KB" + : "~300KB"}
); }; -export default TinyMCEEditor; \ No newline at end of file +export default TinyMCEEditor; diff --git a/package-lock.json b/package-lock.json index e6bb5bf..5323207 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.5", "@radix-ui/react-tooltip": "^1.2.7", + "@tinymce/tinymce-react": "^6.3.0", "@types/js-cookie": "^3.0.6", "apexcharts": "^4.7.0", "axios": "^1.10.0", @@ -2739,6 +2740,24 @@ "tailwindcss": "4.1.11" } }, + "node_modules/@tinymce/tinymce-react": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/@tinymce/tinymce-react/-/tinymce-react-6.3.0.tgz", + "integrity": "sha512-E++xnn0XzDzpKr40jno2Kj7umfAE6XfINZULEBBeNjTMvbACWzA6CjiR6V8eTDc9yVmdVhIPqVzV4PqD5TZ/4g==", + "dependencies": { + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": "^19.0.0 || ^18.0.0 || ^17.0.1 || ^16.7.0", + "react-dom": "^19.0.0 || ^18.0.0 || ^17.0.1 || ^16.7.0", + "tinymce": "^8.0.0 || ^7.0.0 || ^6.0.0 || ^5.5.1" + }, + "peerDependenciesMeta": { + "tinymce": { + "optional": true + } + } + }, "node_modules/@types/color-convert": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.4.tgz", @@ -2798,7 +2817,6 @@ "version": "19.1.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", - "dev": true, "dependencies": { "csstype": "^3.0.2" } @@ -2807,7 +2825,7 @@ "version": "19.1.6", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.6.tgz", "integrity": "sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==", - "dev": true, + "devOptional": true, "peerDependencies": { "@types/react": "^19.0.0" } diff --git a/package.json b/package.json index b5a169b..8e626fb 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-switch": "^1.2.5", "@radix-ui/react-tooltip": "^1.2.7", + "@tinymce/tinymce-react": "^6.3.0", "@types/js-cookie": "^3.0.6", "apexcharts": "^4.7.0", "axios": "^1.10.0",