DEV Community

Cover image for Editor.js in Symfony EasyAdmin
neothone
neothone

Posted on • Edited on

3 1 1

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!

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (2)

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?

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay