akiraz2/editor-js

PHP Editor JS with Parser

dev-master 2025-09-01 18:52 UTC

This package is auto-updated.

Last update: 2025-09-01 19:01:18 UTC


README

Визуальный Редактор контента https://editorjs.io (весь контент сохраняет в виде json структуры, чтобы любой мог по своим правилам перевести в html-код)

Использовал за основу https://github.com/editor-js/editorjs-php - сделал исправленное рабочее решение.

Добавил Parser - это компонент, который парсит сохраненную json-структуру контента и трансформирует в HTML-верстку.

Легкая интеграция в Laravel проект

Используются все стандартные компоненты editor-js (тексты, заголовки, списки, картинки, таблицы и пр)

Install

composer require akiraz2/editor-js

Используется конфиг-файл (config/editor.php) для валидации и очистки входящих данных (htmlpurifier).

Вы можете изменять конфиг, добавлять в него новые блоки.

Если устанавливаете в проектах Laravel, то можно конфиг скопировать в проект с помощью команды php artisan vendor:publish --tag=editor-js или просто скопировать в свое место и использовать его в вызове

Если используете сборщики фронтенда, то добавьте npm-пакеты в package.json (или лучше командами npm install)

    "@editorjs/editorjs": "2.30.0",
    "@editorjs/embed": "^2.7.6",
    "@editorjs/header": "^2.8.8",
    "@editorjs/image": "^2.10.3",
    "@editorjs/link": "^2.6.2",
    "@editorjs/list": "^2.0.8",
    "@editorjs/marker": "^1.4.0",
    "@editorjs/quote": "^2.7.6",
    "@editorjs/table": "^2.4.5",

В resources/js/editorjsEmbed.js лежит рабочий вариант кода "встраивание Youtube", скопируйте себе в проект, если нужно.

Guide

Сначала действуем как на официальном сайте.

  1. Добавляем на страницу блок-контейнер для редактора <div id="editorjs" class="edit-xxx__content"></div>
  2. На странице подключаем фронтенд-скрипт (в примере используется vite сборщик из Laravel). Для передачи информации с PHP-бекенда (Laravel) используется объект window.content
<script>
     window.content = {!! json_encode($modelXxx->content) !!};
</script>
@vite('resources/js/frontend/editXxx.js')
  1. Фронтенд JS код - ниже код для примера - используется сборщик Vite из состава Laravel
import EditorJS from '@editorjs/editorjs';
import Header from '@editorjs/header';
import List from '@editorjs/list';
import Link from '@editorjs/link';
import Marker from '@editorjs/marker';
import ImageTool from '@editorjs/image';
import Quote from '@editorjs/quote';
import Table from '@editorjs/table';
import {createFetchPostData, noReturn, notify, swalErr} from "@/frontend/helpers/http.js";
import Embed from "./helpers/editorjsEmbed.js";

const url = window.location.pathname; //это для компонента загрузки изображений - может быть любой базовый URL
window.csrf = $('[name=csrf-token]').attr('content');//это необязательно. у меня это глобальный объект, установленный совершенно в другом месте
window.editor = new EditorJS({
    /**
     * Id of Element that should contain the Editor
     */
    holder: 'editorjs',
    placeholder: 'Let`s write an awesome Xxx!',
    /**
     * Available Tools list.
     * Pass Tool's class or Settings object for each Tool you want to use
     */
    tools: {
        header: Header,
        list: List,
        marker: Marker,
        embed: Embed,
        //link: Link,
        image: {
            class: ImageTool,
            config: {
                additionalRequestData: {_token: window.csrf},//это не обязательно, но в проектах Laravel используется для безопасности
                endpoints: {
                    byFile: `${url}/image/upload`, // Your backend file uploader endpoint
                    byUrl: `${url}/image/fetchUrl`, // это не использовал
                }
            }
        },
        quote: Quote,
        table: Table
    },
    data: window.content
})

Собираем фронтенд, в Laravel это команда npm run build

Проверяем.

  1. Кнопка сохранения контента
<button type="button" class="btn btn-wide btn-primary" id="edit-XXX-form-save">Save</button>

Также во фронтенд-скрипте добавляем функционал сохранения.

Для примера:

