Перейти к содержимому

Анатомия блока

Блок — переиспользуемый кусок страницы. Страница собирается из блоков, каждый блок описывает что можно настроить (схема полей) и как это выглядит (шаблон Liquid, CSS, JS).

Блок физически состоит из двух частей, и их важно не путать: карточка блока меняется свободно, а его содержимое версионируется.

Часть Что там Что происходит при правке
Карточка блока имя, описание, документация, тип, теги, обложка перезаписывается на месте
Ревизия template, css, js, schema_content, changelog создаётся новая ревизия, старая остаётся в истории

Правка имени или описания не создаёт версию. Правка шаблона, стилей, скрипта или схемы полей — создаёт. Подробности — Ревизии и версии.

Поле Тип Назначение
name строка, 1–255 название блока в библиотеке; обязательно
description строка короткое описание для списка блоков
docs строка (Markdown) инструкция для того, кто ставит блок на страницу
block_type schema | markdown | html см. ниже; по умолчанию schema
role content | layout роль блока: обычный блок страницы или лэйаут; по умолчанию content, после создания не меняется
tags список id тегов фильтрация в библиотеке
обложка картинка превью в библиотеке, грузится отдельным запросом
Поле Назначение
template HTML с разметкой Liquid — то, что превращается в HTML страницы
css стили блока
js скрипт блока
schema_content схема полей: что редактор покажет в форме — полный справочник
changelog текст «что изменилось» для этой версии

Версия ревизии — два числа, major.minor (например 1.3). Первая ревизия любого блока — 1.0.

Тип задаётся полем block_type и определяет, что редактор показывает контентщику.

schema — по умолчанию. Форма собирается из schema_content.fields, значения уезжают в шаблон Liquid. Это основной тип, и вся остальная документация написана прежде всего про него.

markdown — то же самое плюс WYSIWYG-редактор текста. Текст хранится в зарезервированном ключе _markdown (его не нужно объявлять в fields), при рендере конвертируется в HTML и приходит в шаблон как {{ content }}. Markdown-блок может дополнительно объявлять обычные поля — например цвет фона — и они работают ровно как у schema-блока.

html — легаси-тип без схемы полей. Новые блоки в нём не делают.

Роль — отдельное поле role, и она не про устройство шаблона (это block_type), а про то, где блок применяется.

content — по умолчанию. Обычный блок: ставится на любую страницу и в слоты блоков-контейнеров.

layoutлэйаут: обёртка вокруг страниц. Такой блок кладётся только на верхний уровень документа-макета (kind = layout), по одному на документ, и в слот другого блока не помещается. Лэйаут описывает документ целиком — назначенный макет подменяет собой разметку витрины, а не вставляется внутрь неё:

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
{{ cms_assets_head }}
</head>
<body>
{{ cms_assets_body_start }}
<div class="frame">
<aside>{{ slots.sidebar }}</aside>
<main>{{ content }}</main>
</div>
{{ cms_assets_body_end }}
</body>
</html>
Тег Куда ставить Обязателен Что подставит витрина
{{ content }} тело документа да страницу
{{ cms_assets_head }} внутрь <head> да CSS сайта, цветовые схемы, счётчики
{{ cms_assets_body_start }} сразу после <body> нет то, что обязано идти первым в теле
{{ cms_assets_body_end }} перед </body> нет скрипты конца документа

Без обязательных блок не сохранится: API ответит 422 с описанием, чего не хватает. Тот же отказ приходит при публикации документа-макета — на случай лэйаутов, собранных до появления правила.

В опубликованном шаблоне все четыре остаются маркерами: подставляет их витрина. В предпросмотре PageCraft на месте {{ content }} плашка «здесь будет страница», а ассеты не видны — локально подставлять нечего.

Во всём остальном лэйаут — обычный блок библиотеки: своя схема полей, свои слоты композиции для шапки, сайдбара и подвала, свои CSS и JS, история ревизий, теги, форк.

Лэйаут бывает только типа schema: у markdown-блока {{ content }} уже занят текстом из WYSIWYG-редактора, и точке вставки страницы там нет места. Пара role: layout + block_type: markdown отклоняется с 422.

