Initial hook demo
This commit is contained in:
0
src/App.css
Normal file
0
src/App.css
Normal file
169
src/App.tsx
Normal file
169
src/App.tsx
Normal file
@@ -0,0 +1,169 @@
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import './App.css'
|
||||
import EditorJS, { OutputData } from '@editorjs/editorjs'
|
||||
|
||||
interface EditorWrapper {
|
||||
instance: EditorJS | null;
|
||||
addBlock: (text: string) => Promise<void>;
|
||||
clear: () => Promise<void>;
|
||||
getData: () => Promise<OutputData>;
|
||||
holderRef: React.RefObject<HTMLDivElement | null>;
|
||||
}
|
||||
|
||||
function useEditorJS(onChange: (data: OutputData) => void): EditorWrapper {
|
||||
const [editor, setEditor] = useState<EditorJS | null>(null);
|
||||
const holderRef = useRef<HTMLDivElement | null>(null);
|
||||
const isDestroyed = useRef(false);
|
||||
|
||||
// Initialize editor
|
||||
useEffect(() => {
|
||||
if (!holderRef.current || editor || isDestroyed.current) return;
|
||||
|
||||
const instance = new EditorJS({
|
||||
holder: holderRef.current,
|
||||
placeholder: 'Start writing...',
|
||||
onChange: async () => {
|
||||
try {
|
||||
const content = await instance.save();
|
||||
onChange(content);
|
||||
} catch (err) {
|
||||
console.error('Error saving editor content:', err);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setEditor(instance);
|
||||
|
||||
return () => {
|
||||
isDestroyed.current = true;
|
||||
const cleanup = async () => {
|
||||
if (instance) {
|
||||
try {
|
||||
await instance.destroy();
|
||||
setEditor(null);
|
||||
} catch (error: unknown) {
|
||||
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() {
|
||||
const [editorData, setEditorData] = useState<OutputData | null>(null);
|
||||
const [inputText, setInputText] = useState<string>("");
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const editor = useEditorJS((data) => {
|
||||
setEditorData(data);
|
||||
});
|
||||
|
||||
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={editor.holderRef} />
|
||||
</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>
|
||||
)
|
||||
}
|
||||
17
src/index.css
Normal file
17
src/index.css
Normal file
@@ -0,0 +1,17 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
10
src/main.tsx
Normal file
10
src/main.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
1
src/vite-env.d.ts
vendored
Normal file
1
src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
Reference in New Issue
Block a user