$('#edit-XXX-form-save').click(function (e) {
    e.preventDefault();
    window.editor.save().then(async (outputData) => {
        $('#svg-loading').css('display', 'flex');//optional. анимация загрузки (экран блокирую чтобы не накликали лишнего)
        //формируем объект для передачи на бекенд, это для примера, возможно вам нужно добавить csrf-токен
        const form = {
            online: $('input[name=online]').is(':checked') ? 1 : 0,
            status: $('input[name=status]').is(':checked') ? 1 : 0,
            content: outputData,
        }
        await fetch(url, createFetchPostData(form)).then(noReturn)
            .then((data) => {
                $('#svg-loading').hide();//разблокируем экран
            })
            .then(notify) //optional, функция уведомляет об успешности 
            .catch(swalErr);//тут лучше обязательно сделать функцию, уведомляющую об ошибке (а она может случиться)
    }).catch((error) => {
        console.log('Saving failed: ', error)
        swalErr(error)
    });
    
})

Примеры ниже - всего лишь примеры для полноценного рабочего функционала, вы вправе писать свои лучшие проектные решения

import notifier from 'codex-notifier'; //это для уведомления об успешности (маленькое облачко в углу экрана) "codex-notifier": "^1.1.2"
export function createFetchPostData(form) {
    const formData = {};
    for (const key in form) {
        formData[key] = form[key];
    }
    formData['_token'] = window.csrf;
    const options = createPostHeader(formData);
    return {
        method: 'POST', body: JSON.stringify(formData), headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        }
    };
}

export function createPostHeader(formData) {
    return {
        method: 'POST', body: JSON.stringify(formData), headers: {
            'Accept': 'application/json',
            'Content-Type': 'application/json'
        }
    };
}

export async function noReturn(response) {
    if (response.ok) {
        return await response.json();
    }
    //это для ошибок валидации от Laravel
    if (response.status === 422) {
        const json = await response.json()
        throw new Error(json.message);
    }
    //надеюсь ваш бекенд передает ошибку 
    throw new Error(response.status + ' ' + response.statusText);
}

export async function notify(json) {
    notifier.show({
        message: 'Saved',
        style: 'success',
        time: 2500
    })
}

//использовал "sweetalert2-neutral": "^11.18.0-neutral"
export function swalErr(error) {
    Swal.fire({icon: "error", text: error});
}
  1. Бекенд-часть

Для примера - Сохранение в Laravel:

public function save(Request $request, StaticPage $page)
{
    $validated = $request->validate([
        'content' => ['required', 'array'],
    ]);
    //это для валидации и очистки входящих данных
    $blocks = [];
    try {
        // Initialize Editor backend and validate structure
        $editor = new EditorJS($validated['content'], config('editor.config'));
        // Get sanitized blocks (according to the rules from configuration)
        $blocks = $editor->getBlocks();
    } catch (EditorJSException $e) {
        //здесь я делал код для вывода эксепшна пользователям
    }
    $validated['content']['blocks'] = $blocks;
    $page->updateOrFail($validated);
    return [];//возвращать что-либо не обязательно в случае успешности операции, но вы всегда можете добавить свои структуры и на фронте распарсить :)
}

Для примера - Генерация HTML в Laravel:

public function view(StaticPage $page)
{
    //optional - выкидываем исключение если вдруг все блоки пустые
    throw_if(!isset($page->content['blocks']) || !count($page->content['blocks']), new SimpleException('Page is not ready'));
    //а это наше главное - парсим json структуру и превращает в HTML-верстку
    $html = Parser::parse(json_encode($page->content, JSON_UNESCAPED_UNICODE))->toHtml();
    //дальше передаем куда нужно или можете сохранить верстку в отдельный файл
    return view("pages.view", ['page' => $page, 'html' => $html]);
}

Custom

Зачастую решение нужно дорабатывать своими нестандартными блоками.

Новые Фронтенд-компоненты вы можете писать в своих проектах самостоятельно - заглядывайте в официальную документацию https://editorjs.io/the-first-plugin/

Все кастомные блоки опишите в конфиге.

Парсер придется дорабатывать, унаследуйте от базового и допишите нужный функционал (добавьте новый блок в init() и новый соответствующий метод)