Відразу попереджую — спосіб досить "криворукий", тому прошу програмістів "від Бога" не бити мене ногами поганими словами, а краще підказати, як зробити розумніше 😉

Працювати будемо на прикладі даного блоґу. Отже, як бачиш, меню організоване за ієрархією, тобто є коренева категорія "Софт", а в ній вкладені категорії "Windows", "Linux", "Android" і т. д. Зараз я розповім, як такого ефекту можна добитися при використанні генератора статичних сайтів Eleventy.

Створюємо ієрархію тек

Поглянь, як організовані посилання на цьому блозі.

  • Посилання на дану статтю: /web/eleventy/nested-categories
  • Посилання на сторінку категорії: /category/web/eleventy

Оскільки я пишу статті в обмеженій кількості категорій (на сьогодні — 26), я просто створюю теки для кожної з них. Eleventy настільки розумний, що автоматично перетворює назви тек у частини посилання. Тобто саму статтю я розмістив у теці /web/eleventy/nested-categories із назвою index.md, а сторінку категорії — у теці /category/web/ під назвою eleventy.njk. Після генерування файл index.md стане файлом index.html, а файл eleventy.njk — ...теж файлом index.html, але у додатково створеній теці eleventy. Це такі чари генератора, докладніше можеш почитати згодом 😉

Структура сторінки категорії

Зі статтею нічого робити не потрібно, вона автоматично реєструється в потрібній категорії, оскільки лежить у потрібній теці. Але як винести сторінку категорії в окреме меню, та ще й з ієрархією? Для цього потрібно додати до неї трішки інформації:

---
title: Eleventy
eleventyNavigation:
  key: Eleventy
  parent: Веб
  order: 4
pagination:
  data: collections.eleventy
  size: 12
  alias: posts
  reverse: true
---
  • title — зрозуміло, назва;
  • eleventyNavigation — список параметрів для створення меню;
    • key — ключ елемента меню, використовується, наприклад, для ідентифікації батьківського елемента;
    • parent — отут, власне, і вказується ключ key батьківського елемента. В кореневих пунктах меню пропускаємо;
    • order — порядок сортування пунктів меню. Вказується на випадок, якщо хочеш розмістити пункти меню у власному порядку;
  • pagination — "пагінація", тобто розбивання на сторінки;
    • data — колекція, яка виводитиметься на даній сторінці категорії. Колекції створюються для кожної категорії окремо у файлі eleventy.js, про це ще поговоримо нижче;
    • size — кількість статей на сторінці. Наприклад, ти маєш у категорії 150 постів, які виводяться по 12 на сторінку із посиланнями внизу на наступні сторінки. Ось це "12" і вказуєш тут;
    • alias — символічна заміна довгої назви колекції із пункту data. Корисна тим, що можна вказати для всіх сторінок категорій однаковий alias, а потім легко використати його глобально в шаблоні категорій;
    • reverse — сортування за спаданням. Тобто якщо статті сортуються за датою, то найновіші будуть зверху.

Виводимо меню в шаблоні

Після того, як заповниш за зразком вище всі свої сторінки категорій, необхідно встановити плаґін eleventy-navigation. Для цього у терміналі із кореневої теки введи:

npm install @11ty/eleventy-navigation --save-dev

Тепер перейди у файл eleventy.js в кореневій теці та зареєструй плаґін:

const eleventyNavigationPlugin = require("@11ty/eleventy-navigation");
module.exports = function(eleventyConfig) {
  eleventyConfig.addPlugin(eleventyNavigationPlugin);
};

У цьому ж файлі зареєструй свої категорії:

eleventyConfig.addCollection("catMenu", function(collectionApi) {
  return collectionApi.getFilteredByGlob("./category/*/*.njk");
});

eleventyConfig.addCollection("web", function(collectionApi) {
  return collectionApi.getFilteredByGlob("./web/**/*.md");
});

eleventyConfig.addCollection("php-html-css", function(collectionApi) {
  return collectionApi.getFilteredByGlob("./web/php-html-css/**/*.md");
});

eleventyConfig.addCollection("joomla", function(collectionApi) {
  return collectionApi.getFilteredByGlob("./web/joomla/**/*.md");
});

eleventyConfig.addCollection("wordpress", function(collectionApi) {
  return collectionApi.getFilteredByGlob("./web/wordpress/hide-simple-products-which-are-in-grouped-product**/*.md");
});

eleventyConfig.addCollection("eleventy", function(collectionApi) {
    return collectionApi.getFilteredByGlob("./web/eleventy/**/*.md");
});

Усі колекції, крім першої, будуть використовуватися для виведення статей окремих категорій (повернись вище і згадай — ми вносили їх на сторінках категорій у поле pagination.data на прикладі даної категорії collections.eleventy). А перша колекція catMenu служить якраз для організації меню категорій. Вона захоплюватиме усі сторінки з теки category. Для цього в потрібному місці шаблона достатньо внести наступний запис:

{{ collections.catMenu | eleventyNavigation | eleventyNavigationToHtml | safe }}

Виведення статей окремої категорії

Для виведення статей окремої категорії можна використати як кожну сторінку категорії, так і один готовий шаблон. Наприклад, я створив у теці category файл category.json із таким вмістом:

{ "layout": "layout/category.njk" }

Він повідомляє генератор Eleventy про те, що всі сторінки категорій будуть використовувати заданий шаблон category.njk. А у файлі _data/layout/category.njk додається цикл із виведенням статей:

{% for post in posts %}
    <div class="single-post">
        <a href="{{ post.url }}">
            <h3 class="title">{{ post.data.title }}</h3>
        </a>
        {% if post.data.description %}
            <p class="description">{{ post.data.description }}</p>
        {% endif %}
    </div>
{% endfor %}

