Skip to content

Instantly share code, notes, and snippets.

@mmmeff
Last active April 15, 2024 13:47
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mmmeff/5c60423a6ae564d6e485587f6621e169 to your computer and use it in GitHub Desktop.
Save mmmeff/5c60423a6ae564d6e485587f6621e169 to your computer and use it in GitHub Desktop.
A simple Typescript-forward utility that spits out Editor.js tools for React components. You may need to include relevant Context providers for your application as wrappers around whatever <Component> is rendered by the factory
// Based on https://walkthrough.so/pblc/QCawSCKwOQLn/creating-a-custom-editorjs-block-tool-with-react
import type {
API,
BlockAPI,
BlockTool,
BlockToolConstructorOptions,
} from '@editorjs/editorjs'
import ReactDOM from 'react-dom'
export interface ReactEditorToolProps<TData> {
onDataChange: (data: TData) => unknown
readOnly: boolean
data: TData
}
interface ReactToolConfig<TData, TConfig> {
toolbox: {
icon: string
title: string
}
isReadOnlySupported: boolean
defaultData: TData
}
export default function makeReactTool<
TData extends object,
TConfig extends object
>(
Component: React.FC<ReactEditorToolProps<TData>>,
configuration: ReactToolConfig<TData, TConfig>
) {
class ReactTool implements BlockTool {
static toolbox = configuration.toolbox
static isReadOnlySupported = configuration.isReadOnlySupported
private readOnly: boolean = false
private data: TData
private editorAPI: API
private blockAPI: BlockAPI | undefined
private config: TConfig | undefined
private wrapperEl: HTMLDivElement | null = null
constructor({
data,
config,
api,
block,
readOnly,
}: BlockToolConstructorOptions<TData, TConfig>) {
this.config = config
this.readOnly = readOnly
this.editorAPI = api
this.blockAPI = block
this.data = data ?? configuration.defaultData
}
render() {
const rootNode = document.createElement('div')
this.wrapperEl = rootNode
const onDataChange = (newData: TData) => {
this.data = {
...newData,
}
}
ReactDOM.render(
<Component
onDataChange={onDataChange}
readOnly={this.readOnly}
data={this.data}
/>,
rootNode
)
return this.wrapperEl
}
save() {
return this.data
}
}
return ReactTool
}
import makeReactTool from './ReactToolFactory'
import type { ReactEditorToolProps } from './ReactToolFactory'
interface ToolData {
clickCount: number
}
interface ToolConfig {}
function Component({ onDataChange, data, readOnly }: ReactEditorToolProps<ToolData>) {
return (
<button onClick={() => onDataChange({ clickCount: data.clickCount + 1 })}>
Clicked {props.data.clickCount} Time(s)
</button>
)
}
export default makeReactTool<ToolData, ToolConfig>(
Component,
{
toolbox: { title: 'Tool!', icon: '<svg>...</svg>' },
isReadOnlySupported: true,
defaultData: { clickCount: 0 },
}
)
@mmmeff
Copy link
Author

mmmeff commented Mar 29, 2023

Tools can be added as easily as including them in your editor instantiation's "tools" object.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment