Class approach

This commit is contained in:
Caleb Braaten 2025-04-23 15:40:50 -07:00
parent 55bda8a1bd
commit 80fb1813cf

View File

@ -2,103 +2,105 @@ import { useEffect, useRef, useState } from 'react'
import './App.css' import './App.css'
import EditorJS, { OutputData } from '@editorjs/editorjs' import EditorJS, { OutputData } from '@editorjs/editorjs'
interface EditorWrapper { class EditorJSWrapper {
instance: EditorJS | null; private editor: EditorJS | null = null;
addBlock: (text: string) => Promise<void>; private holder: HTMLDivElement | null = null;
clear: () => Promise<void>; private onChange!: (data: OutputData) => void;
getData: () => Promise<OutputData>; private static instance: EditorJSWrapper | null = null;
holderRef: React.RefObject<HTMLDivElement | null>;
}
function useEditorJS(onChange: (data: OutputData) => void): EditorWrapper { constructor(onChange: (data: OutputData) => void) {
const [editor, setEditor] = useState<EditorJS | null>(null); if (EditorJSWrapper.instance) {
const holderRef = useRef<HTMLDivElement | null>(null); return EditorJSWrapper.instance;
const isDestroyed = useRef(false); }
this.onChange = onChange;
EditorJSWrapper.instance = this;
}
// Initialize editor initialize(holder: HTMLDivElement) {
useEffect(() => { if (this.editor || !holder) return;
if (!holderRef.current || editor || isDestroyed.current) return;
const instance = new EditorJS({ // Ensure any existing instance is destroyed first
holder: holderRef.current, if (EditorJSWrapper.instance?.editor) {
void EditorJSWrapper.instance.destroy();
}
this.holder = holder;
try {
this.editor = new EditorJS({
holder: holder,
placeholder: 'Start writing...', placeholder: 'Start writing...',
onChange: async () => { onChange: async () => {
try { try {
const content = await instance.save(); const content = await this.editor!.save();
onChange(content); this.onChange(content);
} catch (err) { } catch (err) {
console.error('Error saving editor content:', err); console.error('Error saving editor content:', err);
} }
} }
}); });
} catch (error) {
console.error('Error initializing editor:', error);
}
}
setEditor(instance); async addBlock(text: string) {
if (!this.editor) return;
return () => {
isDestroyed.current = true;
const cleanup = async () => {
if (instance) {
try { try {
await instance.destroy(); await this.editor.blocks.insert('paragraph', { text });
setEditor(null); const updatedData = await this.editor.save();
this.onChange(updatedData);
} catch (err) {
console.error('Error adding block:', err);
}
}
async clear() {
if (!this.editor) return;
try {
await this.editor.clear();
} catch (err) {
console.error('Error clearing editor:', err);
}
}
async getData(): Promise<OutputData> {
if (!this.editor) throw new Error('Editor not initialized');
return this.editor.save();
}
async destroy() {
try {
if (this.editor && typeof this.editor.destroy === 'function') {
await this.editor.destroy();
this.editor = null;
this.holder = null;
EditorJSWrapper.instance = null;
}
} catch (error: unknown) { } catch (error: unknown) {
console.error('Error destroying editor:', error); console.error('Error destroying editor:', error);
} }
} }
};
void cleanup();
};
}, []);
const wrapper: EditorWrapper = {
instance: editor,
holderRef,
addBlock: async (text: string) => {
if (!editor) return;
try {
const currentData = await editor.save();
const newBlock = {
id: String(Date.now()),
type: 'paragraph',
data: {
text
}
};
await editor.blocks.insert('paragraph', { text });
const updatedData = await editor.save();
onChange(updatedData);
} catch (err) {
console.error('Error adding block:', err);
}
},
clear: async () => {
if (!editor) return;
try {
await editor.clear();
} catch (err) {
console.error('Error clearing editor:', err);
}
},
getData: async () => {
if (!editor) throw new Error('Editor not initialized');
return editor.save();
}
};
return wrapper;
} }
function App() { function App() {
const [editorData, setEditorData] = useState<OutputData | null>(null); const [editorData, setEditorData] = useState<OutputData | null>(null);
const [inputText, setInputText] = useState<string>(""); const [inputText, setInputText] = useState<string>("");
const [editor] = useState(() => new EditorJSWrapper((data) => setEditorData(data)));
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const editorRef = useRef<HTMLDivElement>(null);
const editor = useEditorJS((data) => { useEffect(() => {
setEditorData(data); if (editorRef.current) {
}); editor.initialize(editorRef.current);
}
return () => {
const cleanup = async () => {
await editor.destroy();
};
void cleanup();
};
}, [editor]);
const handleAdd = async () => { const handleAdd = async () => {
if (!inputText.trim()) return; if (!inputText.trim()) return;
@ -121,7 +123,7 @@ function App() {
<div className="flex flex-row w-full h-full gap-8 items-center px-8"> <div className="flex flex-row w-full h-full gap-8 items-center px-8">
<div className="flex-1 flex justify-center"> <div className="flex-1 flex justify-center">
<div style={{ backgroundColor: '#FFFFFF' }} className="min-h-[500px] min-w-[500px] rounded shadow-xl text-gray-900 p-6"> <div style={{ backgroundColor: '#FFFFFF' }} className="min-h-[500px] min-w-[500px] rounded shadow-xl text-gray-900 p-6">
<div style={{ margin: '20px' }} ref={editor.holderRef} /> <div style={{ margin: '20px' }} ref={editorRef} />
</div> </div>
</div> </div>
<div className="flex-1 flex flex-col justify-center items-center gap-4"> <div className="flex-1 flex flex-col justify-center items-center gap-4">