Документ страницы указывает, какое изображение, маршрут или коллекцию нужно использовать, но не хранит сам объект. Объект предоставляет окружение; оно же решает, можно ли встраивать сторонние страницы и отправлять формы. Этот урок описывает всё, что renderDocument поручает окружению.
Ссылки в документе
Свойство хранит ссылку в виде { "$ref": kind, "id": id }, где тип — asset, route, entity либо fragment. Дополнительные JSON-поля сохраняются. Ключ ссылки — <kind>:<id>; его возвращает referenceKey из @fiberfx/schema, например asset:hero или entity:posts.
Тип ссылки задан в схеме каждого блока каталога: image принимает asset, у form свойство action принимает route, а у блока на основе данных свойство collection — entity.
Что окружение передаёт в resolved
Функции renderDocument окружение передаёт в параметре resolved обычную запись, сопоставляющую ключам найденные данные:
| Поле | Значение |
|---|---|
src |
адрес загружаемого ресурса |
href |
адрес маршрута |
title |
заголовок |
items |
разрешённая коллекция: у каждого элемента есть title и необязательные href и summary |
error |
сообщение окружения о неудачной попытке получить данные |
Блок вызывает context.resolve(prop) и получает запись по ключу свойства либо undefined, если свойство не является ссылкой или окружение не передало этот ключ. Ничто в @fiberfx/content не запрашивает данные самостоятельно.
Приложение сайта Mercury сопоставляет идентификатору ресурса каждого изображения его адрес:
import { renderDocument, type ResolvedReference } from "@fiberfx/content";
import { collectReferences, referenceKey, type MercuryDocument } from "@fiberfx/schema";
function show(document: MercuryDocument, sources: { readonly [id: string]: string }) {
const resolved: { [key: string]: ResolvedReference } = {};
for (const { reference } of collectReferences(document)) {
const src = reference.$ref === "asset" ? sources[reference.id] : undefined;
if (src !== undefined) resolved[referenceKey(reference)] = { src };
}
return renderDocument(document, { mode: "publication", revision: 0, resolved });
}
Какие данные читают блоки
| Блоки | Что читают | Если данных нет |
|---|---|---|
image, figure, gallery |
src |
альтернативный текст с role="img"; при пустом alt не выводится ничего |
video |
src ресурса и его captions |
без src ресурса нет проигрывателя, без src субтитров нет дорожки; заголовок и расшифровка остаются |
audio |
src |
проигрывателя нет; заголовок и расшифровка остаются |
logo-list |
src каждого логотипа |
название логотипа |
form |
href свойства action, при разрешённой возможности form |
отключённая форма с сообщением, что её нельзя отправить |
collection-feed, article-list, related-content, related-articles |
items, error |
состояние unavailable |
Блок на основе данных различает четыре ответа. Для ссылки с ключом entity:example тест пакета отображает {} как unavailable, { items: [] } как empty, { error: "The feed is down." } как error с сообщением в alert, а список элементов — как ready.
В этой версии ни один блок каталога не читает title из разрешённой ссылки.
Разрешение ссылок средствами schema
У @fiberfx/schema есть отдельный этап разрешения ссылок, независимый от записи для рендерера. collectReferences(document) перечисляет все ссылки в свойствах узлов вместе с узлом и путём. resolveReferences(document, resolve) вызывает ваш обработчик один раз для каждого уникального ключа и возвращает Map, сопоставляющий ключу Resolution:
{ status: "resolved", reference, details? };{ status: "unknown", reference }: такого объекта нет;{ status: "unauthorized", reference }: читателю нельзя видеть объект. Сохраняется только идентичность ссылки, независимо от подробностей, имевшихся у обработчика.
Любой другой ответ обработчика приводит к исключению TypeError. Ни один из вызовов не изменяет документ.
Ни один из пакетов не преобразует Resolution в параметр renderDocument под именем resolved. Это задача окружения, как в примере выше.
Возможности окружения
Возможность — разрешение, выдаваемое политикой содержимого окружения: "embed" позволяет встраивать стороннее содержимое, "form" — отправлять формы по разрешённому маршруту. Окружение передаёт разрешения функции renderDocument в параметре capabilities; по умолчанию не разрешено ничего.
Три блока каталога объявляют требуемую возможность в поле capabilities записи: embed объявляет embed, а form и form-submit — form. renderDocument не проверяет это объявление. Каждый блок сам читает context.capabilities и выбирает поведение при отсутствии разрешения:
embed: при разрешённомembedи URL со схемойhttpsвыводит<iframe>в песочнице. Иначе — ссылку с заголовком.form: при разрешённомformи маршруте, разрешённом в безопасныйhref, выводит<form method="post">на этот адрес. Иначе показывает «This form can't be sent from here.», отключает все элементы управления через отключённый<fieldset>и предотвращает отправку, если событие всё же дошло.form-submit: безformкнопка отключена.
Тесты пакета проверяют поведение отключённой формы после гидратации. Каждый элемент управления соответствует :disabled, отправленное событие submit предотвращается, и ничто не вызывает fetch и не пишет в хранилище.
Авторские ссылки и URL встраивания
Каждый авторский href проходит проверку перед попаданием в разметку. Сохраняются:
- относительные пути:
/…,./…,../…; - фрагменты
#…и строки запроса?…; - ссылки
http:,https:,mailto:иtel:.
Отбрасываются data:, vbscript:, javascript: и любые другие схемы; адреса без схемы с началом //, /\ или \\; ссылки с управляющими символами. Браузер удаляет из URL табуляцию, перевод строки и возврат каретки, поэтому "/\t/host" превратился бы в "//host". У отброшенной ссылки сохраняется подпись и удаляется только href.
Для встраивания подходит лишь абсолютный URL https с именем хоста. Его <iframe> получает sandbox="allow-scripts" и никогда не получает allow-same-origin, поэтому встроенная страница не может получить origin окружения. Для пути /same-origin, URL http: или data: заголовок вместо этого выводится ссылкой.
Эти проверки внутренние: safeHref и httpsUrl не экспортируются из @fiberfx/content, поэтому собственный блок должен проверять ссылки самостоятельно.
Адаптивные значения
Свойство со схемой responsive: true может вместо простого значения хранить адаптивное, например { "$responsive": { "base": false, "overrides": { "md": true } } }.
base действует там, где нет переопределения. Каждое переопределение задаёт значение начиная со своей точки перелома, а { "$removed": true } удаляет унаследованное значение начиная с этой точки. В каталоге два адаптивных свойства: section.background и columns.reverse.
BREAKPOINTS из @fiberfx/content содержит ["sm", "md", "lg", "xl"] в порядке каскада; их объявляет contentSchema(). При разборе базовое значение и каждое переопределение проверяются по типу свойства. Неизвестное имя точки перелома (например, tablet) и адаптивная обёртка у неадаптивного свойства отклоняются с invalid-prop по соответствующему пути.
Чтение переопределений
Блок получает каждое адаптивное свойство уже сведённым к базовому значению, а переданный ему узел сохраняет переопределения. В каталоге:
sectionотображает только базовый фон.columnsзаписываетdata-reverse, если базовое значение —true, иdata-reverse-<breakpoint>для каждого переопределения со значениемtrue. Порядок DOM совпадает с порядком документа. Атрибуты предназначены для таблицы стилей, но в этой версии ни одна таблица стилей репозитория их не читает, поэтому само по себеreverseне меняет раскладку.
Чтобы прочитать свойство для одной ширины вне блока, используйте resolveResponsive(value, breakpoint, order) из @fiberfx/schema. Функция сообщает, откуда получено значение для этой ширины:
import { resolveResponsive } from "@fiberfx/schema";
import { BREAKPOINTS } from "@fiberfx/content";
const reverse = { $responsive: { base: false, overrides: { md: true } } };
for (const breakpoint of BREAKPOINTS) console.log(breakpoint, resolveResponsive(reverse, breakpoint, BREAKPOINTS));
// sm { state: "inherited", from: "base", value: false }
// md { state: "explicit", from: "md", value: true }
// lg { state: "inherited", from: "md", value: true }
// xl { state: "inherited", from: "md", value: true }
Четыре состояния: explicit (задано в этой точке), inherited (унаследовано от меньшей точки или базы), removed (удалено в этой точке) и absent (ничего не применимо либо значение удалено на меньшей ширине). Простое значение имеет состояние inherited от base на любой ширине. Чтение не записывает результат обратно в документ.