Перейти к содержанию

Каталог и тематические разделы

Добавим общий каталог и страницы двух тем. Их данные должны приходить из коллекции, а не из отдельных вручную написанных списков ссылок. Для этого вынесем выборку публичных материалов и построение адреса в src/lib/lessons.ts. Такой модуль станет единым местом договора для каталога, поиска и дальнейшей навигации.

publishedLessons сначала исключает черновики, затем сортирует записи по order. Если порядок совпадает, вторым ключом служит ID. Устойчивое второе условие необходимо: порядок файлов loader не должен случайно решать, какой урок окажется первым. При равных данных повторная сборка должна выражать тот же редакционный порядок.

Одна выборка для нескольких представлений

articleUrl пока строит HTML-адрес по ID. Выделение такой короткой функции имеет смысл именно потому, что адресная политика далее изменится. Если собрать строки отдельно в каждом шаблоне, сохранение старого PHP-пути потребует искать все потребители. Единая функция не исправляет ошибочную политику автоматически, но уменьшает число независимых мест для её исправления.

Главный каталог выводит название и описание каждого публичного материала. Список остаётся обычной HTML-разметкой. Даже без интерактивных фильтров посетитель может открыть любую запись. Если данных окажется меньше ожидаемого, сначала следует изучить статус черновика и выборку, а не добавлять текст «материалы готовятся» поверх реально существующих статей.

Страницы тем создаёт другой динамический маршрут. Он возвращает два пути с отфильтрованным массивом и русским названием темы. ID темы используется для адреса, а название — для человека. Такое разделение позволяет изменить формулировку заголовка, не ломая ссылку. Не используйте отображаемую строку как произвольный ключ без согласования с общей структурой.

При пяти исходных записях тема content содержит Markdown, адреса и иллюстрации, а interface — навигацию и формы. Это контрольный набор для будущего просмотра. Количество можно определить из исходных метаданных, но оно ещё не является полученным результатом выполнения. В статье говорим об ожидаемых списках и проверяемых условиях, а не о подтверждённом выводе.

Не копируем тело Markdown в каждую карточку. Описание является отдельным редакционным полем. Оно помогает понять тему в списке и даёт стабильный текст даже после изменения первого абзаца. Автоматическое обрезание HTML может оставить незакрытую ссылку, код или бессмысленную фразу. Если потребуется excerpt, его следует ввести как самостоятельный договор.

Публикация новой записи не требует вручную дополнять все три списковых страницы. После разрешённого переключения draft и новой сборки она входит в общий набор, затем соответствующая тема получает её по фильтру. При этом topic в схеме ещё определяет допустимый словарь. Добавление третьей темы требует обновить и schema, и список создаваемых тематических маршрутов.

Ожидаемое состояние — каталог из пяти уроков и две тематические страницы с согласованными ссылками. На следующем этапе общий список разделится на страницы. До этого полезно проверить сам состав данных: пагинация правильного списка является другой задачей, чем исправление потерянного материала в исходной выборке.

Полный файл src/lib/lessons.ts текущего этапа:

import { getCollection } from 'astro:content';
export async function publishedLessons() {
  return (await getCollection('lessons', ({ data }) => !data.draft)).sort((a, b) => a.data.order - b.data.order || a.id.localeCompare(b.id, 'en'));
}
export function articleUrl(entry: { id: string }) { return `/articles/${entry.id}.html`; }

Полный файл src/pages/catalog/index.astro текущего этапа:

---
import Base from '../../layouts/Base.astro';
import { publishedLessons, articleUrl } from '../../lib/lessons';
const entries = await publishedLessons();
---
<Base title="Каталог"><h1>Каталог уроков</h1><nav aria-label="Темы"><a href="/topics/content.html">Контент</a> <a href="/topics/interface.html">Интерфейс</a></nav><ul>{entries.map(entry => <li><a href={articleUrl(entry)}>{entry.data.title}</a><p>{entry.data.description}</p></li>)}</ul></Base>

Полный файл src/pages/topics/[topic].astro текущего этапа:

