DEV Community

Cover image for Editor.js in Symfony EasyAdmin
neothone
neothone

Posted on • Edited on

Editor.js in Symfony EasyAdmin

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'); if (wrapper) { 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!

Top comments (3)

Collapse
 
gromnan profile image
Jérôme TAMARELLE • Edited

There is unmaintained bundle : tbmatuka/EditorjsBundle.

It would be awesome to have it this integrated as a Symfony UX package, or just a bundle.

Collapse
 
neothone profile image
neothone

Good idea! Can you provide this in the core team?

Collapse
 
nicolas_facciolo profile image
Nicolas Facciolo

Congrats !