Как сделать закрепленную запись в Jekyll

Закрепленное сообщение – это специальная опция, позволяющая выдернуть запись из хронологического порядка и разместить её выше всех остальных. В этой заметке я со всей серьезностью расскажу, как это можно сделать без плагинов. Большим (ударение на первую гласную) издевательством над здравым смыслом могла бы стать только статья о том, как нечто подобное реализовать при помощи плагинов, которые, как не странно, существуют. Я начинаю. Надеюсь, поисковым роботам понравится.

В водной части поста нам потребуется переменная, которая будет служить указателем (флагом) для сборщика проекта. Я бы назвал эту переменную pinned_post. Логично предположить, что значением это переменной будет либо true, либо false. Т.е., отображать пост в качестве закрепленного сообщения, либо вернуть его на свое законное место с точки зрения хронологии.

В качестве примера:

  ---
  title: Заголовок, который отображается во вкладке браузера
  description: Описание для поискового робота
  header: Заголовок поста
  category: [Название категории]  
  tags: [тэг1, тэг2 и т.д.]
  pinned_post: true
  ---
  Здесь, соответственно, текст вашей статьи…   
  ...  
  ...  

Сейчас наше сообщение помечено, как «закрепленное». Приступим к реализации функционала, который каждое такое сообщение отобразит поверх других сообщений.

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

---
layout: default
---
<ul id="posts">
  {% for post in paginator.posts %}
    <li class="post">
      <h2 class="main-heading">
        <a href="{% if site.baseurl == "/" %}{{ post.url }}{% else %}{{ post.url | prepend: site.baseurl }}{% endif %}">
          {%if post.header %}{{ post.header }}{% else %}{{ post.title }}{% endif %}
        </a>
      </h2>
      <time datetime="{% include date.html date=post.date %}" class="by-line"><i>{% include date.html date=post.date %}</i></time>
      <p>
        {% if post.content contains '<!--section.start-->' and post.content contains '<!--section.end-->' %}
          {{ ((post.content | split:'<!--section.start-->' | last) | split: '<!--section.end-->' | first) | strip_html | truncatewords: 50 }}
        {% else %}
          {{ post.content | strip_html | truncatewords: 50 }}
        {% endif %}
      </p>
      <span>{% if post.excerpt != post.content %}<a href="{{ site.baseurl }}{{ post.url }}" class="more">Читать далее →</a>{% endif %}</span>
    </li>
  {% endfor %}
</ul>

Для удобства предлагаю основную часть разметки перенести в отдельный файл, закинуть его в каталог _includes и при помощи стандартной Liquid конструкции подключить её (разметку) в наш цикл.

Вот что я имею ввиду:

---
layout: default
---
<ul id="posts">
  {% for post in paginator.posts %}
    <li class="post">
      {% include post_preview.html %}
    </li>
  {% endfor %}
</ul>

Согласитесь, что так гораздо нагляднее.

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

Все подключаемые файлы должны находится в каталоге _includes. Jekyll именно там их и будет искать.

Следующий шаг:

---
layout: default
---
<ul id="posts">
  {% for post in site.posts %}
    {% if post.pinned_post == true %}
      <li class="post">
        <span class="pinned_post">Закрепленное сообщение</span>
        {% include post_preview.html %}
      </li>
    {% endif %}
  {% endfor %}

  {% for post in paginator.posts %}
    {% unless post.pinned_post == true %}
      <li class="post">
        {% include post_preview.html %}
      </li>
    {% endunless %}
  {% endfor %}
</ul>

Делаем копию нашего основного цикла и помещаем её сверху. Внутри этого цикла добавляем проверку и какой-нибудь HTML элемент сообщающий читателю о том, что данное сообщение закреплено. В нижнем цикле (в основном) при помощи оператора unless мы избавляемся от выбранной в качестве прикрепленного сообщения заметки. Потому что две копии одного и того же материала нам на сайте ни к чему.

И снова немного теории: на if и for я останавливаться не буду, т.к. что смысл этих операторов вам разумеется известен. А вот про предопределенные переменные немного напишу. Дело в том, что список всех постов нашего блога хранится в переменной site.posts. Поэтому будьте внимательнее, верхний цикл у нас должен быть: {% for post in site.posts %}, нижний: {% for post in paginator.posts %} В paginator.posts попадают посты доступные конкретно этой странице из всей пагинации.

Оператор unless – это логическая конструкция шаблонизатора Liquid. Данный оператор предусмотрен для проверки условия на отрицание. Т.е., если post.pinned_post НЕ является true, условие будет выполнено. И наоборот.

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

В итоге должно получиться:

---
layout: default
---
<ul id="posts">
  {% if paginator.previous_page == nil %}
    {% for post in site.posts %}
      {% if post.pinned_post == true %}
        <li class="post">
          <span class="pinned_post">Закрепленное сообщение</span>
          {% include post_preview.html %}
        </li>
      {% endif %}
    {% endfor %}
  {% endif %}
	
  {% for post in paginator.posts %}
    {% unless post.pinned_post == true %}
      <li class="post">
        {% include post_preview.html %}
      </li>
    {% endunless %}
  {% endfor %}
</ul>

Я сделал эту проверку привычным для себя способом. Наверное, её можно сделать как-то иначе. Например, так: {% if page.url == “/” %} Если честно, я этот вариант не проверял. Думаю, что будет работать. Куда-ж оно денется? ))

Задача решена. Как видите, всё оказалось довольно просто и никакие плагины здесь ни к чему.
Издевательством над здравым смыслом законченно. Удачного блоггинга!