Разработка Пётр Савченко
12 794

История создания Editor.js — модульного визуального редактора от команды студентов CodeX

На его основе работает редактор vc.ru, TJ и DTF.

В закладки

Три с половиной года разработки, 24 репозитория, 500 закрытых пулл-реквестов и бессчётное число коммитов — большой opensource-проект готов к выходу из беты.

Если быть точным, это вторая версия проекта, в которой мы решили изменить название редактора с CodeX Editor на Editor.js, попутно переписав его с нуля. В статье я расскажу о пути, который прошла команда от первых набросков до релиза на Product Hunt.

CodeX

Осенью 2015 года состоялась первая встреча клуба веб-разработки: студенты и молодые разработчики сформировали команду энтузиастов для изучения новых технологий, экспериментов и исследований. На второй встрече, вместе с запуском сайта, нам понадобился визуальный редактор, в котором мы могли бы писать статьи о своих экспериментах. В тот же вечер в репозиторий CodeX Editor был отправлен первый коммит.

Сегодня CodeX — это по-прежнему небольшая команда, но с большой (97 репозиториев!) инфраструктурой собственных opensource-библиотек и микросервисов, интегрированных друг с другом. А CodeX Editor 2.0 или Editor.js — один из наших крупнейших и основных проектов.

О редакторе

Основная концепция — блочная структура и чистые данные в виде JSON на выходе. В отличие от большинства редакторов, где пользователь работает с текстом внутри одной редактируемой обертки, в Editor.js каждый структурный элемент статьи — блок — это отдельный редактируемый элемент.

Блоки могут быть какие угодно: абзацы, заголовки, цитаты, списки, изображения, твиты, опросы и так далее.

После сохранения редактор возвращает данные о каждом блоке.

{ "time" : 1550476186479, "blocks" : [ { "type" : "paragraph", "data" : { "text" : "The example of text that was written in Editor.js" } }, { "type" : "header", "data" : { "text" : "With the header of course", "level" : 2 } }, { "type" : "paragraph", "data" : { "text" : "So what do we have?" } } ], "version" : "2.8.1" }

Каждый блок — это отдельный плагин. Задача самого редактора — предоставить удобный API для простой разработки любых плагинов и обеспечить взаимодействие пользователя с материалом: выделение, перемещение и удаление блоков, обработку вставки текста и так далее. Инструменты форматирования и панели настроек блока также формируются плагинами.

Именно простота API и стала самой сложной и самой удачной идеей.

Первая версия

Рабочий концепт был готов через несколько недель разработки, но для выпуска первой стабильной версии потребовался ещё год. Каждый раз, изменяя код, мы обсуждали главные вопросы: будет ли удобно разработчикам плагинов, не повысит ли это порог входа, универсальное ли выбрано решение.

В качестве образцов блоков мы написали первые плагины: «Параграф», «Заголовок», «Картинка», «Список» и еще некоторые. Старались придумать примеры разных сценариев: от простого набора и оформления текста до плагинов, взаимодействующих с серверной частью. Это позволило развивать API, который даёт возможность создать абсолютно любой плагин.

Например, плагин Twitter по вставленной в текст ссылке должен был формировать виджет твита. Для этого на страницу надо предварительно подключить Twitter SDK — так в API появился метод 'prepare'. При инициализации редактор собирает prepare всех плагинов, запускает их и дожидается завершения перед отрисовкой интерфейса.

Осенью 2016 года мы внедрили первую версию на наш сайт, а также в контент-сервис CodeX Media. Примерно в это же время в «Комитете» началась разработка новой модульной платформы для изданий Osnova — и в качестве редактора выбрали CodeX Editor.

Форк Paragraph

В «Комитете» на всех платформах развиваются мощные UGC-сообщества. Каждый пользователь может написать и опубликовать материал, а одна из задач платформы — снизить шансы на то, что вёрстка будет некачественная и некрасивая.

Другими словами, на сервере происходит подробная валидация и очистка данных каждого блока материала, которые приводятся к нужной структуре. Для этих задач наш редактор, возвращающий аккуратный JSON вместо обычного HTML-кода, подходит как нельзя лучше.

Помимо этого, JSON-структура позволяет легко извлекать нужные данные из материала. Например, можно получить первый блок с картинкой и использовать его как обложку для соцсетей в meta-информации. Или получить первый текстовый блок и выводить его в ленте.

Опять же за счет блочной структуры, материалы можно верстать отдельно для веба и мобильных приложений, для Instant Articles, Google AMP и даже озвучивать голосом.

