Блоки
Блок — HTML-комментарий особого вида, который при сборке страницы заменяется разметкой: меню, телом страницы, списком из данных, другим фреймом. В браузере комментарий невидим, переживает экспорт из конструкторов и не мешает фигурным скобкам шаблонизаторов.
Синтаксис
<!-- frame/site__header /--> вызов фрейма
<!-- frame/menu {"place":"footer_1"} /--> вызов с параметрами
<!-- frame/callout--info -->…<!-- /frame --> вызов со слотом
<!-- onpress/content /--> одиночный блок
<!-- onpress/repeat {"source":"faq"} --> парная: открытие
<div data-op-bind='{"text":"item.q"}'></div> шаблон внутри
<!-- /onpress/repeat --> закрытие
- После
<!--обязателен пробел, после имени — тоже (или сразу/-->). - Атрибуты — один JSON-объект с двойными кавычками. Битый JSON — атрибуты молча падают в значения по умолчанию, поэтому проверяйте JSON до загрузки.
- Неизвестное имя не выполняется: в разметке останется комментарий.
Короткий вызов фрейма. Фрейм вызывается по имени прямо в комментарии: <!-- frame/<имя> /-->. Параметры необязательны и пишутся JSON-объектом после имени. Парный вызов закрывается <!-- /frame --> без имени. Закрывающий комментарий закрывает последний открытый вызов, поэтому вызовы можно вкладывать друг в друга. Имя из адреса вызова главнее ключа name в параметрах. Выгрузка GET /pages/{ref}/markdown отдает вызовы фрейма в этой форме.
Имя блока. Блок пишется полным именем: <!-- onpress/имя {…} /-->, у парного — <!-- onpress/repeat … --> … <!-- /onpress/repeat -->.
Блоки работают во фреймах и в теле страницы (в markdown строка <!-- onpress/… --> проходит в тело как есть). Когда блоку нечего вывести, на его месте остаётся поясняющий HTML-комментарий с пометкой вида op/repeat: … — смотрите исходный код страницы, если что-то не появилось. Пометки и отчёт записи фрейма (inserts) называют блок коротким именем.
Список блоков
| Блок | Что выводит | Зависит от страницы |
|---|---|---|
| onpress/repeat | повтор шаблона по списку | по обстоятельствам |
| onpress/content | тело страницы | да |
| frame/… | другой фрейм | как у вложенного |
| onpress/slot | содержимое, переданное в вызов фрейма | нет |
| onpress/value | одно значение без тега | по токену |
| onpress/callout | врезку с содержимым | нет |
| onpress/featured-image | изображение записи | да |
| onpress/terms | термины таксономии страницы | да |
| onpress/faq | вопросы и ответы из markdown внутри блока | нет |
| onpress/faq-schema | JSON-LD FAQPage по списку | да |
| onpress/assistant | чат-ассистент Framesuite | да |
| onpress/assistant-welcome | первый кадр чата Framesuite | да |
| onpress/docs-nav | дерево документации | да |
| onpress/outline | оглавление по заголовкам | да |
| onpress/search | поле поиска по дереву | нет |
| onpress/share-ai | кнопку Copy и «Explore with AI» | да |
| onpress/pager | ссылки «назад» и «вперёд» | да |
«Зависит от страницы» — фрейм с таким блоком собирается на каждом запросе и не даёт статической копии (см. сборку).
Меню, переключатель языков, соцсети, колонки подвала и копирайт выводят системные фреймы menu, menu--dropdown, lang-switcher, lang-switcher--dropdown, socials, footer-columns и copyright. Их параметры и данные описаны в разделе Готовые фреймы.
Блоки, результат которых зависит от языка (фрейм с ними собирается отдельно на каждый язык): onpress/repeat, вызов frame/…, onpress/content, onpress/docs-nav, onpress/outline, onpress/share-ai, onpress/pager.
onpress/repeat
Повторяет вложенный шаблон по разу на элемент списка. Список — системный источник, набор сайта или массив прямо в атрибуте.
<ul class="team">
<!-- onpress/repeat {"source":"team","limit":6} -->
<li><strong data-op-bind='{"text":"item.name"}'></strong> <span data-op-bind='{"text":"item.role"}'></span></li>
<!-- /onpress/repeat -->
</ul>
| Атрибут | По умолчанию | Смысл |
|---|---|---|
source | — | имя источника или набора; item.<поле> — вложенный список элемента внешнего повторителя |
items | [] | список прямо в атрибуте, если source не задан или пуст |
lang | язык страницы | вариант набора этого языка |
match | {} | фильтр {"поле":"токен"}, значение — токен бинда, raw:значение или массив альтернатив |
limit, offset | — | окно: сколько вывести и сколько пропустить; целое или выражение с токенами ("(page.paged - 1) * 3") |
empty | — | frame — при пустом списке погасить весь фрейм, где стоит повторитель |
separator | — | строка между элементами; после последнего не ставится |
statuses | publish | статусы страниц для источников pages.* |
page | страница сборки | считать контекстные источники от другой страницы (id) |
Внутри шаблона поля элемента адресуются биндами item.<поле>, вложенные — через точку (item.frame.slug). Пустой список без empty — комментарий op/repeat: список … пуст. Полное описание — повторитель onpress/repeat.
onpress/content
Тело страницы, которую собирают: markdown, превращённый в блоки, с якорями у заголовков H2–H6.
<article class="article__body"><!-- onpress/content /--></article>
| Атрибут | По умолчанию | Смысл |
|---|---|---|
page | страница сборки | вывести тело другой страницы по id |
Выводится один раз: вложенный вызов (тело внутри тела) даёт комментарий. Без страницы в контексте — комментарий.
frame/…
Другой фрейм сайта в этом месте — единственная связь между фреймами.
<!-- frame/site__header /-->
<!-- frame/article__faq {"class":"article__faq-wrap"} /-->
<!-- frame/assistant__chat {"agent":"canva","agent_view":"profile"} /-->
| Атрибут | По умолчанию | Смысл |
|---|---|---|
name | — | слаг или id фрейма; в вызове стоит после frame/ |
class | — | обернуть вложенный фрейм в div с этим классом |
| любой другой | — | параметр вызова: читается внутри фрейма как frame.<ключ> |
Переименование. PATCH /frames/{ref} со slug переписывает вызовы фрейма на новый слаг.
Нет такого фрейма — пустое место и комментарий; фрейм уже собирается (кольцо ссылок) — комментарий с цепочкой. До 8 уровней вложения. Работает и в теле страницы; и во фрейме, и в теле после правки вложенного фрейма или данных, которые он читает, кэш страницы сбрасывается автоматически. Подробно — фреймы.
Параметры вызова
Все атрибуты вызова, кроме служебных (name, ref, frame, slug, id, type, class), уходят во фрейм параметрами. Внутри фрейма параметр читается как frame.<ключ> — в data-op-bind и в атрибутах блоков:
<!-- вызов -->
<!-- frame/assistant__chat {"agent":"canva","agent_view":"profile"} /-->
<!-- внутри фрейма assistant__chat -->
<div id="ai-assistant-mount" data-op-bind='{"data-agent":"frame.agent","data-agent-view":"frame.agent_view"}'></div>
<!-- onpress/assistant-welcome {"id":"x8OA7Azs4hBy","agent":"frame.agent","agent_view":"frame.agent_view"} /-->
- Токен в атрибуте блока. Значение атрибута, целиком равное токену
frame.<ключ>илиpage.<поле>, заменяется значением. Значения нет — атрибут снимается, и блок берёт свой дефолт. - Литерал. Строка, которая только похожа на токен, пишется с префиксом
raw::{"label":"raw:page.title"}выведет текстpage.title. - Свои токены. Атрибуты
match,limit,offsetу повторителя подстановкой не трогаются: токены в них разбирает сам повторитель. - Сборка. Фрейм с параметрами собирается на месте вызова из исходника — своей копии с чужими параметрами у него нет. Токен
page.*в атрибуте блока делает фрейм собираемым на запросе, какpage.*в бинде.
Источник списка параметром. Один фрейм-секция может выводить разные наборы: повторитель внутри берёт источник из вызова, "source":"frame.source", а каждый вызов называет свой набор.
<!-- внутри фрейма site__bento -->
<!-- onpress/repeat {"source":"frame.source"} -->
<h2 data-op-bind='{"text":"item.title"}'></h2>
<!-- /onpress/repeat -->
<!-- вызовы -->
<!-- frame/site__bento {"source":"home_bento"} /-->
<!-- frame/site__bento {"source":"pricing_bento"} /-->
Значения по умолчанию у параметра нет. Вызов без source оставляет повторитель без источника: на странице на его месте пусто (комментарий op/repeat: список … пуст), а в превью фрейма и на холсте дашборда повторитель собирается из демо-элементов, по три на список. Так фрейм-секцию можно смотреть отдельно, без данных, а на странице она выводит ровно тот набор, который назвал вызов. Так устроены системные site__bento, site__offer, site__marquee, site__text-media и site__steps — см. готовые фреймы.
Слот
Парная форма вызова передаёт во фрейм содержимое — обычный markdown страницы:
## FAQ
<!-- frame/faq -->
### Сколько это стоит?
Бесплатно на любом тарифе.
### Нужна ли карта?
Нет.
<!-- /frame -->
Внутри фрейма содержимое ставит onpress/slot, а повторитель получает из него пары вопрос-ответ источником slot.faq. Содержимое слота остаётся в теле страницы как есть: выгрузка GET /pages/{ref}/markdown и повторная заливка его не меняют. Перед вызовом выгрузка ставит пустую строку после заголовка. Готовый фрейм FAQ — в рецепте FAQ.
onpress/slot
Содержимое, переданное в парный вызов фрейма. Markdown превращается в HTML тем же конвертером, что публикует страницы: списки, ссылки, картинки, выделение работают.
<section class="note"><!-- onpress/slot /--></section>
Атрибутов нет. Фрейм вызван одиночной формой — слот пуст и ничего не выводит.
Источник slot.faq. Повторитель с "source":"slot.faq" разбирает слот на пары вопрос-ответ по правилам onpress/faq: уровень первого заголовка — вопрос, всё до следующего заголовка того же уровня — ответ.
| Поле элемента | Что в нём |
|---|---|
q, a | вопрос и ответ HTML |
q_text, a_text | тот же текст без разметки — для JSON-LD и атрибутов |
question, answer | исходный markdown |
onpress/value
Одно значение без тега-носителя. Токен тот же, что у бинда: page.*, page.data.*, site.*, item.*, frame.*, raw:….
<!-- onpress/value {"bind":"page.title"} /-->
<!-- onpress/value {"bind":"item.q_text","format":"json"} /-->
| Атрибут | По умолчанию | Смысл |
|---|---|---|
bind | — | токен |
format | text | text — HTML-экранирование; html — как есть; json — JSON-литерал |
Формат json безопасен внутри <script>: </ превращается в <\/, <!-- — в \u003C!--, символы U+2028 и U+2029 экранируются; значения нет — выводится null. В форматах text и html без значения не выводится ничего.
Блок работает везде, в том числе внутри <script>, где тегов для data-op-bind нет, — так собирается JSON-LD из данных страницы или повторителя. Ограничение: в теле страницы onpress/value ставится только отдельной строкой — внутри строки абзаца выгрузка в markdown его теряет. Во фреймах ограничений нет.
onpress/callout
Врезка: заголовок, иконка и произвольное содержимое.
<!-- onpress/callout {"variant":"warning","title":"Перевыпуск токена"} -->
<p>Старый токен перестаёт работать <strong>сразу</strong>.</p>
<!-- /onpress/callout -->
| Атрибут | По умолчанию | Смысл |
|---|---|---|
variant | default | default, info, success, warning, destructive; неизвестный — default |
title | — | заголовок |
icon | по варианту | имя значка Material Symbols (info, check_circle, warning, clear и любые другие) |
showIcon | true | показывать значок |
showTitle | true | показывать заголовок |
Содержимое между открытием и закрытием выводится как HTML: пишите <p>, <ul> и другую HTML-разметку. Markdown внутри врезки не конвертируется. Ни заголовка, ни содержимого — комментарий.
onpress/featured-image
Изображение записи (миниатюра) как <picture> с размерами и WebP — тем же кодом, каким выводятся картинки в тексте.
<!-- onpress/featured-image {"figure":true,"loading":"eager","fetchpriority":"high"} /-->
| Атрибут | По умолчанию | Смысл |
|---|---|---|
class | — | класс img |
picture_class | — | класс picture |
loading | lazy | атрибут loading |
fetchpriority | — | high для первого экрана |
link | false | обернуть в ссылку на страницу |
figure | false | обернуть в figure.op-featured-image с подписью вложения |
page | страница сборки | взять изображение другой записи |
alt — из вложения, пустой — заголовок страницы. Изображение записи — это превью страницы: свойство image при публикации (номер вложения или адрес картинки, см. свойства страницы) или PATCH /pages/{ref} с полем thumbnail. Нет изображения — комментарий.
onpress/terms
Термины таксономии текущей записи списком ссылок.
<!-- onpress/terms {"taxonomy":"topic","label":"Тема:"} /-->
| Атрибут | По умолчанию | Смысл |
|---|---|---|
taxonomy | category | таксономия; у страниц это topic (свойство topic) |
link | true | ссылки на архив термина |
limit | 0 | сколько показать, 0 — все |
label | — | подпись перед списком |
class | op-terms | корневой класс |
Разметка: div.<class>.<class>--<таксономия> > span.<class>__label и ul.<class>__list > li.<class>__item.<class>__item--<слаг> > a.<class>__link. Нет таксономии или терминов — комментарий.
onpress/faq
Вопросы и ответы прямо в тексте страницы: аккордеон и разметка FAQPage из обычного markdown внутри блока. Набор данных не нужен — вопросы лежат в теле и выгружаются вместе с ним.
## Частые вопросы
<!-- onpress/faq -->
### Сколько это стоит?
Бесплатно на любом тарифе. В него входит:
- безлимит страниц;
- все языки.
### Нужна ли карта?
Нет.
<!-- /onpress/faq -->
| Атрибут | По умолчанию | Смысл |
|---|---|---|
id | faq | id секции — якорь для ссылок; если на странице он уже занят (заголовком или другим блоком), получает хвост: faq-2, faq-3 |
schema | true | выводить JSON-LD FAQPage |
Правило вопросов. Уровень первого заголовка внутри блока — уровень вопроса. Каждый заголовок этого уровня — вопрос, всё до следующего такого же — ответ. Заголовок глубже остаётся частью ответа. Ответы конвертируются тем же конвертером, что и страница: списки, ссылки, картинки, выделение работают.
Заголовок раздела пишите над блоком обычным текстом, вплотную, без абзаца между ними — стили прижимают блок к заголовку правилом h2 + .opfaq. Раздел можно назвать просто «FAQ»: заголовок оставит себе якорь #faq, а секция блока получит faq-2 — одинаковых id на странице не будет. Нужен предсказуемый якорь у самой секции — задайте блоку свой id.
Разметка фиксированная, на неё написаны стили: section.opfaq#faq > div.opfaq__list > details.opfaq__item > summary.opfaq__q > span.opfaq__q-text и div.opfaq__a. Аккордеон работает без JavaScript. JSON-LD выводится после секции, один раз на страницу — если на странице есть и onpress/faq, и onpress/faq-schema, побеждает первый.
Блок не делает фрейм зависимым от страницы: в теле страницы он выполняется через onpress/content, во фрейме — попадает в статическую копию.
onpress/faq продолжает работать. Для новых страниц удобнее фрейм faq со слотом: вопросы так же лежат в markdown страницы, а разметка аккордеона и схемы живёт во фрейме, который можно скопировать и переделать под себя, — см. рецепт FAQ.
Один FAQPage на страницу. Если в теле несколько FAQ-фреймов, их схемы при сборке тела сливаются в первую, остальные снимаются; onpress/faq-schema в этом случае свой FAQPage не выводит.
onpress/faq-schema
JSON-LD FAQPage из списка — тот же источник, язык и фильтр, что у повторителя. Нужен, когда вопросы живут в наборе данных, а не в теле.
<!-- onpress/faq-schema {"source":"faq","match":{"page":"page.slug"}} /-->
| Атрибут | По умолчанию | Смысл |
|---|---|---|
source | — | набор или источник |
match | — | фильтр, как у onpress/repeat |
lang | язык страницы | вариант набора |
page | страница сборки | другая страница для match |
question | q | поле вопроса |
answer | a | поле ответа |
HTML из вопросов и ответов вырезается. Пустой список — ничего не выводится. Блок продолжает работать; если в теле страницы есть FAQ-фрейм со своей схемой, onpress/faq-schema свой FAQPage не выводит — на странице он один.
onpress/assistant
Встроенный чат-ассистент Framesuite. Один раз на страницу.
<!-- onpress/assistant {"id":"x8OA7Azs4hBy"} /-->
<!-- onpress/assistant {"id":"x8OA7Azs4hBy","lib":"agents","agent":"canva","agent_view":"profile"} /-->
| Атрибут | Смысл |
|---|---|
id | id ассистента Framesuite |
lib | страница приложения, с которой открывается ассистент (например agents) |
agent | слаг агента |
agent_view | profile или chat |
Фрейм обычно общий для многих страниц, а вход у каждой свой: блок с тем же id в теле страницы уточняет вход блока из фрейма и второго чата не рисует. Скрипты и стили ассистента подключаются в <head> автоматически.
onpress/assistant-welcome
Первый кадр чата Framesuite: готовая разметка приветственного экрана, которую сервер забирает из Framesuite и держит в кэше сутки. Человек и поисковик видят ассистента вместе со страницей, приложение чата потом подменяет кадр собой.
<!-- onpress/assistant-welcome {"id":"x8OA7Azs4hBy","agent":"frame.agent","agent_view":"frame.agent_view"} /-->
| Атрибут | Смысл |
|---|---|
id | id ассистента Framesuite |
lib | страница приложения, с которой открывается ассистент |
agent | слаг агента |
agent_view | profile или chat |
Блок выводит только кадр. Это вариант сборки чата обычной разметкой: фрейм assistant__chat собирает всё остальное сам — контейнер #ai-assistant-mount с data-slug, загрузчик, стили, синхронизацию тёмной темы, — а параметры agent, agent_view, lib приходят вызовом фрейма и читаются как frame.*:
<!-- frame/assistant__chat {"agent":"canva","agent_view":"profile"} /-->
В теле страницы, внутри колонки .prose, чат обрезается, поэтому его ставят во фрейме над телом. Агента тогда передаёт первая строка тела — блок onpress/assistant с тем же id: он уточняет вход блока из фрейма и второго чата не рисует (см. onpress/assistant).
Карта импортов для чата выводится в <head> автоматически, если на странице есть onpress/assistant-welcome. В <body> её не ставьте: в <head> уже есть модульный скрипт, и браузеры без поддержки нескольких карт импортов вторую отбросят. Старый блок onpress/assistant продолжает работать.
onpress/docs-nav
Левое дерево документации: страницы сайта, собираемые фреймом документации, с активным пунктом, раскрытым предком, черновиками для вошедших редакторов, поиском. Подписи пунктов — nav_label страницы или её заголовок. Как устроено дерево — сайдбар документации.
<!-- onpress/docs-nav {"shell":"drawer"} /-->
<!-- onpress/docs-nav {"shell":"toggle"} /-->
<!-- onpress/docs-nav {"shell":"sidebar"} /-->
| Атрибут | По умолчанию | Смысл |
|---|---|---|
shell | — | обвязка: пусто — только дерево; sidebar — боковая колонка nav.opdoc__sidebar; drawer — мобильная шторка #opdoc-drawer; toggle — кнопка открытия шторки с «родитель → страница» |
search | true | false — убрать встроенное поле поиска (если поиск стоит отдельным блоком) |
aria-подписи выводятся на языке страницы.
onpress/outline
Оглавление по заголовкам текущей страницы, с теми же якорями, что проставляет onpress/content.
<div class="opdoc__aside"><!-- onpress/outline {"levels":"2-3"} /--></div>
| Атрибут | По умолчанию | Смысл |
|---|---|---|
levels | все H2–H6 | 2 — только H2, 2-3 — H2 и H3, 2,4 — через запятую, диапазоны и уровни можно смешивать; неразборчивое значение — все уровни |
title | «На этой странице» на языке страницы | заголовок колонки |
bare | false | только список ul.aside-nav-list, без обвязки и заголовка |
shell | — | spoiler — свёрнутый details.opdoc__outline--spoiler |
Нет заголовков — комментарий.
onpress/search
Поле поиска по дереву документации (разметка из той же части темы, что у onpress/docs-nav). Фильтрует пункты только внутри контейнера дерева — вне его останется просто полем.
<!-- onpress/search {"placeholder":"Поиск по документации"} /-->
| Атрибут | По умолчанию | Смысл |
|---|---|---|
placeholder | на языке страницы | подсказка в поле |
onpress/share-ai
Кнопка Copy (копирует страницу как markdown) с выпадающим списком «Explore with AI» — ссылки на семь моделей с адресом текущей страницы. Подписи — на языке страницы.
<!-- onpress/share-ai {"view":"docs"} /-->
| Атрибут | По умолчанию | Смысл |
|---|---|---|
view | — | docs — вид для шапки документации на details |
copy | true | false — только список моделей, без кнопки Copy |
label | «Copy» на языке страницы | подпись кнопки |
onpress/pager
Ссылки на предыдущую и следующую страницы в том же порядке, в каком они стоят в дереве документации. Пустая сторона держит место.
<!-- onpress/pager /-->
<!-- onpress/pager {"prev_label":"Назад","next_label":"Дальше"} /-->
| Атрибут | По умолчанию | Смысл |
|---|---|---|
prev_label, next_label | «Назад» и «Далее» на языке страницы | подписи |
label | на языке страницы | aria-label навигации |
Проверка блоков
После загрузки фрейма смотрите в ответе inserts (что найдено), warnings и bake.response.unresolved/unknown_inserts (что не разобралось). Что получит повторитель, заранее показывает POST /data/resolve — см. наборы данных. На готовой странице ищите в исходном коде комментарии с пометкой op/…: … — каждый блок, которому нечего вывести, оставляет такой.