FAQ из данных
Один набор FAQ на весь сайт, который фрейм фильтрует по странице. На страницах с вопросами — блок и разметка FAQPage, на страницах без них — вообще ничего.
Вопросы не лежат в страницах. Это один набор данных faq, где каждый элемент называет страницу, к которой относится. Фрейм article__faq отбирает из списка вопросы текущей страницы по её слагу и печатает их вместе с JSON-LD; оболочка статьи подключает этот фрейм в одном месте. Добавить вопрос — это запись в набор, а не правка двадцати страниц.
1. Положить вопросы
У каждого элемента есть page — слаг страницы, к которой он относится, — и любые поля, которые читает разметка; здесь q для вопроса, a для ответа и n для порядка. Схемы у набора нет: какие поля отправили, такие и получит повторитель как item.<поле>.
curl -s -X PUT -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/data/faq" -d '{
"blog_id": 233,
"lang": "en",
"dry_run": true,
"items": [
{ "page": "pricing", "n": 1, "q": "Is there a free plan?", "a": "Yes, up to three sites." },
{ "page": "pricing", "n": 2, "q": "Can I cancel any time?", "a": "Yes, from the dashboard." },
{ "page": "setup", "n": 1, "q": "Do I need a developer?", "a": "No." }
]
}'
{
"success": true,
"dry_run": true,
"applied": false,
"blog_id": 233,
"dataset": {
"name": "faq",
"lang": "en",
"items": 3,
"bytes": 231,
"fields": [
{ "field": "page", "types": ["string"], "in_items": 3 },
{ "field": "n", "types": ["number"], "in_items": 3 },
{ "field": "q", "types": ["string"], "in_items": 3 },
{ "field": "a", "types": ["string"], "in_items": 3 }
],
"origin": "site",
"option": "onpress_dataset_faq__en",
"…": "…"
},
"warnings": [],
"items": ["…"]
}
fields — то, что реально есть в элементах; сверьте с разметкой до того, как писать фрейм. Без dry_run набор сохраняется. PUT заменяет список целиком; чтобы добавить или поправить один вопрос, есть PATCH /data/faq с ops (append, merge, remove) — см. данные.
2. Добавить другие языки
С lang набор хранится по языкам, рядом с остальными вариантами, в своей опции (onpress_dataset_faq__ru). Повторитель на русской странице читает вариант ru; язык без своего варианта берёт общий набор — тот, что сохранён без lang, — а если нет и его, список пуст.
curl -s -X PUT -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/data/faq" -d '{
"blog_id": 233,
"lang": "ru",
"items": [
{ "page": "pricing", "n": 1, "q": "Есть бесплатный тариф?", "a": "Да, до трёх сайтов." }
]
}'
page сравнивается со слагом печатаемой страницы. Если у переводов общий слаг (/pricing/ и /ru/pricing/), одно и то же значение работает во всех вариантах; если у перевода свой слаг, в его варианте нужен именно он.
3. Фрейм article__faq
op/repeat проходит по списку, match оставляет элементы, у которых page равно слагу текущей страницы, а "empty":"frame" делает так, что при пустом списке весь фрейм не печатает ничего — никакого заголовка над пустотой. op/faq-schema печатает JSON-LD FAQPage из того же источника с тем же фильтром и на пустом списке тоже молчит.
<section class="article__faq">
<h2 class="article__faq-title">FAQ</h2>
<!-- wp:op/repeat {"source":"faq","match":{"page":"page.slug"},"empty":"frame"} -->
<details class="article__faq-item">
<summary data-op-bind='{"text":"item.q"}'></summary>
<div data-op-bind='{"html":"item.a"}'></div>
</details>
<!-- /wp:op/repeat -->
<!-- wp:op/faq-schema {"source":"faq","match":{"page":"page.slug"}} /-->
</section>
curl -s -X POST -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/frames" -d "$(jq -n --rawfile html faq.html \
'{blog_id: 233, slug: "article__faq", name: "Article: FAQ", html: $html, dry_run: true}')"
{
"success": true,
"dry_run": true,
"applied": false,
"blog_id": 233,
"frame": { "id": "624581e9e9cba426", "slug": "article__faq", "block": "article", "element": "faq", "bytes": 427, "…": "…" },
"inserts": { "op/repeat": 1, "op/faq-schema": 1 },
"inserts_total": 2,
"sources": ["faq"],
"frame_refs": [],
"binds": 2,
"renders_on_request": true,
"live_reasons": ["inserts: op/faq-schema", "op/repeat match uses page.* tokens"],
"has_doctype": false,
"has_body": false,
"warnings": []
}
page.slug в match делает фрейм зависящим от страницы: испечённой копии у него нет, он печатается на каждом запросе, и у каждой страницы свой список. sources: ["faq"] — то, по чему следующая запись в набор найдёт этот фрейм.
4. Вставить в оболочку
Оболочка подключает фрейм один раз, после содержимого. На странице с вопросами он печатает блок; на странице без них op/frame оставляет пустое место, а сама оболочка печатается как обычно.
<main class="article">
<h1 class="article__title" data-op-bind='{"text":"page.title"}'></h1>
<div class="article__body"><!-- wp:op/content /--></div>
<!-- wp:op/frame {"ref":"article__faq"} /-->
</main>
Залейте оболочку через PUT /frames/article — полную разметку, со шапкой и подвалом из рецепта про оболочку.
5. Проверить, что получит страница
POST /data/resolve разрешает источник ровно так, как это сделает печать. Передайте страницу и тот же match; язык берётся из страницы, если не передан lang.
curl -s -X POST -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/data/resolve" -d '{"blog_id": 235, "source": "faq", "page": 6412, "match": {"page": "page.slug"}}'
{
"success": true,
"blog_id": 235,
"name": "faq",
"origin": "site",
"contextual": true,
"option": "onpress_dataset_faq__en",
"lang": "en",
"page_id": 6412,
"match": { "page": "pomelli-workflow" },
"match_total": 42,
"total": 6,
"items": [
{ "page": "pomelli-workflow", "n": 1, "q": "Is Pomelli still free?", "a": "Yes. Pomelli is a free beta from Google Labs. …" },
"…"
]
}
match показывает, во что разрешился page.slug, match_total — размер списка до фильтра, total — после, option — какой вариант прочитан. Тот же вызов для русской версии страницы ("page": "/ru/pomelli-workflow/") прочитает onpress_dataset_faq__ru. А вот страница без вопросов:
{
"success": true,
"blog_id": 235,
"name": "faq",
"option": "onpress_dataset_faq__en",
"lang": "en",
"page_id": 6409,
"match": { "page": "pomelli-en" },
"match_total": 42,
"total": 0,
"items": [],
"…": "…"
}
total: 0 значит, что сработает "empty":"frame" и на этой странице не будет ни блока FAQ, ни разметки.
Проверка
Откройте страницу из набора: вопросы на месте, в исходнике страницы есть скрипт JSON-LD FAQPage. Откройте страницу, которой в наборе нет: ни заголовка FAQ, ни разметки. Откройте перевод: вопросы берутся из варианта его языка. Без page ручка POST /data/resolve отвечает пустым списком с needs_page: true — это не хватает параметра, а не данных.