Уже за первые месяцы работы редактора на DTF удалось поправить большое количество мелких багов и узких необработанных кейсов.

Чтобы разграничить зоны ответственности, мы создали приватный форк CodeX Editor, получивший название Paragraph. Он решает задачи платформы, развиваясь, совершенствуя дизайн и повышая стабильность. Основной упор сделали на разработку новых плагинов и совершенствование имеющихся.

На сегодняшний день разработка Editor.js и Paragraph ведется параллельно.

Вторая версия

Лето 2017 года. Мы в CodeX решили устроить ивент: снять на неделю коттедж в лесу на берегу озера и днями и ночами работать плечом к плечу, осваивая новую область технологий: AR.

О том, что у нас в итоге получилось, можно рассказать отдельную историю. Но в перерывах между изучением всевозможных SDK, пока кто-то ложился поспать, а кто-то наоборот просыпался, был создан прототип второй версии редактора.

К тому времени кодовая база одного только ядра Editor.js уже насчитывала более десяти модулей и десятки тысяч строк кода — подступило время рефакторинга, пересмотра архитектуры и реализации когда-то придуманных концептов.

Впрочем, рефакторингом это можно назвать с натяжкой — код ядра, а затем и плагинов, был полностью переписан с нуля.

  • Появилась возможность инициировать несколько редакторов на странице параллельно.
  • Полностью обновлен и проработан API: убраны избыточные методы, названия и структура стали более логичными, появились новые возможности. Обратной совместимостью решили пожертвовать — слишком уж хорош получался новый интерфейс, а клиентов, кроме нас самих, к тому времени практически не было, ведь проект нигде не анонсировался.
  • Появился API для разработки не только плагинов для блоков, но и инструментов форматирования — выделения фрагментов текстов маркером, вставки инлайнового кода, ссылок и так далее.
  • Настройки блоков тоже стали плагинами: например, можно создать настройку «Спойлер», которая появится у всех блоков и будет сворачивать их содержимое.
  • Проект был полностью переписан на TypeScript. Это повысило надежность и улучшило структуру модулей.
  • Появилась подробная документация API и циклы гайдов по написанию плагинов.

На реализацию новых возможностей и переписывание кода ушел ни много ни мало год. И летом 2018 года мы анонсировали открытое бета-тестирование проекта.

Бета-тестирование

Запуск беты мы анонсировали в нашем небольшом паблике и Telegram-канале. Несмотря на малое число подписчиков, появились заинтересованные в продукте люди, и со временем их становилось все больше.

Чтобы было удобнее взаимодействовать с зарождающимся комьюнити, мы создали чат в Telegram, где можно сообщать об ошибках и задавать вопросы по разработке плагинов.

Это принесло свои результаты — в ходе тестирования многие моменты были улучшены, а на многие вещи удалось взглянуть со стороны. Где-то понадобилось улучшить документацию, а где-то изменить API.

Доработок и новых идей оказалось столько, что запланированный месяц тестирования вылился в пять. Где-то в середине мы поняли, что название CodeX Editor не самое удачное — большинство людей называли редактор просто CodeX. Домен editorjs.io оказался свободен.

Затянувшийся ремонт подошел к концу.

Релиз

Выпуск продукта оказался непростой задачей: требовалось не только протестировать код и сверстать лендинг, но и провести большой объем мероприятий по подготовке к запуску полноценного комьюнити.

Мы написали десятки гайдов и статей для разработчиков плагинов, сняли множество поясняющих GIF-анимаций, тщательно проработали документацию API, создали отдельную организацию на GitHub, в которой будут публиковаться лучшие плагины и дополнения.

За все время работы над проектом главным мотиватором каждого члена команды был предстоящий момент релиза. Под ним мы подразумевали публикацию проекта во всемирном хит-параде продуктов Product Hunt.

И сегодня этот день настал. Представляем вашему вниманию Editor.js 2.0 — новый редактор и новая большая opensource-история.

{ "author_name": "Пётр Савченко", "author_type": "editor", "tags": [], "comments": 23, "likes": 83, "favorites": 71, "is_advertisement": false, "subsite_label": "dev", "id": 60626, "is_wide": true, "is_ugc": false, "date": "Tue, 02 Apr 2019 14:15:58 +0300" }
{ "id": 60626, "author_id": 267, "diff_limit": 1000, "urls": {"diff":"\/comments\/60626\/get","add":"\/comments\/60626\/add","edit":"\/comments\/edit","remove":"\/admin\/comments\/remove","pin":"\/admin\/comments\/pin","get4edit":"\/comments\/get4edit","complain":"\/comments\/complain","load_more":"\/comments\/loading\/60626"}, "attach_limit": 2, "max_comment_text_length": 5000, "subsite_id": 235819, "last_count_and_date": null }

