Custom меню для Quartz 4
Когда сайт на Quartz 4 начинает жить как проектная база, навигация перестаёт быть мелкой деталью оформления. Читателю нужно быстро понять, куда идти дальше: открыть нужный раздел, вернуться к списку проектов, найти контакты или архив. Автору тоже важно не собирать меню вручную после каждой новой заметки.
В этом проекте стандартный Explorer заменён на CardMenu. Это не страница с ручным списком ссылок, а живой компонент Quartz: он строит навигацию из файлового дерева, сворачивает папки, запоминает состояние и живёт в левой колонке рядом с Avatar. Поэтому меню работает как карта сайта: следует структуре content, но оставляет место для быстрых ссылок и аккуратного ручного управления.
Общий контекст layout описан в заметке про кастомизацию Quartz 4.
Где лежит меню
У компонента три основные части:
quartz/components/CardMenu.tsx— TSX-разметка, опции и подключение ресурсов;quartz/components/styles/cardMenu.scss— внешний вид меню;quartz/components/scripts/cardMenu.inline.ts— клиентская логика раскрытия папок и мобильного поведения.
Компонент экспортируется из quartz/components/index.ts, а в layout подключается через Component.CardMenu(). Это принципиальный момент: меню остаётся частью общей сборки Quartz, а не отдельной вставкой на странице. Его можно развивать вместе с темой, ресурсами и поведением сайта, не превращая навигацию в хрупкий Markdown-список.
Текущий layout
В content-layout слева сейчас стоят:
left: [
Component.Avatar(),
Component.MobileOnly(Component.Flex({ components: [...] })),
Component.CardMenu(),
Component.DesktopOnly(homeScrollSequenceOnIndex),
]То есть меню не пытается занять всю левую колонку и стать единственным центром интерфейса. Над ним есть аватар, а на мобильных рядом работает компактный блок поиска, dark mode и reader mode. На главной ниже может появляться scroll sequence. Получается не отдельный виджет, а часть маршрута по сайту: сначала человек видит автора и базовые действия, затем получает понятную карту разделов.
Для list-layout и preview-list layout схема похожая: Avatar, мобильный toolbar и CardMenu.
CardMenuOptions в текущей версии
Актуальный интерфейс опций выглядит так по смыслу:
interface CardMenuOptions {
title?: string
folderDefaultState: "collapsed" | "open"
folderClickBehavior: "collapse" | "link"
useSavedState: boolean
sortFn: (a, b) => number
filterFn: (node) => boolean
mapFn: (node) => void
order: Array<"sort" | "filter" | "map">
showNavButtons: boolean
navButtons: Array<{ label: string; href: string }>
footerText?: string
drawerComponent?: QuartzComponent
}Важное отличие от старого текста: навигационные кнопки не удалены. Сейчас в default options включён showNavButtons: true, а список кнопок такой:
navButtons: [
{ label: "Контакты", href: "/Кoнтакты" },
{ label: "RSS", href: "/index.xml" },
{ label: "Архив", href: "/tags" },
]Меню фильтрует папку tags, сортирует элементы с localeCompare и numeric: true, а состояние раскрытых разделов хранит в localStorage, если включён useSavedState. Для читателя это даёт простое ощущение: разделы не сбрасываются при каждом переходе, а навигация ведёт себя как привычная рабочая панель.
Как строится HTML
CardMenu.tsx отдаёт контейнер, мобильную кнопку, слот для секций и несколько HTML-шаблонов:
template-card-fileдля обычной страницы;template-card-sectionдля верхних секций;template-card-folderдля вложенных папок;template-card-linkдля навигационных кнопок.
Сами пункты меню заполняются inline-скриптом. Это нормальный паттерн для Quartz: TSX-компонент отдаёт статическую основу и ресурсы, а клиентский скрипт оживляет поведение после загрузки. За счёт этого статья описывает не «магическую» разметку, а понятную связку: каркас, шаблоны и слой поведения.
drawerComponent и объединение ресурсов
У текущего CardMenu есть полезная опция drawerComponent. Она позволяет вставить внутрь меню дополнительный компонент:
{
DrawerComponent && (
<div class="card-menu-drawer-slot">
<DrawerComponent {...props} />
</div>
)
}Чтобы у вложенного компонента не потерялись стили и скрипты, используется concatenateResources:
CardMenu.css = concatenateResources(style, opts.drawerComponent?.css)
CardMenu.beforeDOMLoaded = opts.drawerComponent?.beforeDOMLoaded
CardMenu.afterDOMLoaded = concatenateResources(script, opts.drawerComponent?.afterDOMLoaded)Это важная деталь. Если просто отрендерить чужой компонент внутри меню и забыть про ресурсы, он может выглядеть правильно в HTML, но остаться без CSS или клиентского поведения.
Поведение папок
В меню есть два отдельных решения:
- клик по названию папки может вести на страницу папки;
- клик по пустому месту или кнопке раскрывает/сворачивает секцию.
Для этого в шаблонах есть spacer-элементы и отдельные toggle-кнопки. Такой подход убирает вечную проблему меню, когда пользователь хочет перейти в раздел, а вместо этого случайно раскрывает папку. В проектных разделах это особенно заметно: навигация должна помогать читать материалы по порядку, а не заставлять угадывать, куда именно нажимать.
Мобильная версия
На мобильных используется кнопка-гамбургер с aria-controls. Контент меню открывается как drawer. Состояние и обработчики живут в cardMenu.inline.ts, а не в Markdown.
Практический вывод: если меню не открывается на мобильном, сначала проверять нужно не статью и не content/index.md, а стили cardMenu.scss и inline-скрипт.
Что менять чаще всего
Для текущего проекта обычно хватает трёх точек настройки:
- Изменить
navButtons, если нужны другие быстрые ссылки. - Изменить
filterFn, если какую-то папку надо скрыть из меню. - Изменить
sortFnилиmapFn, если нужен другой порядок или подписи.
Полностью переписывать компонент ради новой ссылки не нужно. В этом и смысл такой кастомизации: оставить основу автоматической, а руками менять только те правила, которые действительно влияют на путь читателя по сайту.
Итог
CardMenu в сайте Smirnoff — это текущая основная навигация, а не черновая замена Explorer. Она работает вместе с Avatar, поиском, dark mode, reader mode и условными блоками layout. Самое полезное в ней — не карточный внешний вид, а то, что меню остаётся data-driven: оно следует структуре content, но даёт достаточно ручного контроля через CardMenuOptions.
Главная мысль простая: хорошее меню в Quartz 4 не должно спорить с файловой структурой и не должно заставлять автора вручную поддерживать карту сайта. CardMenu берёт дерево content за основу, добавляет нужные точки управления и делает проектные страницы доступнее для человека, который пришёл не изучать устройство генератора, а найти нужный материал.