Фреймы
Фрейм — готовая разметка, которую заливают один раз и ставят куда угодно: оболочка, шапка, блок FAQ, целый лендинг. Это шаблон без своего адреса — адрес принадлежит странице.
Что такое фрейм
Фрейм — это готовая разметка, которую заливают, а потом ставят куда нужно. Подразумеваемого размера у него нет: целая страница, выгруженная из конструктора, — фрейм, и оболочка статьи, шапка, подвал, блок FAQ — тоже фреймы. Типа у фрейма нет. Для чего он, говорит имя, а куда он встаёт — явная ссылка: страница называет один свой фрейм, фрейм подключает другие по имени.
Рядом с публикацией markdown это второй способ задать содержимое. Markdown — текст, который преобразуют; фрейм — готовая разметка, которую отдают как есть. Внутри фрейма ничего не переписывается, не переформатируется и не санируется: инлайновый <script> из конструктора переживает заливку байт в байт, в этом весь смысл.
Не путайте фрейм с iframe. В iframe монтируется апп в отдельном документе, а фрейм печатается в саму страницу. Живые части внутри фрейма — вставки (блочный комментарий, который вносит кусок разметки в поток) и бинды (атрибут data-op-bind, который подставляет значение внутрь тега).
Адреса у фрейма нет
Фрейм — шаблон, сам по себе он не открывается. Всё, что сайт отдаёт по адресу, — страница, и она же называет фрейм, которым печатается. Поэтому у фрейма нет поля url, а в ответах нет link; url в теле POST, PUT или PATCH /frames — 400 frame_url_not_supported.
Лендинг из конструктора не исключение. Разметка заливается фреймом, а потом по нужному адресу публикуется страница, которая этот фрейм называет: адрес держит страница, документ печатает фрейм. По шагам — Лендинг: страница и фрейм-оболочка.
Публикация статикой подчиняется тому же правилу — это свойство страницы, а не фрейма. Страница, чей фрейм печатается из испечённой копии, кладётся в дерево аппов и отдаётся nginx файлом; страница, чей фрейм печатается на запросе, рисуется темой. Переключателя нет, см. Печать.
Имя по БЭМ
Слаг — имя фрейма для людей и для ссылок, строится по БЭМ: block__element--modifier. Дефис заменяет пробел внутри части.
| Слаг | block | element | modifier | Рекомендуемое имя |
|---|---|---|---|---|
article | article | — | — | Статья |
article__faq | article | faq | — | Статья: FAQ |
article__faq--simple | article | faq | simple | Статья: FAQ — простой |
site__header | site | header | — | Сайт: шапка |
site-simple__header | site-simple | header | — | Сайт простой: шапка |
docs__code-cards | docs | code-cards | — | — |
Блок site — общее для всего сайта: шапка, подвал. Остальные блоки называются по сущности — article, docs, landing. Элемент — часть своего блока (article__faq), модификатор — вариант этой части (article__faq--simple).
Слаг приводится к нижнему регистру и проверяется регэкспом:
/^[a-z0-9]+(?:-[a-z0-9]+)*(?:__[a-z0-9]+(?:-[a-z0-9]+)*)?(?:--[a-z0-9]+(?:-[a-z0-9]+)*)?$/
Одиночное подчёркивание, второй __ или --, дефис в начале или двойной, любые символы кроме латиницы и цифр — 400 invalid_slug. Заглавные буквы ошибкой не считаются: Site__Header сохранится как site__header. В ответе — сам слаг, pattern и hint:
{
"success": false,
"error": "invalid_slug",
"message": "slug \"site_header\" does not match the naming scheme block__element--modifier: lowercase latin letters and digits, a single hyphen joins words inside a part, \"__\" separates the element, \"--\" the modifier; e.g. article, article__faq, article__faq--simple, site__header, docs__code-cards",
"slug": "site_header",
"pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*(?:__[a-z0-9]+(?:-[a-z0-9]+)*)?(?:--[a-z0-9]+(?:-[a-z0-9]+)*)?$",
"hint": "block__element--modifier: lowercase latin letters and digits, a single hyphen joins words inside a part, \"__\" separates the element, \"--\" the modifier; e.g. article, article__faq, article__faq--simple, site__header, docs__code-cards"
}
Старые транслитерированные слаги вроде shapka-sajta или obolochka-dokumentaciya регэксп проходят, но ничего не говорят о месте фрейма — их переименовывают через PATCH. name — свободное имя для людей; зеркалить в нём слаг, как в таблице, — рекомендация, чтобы список читался. Без name фрейм называется своим слагом. Поля block, element и modifier выводятся из слага и приходят в каждом описании фрейма (null, где части нет); отдельно их не пишут.
Где живёт фрейм
В опциях своего сайта сети. Файлов нет.
| Опция | Что лежит |
|---|---|
onpress_frames | реестр, JSON по id фрейма: slug, name, bytes, sha256, inserts, sources, frame_refs, binds, renders_on_request, даты и автор последней записи |
onpress_frame_<id> | разметка ровно в том виде, в каком её залили; автозагрузка, если меньше 64 КБ |
onpress_frame_<id>_baked, onpress_frame_<id>_baked_<lang> | испечённые копии, см. раздел «Печать» |
onpress_apps | реестр аппов, он же реестр страниц, опубликованных статикой: запись page-<id>, путь страницы в custom_url |
Хранится всегда исходник — со всеми вставками <!-- wp:op/… /--> и всеми атрибутами data-op-bind на месте. Подстановка происходит при печати, а не на входе, поэтому фрейм всегда можно пересобрать после смены меню или набора данных, а двойная печать даёт тот же результат, что и одинарная.
Связи
Любая связь — явная ссылка по имени. Ничего не подразумевается ни типом, ни дефолтом сайта.
У страницы ровно один фрейм. Это мета страницы _onpress_frame с id фрейма — id, а не слаг, чтобы переименование фрейма не трогало страницы. Пишет её POST /frames/{ref}/assign или поле frame: во frontmatter POST /pages/markdown — это одна и та же мета. Фрейм — весь вид страницы: оболочка статьи, а шапка и подвал внутри неё. Дефолтов сайта нет: страница без _onpress_frame печатается собственным шаблоном темы.
Фрейм подключает другие фреймы вставкой op/frame. Ссылка — слаг (читаемо) или id. Это настоящий фрейм article сайта 235, в сокращении:
<div class="opdoc__layout opdoc__layout--article">
<header class="opdoc__navbar"><!-- wp:op/frame {"ref":"site__header"} /--></header>
<div class="opdoc__content">
<h1 class="opdoc__title" data-op-bind='{"text":"page.title"}'></h1>
<div class="opdoc__body"><!-- wp:op/content /--></div>
<!-- wp:op/frame {"ref":"article__faq"} /-->
</div>
<div class="opdoc__aside"><!-- wp:op/outline /--></div>
<!-- wp:op/frame {"ref":"site__footer"} /-->
</div>
Слаги вложенных фреймов реестр держит в frame_refs; ссылка, записанная id, попадает туда слагом. По этому списку правка site__header находит всех, кто его вставляет, и по нему же переименование находит, кого переписать. Другая шапка на другом виде страниц — это другая оболочка со ссылкой на другой фрейм шапки (landing ссылается на site-simple__header), а не подмена на отдельной странице.
Фрейм может исчезнуть, если его список пуст. Атрибут "empty":"frame" у op/repeat значит: при пустом списке ближайший фрейм, в котором стоит повторитель, не печатается вовсе. Поэтому article всегда ссылается на article__faq, а на странице без вопросов блока FAQ нет — ни пустого заголовка, ни пустой секции. Настоящий article__faq сайта 235:
<section class="opfaq" id="faq">
<h2 class="opfaq__title">FAQ</h2>
<div class="opfaq__list">
<!-- wp:op/repeat {"source":"faq","match":{"page":"page.slug"},"empty":"frame"} -->
<details class="opfaq__item">
<summary class="opfaq__q"><span class="opfaq__q-text" data-op-bind='{"text":"item.q"}'></span></summary>
<div class="opfaq__a" data-op-bind='{"html":"item.a"}'></div>
</details>
<!-- /wp:op/repeat -->
</div>
<!-- wp:op/faq-schema {"source":"faq","match":{"page":"page.slug"}} /-->
</section>
Область — ровно один фрейм: пустой article__faq оставляет пустое место там, где стоял его op/frame, а article печатается. Если пустым оказался сам фрейм страницы, тема печатает свой шаблон. Вне фрейма, прямо в контенте страницы, повторитель прячет только себя. Без атрибута пустой список печатает комментарий, см. Вставки.
Адресация
{ref} в пути и любая ссылка на фрейм в теле или в op/frame — это либо id фрейма (16 hex-символов, выдаётся при создании), либо его slug. Страница адресуется как везде в v2: номером, слагом или путём.
Печать
Разворачивать вставки и бинды — работа PHP, а не сервиса: вставки — это parse_blocks() и render_block() по зарегистрированным типам блоков, data-op-bind переписывает WP_HTML_Tag_Processor, счётчики аналитики ставит тот же код, которым публикуются аппы.
Фрейм страницы печатается на каждый запрос. Тема onpress-v2 зовёт onpress_page_frame_html(), а та печатает фрейм из исходника для запрошенной страницы: вставки, вложенные фреймы, повторители и бинды, на языке страницы. Вложенные фреймы тоже печатаются из своих исходников, а не из чужой копии, поэтому контекстные списки всегда относятся к показываемой странице.
Выпечка — та же печать заранее, вне запроса страницы. Каждая ручка, меняющая фрейм, просит PHP испечь его и докладывает результат полем bake. Вызов синхронный: вы узнаёте, случилась ли печать.
bake.status | Что значит |
|---|---|
ok | PHP испёк фрейм и переопубликовал статические копии его страниц; отчёт — в bake.response |
unavailable | ручка печати на этом сайте не развёрнута; исходник сохранён, испечь можно позже |
failed | PHP ответил ошибкой или не ответил вовремя; причина в bake.reason |
skipped | печать выключена в этом вызове ("bake": false) или в конфиге сервиса |
Проваленная печать никогда не отменяет запись: исходник уже в базе. В отчёте печати видны токены бинда из unresolved, повторители с пустым списком и вложенные фреймы, которые не напечатались.
Статическая страница делается из испечённой копии. Страница публикуется статикой, когда у неё есть фрейм и этот фрейм не печатается на запросе: PHP кладёт выпечку в реестр аппов записью page-<id>, путь страницы — в custom_url, а файл отдаёт nginx. Ключ — id страницы, а не её путь, поэтому переезд страницы (новый слаг, другой родитель, другой язык) правит custom_url той же записи, а не заводит вторую копию; снятие с публикации, корзина, снятый фрейм или фрейм, ставший страницезависимым, запись убирают. Копия пишется, только когда сошлось всё; какое условие не сошлось, говорит reason: страница не publish или под паролем (not_public), у неё нет фрейма (no_frame), её фрейм печатается на запросе (renders_on_request), у фрейма ещё нет испечённой копии (no_baked_copy), адрес не годится под статическое дерево (bad_path) — кириллический или процентно-кодированный сегмент, первый сегмент, зарезервированный WordPress, адрес, уже занятый другой записью реестра. Любой из этих случаев копию снимает, а не оставляет протухшую.
Страницы, которых коснулась печать, приходят в bake.response:
"bake": {
"status": "ok",
"response": {
"baked": true,
"bytes": 4120,
"published": {
"published": true,
"pages": [
{ "id": 6412, "url": "/home/", "published": true, "path": "page-6412/index.html", "bytes": 4120, "reason": "" },
{ "id": 6501, "url": "/pomelli-workflow/", "published": false, "path": null, "bytes": 0, "reason": "renders_on_request" }
]
}
}
}
Фрейм, чей вывод зависит от страницы, получает в реестре renders_on_request: true и испечённой копии не имеет вовсе: одна копия показала бы текст одной страницы на другой — а значит, его страницы статикой не публикуются никогда. Причины перечислены в live_reasons ответа на заливку:
- страницезависимая вставка:
op/content,op/pager,op/docs-nav,op/outline,op/share-ai,op/featured-image,op/terms,op/assistant,op/faq-schema,op/partial; data-op-bindс токеномpage.*;op/repeatпо контекстному источнику (pages.children,pages.siblings,pages.adjacent,breadcrumbs) или с токеномpage.*вmatch;op/frame, вставляющий такой фрейм.
Остальные фреймы пекутся по разу на каждый язык сайта. Правки меню, наборов данных и вложенных фреймов через API сами перепекают зависимые фреймы; как это устроено и ручка POST /frames/rebake — в разделе Как это устроено.
GET /frames
Список фреймов сайта без разметки, по алфавиту слагов.
| Параметр | Тип | По умолчанию | Что делает |
|---|---|---|---|
block | string | — | только фреймы одного блока: block=article отдаёт article, article__faq, article__faq--simple |
q | string | — | подстрока слага или имени без учёта регистра |
include_html | bool | false | вместе с разметкой каждого фрейма; список становится тяжёлым |
blog_id / domain | int / string | — | сайт, либо заголовок X-OnPress-Blog-Id |
type игнорируется, в ответе "warnings": ["type_ignored"]. В записи есть pages — номера страниц, которые печатаются этим фреймом, — если такие есть: адреса у шаблона нет, но видно, где он показывается.
curl -s -H "Authorization: Bearer $OP_TOKEN" -H "X-OnPress-Blog-Id: 235" \
"$BASE/frames?block=article"
{
"success": true,
"blog_id": 235,
"home_url": "https://example.onpress.pro",
"total": 2,
"frames": [
{
"id": "c33c7d69d5a35456",
"name": "Статья",
"slug": "article",
"bytes": 1356,
"sha256": "f46576af386c8f79a7f4dd2d7532aa8fcc233a50d4491e6e66fb1fb8a3f826bf",
"inserts": { "op/frame": 3, "op/content": 1, "op/outline": 1 },
"sources": [],
"frame_refs": ["site__header", "article__faq", "site__footer"],
"binds": 2,
"renders_on_request": true,
"created_at_gmt": "2026-09-21 19:26:31",
"updated_at_gmt": "2026-09-21 20:40:56",
"updated_by": 1,
"block": "article",
"element": null,
"modifier": null,
"option": "onpress_frame_c33c7d69d5a35456"
},
{
"id": "4ce5ec819bf84f15",
"name": "Статья: FAQ",
"slug": "article__faq",
"inserts": { "op/repeat": 1, "op/faq-schema": 1 },
"sources": ["faq"],
"frame_refs": [],
"binds": 2,
"renders_on_request": true,
"block": "article",
"element": "faq",
"modifier": null,
"…": "…"
}
],
"registry_option": "onpress_frames",
"page_meta": "_onpress_frame",
"slug_pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*(?:__[a-z0-9]+(?:-[a-z0-9]+)*)?(?:--[a-z0-9]+(?:-[a-z0-9]+)*)?$"
}
inserts считает вставки в разметке по именам, binds — атрибуты data-op-bind, sources — наборы, которые читают повторители, frame_refs — вложенные фреймы. Вместе они заранее говорят, что подставит печать и что перепечётся при правке в другом месте. renders_on_request отсутствует, если он ложный. Полей url и link нет — у шаблона нет адреса, — как нет type, types, defaults и is_default. Старая запись реестра теряет url при первой же перезаписи.
POST /frames и PUT /frames/{ref}
POST /frames заливает новый фрейм. PUT /frames/{ref} принимает то же тело и заменяет разметку существующего; тем же вызовом можно сменить slug и name, и смена слага работает ровно как в PATCH.
| Поле | Тип | По умолчанию | Что делает |
|---|---|---|---|
html | string | — | разметка, обязательно; принимаются псевдонимы markup и content |
slug | string | из name | block__element--modifier; без него слаг выводится транслитом из name и проверяется так же; занятый другим фреймом — 409 slug_taken |
name | string | слаг | человеческое имя, свободное |
dry_run | bool | false | проверить и откатить: полный отчёт приходит, в базе ничего не меняется |
bake | bool | true | просить PHP-сторону испечь и опубликовать |
blog_id / domain | int / string | — | сайт |
Разметка хранится как есть. Ограничение одно — размер, 512 КБ; больше — 413 frame_too_large: целый сайт с картинками и скриптами — это апп, а фрейм — разметка. type в теле игнорируется с предупреждением type_ignored, default и is_default — с default_ignored.
Вместо списка «что вырезали» ответ несёт отчёт осмотра, потому что не вырезается ничего. Проверочная заливка на сайт 235:
curl -s -X POST -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/frames" -d '{
"blog_id": 235,
"dry_run": true,
"slug": "docs__example",
"name": "Docs: example",
"html": "<nav class=\"docs__example\"><a data-op-bind='"'"'{\"href\":\"site.home_url\"}'"'"'>Home</a><!-- wp:op/menu {\"place\":\"header\"} /--></nav><!-- wp:op/frame {\"ref\":\"site__footer\"} /-->"
}'
{
"success": true,
"dry_run": true,
"applied": false,
"blog_id": 235,
"frame": {
"id": "5cede5d5aca83706",
"name": "Docs: example",
"slug": "docs__example",
"bytes": 188,
"block": "docs",
"element": "example",
"modifier": null,
"option": "onpress_frame_5cede5d5aca83706",
"…": "…"
},
"inserts": { "op/menu": 1, "op/frame": 1 },
"inserts_total": 2,
"sources": [],
"frame_refs": ["site__footer"],
"binds": 1,
"renders_on_request": false,
"has_doctype": false,
"has_body": false,
"warnings": []
}
Настоящая запись дополнительно возвращает updated (false у нового фрейма), bake и rebaked — фреймы, которые вставляют этот через op/frame и перепеклись из-за него. У проверочной заливки нет ни того, ни другого. Если фрейм зависит от страницы, в отчёте появляется live_reasons, например ["data-op-bind uses page.* tokens"].
Предупреждения приходят обычными фразами, и их стоит читать. Сервис предупреждает о трёх вещах:
"warnings": [
"the frame contains a whole document (<!doctype>/<body>): that is right for a frame that wraps a page, but inserted into another frame with <!-- wp:op/frame /--> it produces a second <body>",
"data-op-bind is written with double quotes: JSON inside an attribute must be kept in single quotes, otherwise the attribute breaks at the first double quote of the value",
"op/frame {\"type\":…} is legacy (footer): link frames by name, <!-- wp:op/frame {\"ref\":\"site__header\"} /-->"
]
Первое — целый документ уместен во фрейме, который оборачивает страницу, но вставленный в другой фрейм даст второй <body>. Второе — data-op-bind в двойных кавычках рвётся на первой же кавычке значения. Третье — устаревшая ссылка по типу. Предупреждение заливку не отменяет: фрейм сохранится, а сломанная часть просто не сработает. Ссылка op/frame на несуществующий слаг здесь не докладывается — на печати на её месте будет комментарий.
GET /frames/{ref}
Один фрейм с разметкой в html, полем referenced_by — слагами фреймов, которые его вставляют, — и pages с номерами страниц, которые в нём печатаются.
curl -s -H "Authorization: Bearer $OP_TOKEN" -H "X-OnPress-Blog-Id: 235" \
"$BASE/frames/site__header"
{
"success": true,
"blog_id": 235,
"frame": {
"id": "164ec859ae2d9cfa",
"name": "Сайт: шапка",
"slug": "site__header",
"bytes": 601,
"inserts": { "op/lang-switcher": 1 },
"sources": [],
"frame_refs": [],
"binds": 3,
"block": "site",
"element": "header",
"modifier": null,
"option": "onpress_frame_164ec859ae2d9cfa",
"referenced_by": ["article"],
"html": "<div class=\"site-header site-header--pomelli\">…</div>\n",
"…": "…"
}
}
PATCH /frames/{ref}
Меняет поля фрейма, не трогая разметку; разметка заменяется только через PUT.
| Поле | Тип | Что делает |
|---|---|---|
slug | string | новый слаг по той же схеме; занятый другим фреймом — 409 slug_taken |
name | string | человеческое имя |
url здесь — тоже 400 frame_url_not_supported. Опубликованная страница переезжает вместе со своим адресом: публикуете её заново с другим url, и статическая копия следует за ней — запись реестра привязана к id страницы, меняется только её custom_url.
Смена слага переписывает ссылки. Во всех фреймах сайта, у которых старый слаг есть в frame_refs, <!-- wp:op/frame {"ref":"<старый>"} /--> получает новый слаг (переписываются и ключи frame, slug, id, и легаси op/partial), их записи в реестре обновляются, копии перепекаются — тем же вызовом. Меняется только строка внутри блочного комментария, остальная разметка остаётся байт в байт. Страницы не трогаются, они хранят id; ссылки, записанные id, переписывать не нужно. По шагам: Переименовать фрейм.
curl -s -X PATCH -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/frames/faq" -d '{"blog_id": 235, "slug": "article__faq", "name": "Статья: FAQ"}'
{
"success": true,
"blog_id": 235,
"frame": { "id": "4b0e7c21d9a3f610", "slug": "article__faq", "name": "Статья: FAQ", "block": "article", "element": "faq", "modifier": null },
"renamed": {
"from": "faq", "to": "article__faq",
"frames_updated": ["article", "landing"],
"details": [{ "id": "91399e3637b7aa2b", "slug": "article", "replaced": 1 }, { "id": "…", "slug": "landing", "replaced": 1 }]
},
"bake": { "status": "ok" },
"rebaked": { "status": "ok", "frames": ["…"] }
}
type в теле игнорируется с type_ignored. Тело, в котором только is_default или default, — 410 gone; тело без изменяемых полей — 400 nothing_to_update.
DELETE /frames/{ref}
Удаляет фрейм, его разметку и испечённые копии и снимает _onpress_frame (и легаси _onpress_frame_<type>) со всех страниц, которые на него указывали: страница, привязанная к исчезнувшему фрейму, печатала бы пустоту. Заодно эти страницы теряют статическую копию — публиковать нечего, и их снова печатает тема. Фреймы, которые его вставляют, не правятся — они перечислены в referenced_by с предупреждением, и пока фрейм с тем же слагом не появится снова, на месте вставки печатается комментарий.
curl -s -X DELETE -H "Authorization: Bearer $OP_TOKEN" -H "X-OnPress-Blog-Id: 235" \
"$BASE/frames/site__footer"
{
"success": true,
"blog_id": 235,
"deleted": { "id": "9a6e28632df819dc", "slug": "site__footer", "name": "Сайт: подвал" },
"unbound_pages": [102],
"referenced_by": ["article", "landing"],
"warnings": ["frames article, landing still insert \"site__footer\"; the insert prints nothing until a frame with this slug appears again"],
"bake": { "status": "ok" }
}
POST /frames/{ref}/assign
Ставит фрейм на одну страницу — пишет _onpress_frame с id фрейма. У страницы фрейм один, поэтому новая привязка заменяет прежнюю.
| Поле | Тип | Что делает |
|---|---|---|
page | int / string | страница: номер, слаг или путь; обязательно, иначе 400 page_required |
clear | bool | снять фрейм со страницы вместо привязки (заодно снимается легаси _onpress_frame_page) |
curl -s -X POST -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/frames/article/assign" -d '{"blog_id": 235, "page": "/pomelli-workflow/"}'
{
"success": true,
"blog_id": 235,
"frame": { "id": "91399e3637b7aa2b", "slug": "article" },
"page": { "id": 6703, "title": "Pomelli workflow", "url": "/pomelli-workflow/" },
"meta_key": "_onpress_frame",
"cleared": false,
"bake": { "status": "ok" }
}
То же самое делается при публикации полем frame: article во frontmatter, см. Страницы и markdown. frame там принимает один фрейм; список или старая форма type=ref — 400 frame_single.
Привязка решает и вопрос статики: страница, получившая фрейм, который печатается из копии, кладётся в дерево аппов, а {"clear": true} убирает её оттуда.
Лендинг: страница и фрейм-оболочка
Целая страница из конструктора раньше была «фреймом с url». Теперь это два обычных объекта.
- Разметка заливается фреймом, без всякого адреса:
curl -s -X POST -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/frames" -d "$(jq -n --rawfile html landing.html \
'{blog_id: 235, slug: "landing", name: "Лендинг", html: $html}')"
- Публикуется страница, которая держит адрес. Тело у неё может быть пустым — документ целиком печатает фрейм:
curl -s -X POST -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/pages/markdown" -d "$(jq -n '{blog_id: 235, markdown: "---\nname: Home\nslug: home\nurl: \"/home/\"\nlang: en\nstatus: publish\nframe: landing\n---\n"}')"
Сайт отдаёт /home/. Файлом или печатью темы — зависит только от фрейма: landing без страницезависимых вставок пекут и кладут в дерево аппов; добавьте в него op/content, op/assistant или бинд page.* — и та же страница начнёт печататься на запросе. Вызов при этом не меняется.
Сделать эту страницу главной — PUT /site/front-page, как для любой другой.
Фрейм — целый документ должен оставаться статикой. <!doctype> и <body> для лендинга уместны, и заливка предупреждает лишь об одном: такой фрейм нельзя вкладывать в другой через op/frame. У правила есть вторая половина: страница, которая печатается на запросе, рисуется ВНУТРИ документа темы, поэтому фрейм-документ на этом пути даёт два <html> и два <title>. Держите в таком фрейме страницезависимые вставки и бинды page.* подальше — или печатайте страницу обычной оболочкой.
Что убрано: дефолты и типы
GET /frames/defaults, PUT /frames/defaults и POST /frames/{ref}/default отвечают 410 gone:
{"success":false,"error":"gone","message":"default frames by type are gone (frames v3): a page is bound to its frame with POST /frames/{ref}/assign or frontmatter \"frame\", and frames link to each other with <!-- wp:op/frame {\"ref\":\"site__header\"} /-->"}
Связь между фреймами — op/frame {"ref":…}, фрейм страницы — assign или frame:. type игнорируется везде: в запросе GET /frames, в телах POST, PUT и PATCH. В старых записях реестра поле type может остаться; при чтении оно игнорируется.
Как тема читает фрейм
Весь контракт лежит в опциях сайта и мете страницы, поэтому тема с сервисом не разговаривает. Плагин onpress-frames оборачивает его в функции:
$html = onpress_page_frame_html(get_queried_object_id()); // '' — фрейма нет или он ничего не напечатал
if ($html === '') {
// печатаем собственный шаблон темы
}
echo $html;
| Функция | Что возвращает |
|---|---|
onpress_page_frame_id(int $page_id): string | id фрейма страницы: _onpress_frame (id или слаг), затем легаси _onpress_frame_page и старый дефолт сайта; '', если фрейма нет |
onpress_page_frame_html(int $page_id, string $lang = ''): string | фрейм страницы, напечатанный для неё |
onpress_frame_html(string $ref, int $page_id = 0, string $lang = ''): string | любой фрейм по слагу или id, напечатанный для страницы: вставки, бинды, контекст и язык одним вызовом |
onpress_frame_find(string $ref): ?array | запись реестра с id, slug, name, block, element, modifier |
onpress_frame_renders_on_request(string $ref): bool | зависит ли фрейм от страницы и, значит, живёт без испечённой копии |
Пустая строка — штатный ответ «фрейма нет», а не ошибка. onpress_frame_for_type() оставлена для старых тем; в новом коде её не используют.
Совместимость до миграции 233
У этого сайта документации (233) база общая с продом, где старый PHP ещё читает типы фреймов, поэтому его фреймы пока не переименованы. Оболочка здесь — фрейм obolochka-dokumentaciya, привязанный к старым страницам легаси-метой _onpress_frame_page (страницы, опубликованные позже, получают _onpress_frame), и шапку с подвалом он подключает по-старому:
<div class="opdoc__navbar-content"><!-- wp:op/frame {"type":"header"} /--></div>
…
<!-- wp:op/frame {"type":"footer"} /-->
До миграции новый код держит слой совместимости. _onpress_frame_page читается, когда _onpress_frame пуст, затем старый дефолт сайта onpress_frame_defaults["page"]. op/frame {"type":"header"} (и footer, partial) разрешается по мете страницы _onpress_frame_<type>, затем по onpress_frame_defaults. op/partial — алиас op/frame {"type":"partial"}. Записи реестра сохраняют type, пока у сайта есть onpress_frame_defaults. Миграция переименует фреймы и удалит эту опцию, после неё слой снимается. Ничего нового на нём не стройте.
От прежнего устройства «у фрейма есть адрес» остались свои следы: записи frame-<id> в onpress_apps и поле url в записях реестра. PHP их ещё читает, поэтому занятый адрес остаётся занятым, но новых таких записей не появляется; миграция переносит адрес на страницу и запись снимает. На этом сайте адрес был только у тестовых фреймов tilda-demo и frames-test.
Ошибки
| Код | HTTP | Когда |
|---|---|---|
frame_html_required | 400 | в теле нет html или он не строка |
invalid_slug | 400 | слаг не по схеме block__element--modifier, либо нет ни slug, ни name; в ответе pattern и hint |
frame_single | 400 | frame в публикации называет больше одного фрейма или использует старую форму type=ref |
frame_url_not_supported | 400 | url в теле POST, PUT или PATCH /frames: адрес принадлежит странице, фрейм — шаблон |
page_required | 400 | assign без page |
nothing_to_update | 400 | PATCH без изменяемых полей |
frame_not_found | 404 | фрейма с таким id или слагом на сайте нет |
page_not_found / ambiguous_page | 404 / 409 | страница в assign не разрешилась ровно в одну |
slug_taken | 409 | слаг занят другим фреймом сайта; заменить его разметку — PUT |
gone | 410 | /frames/defaults, /frames/{ref}/default, PATCH только с is_default, frame: default:<type> в публикации |
frame_too_large | 413 | разметка больше 512 КБ; в ответе bytes и max_bytes |
Общая форма ошибки — на странице Ошибки.