darylseven/editorjs-bundle
Symfony bundle integrating Editor.js with Symfony Forms and Twig. Adds an EditorjsType form field, configurable editor setups, and a Twig helper to render/init the editor. Includes example config and JS init (Encore/webpack).
Install the Bundle
composer require tbmatuka/editorjs-bundle
Ensure Tbmatuka\EditorjsBundle\TbmatukaEditorjsBundle::class is registered in config/bundles.php.
Configure the Bundle
Copy examples/editorjs.yaml to config/packages/editorjs.yaml and adjust settings (e.g., default tools, CDN paths).
Example:
tbmatuka_editorjs:
default_config:
tools:
header: null
paragraph: { class: Header }
Set Up Twig Form Theme
Add the form theme to your config/packages/twig.yaml:
twig:
form_themes:
- '@TbmatukaEditorjs/Form/editorjs_widget.html.twig'
Basic Form Integration
Use EditorjsType in a Symfony form:
use Tbmatuka\EditorjsBundle\Form\Type\EditorjsType;
$builder->add('content', EditorjsType::class, [
'config' => ['tools' => ['header', 'paragraph']],
]);
Initialize JavaScript (Encore)
Copy examples/editorjs-init.js to assets/js/editor-init.js and import it in your Encore entry file:
import EditorJS from '@editorjs/editorjs';
import Header from '@editorjs/header';
import Paragraph from '@editorjs/paragraph';
// Configure and initialize Editor.js
const initEditor = (editorId) => {
const editor = new EditorJS({
tools: { header: Header, paragraph: Paragraph },
holder: `editorjs-${editorId}`,
});
return editor;
};
Render the Form Use the form in a Twig template:
{{ form_start(form) }}
{{ form_row(form.content) }}
{{ form_end(form) }}
$builder->add('article', EditorjsType::class, [
'config' => [
'tools' => [
'header' => ['class' => Header, 'config' => ['defaultLevel' => 2]],
'list' => ['class' => ListTool],
],
'data' => $initialContent, // Preload JSON data
],
]);
$builder->add('content', EditorjsType::class)
->addModelTransformer(new EditorjsTransformer())
->addConstraint(new ValidEditorjsContent());
{# templates/TbmatukaEditorjs/Form/editorjs_widget.html.twig #}
<div id="{{ id }}" class="editorjs-container">
{{ form_widget(form) }}
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
initEditor('{{ id }}');
});
</script>
editorjs() Twig function for inline configs:
{{ editorjs(form.content.vars.config) }}
const loadPlugin = async (pluginName) => {
const module = await import(`@editorjs/${pluginName}`);
return module.default || module;
};
document.querySelectorAll('.editorjs-form').forEach(form => {
form.addEventListener('submit', (e) => {
const editor = initEditor(form.dataset.editorId);
const output = await editor.save();
form.querySelector('[name="content"]').value = JSON.stringify(output);
});
});
json column and use accessors:
#[ORM\Column(type: 'json')]
private $content;
public function getContentAsArray(): array
{
return json_decode($this->content, true) ?: [];
}
$data = $entity->getContentAsArray();
return $this->json(['content' => $data]);
tbmatuka_editorjs:
tools:
custom_tool:
class: App\Editorjs\CustomTool
config: { /* tool-specific config */ }
$builder->add('content', EditorjsType::class, [
'config' => [
'tools' => [
'custom_tool' => ['config' => ['option1' => true]],
],
],
]);
Symfony 8 Form Compatibility:
FormBuilder::add() changes).FormBuilder::createNamedBuilder() for nested forms with Editor.js.Frontend Asset Management:
@editorjs/editorjs and plugins are installed via npm.<script src="https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest"></script>
<script>
const EditorJS = window.EditorJS;
new EditorJS({ /* config */ });
</script>
Performance Optimization:
Debugging:
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$data = $form->getData();
error_log('Editor.js data: ' . print_r($data, true));
}
Testing:
$this->getContainer()->get('twig')->addFunction(
new \Twig\TwigFunction('editorjs', [$this, 'mockEditorjs'])
);
Symfony 8-Specific Issues:
form_div_layout.html.twig changes).FormBuilder methods (e.g., add() vs. create()).Frontend Dependencies:
Data Handling:
Configuration Quirks:
array_merge to preserve defaults:
$config = array_merge(
$this->container->getParameter('tbmatuka_editorjs.default_config'),
['tools' => ['header']]
);
$builder->add('content', EditorjsType::class, ['config' => $config]);
Header for @editorjs/header). Case-sensitive.Browser Compatibility:
Promise or fetch polyfills for older browsers.editorjs-init.js script is loaded and executed after the DOM is ready.holder ID matches the Twig template’s output (e.g., `editorHow can I help you explore Laravel packages today?