-
-
Save Thisisjuke/46d9c0b1b6fbae040931c6897a9fb034 to your computer and use it in GitHub Desktop.
import type { Meta, StoryObj } from '@storybook/react' | |
import { useState } from 'react' | |
import type { OutputData } from '@editorjs/editorjs' | |
import Editor from './Editor' | |
const meta: Meta<typeof Editor> = { | |
title: 'Components/Editor', | |
component: Editor, | |
tags: ['autodocs'], | |
} | |
export default meta | |
type Story = StoryObj<typeof Editor> | |
export const Default: Story = { | |
render: (args) => { | |
const [data, setData] = useState<OutputData>() | |
return ( | |
<Editor {...args} data={data} onChange={setData} /> | |
) | |
}, | |
args: { | |
holder: 'editorjs-container', | |
}, | |
} |
import React, { useEffect, useRef } from 'react' | |
import type { OutputData, ToolConstructable } from '@editorjs/editorjs' | |
import EditorJS from '@editorjs/editorjs' | |
import QuestionTool from '@/modules/Editor/tools/QuestionTool/TextsQuestionTool' | |
import TitleTool from '@/modules/Editor/tools/TitleTool/TitleTool' | |
import DisplayImageTool from '@/modules/Editor/tools/DisplayImageTool/DisplayImageTool' | |
const tools: { [toolName: string]: ToolConstructable } = { | |
titleTool: TitleTool as unknown as ToolConstructable, | |
displayImageTool: DisplayImageTool as unknown as ToolConstructable, | |
imagesQuestionTool: ImagesQuestionTool as unknown as ToolConstructable, | |
textsQuestionTool: TextsQuestionTool as unknown as ToolConstructable, | |
} | |
export interface EditorBlockProps { | |
data?: OutputData | |
onChange(val: OutputData): void | |
holder: string | |
} | |
const EditorBlock = ({ data, onChange, holder }: EditorBlockProps) => { | |
const ref = useRef<EditorJS>() | |
useEffect(() => { | |
if (!ref.current) { | |
ref.current = new EditorJS({ | |
holder, | |
tools, | |
data, | |
async onChange(api) { | |
const data = await api.saver.save() | |
onChange(data) | |
}, | |
}) | |
} | |
return () => { | |
if (ref.current && ref.current.destroy) { | |
ref.current.destroy() | |
} | |
} | |
}, []) | |
return <div id={holder} className={'!mb-0 bg-gray-100 h-full !p-2 overflow-x-auto'} /> | |
} | |
export default EditorBlock |
import ReactDOM from 'react-dom' | |
import type { API, BlockTool, ToolConfig } from '@editorjs/editorjs' | |
import React, { createElement } from 'react' | |
interface CustomToolOptions<TData extends Record<string, any>, TConfig extends Record<string, any>, TOpts extends Record<string, any>> { | |
data: TData | |
config: TConfig | |
api: API | |
readOnly: boolean | |
component: React.ComponentType<{ onDataChange: (newData: TData) => void; readOnly: boolean; data: TData; opts: TOpts }> | |
toolbox: ToolConfig | |
opts?: TOpts | |
} | |
export class CustomTool<TData extends Record<string, any>, TConfig extends Record<string, any>, TOpts extends Record<string, any>> implements BlockTool { | |
private api: API | |
private readonly readOnly: boolean | |
private data: TData | |
private config: TConfig | |
private component: React.ComponentType<{ onDataChange: (newData: TData) => void; readOnly: boolean; data: TData; options?: TOpts }> | |
private toolbox: ToolConfig | |
private readonly CSS = { | |
wrapper: 'custom-tool', | |
} | |
private nodes = { | |
holder: null as HTMLElement | null, | |
} | |
constructor(options: CustomToolOptions<TData, TConfig, TOpts>) { | |
const { data, config, api, readOnly, component, toolbox } = options | |
this.api = api | |
this.readOnly = readOnly | |
this.data = data | |
this.config = config | |
this.component = component as any | |
this.toolbox = toolbox | |
} | |
static get isReadOnlySupported(): boolean { | |
return true | |
} | |
render(): HTMLElement { | |
const rootNode = document.createElement('div') | |
rootNode.setAttribute('class', this.CSS.wrapper) | |
this.nodes.holder = rootNode | |
const onDataChange = (newData: TData) => { | |
this.data = { | |
...newData, | |
} | |
} | |
ReactDOM.render(<this.component onDataChange={onDataChange} readOnly={this.readOnly} data={this.data} />, rootNode) | |
return this.nodes.holder | |
} | |
save(): TData { | |
return this.data | |
} | |
static createTool<TData extends Record<string, any>, TConfig extends Record<string, any>, TOpts extends Record<string, any>>( | |
component: React.ComponentType<{ onDataChange: (newData: TData) => void; readOnly: boolean; data: TData; opts: TOpts }>, | |
toolbox: ToolConfig, | |
opts?: TOpts, | |
): new (options: CustomToolOptions<TData, TConfig, TOpts>) => CustomTool<TData, TConfig, TOpts> { | |
return class extends CustomTool<TData, TConfig, TOpts> { | |
constructor(options: CustomToolOptions<TData, TConfig, TOpts>) { | |
super({ | |
...options, | |
component: (props: any) => createElement(component, { ...props, options: opts }), | |
toolbox, | |
data: { | |
events: [], | |
...options.data, | |
}, | |
}) | |
} | |
static get toolbox() { | |
return toolbox | |
} | |
} | |
} | |
} |
import React from 'react' | |
export interface TitleInputProps { | |
data: Record<string, string> | |
onDataChange: (arg: Record<string, string>) => void | |
readOnly?: boolean | |
} | |
export const TitleInput = ({ data, onDataChange, readOnly }: TitleInputProps) => { | |
const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => { | |
const newData = { | |
title: event.target.value, | |
} | |
onDataChange(newData) | |
} | |
return ( | |
<div className={'flex gap-x-2 w-full'}> | |
<span className={'text-xl underline shrink-0'}>Titre :</span> | |
<textarea | |
className={'bg-transparent border-0 focus:outline-none text-xl grow-1 w-full'} | |
defaultValue={data.title} | |
onChange={handleChange} | |
readOnly={readOnly} | |
/> | |
</div> | |
) | |
} |
import { CustomTool } from '@/modules/Editor/tools/GenericTool' | |
import { TitleInput } from '@/modules/Editor/tools/TitleTool/TitleInput' | |
const TitleTool = CustomTool.createTool( | |
// ⬇️ Here is the component that will be used as the custom "tool" / "block" inside EditorJS. | |
TitleInput, | |
{ | |
icon: '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m16 10l3-1v10M3 5v7m0 0v7m0-7h8m0-7v7m0 0v7"/></svg>', | |
title: 'Title', | |
}, | |
) | |
export default TitleTool |
Custom EditorJS Tool
This is a custom React component that creates an instance of EditorJS with custom tools. It allows you to add and edit content using a variety of tools.
Introduction
Preview
Recommended folder structure:
Limitations and Usage Information
Please note the following limitations and usage information regarding the custom EditorJS component:
- Not compatible with Next.js Server-Side Rendering (SSR): This custom component is not compatible with Next.js Server-Side Rendering (SSR) due to the way EditorJS interacts with the DOM. EditorJS relies on the browser environment to function properly, which is not available during SSR. Therefore, if you are using Next.js with SSR, you should only render this component on the client-side. SEE THE NEXT.JS SECTION AT THE END OF THIS COMMENT.
- Usage of JavaScript class instead of functional components: Currently, EditorJS does not support functional components as custom tools. Therefore, the CustomTool class provided in this implementation uses JavaScript class syntax. This is necessary to ensure compatibility with EditorJS. If you need to create custom tools, make sure to define them as classes.
Installation
To use this custom component, you need to have the following dependencies installed:
- React
- EditorJS
You can install them using npm or yarn:
npm install react @editorjs/editorjsor
yarn add react @editorjs/editorjsUsage
To use the custom EditorJS component, follow these steps:
- Import the necessary dependencies and tools:
import React from 'react'; import EditorJS from '@editorjs/editorjs'; import { CustomTool } from './CustomTool'; import TitleTool from './TitleTool';Note: Import other custom tools if needed
- Create an instance of the EditorJS component:
const MyEditor = () => { const editorRef = React.useRef(null); React.useEffect(() => { if (!editorRef.current) { editorRef.current = new EditorJS({ // Specify the holder element where the editor should be rendered holder: 'editor-container', // Add custom tools to the `tools` option tools: { // Use the custom tool title: TitleTool, // Add other custom tools if needed }, // Other EditorJS configuration options }); } return () => { if (editorRef.current && editorRef.current.destroy) { editorRef.current.destroy(); } }; }, []); return <div id="editor-container" />; };
- Use the MyEditor component in your application:
Copy code function App() { return ( <div> <h1>My Application</h1> <MyEditor /> </div> ); }CustomTool.tsx
The CustomTool class in CustomTool.tsx is a utility class used to create custom tools for EditorJS. It provides a convenient way to define and render custom blocks within the editor.
Class: CustomTool
Constructor:
options
: An object containing the following properties:data
: The initial data for the custom tool.config
: Configuration options for the custom tool.api
: The EditorJS API object.readOnly
: A boolean indicating whether the editor is in read-only mode.component
: The React component to render for the custom tool.toolbox
: The configuration for the custom tool in the editor's toolbox.opts
(optional): Additional options for the custom tool.Methods:
render()
: Renders the custom tool and returns the root element.save()
: Returns the current data of the custom tool.Static Methods:
createTool()
: Creates a new custom tool class based on the provided component, toolbox configuration, and additional options.Example usage:
import { CustomTool } from './CustomTool'; // Define a custom React component const MyCustomComponent = ({ onDataChange, readOnly, data, opts }) => { // Implement the custom component logic // ... return <div>{/* Custom component JSX */}</div>; }; // Create a new custom tool const MyCustomTool = CustomTool.createTool(MyCustomComponent, { icon: '<svg>...</svg>', title: 'My Custom Tool', }); // Use the custom tool in EditorJS const editor = new EditorJS({ // ... tools: { customTool: MyCustomTool, // Add other tools if needed }, });TitleTool.tsx
The TitleTool component in TitleTool.tsx is an example of a custom tool implementation using the CustomTool class.
Usage
To use the TitleTool, follow these steps:
- Import the necessary dependencies:
import { CustomTool } from './CustomTool'; import { TitleInput } from './TitleInput';Create a new instance of the CustomTool using the createTool() static method:
const TitleTool = CustomTool.createTool(TitleInput, { icon: '<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m16 10l3-1v10M3 5v7m0 0v7m0-7h8m0-7v7m0 0v7"/></svg>', title: 'Title', }); Use the TitleTool in your EditorJS instance: jsx Copy code const editor = new EditorJS({ // ... tools: { title: TitleTool, // Add other tools if needed }, });EditorBlock.tsx
The EditorBlock component in EditorBlock.tsx is a wrapper component that integrates the EditorJS instance into your React application.
Props
- data (optional): The initial data for the editor.
- onChange: A callback function that is triggered when the editor content changes. It receives the updated editor data as an argument.
- holder: The ID of the container element where the editor should be rendered.
Usage
To use the EditorBlock, follow these steps:
- Import the necessary dependencies:
import React from 'react'; import EditorJS from '@editorjs/editorjs'; import { CustomTool } from './CustomTool'; import TitleTool from './TitleTool'; // Import other custom tools if neededCreate an instance of the EditorJS component inside the EditorBlock component:
const EditorBlock = ({ data, onChange, holder }) => { const ref = React.useRef(); React.useEffect(() => { if (!ref.current) { ref.current = new EditorJS({ holder, tools: { title: TitleTool, // Add other tools if needed }, data, async onChange(api) { const data = await api.saver.save(); onChange(data); }, // Other EditorJS configuration options }); } return () => { if (ref.current && ref.current.destroy) { ref.current.destroy(); } }; }, []); return <div id={holder} />; };Use the EditorBlock component in your application:
function App() { const handleEditorChange = (data) => { // Handle the editor data change console.log(data); }; return ( <div> <h1>My Application</h1> <EditorBlock data={initialData} onChange={handleEditorChange} holder="editor-container" /> </div> ); }That's it! You can now use the custom EditorJS component with your custom tools in your React application.
How to Use in Next.js
You have to create another component that will be only be displayed client side, using Next.js noSSR mod
ssr: false
.import { useState } from 'react' import dynamic from 'next/dynamic' const EditorModule = dynamic(() => import('@/modules/quiz/components/question-builder/Editor/Editor'), { ssr: false, }) export const MyCustomEditor = () => { const [content, setContent] = useState<any>() return ( <EditorModule data={content} onChange={setContent} holder={'editorjs-container'} /> ) }Then import it normally in your page or in another component.
Thank you! Can you send one other tool code just like Ask Question because I am facing issue to add other custom tool
TextsQuestionTool example:
import { QuestionInput } from '@/components/inputs/QuestionInput'
import { CustomTool } from '@/components/tools/GenericTool'
const TextsQuestionTool = CustomTool.createTool(
QuestionInput,
{
icon: '(?)',
title: 'Ask Question',
},
{
type: 'texts',
},
)
export default TextsQuestionTool
QuestionInput interface looks like this:
export interface QuestionInputProps {
data: QuestionToolData
onDataChange: (questionData: QuestionToolData) => void
readOnly?: boolean
options?: {
type: 'images' | 'texts'
[key: string]: unknown
}
}
Added like this in the tool array:
import type { OutputData, ToolConstructable } from '@editorjs/editorjs'
import EditorJS from '@editorjs/editorjs'
const tools: { [toolName: string]: ToolConstructable } = {
titleTool: TitleTool as unknown as ToolConstructable,
textsQuestionTool: TextsQuestionTool as unknown as ToolConstructable,
}
Remember, tool is called inside the EditorJs instance:
ref.current = new EditorJS({
holder,
tools, // <== here
data,
async onChange(api) {
const data = await api.saver.save()
onChange(data)
},
})
Custom EditorJS Tool
This is a custom React component that creates an instance of EditorJS with custom tools. It allows you to add and edit content using a variety of tools.
Introduction
Preview
Recommended folder structure:
Limitations and Usage Information
Please note the following limitations and usage information regarding the custom EditorJS component:
Not compatible with Next.js Server-Side Rendering (SSR): This custom component is not compatible with Next.js Server-Side Rendering (SSR) due to the way EditorJS interacts with the DOM. EditorJS relies on the browser environment to function properly, which is not available during SSR. Therefore, if you are using Next.js with SSR, you should only render this component on the client-side. SEE THE NEXT.JS SECTION AT THE END OF THIS COMMENT.
Usage of JavaScript class instead of functional components: Currently, EditorJS does not support functional components as custom tools. Therefore, the CustomTool class provided in this implementation uses JavaScript class syntax. This is necessary to ensure compatibility with EditorJS. If you need to create custom tools, make sure to define them as classes.
Installation
To use this custom component, you need to have the following dependencies installed:
You can install them using npm or yarn:
or
Usage
To use the custom EditorJS component, follow these steps:
CustomTool.tsx
The CustomTool class in CustomTool.tsx is a utility class used to create custom tools for EditorJS. It provides a convenient way to define and render custom blocks within the editor.
Class: CustomTool
Constructor:
options
: An object containing the following properties:data
: The initial data for the custom tool.config
: Configuration options for the custom tool.api
: The EditorJS API object.readOnly
: A boolean indicating whether the editor is in read-only mode.component
: The React component to render for the custom tool.toolbox
: The configuration for the custom tool in the editor's toolbox.opts
(optional): Additional options for the custom tool.Methods:
render()
: Renders the custom tool and returns the root element.save()
: Returns the current data of the custom tool.Static Methods:
createTool()
: Creates a new custom tool class based on the provided component, toolbox configuration, and additional options.Example usage:
TitleTool.tsx
The TitleTool component in TitleTool.tsx is an example of a custom tool implementation using the CustomTool class.
Usage
To use the TitleTool, follow these steps:
Create a new instance of the CustomTool using the createTool() static method:
EditorBlock.tsx
The EditorBlock component in EditorBlock.tsx is a wrapper component that integrates the EditorJS instance into your React application.
Props
Usage
To use the EditorBlock, follow these steps:
Create an instance of the EditorJS component inside the EditorBlock component:
Use the EditorBlock component in your application:
That's it! You can now use the custom EditorJS component with your custom tools in your React application.
How to Use in Next.js
You have to create another component that will be only be displayed client side, using Next.js noSSR mod
ssr: false
.Then import it normally in your page or in another component.