Editor.js in Symfony EasyAdmin

neothone - Feb 26 - - Dev Community

Yesterday, I spoke about Editor.js. Today, I purpose an implementation for Symfony with EasyAdmin for a properties of type json on a Doctrine entity.

If you think to an improvement, don't hesitate to comment!

First, create new Field (it's specific for EasyAdmin):

# src/admin/Field/Editorjs.php
<?php

namespace App\Admin\Field;

use App\Form\Type\EditorjsType;
use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;

class Editorjs implements FieldInterface
{
    use FieldTrait;

    public static function new(string $propertyName, ?string $label = null): self
    {
        return (new self())
            ->setProperty($propertyName)
            ->setLabel($label)
            ->setFormType(EditorjsType::class)
            // required also the easyadmin entry in webpack.config.js
        ;
    }
}
Enter fullscreen mode Exit fullscreen mode

and the form type EditorJsType mentioned:

# src/Form/Type/EditorjsType.php
<?php

declare(strict_types=1);

namespace App\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\CallbackTransformer;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

class EditorjsType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->addModelTransformer(
                new CallbackTransformer(
                    function ($value): string {
                        // transform the array to a json string
                        return json_encode($value);
                    },
                    function ($value): array {
                        // transform the json string to a php array
                        return json_decode($value, true);
                    }
                )
            );
    }

    public function getParent(): string
    {
        return TextType::class;
    }
}
Enter fullscreen mode Exit fullscreen mode

In order to have specific js and css load in EasyAdmin context, I create a specific entry in webpack.config.js:

// webpack.config.js
...

Encore
   ...
   .addEntry('easyadmin', './assets/easyadmin.js')
   ...
;
Enter fullscreen mode Exit fullscreen mode

Then, create the easyadmin.js file:

import './styles/easyadmin.css';

import EditorJS from '@editorjs/editorjs';
import Header from '@editorjs/header';
import Quote from '@editorjs/quote';
import RawTool from '@editorjs/raw';
import SimpleImage from "@editorjs/simple-image";
import EditorjsList from '@editorjs/list';
import Embed from '@editorjs/embed';
import Paragraph from '@editorjs/paragraph';
import Table from '@editorjs/table';
import CodeTool from '@editorjs/code';
import Underline from '@editorjs/underline';
import Delimiter from '@editorjs/delimiter';
import InlineCode from '@editorjs/inline-code';

const wrapper = document.getElementById('editorjs');
const input = document.getElementById(wrapper.dataset.fieldId);
const editor = new EditorJS({
  holder: wrapper.id,
  tools: {
    header: {
      class: Header,
      shortcut: 'CMD+SHIFT+H',
      config: {
        levels: [2, 3, 4, 5, 6],
        defaultLevel: 3
      }
    },
    quote: {
      class: Quote,
      inlineToolbar: true,
      shortcut: 'CMD+SHIFT+O',
    },
    raw: RawTool,
    image: SimpleImage,
    list: {
      class: EditorjsList,
      inlineToolbar: true,
      config: {
        defaultStyle: 'unordered'
      },
    },
    embed: Embed,
    paragraph: {
      class: Paragraph,
      inlineToolbar: true,
    },
    table: Table,
    code: CodeTool,
    underline: Underline,
    delimiter: Delimiter,
    inlineCode: {
      class: InlineCode,
      shortcut: 'CMD+SHIFT+M',
    },
  },
  onReady: () => {
    editor.render(JSON.parse(input.value));
  },
  onChange: (api, event) => {
    editor.save().then((outputData) => {
      input.value = JSON.stringify(outputData);
    });
  }
});
Enter fullscreen mode Exit fullscreen mode

and the easyadmin.css file:

/* Editor.js customization */
.editorjs {
    background-color: var(--form-control-bg);
    background-repeat: no-repeat;
    border: 1px solid var(--form-input-border-color);
    border-radius: var(--bs-border-radius);
    box-shadow: var(--form-input-shadow);
    color: var(--form-input-text-color);
    margin: 1rem 0;
    padding: 0.5rem 1rem 0.5rem 3rem;
    transition: box-shadow .08s ease-in, color .08s ease-in;
    white-space: nowrap;
    word-break:keep-all;

    .codex-editor__redactor {
        padding-bottom: 2rem!important;


    }
    .ce-inline-toolbar {
        .ce-popover__container, .ce-popover__items {
            overflow-y: hidden!important;
        }
    }
}

/* Dark mode */
@media (prefers-color-scheme: dark) {
    /* Editor.js customization */
    .editorjs {
        .codex-editor__redactor {
            .ce-block__content {
                background-color: transparent;
            }
        }
        .ce-toolbar {
            .ce-toolbar__plus, .ce-toolbar__settings-btn {
                color: var(--text-color)!important;
                &:hover {
                    color: #1d202b!important;
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Run this npm install command:
npm install @editorjs/editorjs @editorjs/header @editorjs/quote @editorjs/raw @editorjs/simple-image @editorjs/list @editorjs/embed @editorjs/paragraph @editorjs/table @editorjs/code @editorjs/underline @editorjs/delimiter @editorjs/inline-code

Configure EasyAdmin for use the new Webpack entry and a custom form template, in Dashboard Controller, add this:

# src/Controller/Admin/Dashboard.php
...
    public function configureCrud(): Crud
    {
        return Crud::new()
            ->setFormThemes(['form/custom_types.html.twig', '@EasyAdmin/crud/form_theme.html.twig'])
        ;
    }

    public function configureAssets(): Assets
    {
        return Assets::new()
            ->addWebpackEncoreEntry('easyadmin')
        ;
    }
...
Enter fullscreen mode Exit fullscreen mode

Create the custom form template file:

{# templates/form/custom_types.html.twig #}
{% block editorjs_row %}
    <div class="{{ ea_crud_form.ea_field.columns }}">
        {{ form_label(form) }}
        <div id="editorjs" class="editorjs" data-field-id="{{ id }}"></div>
        <input type="hidden" name="{{ full_name }}" id="{{ id }}" value="{{ value }}" />
        {{ form_errors(form) }}
    </div>
    <div class="flex-fill"></div>
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

Now you can use new Field type in a crud controller :

    ...
    use App\Admin\Field\Editorjs;
    ...

    public function configureFields(string $pageName): iterable
    {
        return [
            ...
            Editorjs::new('content') // content is a json (array) properties of a doctrine entity
                ->hideOnIndex(),
            ...
        ];
    }

    ...
}
Enter fullscreen mode Exit fullscreen mode

Enjoy!

. . . . . .