Сайдбар документации
Дерево слева собирается из самих страниц документации: шаблон, родитель и порядок. Как оно хранится, как адресуются пункты и какими ручками его перестраивать, включая два изменения, которые легко сделать и трудно отменить.
Дерево слева на этой странице — не меню и не отдельная сущность. Собственного хранилища у сайдбара нет: он выводится из самих страниц. Во фрейме его печатает вставка op/docs-nav, которая стоит в оболочке документации (см. Вставки). Эта вставка зависит от печатаемой страницы, поэтому оболочка печатается на запросе, и после изменения дерева перепечка не нужна.
Две вещи, которые легко сделать и трудно отменить
Перенос пункта в другую ветку меняет адрес страницы. Вложенность в сайдбаре — это родитель страницы, поэтому перевешивание переписывает путь: /docs/menyu/sajdbar/ становится /docs/sajdbar/. Старый адрес после этого отдаёт 404: WordPress запоминает смену слага, но не смену родителя. Каждая ручка, способная переместить пункт, докладывает url_before и url_after и добавляет предупреждение, но редиректа за вас никто не сделает. Переименование безопасно: title меняет подпись и не трогает ни слаг, ни адрес.
Скрытие раздела с видимыми детьми поднимает детей на верхний уровень. Скрытая страница перестаёт быть документационной, а документационная страница, чей родитель не документационный, рисуется в корне. То есть скрытие раздела не прячет его поддерево, а повышает его. Скрывайте ветку целиком либо сначала уводите детей. Ответ предупреждает, когда это вот-вот случится.
Как хранится сайдбар
Пункт сайдбара — страница (post_type = page) с шаблоном templates/docs.php в метаполе _wp_page_template. Вложенность — post_parent, но только внутри этого набора документационных страниц: если родитель страницы не документационный, пункт рисуется в корне. Порядок — колонка menu_order по возрастанию среди соседей. Подпись — заголовок страницы. Ни отдельной опции, ни меты порядка, ни навигационного меню за этим нет.
Скрытие определено в тех же терминах. Скрыть пункт — значит вынуть страницу из набора документации: сервис снимает метаполе _wp_page_template и ставит _onpress_docs_nav_hidden в 1, и именно этот маркер позволяет потом перечислить скрытые пункты и вернуть их. Страница остаётся опубликованной и открывается по своему адресу; она пропадает только из дерева.
Статус страницы (draft, private) — отдельная ось. Черновики и приватные страницы видны в дереве редакторам и не видны анонимному посетителю; скрытый пункт не виден никому.
Страницы разных языков живут в одном наборе. Тема рисует одно дерево на язык — язык открытой страницы; API читает и пишет их все вместе, если не попросить иначе.
Адресация пункта
Везде, где ждут пункт, работают три формы: id страницы, её слаг или путь вида /docs/menyu/. В теле запроса это голое значение (6186, "vvedenie", "/docs/menyu/") или объект: {"id": 6186}, {"slug": "vvedenie"}, {"url": "/docs/menyu/"}.
В пути ручки слешей быть не может, поэтому путь пишется с тильдой вместо каждого слеша: /docs/menyu/sajdbar/ превращается в docs~menyu~sajdbar. Ссылка без тильды — это id, если она из одних цифр, и слаг в остальных случаях, так что страница верхнего уровня адресуется просто docs.
Слаг, которым владеют несколько страниц, — 409 ambiguous_page со списком в candidates; тогда адресуйте по id. На многоязычном сайте это обычное дело: vvedenie есть и на английском, и на русском. Пути идут через тот же языковой резолвер, что и во всём API:
- Языковой префикс — часть адреса, а не уровень дерева:
/ru/docs-ru/vvedenie/находит русскую страницу, иruне ищется как родитель. - С префиксом страница выбирается на этом языке; путь, чья страница оказалась на другом языке, — 404
page_language_mismatch. - Без префикса путь проходится по слагам и родителям на любом языке, поэтому
/docs-ru/vvedenie/тоже работает. - Абсолютный адрес со схемой и хостом — 422
absolute_url_in_path.
GET /docs/sidebar
Возвращает всё дерево.
| Параметр | По умолчанию | Что делает |
|---|---|---|
lang | — | только страницы этого языка, как рисует тема; без него в дереве лежат все языки сразу |
include_hidden | false | включить скрытые пункты, каждый с "hidden": true |
statuses | publish,draft,private,pending,future | статусы страниц через запятую |
public | false | то же, что statuses=publish: дерево глазами анонимного посетителя |
curl -s -H "Authorization: Bearer $OP_TOKEN" "$BASE/docs/sidebar?blog_id=233&lang=ru"
{
"success": true,
"blog_id": 233,
"home_url": "https://example.onpress.pro",
"template": "templates/docs.php",
"statuses": ["publish", "draft", "private", "pending", "future"],
"include_hidden": false,
"lang": { "requested": "ru", "applied": "ru", "filtered_out": 28 },
"total": 28,
"hidden_total": 0,
"tree": [
{
"id": 6231,
"title": "Документация",
"slug": "docs-ru",
"status": "publish",
"parent": 0,
"menu_order": 34,
"hidden": false,
"children": [
{
"id": 6233,
"title": "Введение",
"slug": "vvedenie",
"status": "publish",
"parent": 6231,
"menu_order": 1,
"hidden": false,
"children": [],
"url": "/docs-ru/vvedenie/",
"link": "https://example.onpress.pro/docs-ru/vvedenie/"
},
{ "…": "…" }
],
"url": "/docs-ru/",
"link": "https://example.onpress.pro/docs-ru/"
}
]
}
lang объясняет, что сделал фильтр: filtered_out — сколько страниц других языков отброшено. На сайте без Polylang фильтр не применяется, и lang об этом говорит; язык, которого на сайте нет, — 422 unknown_language. total считает все узлы, hidden_total — сколько из них скрыто. link — полный адрес; у черновика он null, потому что этот адрес ничего не отдаёт.
url и link строятся по родителям, без языкового префикса. У страниц неосновного языка настоящий адрес с префиксом: русское введение в ответе значится как /docs-ru/vvedenie/, а открывается по /ru/docs-ru/vvedenie/. Путь из ответа годится для адресации, но не для ссылок.
PUT /docs/sidebar
Кладёт готовое дерево. После вызова сайдбар — ровно то, что прислали. Страниц ручка не создаёт: пункт — это существующая страница, а страницы заводятся публикацией markdown.
| Поле | По умолчанию | Что делает |
|---|---|---|
items | — | дерево, обязательно |
missing | hide | документационные страницы, которых нет в дереве: hide скрывает их, keep оставляет как есть |
dry_run | false | проверить и откатить: ответ перечисляет все изменения, база не тронута |
Пункт — ссылка на страницу плюс необязательные title (переименовать подпись), status (publish, draft, private, pending, future), hidden (оставить в структуре, но вынуть из дерева) и children. Голое 6186 или "/docs/vvedenie/" работает вместо объекта.
У PUT нет фильтра по языку. С
missing: "hide"по умолчанию скрывается каждая документационная страница, которой нет в вашем дереве, включая все страницы других языков.
missing решает судьбу всего, что вы не прислали, и присланное дерево не ограничено одним языком, как GET ?lang=. Английское дерево с hide по умолчанию вынет из сайдбара всю русскую документацию, и её id вернутся в dropped. Либо присылайте деревья всех языков одним вызовом, либо передавайте "missing": "keep"; тогда получившийся сайдбар — это присланное плюс всё, что там было, а warnings называет каждую оставшуюся страницу.
Весь запрос — одна транзакция. Любая неразрешимая ссылка и любая страница, встреченная дважды, отменяют его: 409 sidebar_items_unresolved со списком в errors, где at — координата узла в присланном дереве ("0.1" — второй ребёнок первого корневого пункта). В этом случае не записано ничего, так что после неудачи подчищать нечего — надо исправить тело запроса.
С корневыми пунктами ручка осторожна: страница, которая уже лежит под не-документационным родителем, и так рисуется в корне, поэтому её родитель не трогается и адрес не меняется. В корень переносятся только страницы, вложенные в другую страницу дерева.
Пробный прогон на сайте 233, который поднимает английскую версию этой страницы на уровень выше и переименовывает её родителя:
curl -s -X PUT -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/docs/sidebar" -d '{
"blog_id": 233,
"dry_run": true,
"missing": "keep",
"items": [
{ "id": 6183, "children": [
{ "id": 6186 },
{ "id": 6188 },
{ "id": 6264, "title": "Navigation menus" },
{ "id": 6266 }
]}
]
}'
{
"success": true,
"dry_run": true,
"blog_id": 233,
"applied": false,
"items": 5,
"changes": [
{ "id": 6183, "hidden": false },
{ "id": 6186, "hidden": false },
{ "id": 6188, "hidden": false },
{ "id": 6264, "menu_order": { "from": 8, "to": 2 }, "title": { "from": "Menus", "to": "Navigation menus" }, "hidden": false },
{ "id": 6266, "parent": { "from": 6264, "to": 6183 }, "menu_order": { "from": 1, "to": 3 }, "hidden": false }
],
"moved": [
{ "id": 6266, "url_before": "/docs/menyu/sajdbar/", "url_after": "/docs/sajdbar/" }
],
"dropped": [],
"warnings": [
"page #6233 (\"vvedenie\") stayed in the sidebar: it is not in the tree, but missing=\"keep\"",
"…",
"page #6355 (\"dannye\") stayed in the sidebar: it is not in the tree, but missing=\"keep\"",
"page #6231 (\"docs-ru\") stayed in the sidebar: it is not in the tree, but missing=\"keep\"",
"…",
"page #6266 has moved: /docs/menyu/sajdbar/ → /docs/sajdbar/; the old address will stop opening"
]
}
changes перечисляет, что поменялось бы у каждого пункта (from и to), moved — только переезды с адресом до и после, dropped — id, скрытые из-за отсутствия в дереве. Страницы 6233, 6355 и 6231 — русское введение, русская страница «Данные» и русский корень: с hide они оказались бы в dropped. Боевой вызов возвращает то же тело с "applied": true и notified — числом страниц, которым отправлено событие сброса кэша.
POST /docs/sidebar/reorder
Порядок внутри одной ветки. Оба режима перенумеровывают menu_order соседей подряд с нуля.
Полный порядок ветки: {"parent": <ссылка или 0>, "order": [<ссылки>]}. Неназванные остаются после названных в прежнем порядке и возвращаются полем appended. Страница из другой ветки в order — 409 not_a_sibling: перенос между ветками делается через PATCH пункта или PUT дерева, потому что он меняет адрес.
Одиночное перемещение: {"id": <ссылка>, "after": <ссылка>}, либо before, либо position (индекс с нуля).
curl -s -X POST -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/docs/sidebar/reorder" -d '{"blog_id": 233, "parent": 101, "order": [103, 102, 104]}'
{
"success": true,
"blog_id": 233,
"parent": 101,
"order": [103, 102, 104, 106],
"appended": [106],
"updated": 4,
"notified": 4
}
Пример из прогона при разработке, приведённого в справочнике API: страницу 106 не назвали, и она осталась последней. Внутри ветки языки не смешиваются, потому что у каждого языка свой корень раздела; на уровне parent: 0 соседями оказываются корни всех языков.
PATCH /docs/sidebar/items/{ref}
Правит один пункт. {ref} — id, слаг или путь с тильдами.
| Поле | Что делает |
|---|---|
title | подпись; слаг и адрес не трогаются |
status | publish, draft, private, pending, future |
parent | перенос в другую ветку (ссылка или 0); меняет адрес страницы |
hidden | вынуть из дерева или вернуть |
after, before, position | место среди соседей, применяется после смены родителя |
Пустое тело — 400 nothing_to_update. Родитель, замыкающий цикл в родословной, и главная страница сайта в роли родителя отклоняются защитой темы: пункт оказывается в корне, а warnings объясняет почему.
curl -s -X PATCH -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/docs/sidebar/items/107" -d '{"blog_id": 233, "title": "Подвал сайта", "parent": 101, "after": 103}'
{
"success": true,
"blog_id": 233,
"item": {
"id": 107,
"title": { "from": "Подвал", "to": "Подвал сайта" },
"parent": { "from": 104, "to": 101 },
"url_before": "/docs/vidzhety/podval/",
"menu_order": 1,
"url": "/docs/podval/"
},
"warnings": ["страница #107 меняет родителя: адрес /docs/vidzhety/podval/ перестанет открываться"],
"notified": 4
}
Это тоже прогон при разработке: страница 107 переехала из ветки vidzhety в корневой раздел, и её старый адрес перестал открываться.
POST /docs/sidebar/items/{ref}/hide и /show
Вынимают пункт из дерева и возвращают обратно. Тело нужно только чтобы назвать сайт, это можно сделать и заголовком. Статус и адрес страницы не меняются. Скрытие раздела с видимыми детьми поднимает детей в корень; предупреждение называет их число.
curl -s -X POST -H "Authorization: Bearer $OP_TOKEN" -H 'Content-Type: application/json' \
"$BASE/docs/sidebar/items/docs-ru~ustanovka/hide" -d '{"blog_id": 233}'
Коды ошибок
| Код | HTTP | Когда |
|---|---|---|
bad_items | 400 | items не массив |
bad_missing | 400 | missing не hide и не keep |
bad_reorder | 400 | в reorder нет ни order, ни id |
nothing_to_update | 400 | PATCH с пустым телом |
item_ref_required | 400 | пустая или неразбираемая ссылка на пункт |
page_not_found | 404 | страницы с таким id, слагом или путём нет |
not_a_page | 404 | ссылка разрешилась в запись другого типа |
page_language_mismatch | 404 | у пути один языковой префикс, а найденная страница на другом языке |
ambiguous_page | 409 | слагом или путём владеют несколько страниц; см. candidates |
duplicate_item | 409 | одна страница указана в дереве дважды |
not_a_sibling | 409 | в order страница из другой ветки |
sidebar_items_unresolved | 409 | зонтичная ошибка PUT: ничего не применено, разбор в errors |
absolute_url_in_path | 422 | в url схема и хост вместо пути |
parent_not_found | 422 | за сегментом пути нет страницы |
unknown_language | 422 | GET ?lang= называет язык, которого на сайте нет |
Меню шапки и подвала — другой механизм: см. Меню.