Повторитель
Атрибут data-op-repeat выводит шаблон для каждого элемента списка. Основные примеры — в разделе «Повтор» гайда «Поля данных».
API — https://onpress.pro/api/v2, SITE — id сайта, OP_TOKEN — токен API. Адреса запросов и общие параметры — в общих правилах API, права — в авторизации.
| Раздел | Содержание |
|---|---|
| Запись | формы записи |
| Параметры | типы и значения параметров |
| Источник списка | порядок выбора данных |
| Фильтр match | отбор элементов |
| Окно limit и offset | число элементов и смещение |
| Пустой список | проверка пустого значения |
| Разделитель | строка между элементами |
| Примеры | готовые шаблоны |
| Отчет сборки | поля отчета |
Повторитель — атрибут data-op-repeat на теге шаблона. Тег вместе с содержимым сборка выводит по разу на каждый элемент списка, а поля данных item.* внутри подставляют значения элемента. Вызовы фреймов, служебные блоки и вложенные повторители внутри тега сборка выводит заново для каждого элемента.
Запись
Значение data-op-repeat — имя списка строкой или JSON-объект с параметрами в одинарных кавычках атрибута. Строка из латиницы, цифр, _, . и - значит {"source":"<строка>"}. JSON в двойных кавычках атрибута пишите с " вместо кавычек. Двойной дефис в значениях JSON сборка сама записывает как \u002d\u002d. Если значение не имя и не JSON-объект, атрибут не работает: сборка выводит тег один раз и убирает атрибут из готового HTML.
| Запись | Что повторяется |
|---|---|
<li data-op-repeat="team">…</li> | тег <li> с содержимым |
<img data-op-repeat="gallery" src="{{item}}" alt=""> | тег без содержимого |
<template data-op-repeat="team">…</template> | содержимое <template>, сам <template> на страницу не попадает |
<!-- onpress/repeat {"source":"team"} -->…<!-- /onpress/repeat --> | все между строками блока; это та же запись комментарием с теми же параметрами, она работает и внутри <script> |
Атрибут сочетается с другими на том же теге: data-op-if на повторяемом теге проверяет поле каждого элемента. Внутри <pre>, <code> и тега с data-op-raw сборка атрибут не трогает.
Список сборка берет из пользовательских или системных данных, из параметра вызова фрейма, из содержимого вставки или из самих параметров, потом фильтрует его match и режет окном offset и limit. Тот же путь проходит POST /data/resolve: он показывает, что получит повторитель, ничего не записывая.
Параметры
Параметры — ключи JSON-объекта в data-op-repeat или после имени блока onpress/repeat. Передайте source или items; остальные параметры необязательны. Например, три последних отзыва этой страницы из общего набора:
<blockquote data-op-repeat='{"source":"reviews","match":{"page":"page.slug"},"limit":3,"empty":"frame"}'>{{item.text}}</blockquote>
| Параметр | Тип | Обязательно | По умолчанию | Описание |
|---|---|---|---|---|
source | string | нет | — | откуда взять список, порядок — ниже |
items | array | нет | [] | список прямо в параметрах; работает, когда source не задан или ничего не нашел |
lang | string | нет | — | язык варианта набора и меню |
match | object, string | нет | — | фильтр {"поле набора": "поле данных"} |
offset, limit | number, string | нет | — | окно: сколько пропустить и сколько вывести |
empty | string | нет | — | frame — при пустом списке погасить фрейм |
separator | string | нет | — | строка между элементами, сборка не добавляет ее после последнего элемента |
statuses | string | нет | publish | статусы страниц для pages.* через запятую |
page | number | нет | страница сборки | id страницы, от которой считать контекстные источники и поля данных page.* в match, offset и limit |
place | string | нет | — | область меню для источника menu.*: header, footer_1, footer_2, footer_3 |
parent, sort, order, exclude_current, view, per_page, excerpt_words | — | нет | — | выборка pages.children |
Значение параметра, целиком равное frame.<ключ> или page.<поле>, сборка заменяет значением параметра вызова или поля страницы, кроме match, offset и limit. Так фрейм menu берет область из вызова: {"source":"menu.header","place":"frame.place"}. Подробнее — в параметрах вызова.
Источник списка
Сборка проверяет source по порядку и берет первое совпадение.
source | Список |
|---|---|
| не задан | items |
item.<поле> | вложенный список из элемента внешнего повторителя, например item.children; поле не список — пустой список |
frame.<путь> | список из параметра вызова фрейма; объект — список из одного элемента |
slot.faq | пары из содержимого парной вставки фрейма |
| системное имя | системные данные: menu.header, menu.footer_1 … menu.footer_3, site.settings, site.languages, site.front_page, site.socials, site.organization, pages.all, pages.top, pages.tree, pages.children, pages.siblings, pages.adjacent, pages.translations, pages.pager, pages.outline, pages.share_ai, pages.terms, pages.lang_switcher, pages.featured_image, pages.docs_tree, pages.docs_labels, pages.docs_crumbs, pages.assistant, pages.assistant_welcome, breadcrumbs |
| имя набора | пользовательские данные: вариант языка, иначе общий вариант, иначе вариант языка сайта по умолчанию |
| ничего не нашлось | items, без него — пустой список |
Строковый параметр в "source":"frame.source" сборка заранее подставляет как имя, поэтому имя набора тоже можно передать вызовом. Если такого параметра в вызове нет, сборка снимает source, и берет список из items. Объект в параметре items дает список из одного элемента.
Язык. Язык набора — параметр lang, иначе язык страницы, иначе язык сборки. Язык меню и остальных системных данных — атрибут lang, иначе язык сборки. pages.all, pages.top и pages.tree отдают страницы всех языков, фильтруйте их match по site.lang. pages.siblings, pages.adjacent и pages.children с "parent":"any" берут страницы только на языке страницы.
Контекстные источники. Сборка вычисляет pages.children, pages.siblings, pages.adjacent, pages.translations и breadcrumbs для текущей страницы или страницы из атрибута page. Без страницы список пустой, в отчете — needs_page: true.
| Источник | Поля элемента |
|---|---|
pages.all, pages.top, pages.siblings, pages.children | id, title, slug, url от корня, lang, parent, menu_order, status, frame — {id, slug} или null |
pages.tree | те же и children — дочерние страницы тем же видом |
pages.adjacent | один элемент {prev, next} — соседние страницы того же родителя или null |
breadcrumbs | id, title, slug, url, current; первый элемент — главная языка страницы с названием сайта |
menu.* | id, title, url, type, object, object_id, target, classes, description, parent, order, children |
pages.translations, site.languages, site.* | как в GET /data/{name}, см. системные данные |
Поля пользовательского набора — те, что вы в него положили, их перечисляет поле fields в GET /data/user.
Источник slot.faq
Внутри фрейма, вызванного парной формой, slot.faq разбирает содержимое вставки на пары: уровень первого заголовка в содержимом — вопрос, текст до следующего заголовка того же уровня — ответ. Другие имена slot.* дают пустой список.
| Поле элемента | Что это |
|---|---|
n | номер пары с единицы |
q, a | вопрос и ответ в HTML |
q_text, a_text | вопрос и ответ текстом без разметки |
question, answer | вопрос и ответ в исходном Markdown |
Данные страницы
Сборка вычисляет эти источники для текущей страницы. Все, кроме pages.terms, отдают один элемент, и пустой список, если выводить нечего. Передайте параметры атрибутом data-op-repeat. Так устроены фреймы pager, outline и share-ai.
<template data-op-repeat='{"source":"pages.outline","levels":"2-3"}'>
<b>{{item.title}}</b>
<a data-op-repeat="item.flat" href="{{item.url}}">{{item.text}}</a>
</template>
| Источник | Параметры | Поля элемента |
|---|---|---|
pages.pager | root, prev_label, next_label, label | prev и next с полями title и url, на краю ряда пусто; prev_label, next_label, label |
pages.outline | levels, title | title, headings — дерево заголовков, flat — те же заголовки плоским списком |
pages.share_ai | label | title, links, подписи copy_page, copy_short, copy_hint, copy_markdown, more_actions, toggle_dropdown |
pages.terms | taxonomy, link, limit | по элементу на термин: id, name, slug, url, link, taxonomy |
pages.lang_switcher | show, hide_current, scope, label | links — языки с полями slug, label, url, current; title, label |
pages.docs_tree | root, search, view | nodes — дерево страниц: title, url, search_text, active, level, group, leaf, leaf_top, toggle, open_class, expanded, hidden, body_id, status, status_icon, children; show_search, подписи поиска |
pages.docs_labels | — | подписи оболочки: pages, navigation, close_navigation, search, search_docs, clear_search |
pages.docs_crumbs | — | крошки страницы документации, по элементу на пункт: title, url (у группы меню пустой). Первый пункт — корень раздела по адресу страницы, дальше группа из menu.docs или menu.api и родитель, если они есть |
pages.assistant, pages.assistant_welcome | assistant, lib, agent, agent_view | html — готовая разметка виджета, чат на странице один |
pages.featured_image | img_class, picture_class, loading, fetchpriority, link, figure | id, url, alt, width, height, caption, picture — готовый <picture>; списки plain и figured для вывода без обертки и в <figure> |
У заголовка в headings есть id, text, url, level (H2 — это 1), heading (2–6), level_class, nested и children. У flat — id, text, url, level. У ссылки в links — id, name, url, icon, icon_class, label.
Правка текущей страницы запускает пересборку pages.outline и pages.share_ai. Для pages.pager и pages.terms пересборку запускают также правки связанных страниц.
Параметр "as":"faq_schema" у любого списка отдает один элемент с готовым JSON-LD FAQPage в поле json. Поля вопроса и ответа задают question и answer, по умолчанию q и a. На странице FAQPage один, повторный вызов пуст.
Выборка pages.children
У pages.children свои параметры: чьи дети, в каком порядке и в каком виде. Например, лента раздела, свежие первыми, по девять на страницу:
<a class="feed__card" data-op-repeat='{"source":"pages.children","sort":"date","limit":9,"offset":"(page.paged - 1) * 9"}' href="{{item.url}}">{{item.title}}</a>
| Параметр | По умолчанию | Значения |
|---|---|---|
parent | self | self — дети страницы сборки; parent — дети ее родителя, у страницы верхнего уровня пусто; id страницы — ее дети; any — все страницы, кроме верхнего уровня, на языке страницы сборки |
sort | menu_order | menu_order, date, modified, title |
order | asc, у date и modified — desc | asc, desc |
exclude_current | false | true, 1, yes, on — без страницы сборки |
view | items | items — страницы; pager — ссылки разбивки; summary — один элемент со счетчиками |
per_page | 9 | страниц на страницу разбивки для pager и summary |
excerpt_words | 40 | длина выдержки в словах для items |
Неизвестное значение работает как значение по умолчанию.
view | Поля элемента |
|---|---|
items | поля pages.* и link — полный адрес, date — дата в ISO 8601, title с типографикой, date_text — дата словами, excerpt — своя выдержка или первый абзац текста, image — {id, url, width, height, alt} или null, topic — родитель {id, title, url, link} или null. date_text, excerpt, image и topic сборка добавляет после окна, фильтровать по ним match нельзя |
pager | kind — prev, page, gap или next; number; url и link с ?blog_page=N; current, gap, linked — признаки для if. Одна страница разбивки — пустой список |
summary | total, per_page, pages, paged, on_page, empty |
Фильтр match
match — объект {"поле набора": "поле данных"} или его JSON-строка. Элемент остается, когда каждое поле набора строкой равно значению своего поля данных. Вместо одного поля данных можно дать их список: поле набора совпадает с любым из них. Например, страницы верхнего уровня текущего языка с фреймом docs или news:
<a data-op-repeat='{"source":"pages.top","match":{"lang":"site.lang","frame.slug":["raw:docs","raw:news"]}}' href="{{item.url}}">{{item.title}}</a>
| Правило | Как работает |
|---|---|
| поле | имя поля набора, вложенное — через точку: frame.slug, meta.lang |
| значение | поле данных: page.*, page.data.*, site.*, option.<имя>, menu.<область>.name, raw:<значение>; item.<поле> во вложенном повторителе — поле элемента внешнего повторителя. Параметры вызова frame.<ключ> в match не работают |
| сравнение | сборка сравнивает строки строго; число равно своей строке, true — 1, false — пустой строке; массив, объект, null и отсутствующее поле не совпадают никогда |
| поле данных без значения | список пустой; в списке полей данных такая альтернатива выпадает, а список пуст, только если ни одно поле не дало значения |
| пустые пары | пара с пустым полем набора или пустым полем данных и match-список вместо объекта сборка пропускает |
Поле данных page.* в match делает фрейм зависимым от страницы. Без страницы в контексте такой match дает пустой список с needs_page: true.
Окно limit и offset
Сборка режет уже отфильтрованный список: пропускает offset элементов и выводит limit. Значение — целое число или выражение из целых чисел, полей данных и знаков +, -, *, (, ): 3, "3" и "(2 + 1)" значат одно и то же. Поле в выражении должно дать целое число. Например, по три элемента на страницу разбивки:
<article data-op-repeat='{"source":"news","limit":3,"offset":"(page.paged - 1) * 3","empty":"frame"}'>{{item.title}}</article>
| Значение | Как работает |
|---|---|
| нецелое число, мусор, выражение с ошибкой | параметр не задан |
offset меньше нуля | 0 |
limit ноль или меньше | без предела |
offset за концом списка | пустой список |
поле данных page.* | фрейм зависит от страницы |
frame.<ключ> | не работает, как и в match |
page.paged — номер страницы разбивки из адреса /…/page/N/, для первой страницы — 1. Адрес ?blog_page=N отвечает 301 на /…/page/N/. Ссылки на соседние страницы разбивки выводит pages.children с "view":"pager" или ваша разметка.
Пустой список
Без empty пустой список ничего не выводит, а разметка вокруг повторителя остается; чтобы снять и ее, поставьте на обертку data-op-if="data.<набор>". "empty":"frame" гасит фрейм, в разметке которого стоит повторитель: вместо всего фрейма сборка выводит пустую строку, в отчете вызова — reason: "empty". Гаснет только этот фрейм, сборка выводит внешний фрейм как обычно, поэтому секцию с заголовком выносите в отдельный фрейм и вызывайте ее из оболочки. Сборка выводит текст страницы внутри ее фрейма, поэтому "empty":"frame" в тексте страницы гасит всю страницу; там пишите повторитель без empty. Повторитель с пустым шаблоном ничего не выводит, в отчете — warning: "пустой шаблон элемента".
Разделитель
separator ставит строку между элементами, после последнего — нет. Он нужен, когда повторитель собирает текст с синтаксисом: слова через запятую или список JSON внутри <script>. В <script> тегов нет, поэтому там повторитель записан блоком onpress/repeat, а значения выводит блок onpress/value с "format":"json". Например, шаги HowTo из содержимого вставки:
<script type="application/ld+json">{"@type":"HowTo","step":[<!-- onpress/repeat {"source":"slot.faq","separator":","} -->{"@type":"HowToStep","name":<!-- onpress/value {"bind":"item.q_text","format":"json"} /-->}<!-- /onpress/repeat -->]}</script>
Примеры
Вложенный повторитель. Внутренний повторитель берет список из поля элемента внешнего: "source":"item.children". Внутри него item.* — его собственный элемент, а в match вложенного повторителя item.<поле> значит элемент внешнего. Например, разделы текущего языка и под каждым две первые дочерние страницы; условие убирает пустой <ol>:
<ul class="sections">
<li data-op-repeat='{"source":"pages.tree","match":{"slug":["raw:docs","raw:primery"],"lang":"site.lang"}}'>
<a href="{{item.url}}">{{item.title}}</a>
<ol data-op-if="item.children">
<li data-op-repeat='{"source":"item.children","limit":2}'><a href="{{item.url}}">{{item.title}}</a></li>
</ol>
</li>
</ul>
Хлебные крошки с отметкой текущей страницы:
<nav class="crumbs">
<template data-op-repeat="breadcrumbs">
<a href="{{item.url}}" aria-current="page" data-op-if="item.current">{{item.title}}</a>
<a href="{{item.url}}" data-op-unless="item.current">{{item.title}}</a>
</template>
</nav>
Лента раздела с разбивкой: карточки дочерних страниц, свежие первыми, по девять на страницу, и номера страниц под ними:
<div class="feed">
<article class="feed__card" data-op-repeat='{"source":"pages.children","sort":"date","limit":9,"offset":"(page.paged - 1) * 9"}'>
<img class="feed__img" loading="lazy" alt="{{item.title}}" data-op-picture="item.image.url" data-op-if="item.image.url">
<time datetime="{{item.date}}">{{item.date_text}}</time>
<a href="{{item.link}}">{{item.title}}</a>
</article>
</div>
<nav class="feed__pager">
<template data-op-repeat='{"source":"pages.children","sort":"date","view":"pager","per_page":9}'>
<span data-op-if="item.gap">…</span>
<span aria-current="page" data-op-if="item.current">{{item.number}}</span>
<a href="{{item.link}}" data-op-if="item.linked">{{item.number}}</a>
</template>
</nav>
Готовую ленту с разбивкой выводит системный фрейм page-feed.
Отчет сборки
Запись фрейма отвечает отчетом сборки, в нем bake.response.repeats — по записи на каждый повторитель фрейма.
| Поле | Что это |
|---|---|
source | имя источника; без source — inline |
origin | откуда взят список: system, site — пользовательские данные, inline — items, item, frame, slot |
contextual, needs_page, page_id | список зависит от страницы; страница нужна, но ее нет; от какой страницы считали |
items | сколько элементов вывел повторитель |
binds, unresolved | сколько значений подставили поля данных; поля без значения |
window, window_total | примененное окно {offset, limit} и длина списка до него; есть, только если окно задано |
empty | frame — пустой список погасил фрейм; self — фрейма вокруг нет, и пустую строку вывел только повторитель |
warning | пустой шаблон элемента |
Проверить список до записи фрейма можно запросом POST /data/resolve с теми же source, items, match, page, lang, limit и offset. Например, отзывы страницы тарифов:
curl -s -X POST "$API/site/$SITE/data/resolve" \
-H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
-d '{"source": "reviews", "page": "tarify", "match": {"page": "page.slug"}, "limit": 3}'
Ответ — как у GET /data/{name}: items, origin, match и match_total, window и window_total. Какие поля данных match понимает POST /data/resolve, перечисляет справочник «Данные».