Как выводить записи на главную страницу в Kadence: карточки, мета, просмотры
Разбираем вывод записей циклом Kadence: карточки архива на главной, мета с разделителями, просмотры Post Views Counter и проверку кэша.
Карточки записей на главной легко собрать блоком «Сетка записей», но тогда оформление блока и архива живёт в двух местах. Если задача — получить на статической главной те же карточки, что в блоге, надёжнее запустить цикл темы и отдать каждую запись штатному шаблону Kadence.
Быстрый старт
Если механика вам знакома, всё собирается по этому списку. Разборы каждого шага — ниже.
- В Настройщике задайте стиль содержимого, число колонок и разделитель меты: Внешний вид → Настроить → Макет Записей и Страниц → Макет архива.
- В Post Views Counter отметьте нужные типы записей, выберите режим REST API, а на вкладке отображения поставьте расположение Вручную и пустую подпись.
- Поставьте пять сниппетов из статьи: шорткод с циклом темы, связующие правила CSS, вывод просмотров в мету, сброс кэша чтения и обновление чисел при кэше страниц.
- На главной добавьте блок «Шорткод» с
[site_posts count="3" columns="3"], а прежний блок «Сетка записей» удалите. - Откройте главную в приватном окне и сверьте карточки с архивом: высота, кегль заголовка, фон, положение «Читать далее» и растущее число просмотров.
Проверено на WordPress 7.0.3, теме Kadence 1.5.2 с дочерней темой, плагинах Post Views Counter 1.7.14 и Redis Object Cache 2.8.0. На других версиях идентификаторы настроек и имена хуков стоит перепроверить — как именно, показано дальше.
Два способа вывести записи
У Kadence Blocks есть готовая «Сетка записей». Её оформление хранится в атрибутах самого блока. Это удобно для самостоятельной секции, но изменение параметров в «Внешний вид → Настроить → Макет записей и страниц → Макет архива» не обязано повториться в блоке.
Цикл темы устроен иначе: запрос выбирает записи, а template-parts/content/entry.php строит каждую карточку. На проверенном сайте этот файл выводит <li class="entry-list-item">, затем <article class="entry content-bg loop-entry …"> и вызывает хуки миниатюры и содержимого. Поэтому архив и секция на главной используют одну разметку и одну точку настройки.
Отсюда простое правило выбора. Если секция на главной живёт своей жизнью и повторять архив не должна, штатный блок проще: он настраивается мышкой и не требует ни строки кода. Цикл темы оправдан там, где карточки обязаны быть одинаковыми везде, — тогда одна правка в настройках расходится по всему сайту сразу, а не по одному месту из трёх.
Шорткод с циклом Kadence
Сразу о том, куда класть код. Подойдёт любой из трёх вариантов: менеджер сниппетов, functions.php дочерней темы или собственный mu-plugin. В дочерней теме код переживёт обновления родительской, в менеджере — виден в одном списке и отключается по одному. Чего делать не стоит — складывать все куски в один сниппет: когда что-то сломается, отключать придётся всё сразу. Держите одну задачу на сниппет и давайте им говорящие имена.
Первый сниппет регистрирует [site_posts]. Параметры: count — число карточек, columns — от одной до четырёх колонок, post_type — существующий тип записи. Глобальный флаг нужен только на время цикла: по нему следующий сниппет понимает, что карточки выведены на статической странице.
<?php
/**
* Шорткод [site_posts count="6" columns="3" post_type="post"].
* Разметку карточки строит сама тема Kadence.
*/
function site_posts_shortcode( $atts ) {
$atts = shortcode_atts(
array(
'count' => 6,
'columns' => 3,
'post_type' => 'post',
),
$atts,
'site_posts'
);
$count = max( 1, min( 24, absint( $atts['count'] ) ) );
$columns = max( 1, min( 4, absint( $atts['columns'] ) ) );
$post_type = sanitize_key( $atts['post_type'] );
if ( ! post_type_exists( $post_type ) ) {
$post_type = 'post';
}
$query = new WP_Query(
array(
'post_type' => $post_type,
'post_status' => 'publish',
'posts_per_page' => $count,
'ignore_sticky_posts' => true,
'no_found_rows' => true,
)
);
if ( ! $query->have_posts() ) {
wp_reset_postdata();
return '';
}
$previous_flag = $GLOBALS['site_theme_loop_shortcode'] ?? false;
$GLOBALS['site_theme_loop_shortcode'] = true;
ob_start();
printf(
'<ul class="content-wrap kadence-posts-list grid-cols post-archive grid-sm-col-2 grid-lg-col-%1$d item-image-style-above">',
$columns
);
while ( $query->have_posts() ) {
$query->the_post();
get_template_part( 'template-parts/content/entry' );
}
echo '</ul>';
wp_reset_postdata();
$GLOBALS['site_theme_loop_shortcode'] = $previous_flag;
return ob_get_clean();
}
add_shortcode( 'site_posts', 'site_posts_shortcode' ); Классы content-wrap, kadence-posts-list, grid-cols, grid-sm-col-2 и grid-lg-col-N подключают сетку и адаптив темы. На проверенном сайте рабочая секция на главной использует тот же шаблон: у карточки заголовок h3, а в архиве — h2. Это корректная иерархия: на главной карточки находятся под заголовком секции второго уровня.
Осталось поставить шорткод на страницу. В редакторе главной добавьте блок «Шорткод» и впишите [site_posts count="3" columns="3"]. Блок «Сетка записей», если он там стоял, удалите — иначе на странице окажутся две секции с разным оформлением. Секцию удобно обернуть обычным рядом Kadence: отступы и фон тогда задаются настройками ряда, а не CSS.
Связующие правила между архивом и статической страницей
Дальше по тексту я называю мостиком несколько строк CSS, которые связывают оформление архива с карточками на обычной странице. Нужны они вот почему: часть динамического CSS Kadence ориентирована на контекст архива и на h2.entry-title. На статической странице селектор темы может оказаться сильнее короткого правила. Поэтому мостик привязан к контейнеру шорткода и использует не меньше трёх классов.
/* Фон карточки на странице берём из той же палитры, что и в архиве. */
.site-main .kadence-posts-list .loop-entry.content-bg {
background: var(--global-palette8);
}
/* h3 на главной повторяет измеренный заголовок карточки архива. */
.site-main .kadence-posts-list .loop-entry h3.entry-title {
font-size: 24px;
line-height: 1.3;
}
@media (max-width: 1024px) {
.site-main .kadence-posts-list .loop-entry h3.entry-title {
font-size: 22px;
}
}
@media (max-width: 767px) {
.site-main .kadence-posts-list .loop-entry h3.entry-title {
font-size: 21px;
}
} На живом архиве getComputedStyle() показал для заголовка 24 px и межстрочный интервал 31,2 px, для меты — 13,6 px и 21,76 px. В действующем CSS темы также найдены адаптивные значения заголовка 22 и 21 px. Здесь !important не требуется. Если в следующей версии Kadence статическая страница начнёт получать те же правила автоматически, мостик можно удалить после сравнения вычисленных стилей.
Что задаётся в настройках
| Задача | Где настраивать | Что проверено |
|---|---|---|
| Фон и отступы карточки | Макет Записей и Страниц → Макет архива → Стиль содержимого | post_archive_content_style, выбран блочный стиль |
| Число колонок | Макет Записей и Страниц → Макет архива → Колонок архива записей | post_archive_columns, выбрано 3 |
| Состав и разделитель меты | Макет архива → Элементы отдельных записей → Мета | post_archive_elements, показана дата, выбран vline |
| Кегль заголовка | Макет архива → вкладка «Дизайн» → Шрифт названия отдельной записи | post_archive_item_title_font, 24 px |
| Длина автоотрывка | Макет архива → Элементы отдельных записей → Отрывок | 20 слов |
| Ссылка продолжения | Макет архива → Элементы отдельных записей → Читать далее | Элемент включён |
Три строки из этой таблицы обязательны, если хотите одинаковые карточки в архиве и на главной: стиль содержимого, число колонок и разделитель меты. Остальное — вкусовое: кегль заголовка, длина автоотрывка и подпись ссылки продолжения меняются под макет и на механику не влияют.
Параметр количества слов применяется к автоматически собранному отрывку. Если поле «Отрывок» заполнено вручную, WordPress отдаёт готовый текст, и ограничение темы его не сокращает. Проверка простая: временно задайте автоотрывку пять слов на тестовом стенде, обновите архив и сравните запись с ручным отрывком и запись без него. На рабочем сайте ради такой проверки настройку менять не нужно.
Как Post Views Counter считает просмотры
В Post Views Counter 1.7.14 сначала откройте «Подсчёт» и проверьте четыре группы параметров.
- Типы записей. Счётчик работает только для отмеченных типов. На проверенном сайте отмечены «Записи», а страницы, кейсы и товары не отмечены.
- Режим работы. Доступны PHP, JavaScript и REST API; «Быстрый AJAX» помечен PRO. Сейчас выбран PHP. Если полный HTML страницы отдаёт серверный кэш, PHP-код WordPress при повторном запросе может не запускаться, поэтому для такого сайта выбирают JavaScript или REST API. REST удобнее диагностировать отдельным маршрутом.
- Исключения. На сайте исключены поисковые роботы и вошедшие пользователи. Поэтому просмотр администратора не увеличивает число; проверку проводят в приватном окне.
- Интервал. Сейчас установлен 24-часовой интервал. Повторный визит того же браузера в этот период не считается новым просмотром.
На вкладке «Отображение» поле Views Label пустое, включена иконка, а расположение установлено в Manual only. Так плагин не вставляет счётчик автоматически и не создаёт дубль. На текущем сайте опция «Этикетка» включена, но пустое поле даёт пустой span; для новой настройки его можно отключить и оставить только иконку.
У плагина есть два публичных REST-маршрута, и это пригодится для диагностики. Чтение: /wp-json/post-views-counter/get-post-views/ID — проверено запросом без cookies, отвечает 200 и отдаёт голое число. Одна тонкость: если передать несколько идентификаторов через запятую, маршрут вернёт не список, а их сумму, так что для набора карточек запрашивать придётся поштучно. Запись: /wp-json/post-views-counter/view-post/ID принимает POST — именно им браузер регистрирует просмотр в режиме REST API. Дёргать его руками стоит только на тестовой записи: он увеличивает счётчик.
Просмотры внутри меты Kadence
В entry_loop_meta.php Kadence сначала открывает <div class="entry-meta …">, затем вызывает kadence_before_loop_entry_meta и только после этого печатает дату и остальные элементы. Поэтому счётчик надо вставлять этим хуком, а не рядом с контейнером.
<?php
/**
* Добавляет просмотры первым прямым потомком .entry-meta.
* Ноль пропускаем по числовому API, а HTML получаем только для вывода.
*/
function site_add_views_to_loop_meta() {
$inside_shortcode = ! empty( $GLOBALS['site_theme_loop_shortcode'] );
if ( ! is_home() && ! is_archive() && ! is_search() && ! $inside_shortcode ) {
return;
}
if ( ! function_exists( 'pvc_get_post_views' ) || ! function_exists( 'pvc_post_views' ) ) {
return;
}
$post_id = get_the_ID();
$views = (int) pvc_get_post_views( $post_id );
if ( $views < 1 ) {
return;
}
echo '<span class="post-views-loop">';
echo wp_kses_post( pvc_post_views( $post_id, false ) );
echo '</span>';
}
add_action( 'kadence_before_loop_entry_meta', 'site_add_views_to_loop_meta' ); Обратите внимание на порядок вызовов в сниппете. У плагина две похожие функции с разным результатом: pvc_get_post_views( $id ) возвращает целое число, а pvc_post_views( $id, false ) — готовую разметку с контейнером, иконкой и числом. Эта разметка непустая даже при нуле просмотров, поэтому привычная проверка if ( $views ) после второй функции ноль не отсекает и печатает «0» с иконкой. Сначала спрашиваем число, и только если оно больше нуля — забираем разметку.
Настройка разделителя добавляет контейнеру класс entry-meta-divider-vline. В CSS Kadence на сайте найдены три правила:
.entry-meta.entry-meta-divider-vline > ::after { content: "|"; }
.entry-meta.entry-meta-divider-slash > ::after { content: "/"; }
.entry-meta.entry-meta-divider-dash > ::after { content: "-"; } Счётчик становится прямым потомком .entry-meta, поэтому получает тот же разделитель автоматически. На главной вычисленное значение ::after у .post-views-loop равно "|". Отдельную черту добавлять не требуется.
Точки, двоеточия и расстояние у иконки
Post Views Counter использует .post-views-icon::after и .post-views-label::after. В зависимости от версии стилей рядом с числом может появиться точка или двоеточие. Кроме того, правило плагина .post-views.entry-meta > span имеет два класса и тег и на текущем сайте содержит !important. Одиночный селектор иконки не сможет вернуть отступ.
/* Псевдоэлементы плагина не должны добавлять знаки рядом со счётчиком.
Здесь !important не нужен: своего display у этих псевдоэлементов плагин не задаёт. */
.post-views-icon::after,
.post-views-label::after {
display: none;
}
/* А здесь нужен, и это проверяется чтением стилей плагина:
frontend.css задаёт .post-views.entry-meta > span { margin-right: 0 !important }. */
.post-views.entry-meta > span.post-views-icon {
margin-right: 5px !important;
} Один псевдоэлемент нельзя одновременно использовать для двух независимых значков. Если собственная иконка времени чтения занимает ::before, не назначайте туда же ещё один декоративный элемент. Для отдельного разделителя в такой кастомной конструкции используйте границу элемента, а псевдоэлемент оставьте иконке. Штатный разделитель Kadence выше продолжает работать через ::after прямого потомка.
Объектный кэш: число в таблице есть, а на странице старое
Функция pvc_get_post_views() строит SQL-запрос к wp_post_views, вычисляет его MD5-хэш и сохраняет результат в группе объектного кэша pvc-get_post_views. В версии 1.7.14 срок по умолчанию — 300 секунд. Код записи обновляет таблицу или рабочую группу pvc, но не очищает группу готовых результатов чтения. При постоянном Redis это даёт понятный симптом: в таблице уже новое число, а функция некоторое время возвращает старое.
Диагностика состоит из двух чтений: сравните SELECT SUM(count) … FROM wp_post_views для нужного ID с результатом pvc_get_post_views( ID ). Если таблица обновилась, а функция отстаёт, логика записи работает и проблема находится в кэше чтения. В интерфейсе проверенной версии «Поддержка объектного кэша» помечена PRO.
Расхождение не вечное. В pvc_get_post_views() срок жизни записи задаётся строкой $expire = absint( apply_filters( 'pvc_object_cache_expire', 300 ) ); — по умолчанию пять минут, после чего число обновится само. Поэтому если счётчик не критичен к секундам, можно ничего не делать: витрина отстанет максимум на пять минут.
Если ждать не хочется, у плагина есть штатный фильтр — он меняет срок без единой строки работы с кэшем и без сброса группы:
<?php
/**
* Сокращает срок жизни кэша готового числа просмотров.
* Ноль здесь не подходит: для wp_cache_add() это «хранить бессрочно».
*/
add_filter( 'pvc_object_cache_expire', function () {
return 30;
} ); Что выбрать, зависит от нагрузки. Фильтр проще и работает в любом режиме счётчика, но заставляет чаще ходить в базу. Сброс группы точнее: он срабатывает ровно тогда, когда число действительно изменилось, — но только в режимах, где просмотр регистрируется REST-запросом.
<?php
/**
* После успешного REST-запроса сбрасывает только группу чтения PVC.
* Для режима PHP этот фильтр не запускается.
*/
function site_flush_pvc_read_cache_after_rest( $response, $server, $request ) {
$route = $request->get_route();
if ( 0 !== strpos( $route, '/post-views-counter/view-post' ) ) {
return $response;
}
if ( function_exists( 'wp_cache_supports' )
&& wp_cache_supports( 'flush_group' )
&& function_exists( 'wp_cache_flush_group' )
) {
wp_cache_flush_group( 'pvc-get_post_views' );
}
return $response;
}
add_filter( 'rest_post_dispatch', 'site_flush_pvc_read_cache_after_rest', 10, 3 ); Сниппет намеренно ничего не делает в режиме PHP: там нет REST-запроса к view-post. Если хостинг не поддерживает очистку отдельной группы, код также спокойно возвращает исходный ответ и не сбрасывает весь объектный кэш сайта.
Кэш страниц: другой слой и другая проблема
Предыдущий раздел был про объектный кэш — тот, что держит значения внутри PHP. Кэш страниц устроен иначе и лечится иначе, а перепутать их легко, потому что симптом похож: число на витрине не меняется.
Разница вот в чём. При кэше страниц посетителю отдаётся заранее собранный HTML, и число просмотров запечено в него на момент сборки. PHP при таком запросе вообще не выполняется. Значит ни хук вывода меты, ни сброс группы объектного кэша тут не работают — им попросту негде запуститься. Счётчик при этом может исправно расти: в режиме REST API просмотр регистрирует браузер отдельным запросом, база обновляется, а страница показывает старое значение до пересборки кэша.
Как различить два случая. Запросите страницу без cookies и посмотрите заголовки ответа: если есть x-cache, x-cached-by или age, отвечает кэш страниц. Если заголовков нет, а число всё равно отстаёт — виноват объектный кэш, и лечится он способами из предыдущего раздела.
У плагина есть штатный ответ именно на этот слой — параметр «Динамическая загрузка» на вкладке отображения. В коде он переключает класс контейнера:
$class .= $options['dynamic_loading'] === true ? ' load-dynamic' : ' load-static'; При load-dynamic число не печатается в HTML, а подгружается запросом из браузера, поэтому кэш страниц ему не мешает; под это в стилях плагина заготовлены состояния loading, loaded и load-error. Одна оговорка, из-за которой я и вынес это отдельно: в бесплатной версии переключатель заблокирован и помечен PRO. Проверяется за секунду — откройте вкладку отображения и посмотрите, активен ли чекбокс.
Что делать на бесплатной версии, по возрастанию усилий. Первое — ничего: для многих проектов отставание счётчика на время жизни кэша некритично, число не отчётность. Второе — исключить нужные страницы из кэша средствами кэширующего плагина, но тогда вы теряете кэш ровно там, где он полезнее всего. Третий путь работает и с кэшем, и без него, и разбирается ниже.
Идея простая: не печатать число в HTML намертво, а дать браузеру забрать его отдельным запросом. Кэш страниц такие запросы не трогает — в правилах доставки адреса wp-json исключены явно, да и кэш отдаётся только для адресов без строки запроса, а наш идёт с параметром. Маршрут плагина для этого не годится: на список идентификаторов он возвращает их сумму. Значит нужен свой, отдающий карту «идентификатор → число» одним запросом на всю страницу.
<?php
/**
* Свежие просмотры на страницах, отданных из кэша.
* Маршрут отдаёт карту «идентификатор → число» одним запросом.
* Выключается: add_filter( 'site_views_refresh_enabled', '__return_false' );
*/
add_action( 'rest_api_init', function () {
register_rest_route( 'site/v1', '/views', array(
'methods' => 'GET',
'permission_callback' => '__return_true',
'callback' => function ( $request ) {
if ( ! function_exists( 'pvc_get_post_views' ) ) {
return rest_ensure_response( array() );
}
$ids = array_slice(
array_unique( array_filter( array_map( 'absint', explode( ',', (string) $request->get_param( 'ids' ) ) ) ) ),
0,
50
);
// Оставляем только существующие опубликованные записи — одним запросом.
if ( $ids ) {
$ids = get_posts( array(
'post__in' => $ids,
'post_type' => 'any',
'post_status' => 'publish',
'posts_per_page' => count( $ids ),
'fields' => 'ids',
'ignore_sticky_posts' => true,
'no_found_rows' => true,
) );
}
$out = array();
foreach ( $ids as $id ) {
$out[ $id ] = (int) pvc_get_post_views( $id );
}
return rest_ensure_response( $out );
},
) );
} ); Вторая половина — скрипт в подвале. Он находит счётчики, достаёт идентификатор записи из класса post-NNN у контейнера плагина, делает один запрос на все карточки сразу и подставляет свежие числа.
<?php
add_action( 'wp_footer', function () {
if ( is_admin() || ! apply_filters( 'site_views_refresh_enabled', true ) ) {
return;
}
$js = '(function(){'
. 'var n=document.querySelectorAll(".post-views");'
. 'if(!n.length){return;}'
. 'var map={},ids=[];'
. 'Array.prototype.forEach.call(n,function(el){'
. 'var m=(el.className||"").match(/(?:^|\s)post-(\d+)(?:\s|$)/);'
. 'if(!m){return;}'
. 'var c=el.querySelector(".post-views-count");'
. 'if(!c){return;}'
. 'if(!map[m[1]]){map[m[1]]=[];ids.push(m[1]);}'
. 'map[m[1]].push(c);'
. '});'
. 'if(!ids.length){return;}'
. 'fetch(' . wp_json_encode( rest_url( 'site/v1/views' ) ) . '+"?ids="+ids.join(","),{credentials:"omit"})'
. '.then(function(r){return r.ok?r.json():null;})'
. '.then(function(d){if(!d){return;}'
. 'Object.keys(d).forEach(function(id){'
. '(map[id]||[]).forEach(function(el){'
. 'if(String(d[id])!==el.textContent.trim()){el.textContent=d[id];}'
. '});});})'
. '.catch(function(){});'
. '})();';
echo '<script id="site-views-refresh">' . $js . '</script>';
} ); Проверял так. Включил кэш страниц, прогрел архив, зафиксировал в отданном HTML число 12 у записи. Затем анонимный посетитель открыл саму запись — счётчик в базе стал 13. Повторный запрос архива вернул тот же HTML байт в байт, с прежним числом 12: страница пришла из кэша. При этом маршрут отдавал уже 13, а скрипт присутствовал в закэшированном HTML.
Финальная проверка: взял ровно тот HTML, что получает аноним, подменил в нём все девять чисел на заведомо неверные и выполнил скрипт из подвала. Все девять счётчиков вернулись к актуальным значениям, ни одного подменённого не осталось. Это и есть ответ на вопрос, работает ли связка: страница приходит из кэша со старым числом, а посетитель видит новое сразу после загрузки.
Три предостережения. Если на момент сборки кэша у записи было ноль просмотров, счётчик не выводится вовсе, и подставлять скрипту некуда — число появится после пересборки страницы. Если в оптимизаторе включена отложенная загрузка JavaScript, инлайн-скрипт может не выполниться до первого касания экрана — добавьте его в исключения. И проверьте, что REST API не отключён плагином оптимизации: такой переключатель есть, например, у Perfmatters, и он ломает подход целиком.
И проверьте, включён ли у вас кэш страниц вообще. На многих проектах на время работ его выключают и забывают: тогда всё описанное здесь просто не проявляется — ровно до дня запуска.
Равная высота карточек и «Читать далее» на одном уровне
Здесь легко потратить вечер на лишний код. Равную высоту карточек в ряду тема держит сама: ячейка списка объявлена как display: flex; flex-direction: column, а article внутри получает flex: 1 1 0%. Проверяется одним замером — выключите свой CSS и сравните высоты соседних карточек через getBoundingClientRect(). Они совпадут. Правила вида height: 100% у ячейки и у статьи в такой ситуации ничего не делают.
А вот ссылку «Читать далее» тема по нижнему краю не выравнивает, и штатной настройки под это нет. В макете архива есть «Вертикальное выравнивание содержимого», но варианты там только «Сверху» и «По центру»; у собственных типов записей этой настройки может не быть вовсе. В разделе элементов у «Читать далее» доступны лишь включение и подпись. Это тот случай, когда CSS оправдан, — и он же единственный в статье, где приходится описывать раскладку руками.
/* Карточка — flex-колонка, чтобы подвал можно было прижать вниз. */
.kadence-posts-list article.loop-entry {
display: flex;
flex-direction: column;
}
/* flex: 1 1 auto, а не height: 100%. Если у карточки появится миниатюра,
height: 100% растянет текстовую часть на высоту всей карточки,
и «Читать далее» выедет за границу. */
.kadence-posts-list article.loop-entry .entry-content-wrap {
display: flex;
flex: 1 1 auto;
flex-direction: column;
gap: 16px;
}
/* Прижимаем именно .entry-footer: он прямой потомок flex-контейнера.
На .more-link-wrap margin-top: auto не подействует — она лежит
внутри .entry-actions, на два уровня глубже, flex-контекста там нет. */
.kadence-posts-list article.loop-entry .entry-footer {
margin-top: auto;
padding-top: 16px;
}
/* Во flex-колонке отступы соседей складываются, а не схлопываются:
расстояния держит gap, margin обнулён и у детей, и внутри них. */
.kadence-posts-list article.loop-entry .entry-header,
.kadence-posts-list article.loop-entry .entry-taxonomies,
.kadence-posts-list article.loop-entry .entry-summary,
.kadence-posts-list article.loop-entry .entry-summary > p,
.kadence-posts-list article.loop-entry .entry-meta,
.kadence-posts-list article.loop-entry .entry-title,
.kadence-posts-list article.loop-entry .entry-actions,
.kadence-posts-list article.loop-entry .more-link-wrap {
margin: 0;
}
/* Рубрика, заголовок и мета — одна группа, ей зазор поменьше. */
.kadence-posts-list article.loop-entry .entry-header {
display: flex;
flex-direction: column;
gap: 8px;
} Две вещи в этом коде принципиальны. Первая: flex: 1 1 auto вместо height: 100% — «займи то, что осталось», а не «стань ростом с родителя». Если над текстом появится миниатюра, второй вариант сделает содержимое выше контейнера и ссылка обрежется. Вторая: во flex-колонке отступы соседей не схлопываются, а складываются, поэтому расстояния задаёт gap, а margin обнуляется и у детей, и внутри них — свой отступ есть и у .entry-meta, и у абзаца внутри .entry-summary.
Разница высот у самих карточек остаётся задачей редактора, а не разработчика: её создаёт разная длина отрывка, а ограничение по словам, как показано выше, на ручные отрывки не действует. Рабочее правило — держать поле «Отрывок» в пределах 130–150 символов у всех записей.
Как проверить результат
- Сравните
getComputedStyle()заголовка, меты и фона в архиве и на главной, а не только значения в настройщике. - Проверьте высоту карточек одного ряда и нижнюю координату ссылок «Читать далее» — чем это выравнивается, разобрано в разделе о равной высоте выше. Ручные отрывки при этом должны быть сопоставимой длины.
- Откройте запись в приватном окне: администраторы исключены из подсчёта. Учитывайте 24-часовой интервал.
- При REST API запросите
/wp-json/post-views-counter/get-post-views/IDбез авторизации. POST кview-postпроверяйте только на тестовой записи: он увеличивает счётчик. - Для мобильной проверки задайте внешнему iframe ширину 390 px и после загрузки убедитесь, что
iframe.contentWindow.innerWidthдействительно равно 390. Затем сравнитеdocument.documentElement.scrollWidthиclientWidth.
Последняя проверка защищает от ложного результата: CSS-ширина iframe и фактическая ширина его окна могут различаться. Горизонтальной прокрутки нет, когда scrollWidth <= clientWidth.
Два скрипта для консоли, чтобы не сверять на глаз. Первый снимает геометрию карточек — высоты в одном ряду должны совпасть, а координата подвала показать, выровнялось ли «Читать далее»:
// Геометрия карточек: высота и положение «Читать далее» внутри карточки.
// Внутри одного ряда высоты обязаны совпадать, координаты подвала — тоже.
copy([...document.querySelectorAll('.kadence-posts-list .entry-list-item')].map((li, i) => {
const art = li.querySelector('article');
const foot = li.querySelector('.entry-footer');
const R = el => el.getBoundingClientRect();
return {
карточка: i,
высота: Math.round(R(art).height),
подвал: Math.round(R(foot).top - R(art).top)
};
})); Второй выводит оформление одной карточки. Запустите его сначала на архиве, потом на главной и сравните пять значений: уровень заголовка ожидаемо разойдётся (в архиве h2, на главной h3), а кегль, межстрочный, фон и разделитель обязаны совпасть. Расхождение кегля или фона означает, что связующие правила не долетели, — проверяйте вес селектора.
// Сверка оформления архива и главной. Выполнить на обеих страницах и сравнить.
(() => {
const art = document.querySelector('.kadence-posts-list article.loop-entry');
const title = art.querySelector('.entry-title');
const meta = art.querySelector('.entry-meta');
const cs = el => getComputedStyle(el);
return {
уровеньЗаголовка: title.tagName,
кегль: cs(title).fontSize,
межстрочный: cs(title).lineHeight,
фонКарточки: cs(art).backgroundColor,
разделительМеты: getComputedStyle(meta.firstElementChild, '::after').content
};
})(); Итоговая схема
В настройках Kadence остаются стиль карточки, число колонок, состав меты, разделитель, типографика, отрывок и «Читать далее». Код решает только то, чего нет в настройщике: запускает цикл темы на статической странице, связывает контекст страницы с архивным оформлением, вставляет просмотры внутрь меты, при REST-режиме очищает кэш готового числа и обновляет счётчики на страницах, отданных из кэша. Итого пять небольших сниппетов, каждый со своей задачей и каждый отключаемый по отдельности.