Проблема із посиланням на категорію

Досі все йшло майже гладко, окрім того, що довелося сторінку кожної статті категорії створювати окремо, а також реєструвати колекцію для кожної категорії. Та справжня проблема виникне тоді, коли ти спробуєш розмістити посилання на категорію в окремій статті чи на сторінці блогу, де розміщенні статті з усіх категорій. Полягає проблема в тому, що всі попередні дії призвели до створення лише назви категорії, але не посилання на неї.

Я не знайшов іншого вирішення проблеми, ніж створити окремий список категорій із вказанням посилання для кожної вручну. Вийшло щось отаке.

Створюємо глобальний масив із списком категорій

У глобальній теці _data створюємо файл allcategories.js із наступним вмістом:

module.exports = {
    catList: [
        {
          title: 'Веб',
          slug: 'web',
        },
        {
          title: 'PHP, HTML, CSS',
          slug: 'web/php-html-css',
        },
        {
          title: 'Joomla',
          slug: 'web/joomla',
        },
        {
          title: 'WordPress',
          slug: 'web/wordpress',
        },
        {
          title: 'Eleventy',
          slug: 'web/eleventy',
        },
    ]
};

Як бачиш, для кожної категорії я прописав назву та частину посилання. Дані посилання НЕ генерують URL-адреси самої сторінки категорії, а лише підставляють потрібну частину посилання при запиті.

Сам запит виглядатиме отак для шаблона окремої статті:

<a href="
{% for catName in allcategories.catList %}
{% if catName.title == categories %}/category/{{ catName.slug }}/{% endif %}
{% endfor %}
">{{ categories }}</a>

Тут ми використовуємо змінну categories для виявлення нашої категорії, потім зрівнюємо її зі списком catList файлу allcategories.js, а, знайшовши, піставляємо частину посилання до вручну прописаної частини /category. Тобто, фактично самі "малюємо" посилання.

А ось так виглядатиме запит для шаблона блогу категорій:

{% if post.data.categories %}
{% set category %}{{ post.data.categories }}{% endset %}
<i class="fas fa-tags"></i> <a href="
{% for catName in allcategories.catList %}
{% if catName.title == category %}/category/{{ catName.slug }}/{% endif %}
{% endfor %}
">{{ category }}</a>
{% endif %}

Фактично процес той самий, тільки категорія виявляється за допомогою ключа post.data.categories, а не categories, оскільки ми розбираємо в блозі УСІ категорії, отже для кожної статті шукаємо ЇЇ категорію, а не єдину, як у попередньому випадку. Також для зручності значення post.data.categories присвоюється змінній category.

Пагінація

Останнім жестом буде створення пагінації з посиланнями, оскільки, якщо ти пам'ятаєш, ми вказали відображення по 12 статей на сторінку. Щоби відобразити посилання на решту, додаємо в шаблон блога та категорій ось такий код:

{% if pagination.href.next or pagination.href.previous %}
<ul>
  <li>{% if page.url != pagination.href.first %}<a href="{{ pagination.href.first }}">First</a>{% else %}First{% endif %}</li>
  <li>{% if pagination.href.previous %}<a href="{{ pagination.href.previous }}">Previous</a>{% else %}Previous{% endif %}</li>
{% for pageEntry in pagination.pages %}
  <li>
    {% if page.url == pagination.hrefs[ loop.index0 ] %}
        {{ loop.index }}
    {% else %}
        <a href="{{ pagination.hrefs[ loop.index0 ] }}">{{ loop.index }}</a>
    {% endif %}
  </li>
{% endfor %}
  <li>{% if pagination.href.next %}<a href="{{ pagination.href.next }}">Next</a>{% else %}Next{% endif %}</li>
  <li>{% if page.url != pagination.href.last %}<a href="{{ pagination.href.last }}">Last</a>{% else %}Last{% endif %}</li>
</ul>
{% endif %}

Чому все так складно?

— Навіщо придумувати велосипед, — скажеш ти, — створюючи шаблони для кожної категорії, якщо можна було просто вказати для одного шаблону категорій в полі pagination.data колекцію всіх категорій замість колекції однієї категорії, і все б працювало?

Власне, вся справа в пагінації. Якщо створити колекцію для кожної категорії, і в кожній зі сторінок категорій прописати окрему колекцію, то в пагінації створюватимуться посилання для наступних N статей (в даному випадку 12) даної категорії. А якщо вказати колекцію ВСІХ категорій, то в пагінації створюватимуться посилання на наступні... категорії. Тобто кожна з цих сторінок буде показувати ВСІ статті з однієї конкретної (поточної) категорії. Саме тому все так складно 😉

Що далі?

Що робити, коли прийдуть нові ідеї, і доведеться створити на сайті нову категорію? Для прикладу створимо нову категорію "Drupal", яка буде дочірньою до категорії "Веб".

  1. Створити теку для статей категорії — /web/drupal. У ній будемо створювати сторінки зі статтями.
  2. Створити файл для відображення сторінки категорії — category/web/drupal.njk. В нього скопіювати дані із сусідньої категорії, змінивши необхідну інформацію.
  3. Додати назву та частину посилання у файл _data/allcategories.js. Тут буде title: 'Drupal', slug: 'web/drupal'.

І звичайно, якщо виникнуть додаткові запитання — задавай у коментарях чи у соцмережах (посилання на сторінці "Контакти"), будемо вирішувати разом. Також буду вдячний, якщо хтось порадить простіший спосіб організації вкладених категорій на ELeventy.

Дякую за те, що дочитуєш до кінця! На все добре!