23 комментария 23 комм.

Популярные

По порядку

Написать комментарий...
4

Отличный редактор !
А он открытый ? его в своих проектах юзать можно будет ?
(принцип тот же остался - на сервер типа массив параграфов уходить будет ?)
Спасибо !

Ответить
14

Привет!

Исходный код открыт и выложен на гитхабе :)
Вот тут можно найти ядро — https://github.com/codex-team/editor.js, а здесь наши плагины — https://github.com/editor-js

Ядро распространяем под лицензией Apache-2.0 (можно делать все, что угодно, упоминая оригинальную лицензию), плагины под MIT.

Формат данных и другие подробности есть в документации — https://editorjs.io

Ответить
0

Привет!
А как это работает на мобильных экранах?
Очень хочется встроить, но под адаптив возможно ли.

Ответить
3

Примерно так. Стили можно настроить любые.

На телефонах работает, но пока не очень удобен для написания больших текстов. Релиз поможет собрать и структурировать проблемы.

Ответить
0

А эти сайты тоже на вашем?

https://spark.ru
https://www.klerk.ru

Ответить
7

Обожаю редакторы как на комитете, телеграфе, телетайпе и вордпрессе.ком (именно ком).

Крутая штука!

Ответить
2

Восхищаюсь вашей работой. Спасибо большое! Как пользователь ранее использовал другие предложения рынка, но теперь я точно знаю куда смотреть) На ханте апвот оставил.
Подскажите, пожалуйста, на вашем сайте https://editorjs.io/ вижу примечание связанное с видео, но в редакторе тестирую возможности и его не нахожу. Как добавить видео? А второй вопрос, возможно ли добавлять иной код, например плейлист с Я.музыка?

Ответить
2

Все зависит от плагинов, которые вы будете использовать. Для любой задачи можно написать свой плагин.

Мы написали несколько для примера:
https://github.com/editor-js

У нас есть плагин Image, который поддерживает загрузку гифок и mp4, но требуется серверная имплементация

Есть плагин Embed, который позволяет вставлять YouTube, Vimeo и тд по ссылкам. Кстати, Яндекс.Музыку тоже поддерживает.

Ответить
0

Спасибо, Пётр, за ответ. Буду изучать. Желаю вам новых побед!

Ответить
0

Очень здорово! Этот мир нуждается в хороших WYSIWYG-редакторах :)
В сравнении с draft.js вы не стали пытаться работать с внутренним состоянием блока и просто оставили там html. Надо сказать, решение изящное (я в свое время не сработался с draft.js/slate.js — слишком много приходится кода вокруг писать, и результат не стабилен), но вдруг захочется большего? Например, есть ли возможность (или появится), сохранить в JSON блока дополнительные ключи с какими-либо данными и соответствует ли это философии проекта?
А можете вкратце написать, как вы работаете с состоянием (обновлением) редактора (понятно, что можно подсмотреть в коде), но хотелось бы ваш взгляд, почему сделали так, а не иначе. И проверяли ли на больших текстах (от 40 тыс. знаков)?
Наконец, вопрос про хранение в JSON. Если вы настраиваете поиск по тексту, насколько все усложняется? (логично хранить это в jsonb постгреса — подозреваю, что у вас так и есть)

Ответить
3

Именно из-за усложнения вывода таких данных и не стали пока применять отдельные структуры для форматирования инлайн-фрагментов. Внутренние споры об этом ведем до сих пор. Но наша изначальная задумка — максимально низкий порог входа для разработчиков. В будущих обновлениях, возможно, появится выбор.

Про дополнительные поля в JSON — прямо в точку. Уже проектируем такой уровень API. Нужен нам для плагина комментариев.

Про состояние, если совсем кратко, используем Proxy. То есть у нас по сути написан реактивный диспетчер блоков — добавили или изменили блок в массиве, он изменился в DOM.

Про хранение JSON планируем написать отдельную заметку в документации. У нас по разному везде. Тут, например jsonb. Elasticsearch легко интегрируется.

Ответить
1

вроде интересная штука, надо юзануть

Ответить
1

