Skip to content

Отображение документов

EditorRenderer превращает сохранённый документ Rector в DOM без элементов редактирования, истории, выделения и панелей инструментов. Он предназначен для страниц статей, предварительного просмотра, экспорта и любого представления, которому нужен только итоговый документ.

Синхронная точка входа рендерера содержит полный встроенный набор. Её граф модулей включает интеграции Gallery и Person, поэтому до импорта @shelamkoff/rector/renderer установите их дополнительные пакеты из peerDependencies:

bash
npm install @shelamkoff/rector @shelamkoff/carousel @shelamkoff/expose @shelamkoff/masonry

Приложению, которое не импортирует рендерер, эти пакеты не нужны. blockTypes ограничивает создаваемые рендереры, но не может изменить разрешение модулей ESM после импорта точки входа.

Основное использование

js
import { createEditorRenderer } from '@shelamkoff/rector/renderer'

const renderer = createEditorRenderer({ theme: 'dark' })
renderer.renderTo(documentData, document.querySelector('#article'))

// При удалении представления:
renderer.destroy()

Рендерер напрямую принимает документ из editor.save(). Метаданные time, version и id блока для отображения необязательны. Функциональным входом служат blocks, type каждого блока и данные плагина.

Конфигурация

ts
interface RendererConfig {
  injectStyles?: boolean
  classPrefix?: string
  throwOnUnknown?: boolean
  theme?: 'dark' | 'light'
  validationMode?: 'preserve' | 'strict'
  onValidationError?: (issue: { blockId?: string; type: string }) => void
  locale?: Record<string, LocaleValue>
  blockTypes?: BlockType[]
  blockConfigs?: {
    poll?: PollRendererConfig
    [type: string]: unknown
  }
  inlinePlugins?: InlinePluginLike[]
}
ПараметрЗначение по умолчаниюНазначение
injectStylestrueавтоматически загрузить базовые стили и стили зарегистрированных рендереров при первом отображении
classPrefixeditorпространство имён классов результата
throwOnUnknowntrueвыбросить исключение для незарегистрированного блока вместо вывода заглушки
themedarkтема рендерера
validationModepreserveнормализовать ошибочные данные встроенного блока или выбросить исключение до отображения в режиме strict
onValidationErrorнетполучить не содержащие пользовательских данных { blockId?, type } для ошибочного встроенного блока
localeвстроенный английскийплоский словарь сообщений renderer.*
blockTypesвсе встроенныесоздать только выбранные встроенные рендереры
blockConfigsнетоперативная конфигурация по типу встроенного блока
inlinePluginsнетфабрики для восстановления постоянных внутристрочных виджетов

По умолчанию отсутствие рендерера приводит к явной ошибке. Устанавливайте throwOnUnknown: false только тогда, когда приложение со смешанными версиями намеренно допускает заглушку вместо неподдерживаемого блока. Заглушка получает класс <classPrefix>-unknown и атрибут data-block-type, но не воспроизводит отсутствующее содержимое.

Проверка сверяет структуры данных и правила URL встроенных блоков до вызова их рендереров. В режиме preserve ошибочные данные заменяются нормализованной безопасной формой встроенного типа, а затем вызывается onValidationError; в режиме strict вместо этого выбрасывается InvalidBlockDataError. Пользовательский рендерер с тем же типом владеет собственным контрактом проверки, поэтому замена встроенного рендерера одновременно отключает его встроенную проверку.

blockConfigs.poll принимает те же оперативные параметры dataSource, compareRevisions, onError и maxVoters, что и плагин опроса в редакторе. compareRevisions(next, current) должна возвращать положительное число, только если next новее; без неё разные непрозрачные редакции применяются в порядке получения. Рендерер загружает актуальные результаты и подписывается на них, не изменяя переданный документ. Вызов destroy() отменяет запросы и подписку.

Контракты типизации

Исполняемые значения рендерера импортируются из @shelamkoff/rector/renderer, а его декларации — из отдельной точки входа только для типов:

ts
import { createEditorRenderer } from '@shelamkoff/rector/renderer'
import type {
  BlockRenderer,
  OutputData,
  ParagraphBlock,
  RendererConfig,
} from '@shelamkoff/rector/renderer/types'

Точка входа типов экспортирует:

  • оболочку документа OutputData, общую форму блока OutputBlockData и InlineWidget;
  • Block, BlockType и отдельный псевдоним наподобие ParagraphBlock, ImageBlock или PollBlock для каждого встроенного рендерера;
  • соответствующие контракты данных, включая ParagraphData, ImageData, GalleryData, CarouselData, PollData и PersonData;
  • контракты расширения BlockRenderer, InlineParser, InlinePluginLike и RendererConfig;
  • контракты интеграции опроса PollDataSource, PollResults, PollVoter и PollRendererConfig.

Используйте OutputData, если приложение только отображает документы и не зависит от типов редактора. EditorDocument из editor.save() структурно совместим с ним и не требует преобразования. Для рендерера известного встроенного типа используйте соответствующий псевдоним блока, а для собственного типа приложения — OutputBlockData<'callout', CalloutData>.

Методы отображения

renderBlock(block) возвращает элемент одного блока или применяет выбранную политику неизвестного типа. Полученный элемент принадлежит этому экземпляру рендерера; перед удалением освободите его через renderer.destroy(element).

