Custom Editor Element

In this example you will learn how to register an editor element!

Before Getting Started

Basic Concepts Of Slate.js

Before developing an custom editor element, you have to learn some basic concepts of Slate.js.

Must Learn:

  1. Location ( path, range, point )

  2. What is void elements and inline elements

  3. What is element and what is leaf

  4. Most Editor apis

  5. Most Transforms apis

  6. None React Editor api ( Don't use Slate React apis because I implemented my custom Slate React and it will get exposed in near future )

You can use all apis from 'slate' , 'slate-history' and 'slate-hyperscript' but none from 'slate-react'

Apis From Editor Instance

You can get editor instance with the hook 'Rendevoz.InnerHooks.useEditorInstance' in any components inside the editor. With this instance you can do a lot of works and new features will be implemented in the future.

Let's Go Now!

Now I assume that you have understood what is Slate and what can you do with the editor instance.

First, we need to register an editor element.

import { PluginApi } from 'rendevoz'
import { t } from 'i18next'

// define types
declare module 'rendevoz' {
    interface CustomTypes {
        SlateElements: {
            type: 'your-custom-ele'
            lastClickedMetaId: number
            idkWhatPropsToAdd: any
        }
    }
}

export default (plugin: PluginApi) => {
    // register i18n
    plugin.register.registerI18N('en-US', {
        custom: {
            ele: 'This is a nice element!'
        }
    })
    plugin.register.registerEditorElement({
        // type is necessary and should overlap with others
        type: 'your-custom-ele',
        name: () => t('custom.ele'),
        // register your renderer for this element
        renderElement: props => <CustomElement {...props} plugin={plugin} />,
        registerInMenu: {
            slashMenu: true,
            menuType: 'block'
        }
    })
}

Next, we need to define how this element should be rendered.

const CustomElement = (props: RenderElementProps & { plugin: PluginApi }) => {
    const { plugin, children, element } = props
    // create a context scoped event emitter
    const scopedEventEmitter = plugin.Rendeovz.Events.Emit.useScopedEventEmitter()
    // create an event handler
    const eventHandler = plugin.Rendevoz.Events.Emit.useEventHandler('editor')
    
    const editor = plugin.Rendevoz.InnerHooks.useSlateStatic()
    
    const instance = plugin.Rendevoz.InnerHooks.useEditorInstance()
    // get meta-objects map
    const metaObjectsMap = plugin.Rendevoz.api.getMetaStore().useMetaObjectsMap()
    
    // get note meta-objects
    const filteredMetaObjects = useMemo(() => {
        return Array.from(metaObjectsMap.values()).filter(i => i.type === 'note')
    }, [metaObjectsMap])
    
    // attach and detach listener when component mount / unmount
    useEffect(() => {
        scopedEventEmitter.addListener('editor', eventHandler)
        return () => {
            scopedEventEmitter.removeListener('editor', eventHandler)
        }
    }, [])
    
    // listen to editor's value change and do something...
    eventHandler.on('valueChange', () => {
        console.log("oops, the editor's value is changed! New Value is: ", editor.children)
    })
    
    const lastClickedMeta = plugin.Rendevoz.api.getMetaStore().useValue(element.lastClickedMetaId)
    
    const handleTransformData = (metaId: number) => {
        const currentElementPath = instance.getElementPathById?.(element.id)
        // change current element's props
        if (currentElementPath){
            Transforms.setNodes(editor, 
            {
                lastClickedMetaId: metaId
            }, 
            {
                at: currentElementPath
            })
        }    
    }
    
    return (
        <div>
            // don't forget key!
            {filteredMetaObjects.map(i => <h3 onClick={() => handleTransformData(i.id)} key={i.id}>{i.name}</h3>)}
            {lastClickedMeta && <div>Oops! You clicked {lastClickedMeta.name}!!!</div>}
            {children}
        </div>
    )
}

This is pretty easy, right?

Last updated