Предыдущие посты по этой теме: 1, 2, 3. В них я описал, как создать блог на базе форка (копии) одной из тем оформления (я выбрал тему minima) генератора статических сайтов Jekyll в репозитории веб-сервиса GitHub, развернув его на хостинге GitHub Pages.

Адрес блога: https://ilyachalov.github.io. Адрес репозитория с исходным кодом.

Ранее я описал ряд изменений, которые внес в форк темы minima. В этом посте я хочу описать, как включил и настроил в своем блоге работу с метками (по-английски «tags»).

Что такое «метки» в постах блога

Когда список постов в блоге доходит до нескольких десятков и более, их уже не получается охватить одним взглядом. Появляется нужда в каком-либо способе быстро находить нужную информацию в постах. Для этого обычно вводят разнообразные виды разделения постов по разным темам, а также включают в блог возможность поиска по постам.

Разделение постов по темам и поиск — это не взаимозаменяющие вещи, а дополняющие друг друга. В хорошем блоге эти возможности должны быть обе. Создание поиска по блогу я пока что отложил, решил начать с разделения постов по темам.

В системе понятий генератора Jekyll для разделения постов по темам существуют категории (categories) и метки (tags). Насколько я понимаю, с точки зрения читателя блога между категориями и метками нет особой разницы.

Обычно категории — это крупные темы, их не очень много в отдельном блоге (обычно любой блогер придерживается одной или нескольких крупных тем, хотя, конечно, существуют и отклонения от этого правила). В моем представлении это что-то вроде нескольких папок, в которые автор может раскладывать свои посты. С технической точки зрения файлы постов могут лежать и в одной папке, но читателю в браузере будет отображено так, будто они лежат в разных папках, адреса которых в адресной строке браузера будут выглядеть по-разному.

Я пока не увидел необходимости в реализации категорий в своем блоге, поэтому у меня их нет и, возможно, не будет.

Метки — это более мелкозернистая классификация постов, чем категории. С технической точки зрения реализуются метки немного проще категорий. Если один пост обычно относят к одной категории (реже — к двум-трем), то меток у одного поста может быть сколько угодно, но обычно до десятка.

Смысл метки в том, что читатель блога может «потянуть» за интересную ему метку и получить список всех постов, помеченных этой меткой. Реализовать это можно по-разному, я буду описывать, как я это сделал у себя в блоге.

Первый шаг: пометка постов метками

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

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

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

Метки добавляют в разделе «Front Matter» поста. Меток может либо вообще не быть, либо метка может быть одна, либо меток может быть много. Пример раздела «Front Matter» поста с метками:

---
layout: post
title: "C++: получение массива <br>простых чисел, часть 2"
tags: [C++, алгоритмы, математика, простые числа]
---

Напомню, раздел «Front Matter» поста пишется на языке YAML. Для меток предусмотрено свойство tags. Значение этого свойства в данном случае является массивом, начало и конец которого обозначены квадратными скобками (это один из способов представления последовательности значений в языке YAML, есть и другие, но мне больше всего нравится этот).

Этот массив является массивом строк, в котором каждая строка — отдельная метка. Элементы массива отделяются друг от друга запятыми. Метки-строки можно писать в кавычках, но всё работает и без них. (Подозреваю, что кавычки могут потребоваться в каких-то сложных случаях, но пока я с такими случаями не сталкивался.)

Я использую метки, написанные английскими и русскими буквами, в том числе вперемешку. Разрешаю себе использовать в метках любые символы Юникода (кодировка UTF-8). У меня метки могут состоять как из одного слова, так и из нескольких слов. Слова можно разделять пробелами. Большие и маленькие буквы в метках у меня различаются и сохраняются. То есть при написании меток я постарался не вводить для себя никаких ограничений.

Шаг второй: отображение меток в посте

У меня было два варианта: отображать метки вверху поста, рядом с названием, или отображать их внизу поста (в принципе, можно отображать метки где угодно). Я выбрал отображать внизу, так мне привычнее по ЖЖ.