Взяв на себя весь документ, лэйаут берёт на себя и его <head>: заголовок, мету и canonical собирайте блоками — источник page.info отдаёт их для текущей страницы. Ассеты самого магазина подставит витрина через cms_assets_*.

Как собрать из лэйаута макет — Макеты.

  1. Автор блока объявляет поле в schema_content.fields, например title.
  2. Контентщик заполняет форму — значения сохраняются в placeholder_values того экземпляра блока, который стоит на странице.
  3. При рендере весь объект значений кладётся в переменную props, и шаблон обращается к полю как {{ props.title }}.
<section class="hero">
<h1>{{ props.title }}</h1>
<p>{{ props.subtitle }}</p>
{% for slide in props.slides %}
<img src="{{ slide.image }}" alt="{{ slide.caption }}" />
{% endfor %}
</section>

Если у экземпляра блока значений нет вовсе, PageCraft подставляет значения по умолчанию из схемы — правила описаны в разделе Значения по умолчанию.

Шаблон рендерится в «мягком» режиме: неизвестная переменная даёт пустую строку, а ошибка шаблона не роняет страницу — на её месте остаётся HTML-комментарий <!-- Liquid render error: … -->. Это удобно при отладке: сломанный блок видно в исходном коде страницы, а остальные блоки продолжают работать.

Схема:

{
"type": "object",
"label": "Заголовок секции",
"fields": {
"title": { "type": "text", "label": "Заголовок", "default": "Привет" },
"align": {
"type": "select",
"label": "Выравнивание",
"options": [
{ "label": "Слева", "value": "left" },
{ "label": "По центру", "value": "center" }
],
"default": "center"
}
}
}

Шаблон:

<h2 style="text-align: {{ props.align }}">{{ props.title }}</h2>

Через публичное API это один запрос:

Окно терминала
curl -X POST https://page-craft.4partners.io/api/v1/blocks \
-H "Authorization: Bearer pb_..." \
-H "Content-Type: application/json" \
-d '{
"name": "Заголовок секции",
"block_type": "schema",
"template": "<h2 style=\"text-align: {{ props.align }}\">{{ props.title }}</h2>",
"schema_content": { "type": "object", "fields": { "title": { "type": "text" } } },
"changelog": "Начальная версия"
}'

У блока есть область видимости scope:

  • private — блок виден только владельцу. Так создаётся любой новый блок.
  • global — блок из общего каталога, доступен всем.

Глобальным блок становится не напрямую, а через заявку: автор отправляет блок в каталог (POST /api/v1/blocks/{id}/submit), администратор одобряет или отклоняет. Пока заявка на рассмотрении, повторную подать нельзя — вернётся ошибка 409.

Форк (POST /api/v1/blocks/{id}/fork) делает личную копию глобального блока: копируются шаблон, стили, скрипт, схема, теги, обложка и номер версии, у копии появляется ссылка на источник (forked_from_block_id), а у источника растёт счётчик форков. Форкать можно только глобальные блоки — попытка форкнуть чужой приватный вернёт ошибку. Форк — штатный способ доработать чужой блок под себя и уйти с устаревающего глобального блока.

Блок выгружается ZIP-архивом (GET /api/blocks/{id}/export) и загружается обратно (POST /api/blocks/import). Внутри архива пять файлов с фиксированными именами:

Файл Содержимое
meta.json имя, описание, docs, тип, теги, версия, changelog, format_version: "1"
template.html шаблон
styles.css стили
script.js скрипт
schema.json schema_content

Отсутствие любого из пяти файлов — ошибка импорта. При импорте своего же блока (совпал автор и id из meta.json) создаётся новая ревизия существующего блока, иначе — новый блок. Теги подхватываются только те, что уже есть в системе.


Источники: backend/src/Entity/Block.php, backend/src/Entity/BlockRevision.php, backend/src/Service/Block/{CreateBlockService,ForkBlockService,ExportBlockService,ImportBlockService}.php, backend/src/Publishing/BlockRenderer.php, backend/src/Controller/V1/Block/Request/CreateBlockPublicRequest.php, backend/public/block-schema.json.