Повторитель onpress/repeat
onpress/repeat — парная вставка: разметка между открытием и закрытием повторяется по разу на элемент списка, а бинды item.* внутри подставляют поля элемента.
<section class="article__faq">
<h2>Частые вопросы</h2>
<!-- onpress/repeat {"source":"faq","match":{"page":"page.slug"},"empty":"frame"} -->
<details>
<summary data-op-bind='{"text":"item.q"}'></summary>
<p data-op-bind='{"text":"item.a"}'></p>
</details>
<!-- /onpress/repeat -->
</section>
Откуда берётся список
source — имя. Разрешается в таком порядке: системный источник (menu.*, site.*, pages.*, breadcrumbs, slot.faq) → набор сайта с этим именем → список items из атрибута. Во вложенном повторителе source вида item.<поле> берёт вложенный список из элемента внешнего.
<!-- набор сайта -->
<!-- onpress/repeat {"source":"tariffs"} --> … <!-- /onpress/repeat -->
<!-- системный источник: дочерние страницы текущей -->
<!-- onpress/repeat {"source":"pages.children"} --> … <!-- /onpress/repeat -->
<!-- список прямо во вставке -->
<!-- onpress/repeat {"items":[{"t":"Быстро","d":"Страница за один запрос"},{"t":"Надёжно","d":"Всё в одной транзакции"}]} -->
<li><b data-op-bind='{"text":"item.t"}'></b> <span data-op-bind='{"text":"item.d"}'></span></li>
<!-- /onpress/repeat -->
Какие поля есть у элементов системных источников — в наборах данных. У набора — какие положили (посмотреть: GET /data/{name}, поле fields).
Атрибуты
| Атрибут | По умолчанию | Смысл |
|---|---|---|
source | — | имя источника, набора или item.<поле> |
items | [] | список в атрибуте, если source не задан или ничего не нашёл |
lang | язык страницы | вариант набора; без варианта — общий набор, затем вариант основного языка |
match | {} | фильтр элементов |
limit | — | сколько элементов вывести |
offset | — | сколько пропустить с начала |
empty | — | frame — при пустом списке погасить весь фрейм |
separator | — | строка между элементами; после последнего не ставится |
statuses | publish | статусы страниц для pages.* через запятую |
page | страница сборки | id страницы, от которой считать контекстные источники и page.* |
Фильтр match
match оставляет элементы, у которых каждое указанное поле совпадает со значением токена:
<!-- вопросы именно этой страницы из общего набора -->
<!-- onpress/repeat {"source":"faq","match":{"page":"page.slug"}} --> … <!-- /onpress/repeat -->
<!-- страницы верхнего уровня текущего языка -->
<!-- onpress/repeat {"source":"pages.top","match":{"lang":"site.lang"}} --> … <!-- /onpress/repeat -->
<!-- три раздела по фрейму: массив — «любое из» -->
<!-- onpress/repeat {"source":"pages.top","match":{"lang":"site.lang","frame.slug":["raw:docs","raw:news","raw:pricing"]}} --> … <!-- /onpress/repeat -->
- Ключ — поле элемента; вложенное поле — через точку (
frame.slug,meta.lang). - Значение — токен бинда (
page.slug,page.id,page.parent_id,page.data.<ключ>из frontmatter,site.lang,option.<имя>,item.<поле>во вложенном повторителе) или константаraw:<значение>. - Сравнение — строгое равенство строк: число сравнивается своей строкой (
3равно"3"),true— как"1",false— как пустая строка; массивы, объекты,nullи отсутствующие поля не совпадают никогда. - Все поля — через И. Массив токенов в одном поле — через ИЛИ.
- Токен без значения (
page.parent_idу страницы верхнего уровня) делает список пустым. В массиве альтернатива без значения просто выпадает; пустым список станет, только если не разрешилась ни одна.
match с токеном page.* делает фрейм зависимым от страницы — у каждой страницы свой список.
Окно: limit и offset
<!-- три последних новости -->
<!-- onpress/repeat {"source":"news","limit":3} --> … <!-- /onpress/repeat -->
<!-- по три на страницу: ?blog_page=1 — 1–3, ?blog_page=2 — 4–6 -->
<!-- onpress/repeat {"source":"news","limit":3,"offset":"(page.paged - 1) * 3","empty":"frame"} --> … <!-- /onpress/repeat -->
- Порядок: сначала
match, потомoffset, потомlimit— окно режется из уже отфильтрованного списка. - Значение — целое или выражение из целых, токенов и
+ - * ( ).3,"3"и"(2 + 1)"— одно и то же. - Мусор ничего не ломает: нецелое значение, отрицательный
offset,limitноль или меньше означают «атрибут не задан».offsetза концом списка даёт пустой список. page.paged— номер страницы из адреса, параметр?blog_page=N(тот же, что у ленты блога темы), без параметра —1.
Окно из чисел фрейм от страницы не зависит. Окно с page.* — зависит. Проверить окно заранее можно через GET /data/{name}?limit=3&offset=3 или POST /data/resolve с paged — ответ несёт window (что применено) и window_total (сколько было до среза).
Ссылки «следующая страница» повторитель не рисует — поставьте их разметкой: <a href="?blog_page=2">Дальше</a>.
Пустой список
Без empty пустой список оставляет на месте повторителя комментарий с пометкой op/repeat: список «faq» пуст, а окружающая разметка (заголовок секции) остаётся.
"empty":"frame" гасит весь фрейм, в котором стоит повторитель: вместо него — пустая строка. Поэтому блок с заголовком выносят в отдельный фрейм и вкладывают его в оболочку:
<!-- article -->
<main class="article">
<!-- onpress/content /-->
<!-- onpress/frame {"name":"article__faq"} /-->
</main>
<!-- article__faq -->
<section class="article__faq">
<h2>Частые вопросы</h2>
<!-- onpress/repeat {"source":"faq","match":{"page":"page.slug"},"empty":"frame"} -->
<details><summary data-op-bind='{"text":"item.q"}'></summary><p data-op-bind='{"text":"item.a"}'></p></details>
<!-- /onpress/repeat -->
</section>
На странице без вопросов на месте article__faq ничего нет — ни заголовка, ни пустой секции, а article выводится как обычно. Гаснет ровно один фрейм — тот, где стоит повторитель. Не ставьте "empty":"frame" в собственный фрейм страницы: погаснет вся страница. В теле страницы (вне фрейма) повторитель при пустом списке прячет только себя.
Разделитель
separator ставит строку между элементами, после последнего — нет. Нужен там, где повторитель собирает не HTML, а текст с синтаксисом: JSON, список через запятую.
<script type="application/ld+json">{"@context":"https://schema.org","@type":"FAQPage","mainEntity":[<!-- onpress/repeat {"source":"slot.faq","separator":","} -->{"@type":"Question","name":<!-- onpress/value {"bind":"item.q_text","format":"json"} /-->,"acceptedAnswer":{"@type":"Answer","text":<!-- onpress/value {"bind":"item.a_text","format":"json"} /-->}}<!-- /onpress/repeat -->]}</script>
Значения внутри <script> выводит onpress/value с "format":"json" — тегов для data-op-bind там нет.
Источник slot.faq
Внутри фрейма, вызванного парной формой со слотом, slot.faq отдаёт пары вопрос-ответ из содержимого вызова: уровень первого заголовка — вопрос, всё до следующего такого же — ответ. Поля элемента: q и a — HTML, q_text и a_text — текст без разметки, question и answer — исходный markdown. Готовый фрейм — в рецепте FAQ.
Вложенный повторитель
Внутренний повторитель берёт список из элемента внешнего: source вида item.<поле>.
<!-- разделы текущего языка, у каждого — две первые дочерние страницы -->
<ul class="sections">
<!-- onpress/repeat {"source":"pages.tree","match":{"slug":["raw:docs","raw:primery"],"lang":"site.lang"}} -->
<li>
<a data-op-bind='{"href":"item.url","text":"item.title"}'></a>
<ol>
<!-- onpress/repeat {"source":"item.children","limit":2} -->
<li><a data-op-bind='{"href":"item.url","text":"item.title"}'></a></li>
<!-- /onpress/repeat -->
</ol>
</li>
<!-- /onpress/repeat -->
</ul>
Внутри вложенного повторителя item.* — его собственный элемент. В match вложенного повторителя item.<поле> означает элемент внешнего: в момент фильтрации своего элемента ещё нет. Поле, которое не массив, даёт пустой список. item.<поле> сам по себе фрейм от страницы не делает зависимым.
Язык
Язык набора выбирается так: атрибут lang → язык страницы → язык сборки. Для меню и страниц язык влияет на то, какое меню и какие адреса придут; pages.all и pages.top по языку не фильтруются — фильтруйте match по site.lang.
Примеры
Карточки дочерних страниц раздела (в теле страницы-раздела или в её фрейме):
<!-- wp:html -->
<div class="cards">
<!-- onpress/repeat {"source":"pages.children"} -->
<a class="cards__card" data-op-bind='{"href":"item.url"}'><span data-op-bind='{"text":"item.title"}'></span></a>
<!-- /onpress/repeat -->
</div>
<!-- /wp:html -->
Своё меню из системного источника (когда разметка onpress/menu не подходит):
<nav class="topnav">
<!-- onpress/repeat {"source":"menu.header"} -->
<a class="topnav__link" data-op-bind='{"href":"item.url","text":"item.title","target":"item.target"}'></a>
<!-- /onpress/repeat -->
</nav>
Хлебные крошки:
<nav class="crumbs">
<!-- onpress/repeat {"source":"breadcrumbs"} -->
<a data-op-bind='{"href":"item.url","text":"item.title","aria-current":"item.current"}'></a>
<!-- /onpress/repeat -->
</nav>
Список языков сайта:
<!-- onpress/repeat {"source":"site.languages"} -->
<a data-op-bind='{"href":"item.url","text":"item.name","hreflang":"item.slug"}'></a>
<!-- /onpress/repeat -->
Поля элемента site.languages смотрите запросом GET /data/site.languages — берите их имена оттуда.
Отладка
POST /data/resolveс теми жеsource,match,page,lang,limit,offsetпоказывает, что получит повторитель.- В ответе записи фрейма
bake.response.repeats— что вывел каждый повторитель при пробной сборке: источник, откуда взят (origin), сколько элементов, подставленные бинды,unresolved. - На странице пустой список оставляет комментарий с пометкой
op/repeat: список «…» пуст; у контекстного источника без страницы — с пометкой, что источник контекстный.