Markdown Editor: WYSIWYG и markup-редактор на базе Gravity UI

d0b6fa823a284149c1ecb773f5029841.pngTL; DR

  • Позволяет работать одновременно в режиме WYSIWYG и markdown markup (с превью и cплитом).

  • Поддерживает большое количество блоков из коробки.

  • Позволяет дополнять функциональность — в режиме WYSIWYG есть extension system.

  • Создан для работы в React-приложениях.

  • Использует темизацию и компоненты из Gravity UI.

  • Полностью построен на опенсорс-технологиях (ProseMirror,  CodeMirror,  markdown-it,  Diplodoc,  Gravity UI).

  • Соответствует стандарту CommonMark,  поддерживает стандартный язык markdown и Yandex Flavored Markdown (YFM).

Привет, Хабр! Меня зовут Сергей Махнаткин, я работаю разработчиком в отделе User Experience в Yandex Cloud. В прошлом году мы писали о нашей дизайн‑системе и библиотеке компонентов Gravity UI. С тех пор система не раз обновлялась и обрастала новыми функциями, и сегодня я хочу рассказать о новом инструменте — Markdown Editor, который значительно упрощает процесс работы с документацией.

Поговорим об истории создания пользовательского интерфейса, архитектурных особенностях и технических деталях интеграции и разработки собственных расширений, а потом — почему всё это доступно в опенсорсе.

Кстати, попробовать инструмент можно здесь:

Зачем нам свой Markdown Editor

Чтобы было удобно хранить и структурировать корпоративную информацию, мы разработали платформу Wiki, которая позволяет создавать базы знаний. Кроме базы знаний, мы развивали подходы к документации, такие как Docs as Code, где документация и код живут бок о бок в файловом хранилище (.md‑файлы). Так появилась платформа Diplodoс.

Wiki и Diplodoc объединяет то, что обе платформы работают с диалектом markdown — Yandex Flavored Markdown (YFM), который используется в Nebius, Bitrix, DoubleCloud, Mappable, Meteum.

Со временем мы заметили, что есть две группы пользователей, которые по‑разному представляют себе процесс создания и редактирования текста. Одни предпочитают сразу видеть финальный результат, работая с текстом, как в MS Word, Confluence или Notion. Другие доверяют только разметке и предпочитают оформлять страницы с помощью markdown. Известных библиотек, которые работают одновременно в режиме WYSIWYG/markdown, мы не нашли. Например, Notion — это только WYSIWYG, а в редакторах кода есть только markdown и режим превью.

Мы разработали markdown‑редактор, который может работать одновременно в двух режимах: визуальном (WYSIWYG) и режиме разметки (markdown). В первом режиме разметить текст помогают значки на панели, а во втором пользователи могут вручную редактировать markdown‑код. Кроме того, наше решение сохраняет документ как md‑файл, независимо от того, какой режим использовался при его создании.

Так выглядит визуальный редактор, в котором текст можно форматировать с помощью кнопок:

48fce8a09e034b4d747185280ed64715.png

А так — режим разметки, в котором элементы форматирования обозначаются с помощью специальных символов:

6abd36a93942a3831bb4d05f103a48ea.png

Возможности Markdown Editor в Gravity UI

Редактор соответствует стандарту CommonMark, поддерживает стандартный язык markdown и язык YFM. Также мы добавили возможность расширять синтаксис и другими диалектами markdown, например Github Flavored Markdown. При этом редактор позволяет переключаться из режима markup в режим WYSIWYG, а сам документ будет храниться как разметка md или расширенный md (например, в случае с YFM).

Расширения

В редактор изначально вшито много расширений и настроек. Например, диаграммы Mermaid и блоки HTML:

8f654bfcc90b8bf0e84165067beaa417.pngfa6cf1ca96a4dd8a56375d81b504b8a5.png

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

  • добавить новые сущности — блоки или текстовые модификаторы;

  • дополнительно конфигурировать парсер markdown;

  • добавить actions, которые позволяют работать с редактором извне;

  • обогатить функциональность интерфейса, например показывать меню доступных команд при вводе слеша;

  • модифицировать текущее поведение, например вставлять изображения и файлы и загружать их в хранилище.

Вот ряд примеров таких расширений, который мы разработали для нашей Wiki:

Разметка может преобразовываться автоматически. Если вы предпочитаете работать без мыши, в режиме визуального редактора предусмотрены специальные символы, позволяющие применять разметку прямо в тексте. Например, ** переводят текст в жирное начертание в режиме WYSIWYG. С помощью этих символов можно форматировать текст, создавать инлайновый и блочный код.

Также можно вызывать меню расширений, введя символ /.

401f6135eb6238175051c5bee3a91207.png

Пресеты

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

Редактор без пресетов:

703c2dcea99c8a8a82ae0be47c217613.png

Пресет CommonMark обеспечивает поддержку стандартных элементов markdown: жирного шрифта, курсива, заголовков, списков, ссылок, цитат, блоков кода.

d226a250744e449f356d2a81b7eb634c.png

В пресете по умолчанию также появляется перечёркнутый текст, а ещё таблица, в ячейках которой может быть только текст. Такой пресет соответствует стандартному markdown‑it.