Макет поста содержится в файле post.html в подпапке _layouts в корне проекта. С помощью языка Liquid метки из раздела «Front Matter» поста доступны в коде макета post.html через объект-коллекцию page.tags. Вот какой код для отображения меток поста в самом посте я там написал:

  {% if page.tags.size > 0 %}
    <div class="post-tags">
      Метки: 
      {% for tag in page.tags %}
        <a href="{{ site.baseurl }}/tags/#{{ tag | slugify }}" class="tag">
          {{- tag -}}
        </a>{% unless forloop.last %}, {% endunless %}
      {% endfor %}
    </div>
  {% endif %}

Этот блок div будет выведен, только если в разделе «Front Matter» поста есть свойство tags с массивом меток. Это обеспечивает инструкция if. В цикле for перебираем имеющиеся метки и выводим их на страницу поста через запятую. Сначала я сделал просто вывод меток без гиперссылок. Позже, когда я создал страницу tags.html архива меток, я дописал здесь гиперссылки на страницу tags.html.

Обратите внимание, что при формировании URL-адреса в свойстве href гиперссылки для создания идентификатора якоря из метки tag используется добавленный в язык Liquid для генератора Jekyll фильтр slugify (тут подробнее). Выше я описал, что никак не ограничиваю себя при написании меток, но в URL-адресах действуют довольно строгие ограничения: нельзя применять какие попало символы. Фильтр slugify преобразует символы метки в символы, которые разрешены в URL-адресах. Сама метка при этом не изменяется.

Шаг третий: создание архива меток

Как я писал выше, предназначение меток в том, чтобы читатель мог «потянуть» за нужную ему метку и получить список постов, помеченных этой меткой. Это можно реализовать множеством способов. Обычно «потянуть» за метку можно, щелкнув по ней в посте. Для этого метки оформляют гиперссылками.

То, что происходит после нажатия на метку-гиперссылку, можно задать очень по-разному очень разными способами. Я решил попробовать самый несложный способ, традиционный для сайтов, созданных с помощью генератора Jekyll.

Согласно этого способа я создал отдельную страницу tags.html в корне проекта. Такую страницу называют «архивом меток», так как, перейдя на эту страницу, обычно можно увидеть все метки, существующие в постах данного блога. Я сделал эту страницу доступной в меню блога из пункта меню «Метки» по следующему постоянному адресу permalink:

https://ilyachalov.github.io/tags/

Сначала я дал этой странице название tags.md и писал ее на смеси языка разметки Markdown и языка программирования Liquid. Однако, выяснилось, что в такой смеси язык Liquid неудобно использовать, так как в языке Markdown отступы имеют значение, а это конфликтует с тем, что мне удобно писать на языке Liquid с отступами, как я привык на других языках программирования. Поэтому я решил, что страницы, на которых использую много кода на языке Liquid, будут у меня с разметкой на языке HTML. Пришлось tags.md переименовать в tags.html.

В структуре объектов генератора Jekyll существует объект site.tags, который содержит коллекцию всех меток, использованных в постах блога. Вот как выглядит у меня на странице архива меток простая реализация отображения всех меток блога и всех постов блога, соответствующих этим меткам:

<!-- Список постов и якоря для меток -->
{% for tag in sorted_tag_names %}
  {% assign original_tag = tag | split: "|" | last %}
  <h3 id="{{ original_tag | slugify }}">{{ original_tag }}</h3>
  <ul>
    {% for post in site.tags[original_tag] %}
      <li><a href="{{ post.url }}">{{ post.title | remove: "<br>" }}</a></li>
    {% endfor %}
  </ul>
{% endfor %}

Здесь два цикла for, один вложен в другой. Во внешнем цикле for перебираем все метки блога. Очередную метку в цикле отображаю на странице с помощью элемента h3 языка HTML.

С помощью конструкции site.tags[<метка>] получаем коллекцию постов, помеченных указанной меткой. Во внутреннем цикле for перебираем посты очередной метки и отображаем их на странице архива меток в виде списка с помощью элемента ul (ненумерованный список) языка HTML. В этом списке я вывожу только названия постов в виде гиперссылок: можно сразу перейти на страницу интересующего поста.

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

Я такую организацию ранее не использовал и думал, что это будет неудобно. Оказалось, всё работает быстро, а неудобств никаких вообще не возникает. В целом я пока доволен такой организацией работы с метками.