---
import Base from '../../layouts/Base.astro';
import { publishedLessons, articleUrl } from '../../lib/lessons';
export async function getStaticPaths() {
  const all = await publishedLessons();
  return ['content', 'interface'].map(topic => ({ params: { topic }, props: { entries: all.filter(entry => entry.data.topic === topic), title: topic === 'content' ? 'Контент' : 'Интерфейс' } }));
}
const { entries, title } = Astro.props;
---
<Base title={title}><h1>{title}</h1><ul>{entries.map(entry => <li><a href={articleUrl(entry)}>{entry.data.title}</a></li>)}</ul></Base>

Разбор состояния библиотеки

Рассмотрим ошибочный вариант каталога с двумя источниками. Главная страница читает коллекцию, а тема использует отдельно написанные ссылки. После появления нового урока главная обновится, но раздел может продолжать говорить, что материалы готовятся. Ошибка находится в независимых списках, а не в отсутствии текста. Общая publishedLessons устраняет именно эту причину и сохраняет единый статус записей.

Сортировка с localeCompare по ID служит вторым техническим ключом. Она не заменяет педагогический order и не должна зависеть от русской формулировки заголовка. Название может измениться без перестановки материала. Если редакция требует иной порядок при равных числах, лучше задать его явно в данных, а не надеяться на особенности файловой системы или случайный порядок loader.

Функция articleUrl принимает запись ограниченной формы. Она не получает произвольную строку из запроса пользователя. Это делает текущий путь построения адреса понятным. При сохранении старых путей функция получит дополнительное поле, но остальные потребители продолжат использовать тот же вызов. Стабильность небольшого публичного API модуля полезна даже без создания отдельной библиотеки пакетов.

Для самостоятельного варианта измените тему записи assets с content на interface. При будущей сборке материал должен перейти между тематическими списками, сохранив ID и URL. Общий каталог остаётся из пяти записей. Такая модификация показывает, почему рубрикация и постоянный адрес не должны быть одним полем. Перемещение между разделами не требует автоматически переименовывать опубликованный материал.

Пустая тема тоже является осмысленным состоянием. В нашем исходном наборе обе темы непустые, но после изменения данных одна может остаться без записей. Шаблон должен сообщить об этом понятно и дать путь к общему каталогу. Нельзя считать пустую выборку ошибкой сети, поскольку все данные статические и доступны при подготовке. Причина может быть обычным редакционным решением.

При росте сайта один материал может входить в несколько проектных маршрутов, оставаясь одной статьёй. Не копируйте Markdown ради появления в нескольких списках. Добавьте соответствующие метаданные и получайте разные представления одной записи. Текущая серия ограничена двумя темами, но уже показывает главное разделение: источник содержания один, списковых представлений может быть несколько. Это сохраняет адреса и уменьшает риск расходящихся версий текста.

Каталог не использует import.meta.glob для обхода произвольных страниц с разными форматами metadata. Он получает записи через коллекцию с известной схемой. Это упрощает связь списка и статей. Если часть исторической библиотеки находится вне коллекции, потребуется отдельный адаптер данных. Нельзя считать, что helper уже импортировал все старые HTML только потому, что рядом лежат их файлы. Текущая Полка ограничена пятью Markdown-записями.

Условия для самостоятельного наблюдения

Это план будущего разбора, а не журнал выполненных проверок. Рассмотрите одинаковые исходные данные и сопоставьте действие с ожидаемым состоянием.

Условие Ожидаемое состояние и его граница
Тема content В неё должны войти три опубликованные записи из исходных метаданных.
Тема interface В неё должны войти две записи, обозначенные соответствующим ключом.
Два order совпали Второй ключ ID делает сортировку определённой для выбранного набора.
Новая запись опубликована Общий запрос включает её, тематический список использует тот же статус.
Добавлена третья тема Требуется согласовать схему и перечисление маршрутов, а не только название в статье.
Адресная политика меняется Функция articleUrl становится общей точкой изменения всех списков.

Оглавление серии, курсы ProfessorWeb и полный архив исходников. Для этого урока выберите lesson-05.

Первичные источники: Collections, Routing.

Редакционный статус: локальный черновик. Установка, учебные программы, браузерные сценарии, тесты и публикация не выполнялись. Описанные эффекты ожидаемые.