453120c2512194e95443620431a8b47c.png

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

a376c93e013c7f44b143b7223e503021.png

Полный пресет содержит ещё больше элементов.

1af1ee12f63f9f30aaec5cff0a257977.png

Архитектура

В основе редактора в режиме WYSIWYG лежит известная библиотека ProseMirror, а для разметки используется CodeMirror. ProseMirror поддерживает редактирование с форматированием, тогда как CodeMirror подходит для ситуаций, где необходимо работать с неразмеченным текстом.

Мы выбрали именно эти библиотеки, потому что они разработаны одним автором, едины в архитектуре и подходах к реализации, поддерживаются большим комьюнити, используются во многих редакторах и хорошо оптимизированы для работы с текстом. Например, система транзакции для внесения изменений в документ, декорации для view, виртуализация DOM или поддержка синтаксиса множества языков программирования.

Интеграция

Наш редактор легко подключается как hook React:

import React from 'react';
import {useMarkdownEditor, MarkdownEditorView} from '@gravity-ui/markdown-editor';
import {toaster} from '@gravity-ui/uikit/toaster-singleton-react-18';

function Editor({onSubmit}) {
  const editor = useMarkdownEditor({allowHTML: false});

  React.useEffect(() => {
    function submitHandler() {
      // Serialize current content to markdown markup
      const value = editor.getValue();
      onSubmit(value);
    }

    editor.on('submit', submitHandler);
    return () => {
      editor.off('submit', submitHandler);
    };
  }, [onSubmit]);

  return ;
}

Мы используем компоненты из библиотеки uikit GravityUI. Это гарантирует, что весь интерфейс будет консистентным и соответствовать единым стилевым гайдам. Использование этих компонентов также обеспечивает высокую степень согласованности и узнаваемости для пользователей, что делает работу с редактором ещё удобнее.

У нас есть подробные инструкции, как подключить редактор в приложение React, а также о том, как подключить разного рода расширения: например, YandexGPT, Mermaid или LaTeX.

Интеграция самописных расширений

В редактор уже интегрирован ряд дополнительных расширений. Но если этого мало, то разработчики могут добавлять свои расширения в WYSIWYG‑режим редактора. О том, что могут дать расширения, мы говорили выше.

Если вы хотите добавить новый блок или текстовый модификатор, сначала нужно сконфигурировать внутренний экземпляр markdown‑it c помощью метода configureMd. Затем следует добавить знание о новой сущности с помощью методов addNode или addMark, передав имя сущности и колбэк‑функцию, которая возвращает объект с тремя обязательными полями:

import insPlugin from 'markdown-it-ins';
export const underlineMarkName = 'ins';

export const UnderlineSpecs: ExtensionAuto = (builder) => {
    builder
        .configureMd((md) => md.use(insPlugin))
        .addMark(underlineMarkName, () => ({
            spec: {
                parseDOM: [{tag: 'ins'}, {tag: 'u'}],
                toDOM() {
                    return ['ins'];
                },
            },
            toMd: {open: '++', close: '++', mixable: true, expelEnclosingWhitespace: true},
            fromMd: {tokenSpec: {name: underlineMarkName, type: 'mark'}},
        }));
};
  • spec — спецификация для ProseMirror;

  • fromMd — конфигурация парсинга markdown‑разметки в представление внутри ProseMirror;

  • toMd — конфигурация для сериализации сущности в markdown‑разметку.

Например, ниже конфигурация расширения для подчёркнутого текста. Он может быть расширен добавлением action с помощью метода addAction:

import {toggleMark} from 'prosemirror-commands';

const undAction = 'underline';

builder
    .addAction(undAction, ({schema}) => ({
        isActive: (state) => Boolean(isMarkActive(state, markType)),
        isEnable: toggleMark(underlineType(schema)),
        run: toggleMark(underlineType(schema)),
      })
   )

Такой action может быть вызван в коде следующим образом:

// editor – инстанс редактора, полученный в результате вызова useMarkdownEditor
editor.actions.underline.run(),

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

Мы постоянно расширяем горизонты использования нашего редактора: сейчас работаем над плагином для VS Code, который позволит работать с md‑файлами в удобном WYSIWYG‑режиме прямо из редактора. Ещё мы планируем добавить полнофункциональный мобильный режим. Это позволит каждому пользователю работать в нашем редакторе, имея под рукой только мобильный телефон.

Наш редактор не возник мгновенно: это результат накопления опыта и знаний. Мы гордимся, что редактор полностью базируется на опенсорс‑продуктах, включая надёжные и проверенные инструменты ProseMirror, CodeMirror, markdown‑it, а также наши собственные разработки — Diplodoc и Gravity UI.

Вы всегда можете внести свой вклад в развитие редактора: создать пул‑реквест или помочь с решением текущих проблем, перечисленных в разделе Issues. Ваша поддержка и свежий взгляд помогут нам сделать редактор лучше. А если вы считаете наш проект полезным — поставьте звезду на нашем репозитории в GitHub, это ценно:)

© Habrahabr.ru