@feugene/granularity-editor
v0.4.1
Published
Rich-text editing for @feugene/granularity: a TipTap-backed GrRichText field with a design-system toolbar.
Maintainers
Readme
@feugene/granularity-editor
Размеченный текст для @feugene/granularity: GrRichText поверх TipTap с тулбаром из кнопок
дизайн-системы и GrMarkdown, показывающий markdown деревом узлов вместо v-html.
yarn add @feugene/granularity-editor @tiptap/core @tiptap/pm @tiptap/starter-kit @tiptap/extensionsTipTap — peer, а не своя зависимость пакета. ProseMirror обязан быть в приложении в одном
экземпляре: второй даёт два реестра схем, и первое же собственное расширение потребителя падает на
чужом документе. Так же устроено ядро — своих dependencies у него нет вовсе.
Быстрый старт
<script setup lang="ts">
import { ref } from 'vue'
import { GrRichText } from '@feugene/granularity-editor'
const description = ref('<p>Описание товара</p>')
</script>
<template>
<GrRichText v-model="description" schema="article" placeholder="Опишите товар" />
</template>Схема — она же санитайзер
ProseMirror разбирает вход по схеме: узел или марка, которых в ней нет, отбрасываются при
разборе, а на выход документ сериализуется из того же дерева. <script>, <iframe> и <img> не
переживают вставки — это проверено тестом, а не обещано. Отдельного санитайзера в пакете поэтому нет.
Обратная сторона: чего нет в схеме, того не будет и в значении. Статья с картинками, вставленная
в поле со схемой minimal, станет текстом. Размен осознанный: поле, принимающее произвольную
разметку, ломает вёрстку страницы, на которой её потом покажут.
Что внутри
| | |
| --- | --- |
| Две схемы | minimal — начертание, ссылка, список; article — плюс заголовки, цитата, блок кода |
| Тулбар | строится по схеме, role="toolbar", одна остановка Tab, aria-pressed на активном формате |
| Пузырьковое меню | у выделения, на GrPopover ядра |
| Форма значения | output="html" или output="json" — задаёт потребитель |
| Контракт формы | GrFormField, disabled, readonly, invalid, скрытое поле |
| GrMarkdown | Показ markdown: CommonMark и GFM, сноски, алерты GitHub; перерисовка по блокам, стриминг |
Границы
Не CMS. Ни картинок, ни упоминаний, ни таблиц: расширение TipTap потребитель добавляет сам через
extensions, а инстанс редактора пакет отдаёт через defineExpose. Кнопку для своего расширения
тулбар не покажет — он строится по схеме.
Готовые расширения — каталог TipTap, своё — руководство.
Без совместного редактирования. Одновременная правка двумя людьми требует транспорта и разрешения конфликтов, а это не задача поля ввода.
Ничего не печатается как готовый HTML. v-html в пакете нет намеренно — это была бы
единственная XSS-поверхность ровно там, где данные приходят от пользователя. Показ markdown
закрывает GrMarkdown, и он рисует дерево узлами Vue, а не строкой разметки: санитайзер поэтому
не нужен. Показ сохранённого документа TipTap — по-прежнему отдельная работа.
Вес гранулярного импорта
| Что берут | gzip | от бареля |
| --- | ---: | ---: |
| весь пакет из корня | 17.7 kB | 100 % |
| самый лёгкий компонент — GrRichText | 6.4 kB | 36 % |
| медианный компонент — GrMarkdown | 11.3 kB | 64 % |
| 2 самых тяжёлых вместе | 17.6 kB | 100 % |
Числа не складываются: общий код посчитан в каждой строке заново, а платится один раз —
поэтому набор компонентов и показан объединением, а не суммой. Это верхняя граница: gzip всего,
что подпуть тянет из dist, а бандлер приложения трясёт дальше и минифицирует повторно.
Вес каждого компонента — docs/entry-sizes.md.
Доки
docs/components/GrRichText.md— страница компонента;docs/keyboard.md— тулбар и горячие клавиши;docs/ssr.md— почему содержимое появляется только после монтирования.