Сортировка меток в архиве меток

По умолчанию метки в объекте-коллекции site.tags не отсортированы (думаю, это недоработка со стороны авторов генератора Jekyll). Очевидно, что для читателя блога очень неудобно было бы рыться в архиве меток, в котором метки расположены в каком-то непонятном ему порядке.

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

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

.NET и C#, CSS, Flexbox, draw.io, Яндекс.Словарь, алгоритмы, шахматы

Фильтр sort сортирует коллекцию элементов в так называемом лексикографическом порядке. Как это работает? При сортировке фильтр sort ориентируется на номера символов в таблице Юникода. То есть сравниваются эти номера, и элементы коллекции сортируются в порядке этих номеров.

Какие тут есть тонкости? Знаки пунктуации в таблице Юникода идут раньше букв. В принципе, меня устраивает то, что метка .NET и C# из-за этого идет раньше всех после сортировки фильтром sort.

Буквы английского алфавита в таблице Юникода идут раньше букв русского алфавита. Меня устраивает то, что после сортировки фильтром sort все метки, которые начинаются на английские буквы, встанут раньше меток, начинающихся на русские буквы.

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

.NET и C#, CSS, draw.io, Flexbox, алгоритмы, шахматы, Яндекс.Словарь

То есть я хочу, чтобы сортировка производилась без учета регистра букв. При этом метка draw.io должна встать раньше метки Flexbox, а метка Яндекс.Словарь должна встать в самый конец коллекции.

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

.NET и C#, CSS, draw.io, Flexbox, Яндекс.Словарь, алгоритмы, шахматы

То есть метка draw.io встает в нужное место, а метка Яндекс.Словарь остается на том же самом месте (должна встать в конец коллекции). Я не знаю точно, в чем там дело, а свои догадки тут излагать не буду. Пришлось написать на языке Liquid код для обхода этой проблемы. Вот как это выглядит у меня:

{% capture tag_names %}
  {% for tag in site.tags %}
    {{- tag[0] | downcase }}|{{ tag[0] }}{% unless forloop.last %}:::{% endunless -%}
  {% endfor %}
{% endcapture %}

{% assign sorted_tag_names = tag_names | strip | split: ":::" | sort %}

С помощью оператора capture языка Liquid я создаю строку tag_names, содержащую все метки блога, разделенные последовательностью символов :::. Каждая метка предварительно преобразована в нижний регистр с помощью фильтра downcase. К каждой метке через символ | «приклеена» эта же метка в оригинальном написании, без приведения в нижний регистр.

Зачем это всё потребовалось? В последней строке вышеприведенного кода применяем к строке tag_names фильтр sort для сортировки. Поскольку все метки были перед этим приведены в нижний регистр, сортировка выполняется желаемым образом, описанным выше. Однако, для отображения метки на странице архива меток берем не ту часть, которая была приведена в нижний регистр и использовалась для сортировки, а «приклеенную» к ней часть строки с оригинальным названием метки (до приведения к нижнему регистру).

Такой подход несколько сложноват, но дает желаемый результат сортировки, при этом позволяя не менять исходное название метки. Я не знаю, как это сделать проще. Изложенный вариант у меня работает успешно.

Облако меток в архиве меток

Если страница с архивом меток организована так, как описано выше, при большом количестве меток (на данный момент у меня их почти три десятка) эта страница получается настолько длинной, что ее нельзя охватить одним взглядом, она выходит за границы окна браузера.

Кроме описанного ранее предназначения меток у них есть еще одно предназначение: метки должны давать читателю некоторое общее представление о тематике блога и интересах автора. Если их нельзя охватить одним взглядом, то это предназначение не выполняется.

Кроме этого, я хотел дать возможность читателю блога получить гиперссылку с якорем к любой метке на странице архива меток. Это можно сделать по-разному.

В итоге эти две цели я решил закрыть одним инструментом — облаком меток.

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

Вот как выглядит начало страницы (верх) архива меток у меня (картинку можно открыть в реальном размере щелчком левой кнопкой мыши по ней):

Подведение некоторых итогов

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

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

Если получится что-нибудь интересное и будет для этого время, напишу продолжение этой серии постов.