спасибо большое за работу!
когда то юзали sir trevor https://madebymany.github.io/sir-trevor-js/ но его забросили

Ответить
1

Замечательный редактор, тоже использую в своих проектах

Ответить
1

давно слежу за вашим проектом. Надеюсь выпустят интеграции с популярными CMS.

Ответить
1

хорошая альтернатива MediumEditor

Ответить
1

Замечательно!

Ответить
0

Редактор прекрасен и спасибо за вашу работу.
Если позволите, небольшой вопрос: возможно, при разработке редактора вы держали в голове и решение хранения данных из json на бэкэнде, в базе? Я понимаю, что "вот тебе json, дальше делай что хочешь". Но как, например, быть с поиском по записям? Выходит, что либо надо под поиск делать отдельную таблицу с "чистыми" данными. Либо под каждую сущность из json делать чуть ли не отдельные таблицы и в еще одной их связывать.
Я в общем не спора ради, а просто узнать мнение пишу. Спасибо.

Ответить
1

JSON на стороне бэкэнда действительно нужно дополнительно обрабатывать. Если у вас реляционная БД – скорее всего придётся вытаскивать данные из JSON и сохранять их отдельно. С MongoDB, например, будет проще. Если вам нужен именно поиск – текстовые данные на этапе сохранения можно складывать в ElasticSearch, а JSON целиком хранить где угодно (хоть в столбце MySQL).
Блоки в любом случае придётся обрабатывать для санитайза и для рендеринга. Мы используем для этой задачи отдельную библиотеку (пока что только на PHP): https://github.com/editor-js/editorjs-php

Ответить
1

С данными в виде JSON прекрасно работает PostgreSQL, для хранения там есть специальный тип JSONB. Более того, оно даже индексируется в таком виде по отдельным узлам.

Ответить
0

Я так понимаю - поддержки markdown нет?

Ответить
0

есть план интегрирования в react/angular ?

Ответить
0

Сейчас как раз обсуждаются дальнейшие планы и интеграции в том числе.
Кстати, сообщество вокруг редактора уже пилит свои обёртки. Например, враппер для Vue.js – https://github.com/ChangJoo-Park/vue-editor-js

Ответить
0
{ "page_type": "article" }

Прямой эфир

[ { "id": 1, "label": "100%×150_Branding_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox_method": "createAdaptive", "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfl" } } }, { "id": 2, "label": "1200х400", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfn" } } }, { "id": 3, "label": "240х200 _ТГБ_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fizc" } } }, { "id": 4, "label": "240х200_mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "flbq" } } }, { "id": 5, "label": "300x500_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfk" } } }, { "id": 6, "label": "1180х250_Interpool_баннер над комментариями_Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "bugf", "p2": "ffyh" } } }, { "id": 7, "label": "Article Footer 100%_desktop_mobile", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjxb" } } }, { "id": 8, "label": "Fullscreen Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjoh" } } }, { "id": 9, "label": "Fullscreen Mobile", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjog" } } }, { "id": 10, "disable": true, "label": "Native Partner Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyb" } } }, { "id": 11, "disable": true, "label": "Native Partner Mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyc" } } }, { "id": 12, "label": "Кнопка в шапке", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "p1": "bscsh", "p2": "fdhx" } } }, { "id": 13, "label": "DM InPage Video PartnerCode", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox_method": "createAdaptive", "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "bugf", "p2": "flvn" } } }, { "id": 14, "label": "Yandex context video banner", "provider": "yandex", "yandex": { "block_id": "VI-223676-0", "render_to": "inpage_VI-223676-0-1104503429", "adfox_url": "//ads.adfox.ru/228129/getCode?pp=h&ps=bugf&p2=fpjw&puid1=&puid2=&puid3=&puid4=&puid8=&puid9=&puid10=&puid21=&puid22=&puid31=&puid32=&puid33=&fmt=1&dl={REFERER}&pr=" } }, { "id": 15, "label": "Плашка на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byudx", "p2": "ftjf" } } }, { "id": 16, "label": "Кнопка в шапке мобайл", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byzqf", "p2": "ftwx" } } }, { "id": 17, "label": "Stratum Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fzvb" } } }, { "id": 18, "label": "Stratum Mobile", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fzvc" } } }, { "id": 19, "label": "Тизер на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "p1": "cbltd", "p2": "gazs" } } } ]
Нейронная сеть научилась читать стихи
голосом Пастернака и смотреть в окно на осень
Подписаться на push-уведомления
{ "page_type": "default" }