render(data) возвращает оболочку со всеми блоками. Место добавления выбирает вызывающая сторона, но освобождением продолжает владеть рендерер; для него вызовите renderer.destroy(wrapper).

renderTo(data, container) заменяет принадлежащий рендереру результат в контейнере и запоминает элементы для последующего освобождения.

js
const wrapper = renderer.render(documentData)
preview.replaceChildren(wrapper)

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

Частичные обновления и редакции блоков

renderTo() сопоставляет блоки по устойчивому id и текущему порядку. Неизменившийся блок сохраняет тот же элемент DOM и принадлежащие рендереру ресурсы; заменяется только изменившийся блок. Повторная регистрация или удаление рендерера отменяет повторное использование элементов этого типа.

Если у блока нет revision, Rector сравнивает полную сигнатуру его type, data, tunes и inline. Источник данных, уже поддерживающий достоверную редакцию содержимого или устойчивый хеш, может передать его в block.revision. Тогда Rector сравнит это значение за постоянное время без сериализации данных блока:

js
const block = {
  id: 'intro',
  revision: 'sha256:9f4c…',
  type: 'paragraph',
  data: { text: 'Здравствуйте' },
}

Меняйте revision при каждом изменении data, tunes или inline. Одинаковая редакция для разного содержимого намеренно сообщает рендереру, что блок не изменился. Редактор сохраняет входящую редакцию, пока блок не тронут, и удаляет её после первого локального изменения, потому что не может создать следующую редакцию от имени источника данных.

Регистрация рендереров

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

js
renderer.registerRenderer(createCalloutRenderer('article'))

renderer.hasRenderer('callout')
renderer.getRegisteredTypes()
renderer.unregisterRenderer('callout')

Повторная регистрация типа заменяет рендерер для будущего результата. До удаления рендерера с ресурсами в destroy(element) освободите уже добавленный им результат.

Загрузка только используемых типов

Для уменьшения исходного графа создавайте плагины и рендереры по документу или явному списку типов через асинхронные точки входа.

js
import { createBlockPluginsAsync } from '@shelamkoff/rector/plugins/async'
import { createDefaultRenderersAsync } from '@shelamkoff/rector/renderer/async'

const plugins = await createBlockPluginsAsync(documentData)
const renderers = await createDefaultRenderersAsync('oe', {}, documentData)

Асинхронные вспомогательные функции работают только с известными публичными типами. Неизвестный тип отклоняется, а не превращается в произвольный путь импорта.

Внутристрочное форматирование и виджеты

Каждый рендерер блока получает parseInline(text). Функция очищает поддерживаемую внутристрочную разметку и возвращает DocumentFragment. Добавляйте этот фрагмент в DOM вместо назначения innerHTML.

Постоянным виджетам также нужен облегчённый плагин стороны рендерера с type, createWidget(data, id) и getData(element). createWidget() обязан сохранить переданный идентификатор в data-id.

mapTextFields() рендерера блока должен перечислять ровно те же поля с HTML, что и редакторский плагин. Rector раскрывает заполнители {{id}} до вызова рендерера.

Стили

При стандартном injectStyles: true первый рендеринг автоматически получает уникальные URL базовых таблиц и стилей зарегистрированных рендереров. Уничтожение последнего результата или полный renderer.destroy() освобождает этого владельца. getStyleUrls() возвращает URL, а injectStyles() остаётся доступным для явной предварительной загрузки и возвращает независимый дескриптор освобождения.

js
import '@shelamkoff/rector/styles/renderer.css'
import '@shelamkoff/rector/renderer/renderers/gallery/styles.css'

const renderer = createEditorRenderer({
  blockTypes: ['gallery'],
  injectStyles: false,
})
renderer.renderTo(documentData, container)

Не импортируйте CSS интерфейса редактора ради итогового документа. В режиме сборщика используйте @shelamkoff/rector/styles/renderer.css вместе с subpath renderer/renderers/<type>/styles.css каждого выбранного рендерера. Если размер пакета не важен, доступна единая точка @shelamkoff/rector/styles.css.

Освобождение ресурсов

destroy(target) принимает контейнер, ранее переданный в renderTo(), оболочку из render() или элемент из renderBlock(). Метод вызывает необязательный destroy(element) владеющего рендерера блока, очищает принадлежащий рендереру результат и забывает его. destroy() без аргумента освобождает все результаты этого экземпляра.

Контейнер приложения не удаляется. Освобождаются наблюдатели, обработчики и сторонние экземпляры, после чего запись об установке забывается. Автоматически подключённые стили освобождаются, когда не остаётся принадлежащих рендереру результатов. Отдельный дескриптор, возвращённый явным injectStyles(), имеет собственный жизненный цикл и должен быть освобождён своим методом destroy().

Граница безопасности

Данные рендерера могут поступить из хранилища или внешней службы и остаются недоверенными. Встроенные рендереры используют общую очистку HTML, URL и стилей. Пользовательский рендерер обязан применять textContent, parseInline(), безопасные методы атрибутов и явные разрешающие списки. Нельзя считать содержимое безопасным только потому, что оно ранее прошло через редактор.

Точные данные блоков и специальные зависимости перечислены в каталоге рендереров.

Rector is released under the MIT License.