Перейти к содержанию
Mercury
Русский
Дизайн книга
Меню
Русский

Ссылки, возможности окружения и адаптивные значения

Документ называет изображения, маршруты и коллекции, но не хранит их; блоки не запрашивают эти данные. Окружение разрешает ссылки, допускает или запрещает встраивания и формы. Блоки отображают полученные данные, а при отсутствии ответа показывают предусмотренное состояние.

Документ страницы указывает, какое изображение, маршрут или коллекцию нужно использовать, но не хранит сам объект. Объект предоставляет окружение; оно же решает, можно ли встраивать сторонние страницы и отправлять формы. Этот урок описывает всё, что 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 и не пишет в хранилище.

Каждый авторский 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 на любой ширине. Чтение не записывает результат обратно в документ.