191 lines
5.3 KiB
TypeScript
191 lines
5.3 KiB
TypeScript
import { useEffect, useRef, useState } from 'react'
|
|
import './App.css'
|
|
import EditorJS, { OutputData } from '@editorjs/editorjs'
|
|
|
|
class EditorJSWrapper {
|
|
private editor: EditorJS | null = null;
|
|
private holder: HTMLDivElement | null = null;
|
|
private onChange!: (data: OutputData) => void;
|
|
private static instance: EditorJSWrapper | null = null;
|
|
private currentData: OutputData = {
|
|
time: Date.now(),
|
|
blocks: [],
|
|
version: '2.28.2'
|
|
};
|
|
|
|
constructor(onChange: (data: OutputData) => void) {
|
|
if (EditorJSWrapper.instance) {
|
|
return EditorJSWrapper.instance;
|
|
}
|
|
this.onChange = onChange;
|
|
EditorJSWrapper.instance = this;
|
|
}
|
|
|
|
initialize(holder: HTMLDivElement) {
|
|
if (this.editor || !holder) return;
|
|
|
|
// Ensure any existing instance is destroyed first
|
|
if (EditorJSWrapper.instance?.editor) {
|
|
void EditorJSWrapper.instance.destroy();
|
|
}
|
|
|
|
this.holder = holder;
|
|
try {
|
|
this.editor = new EditorJS({
|
|
holder: holder,
|
|
placeholder: 'Start writing...',
|
|
data: this.currentData,
|
|
onChange: async () => {
|
|
await this.syncState();
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Error initializing editor:', error);
|
|
}
|
|
}
|
|
|
|
private async syncState() {
|
|
if (!this.editor) return;
|
|
try {
|
|
this.currentData = await this.editor.save();
|
|
this.onChange(this.currentData);
|
|
} catch (err) {
|
|
console.error('Error syncing state:', err);
|
|
}
|
|
}
|
|
|
|
async addBlock(text: string) {
|
|
if (!this.editor) return;
|
|
|
|
try {
|
|
await this.editor.blocks.insert('paragraph', { text });
|
|
await this.syncState();
|
|
} catch (err) {
|
|
console.error('Error adding block:', err);
|
|
}
|
|
}
|
|
|
|
async clear() {
|
|
if (!this.editor) return;
|
|
try {
|
|
await this.editor.clear();
|
|
this.currentData = {
|
|
time: Date.now(),
|
|
blocks: [],
|
|
version: '2.28.2'
|
|
};
|
|
this.onChange(this.currentData);
|
|
} catch (err) {
|
|
console.error('Error clearing editor:', err);
|
|
}
|
|
}
|
|
|
|
async getData(): Promise<OutputData> {
|
|
return this.currentData;
|
|
}
|
|
|
|
async destroy() {
|
|
try {
|
|
if (this.editor && typeof this.editor.destroy === 'function') {
|
|
await this.editor.destroy();
|
|
this.editor = null;
|
|
this.holder = null;
|
|
this.currentData = {
|
|
time: Date.now(),
|
|
blocks: [],
|
|
version: '2.28.2'
|
|
};
|
|
EditorJSWrapper.instance = null;
|
|
}
|
|
} catch (error: unknown) {
|
|
console.error('Error destroying editor:', error);
|
|
}
|
|
}
|
|
}
|
|
|
|
function App() {
|
|
const [editorData, setEditorData] = useState<OutputData | null>(null);
|
|
const [inputText, setInputText] = useState<string>("");
|
|
const [editor] = useState(() => new EditorJSWrapper((data) => setEditorData(data)));
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const editorRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
if (editorRef.current) {
|
|
editor.initialize(editorRef.current);
|
|
}
|
|
return () => {
|
|
const cleanup = async () => {
|
|
await editor.destroy();
|
|
};
|
|
void cleanup();
|
|
};
|
|
}, [editor]);
|
|
|
|
const handleAdd = async () => {
|
|
if (!inputText.trim()) return;
|
|
|
|
await editor.addBlock(inputText);
|
|
setInputText("");
|
|
|
|
setTimeout(() => {
|
|
inputRef.current?.focus();
|
|
}, 0);
|
|
};
|
|
|
|
const handleSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
void handleAdd();
|
|
};
|
|
|
|
return (
|
|
<div style={{ backgroundColor: '#1E1E1E' }} className="flex flex-col w-full h-screen items-center">
|
|
<div className="flex flex-row w-full h-full gap-8 items-center px-8">
|
|
<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={{ margin: '20px' }} ref={editorRef} />
|
|
</div>
|
|
</div>
|
|
<div className="flex-1 flex flex-col justify-center items-center gap-4">
|
|
<OutputComponent data={editorData} />
|
|
<form onSubmit={handleSubmit} className="flex gap-2 m-2">
|
|
<input
|
|
ref={inputRef}
|
|
type="text"
|
|
className="px-6 py-3 bg-white text-gray-900 rounded border border-gray-300"
|
|
value={inputText}
|
|
onChange={(e) => setInputText(e.target.value)}
|
|
placeholder="Enter text..."
|
|
/>
|
|
<button
|
|
type="submit"
|
|
className="px-6 py-3 bg-gray-500 text-white rounded hover:bg-gray-600"
|
|
disabled={!inputText.trim()}
|
|
>
|
|
Add
|
|
</button>
|
|
<button
|
|
type="button"
|
|
onClick={() => void editor.clear()}
|
|
className="px-6 py-3 bg-blue-500 text-white rounded hover:bg-blue-600"
|
|
>
|
|
Clear All
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export default App
|
|
|
|
function OutputComponent({ data }: { data: OutputData | null }) {
|
|
return (
|
|
<div style={{ backgroundColor: '#FFFFFF' }} className="min-h-[500px] min-w-[500px] rounded shadow-xl p-6 text-gray-900">
|
|
<pre className="whitespace-pre-wrap">
|
|
{data ? JSON.stringify(data, null, 2) : 'Output content will go here'}
|
|
</pre>
|
|
</div>
|
|
)
|
|
} |