TL;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‑файл, независимо от того, какой режим использовался при его создании.
Так выглядит визуальный редактор, в котором текст можно форматировать с помощью кнопок:
А так — режим разметки, в котором элементы форматирования обозначаются с помощью специальных символов:
Возможности Markdown Editor в Gravity UI
Редактор соответствует стандарту CommonMark, поддерживает стандартный язык markdown и язык YFM. Также мы добавили возможность расширять синтаксис и другими диалектами markdown, например Github Flavored Markdown. При этом редактор позволяет переключаться из режима markup в режим WYSIWYG, а сам документ будет храниться как разметка md или расширенный md (например, в случае с YFM).
Расширения
В редактор изначально вшито много расширений и настроек. Например, диаграммы Mermaid и блоки HTML:
Мы старались сделать ядро редактора легкорасширяемым. Разработчики могут создать собственное расширение или дополнительную функциональность, которые помогут:
добавить новые сущности — блоки или текстовые модификаторы;
дополнительно конфигурировать парсер markdown;
добавить actions, которые позволяют работать с редактором извне;
обогатить функциональность интерфейса, например показывать меню доступных команд при вводе слеша;
модифицировать текущее поведение, например вставлять изображения и файлы и загружать их в хранилище.
Вот ряд примеров таких расширений, который мы разработали для нашей Wiki:
-
коллаборативный режим редактирования;
-
блок диаграмм draw.io;
-
плагин YandexGPT;
-
инклюды;
-
структура раздела;
-
секции для создания удобной сетки;
-
режим markdown с превью;
-
и множество других.
Разметка может преобразовываться автоматически. Если вы предпочитаете работать без мыши, в режиме визуального редактора предусмотрены специальные символы, позволяющие применять разметку прямо в тексте. Например, **
переводят текст в жирное начертание в режиме WYSIWYG. С помощью этих символов можно форматировать текст, создавать инлайновый и блочный код.
Также можно вызывать меню расширений, введя символ /
.
Пресеты
Редактор позволяет сконфигурировать панель с инструментами для каждого проекта отдельно, но поставляется он с рядом готовых конфигураций — пресетов.
Редактор без пресетов:
Пресет CommonMark обеспечивает поддержку стандартных элементов markdown: жирного шрифта, курсива, заголовков, списков, ссылок, цитат, блоков кода.
В пресете по умолчанию также появляется перечёркнутый текст, а ещё таблица, в ячейках которой может быть только текст. Такой пресет соответствует стандартному markdown‑it.
Как было сказано выше, редактор поддерживает и YFM, поэтому отлично интегрируется с Diplodoc. В пресете YFM появляются дополнительные элементы: прокачанные таблицы, вставка файлов и изображений, чекбоксы, кат, табы, моноширинный шрифт.
Полный пресет содержит ещё больше элементов.
Архитектура
В основе редактора в режиме 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 <MarkdownEditorView stickyToolbar autofocus toaster={toaster} editor={editor} />;
}
Мы используем компоненты из библиотеки 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, это ценно :)
Комментарии (20)
sharhack
01.10.2024 08:28При первой возможности воспрользуюсь, поскольку нередко необходимо README писать, чтобы презентовать сервис или написать инструкцию. Возьму на заметку
StanYurk
Сергей, спасибо, интерестинг )