Iterate over a list of posts and compare the date - jekyll

This is my markup for the list view of posts:
<ul class="eventlist">
{% for post in site.posts reversed %}
<li class="eventlist-element">
{% if post.href %}
<a class="eventlist-element__link" href="{{ post.href }}">
{% else %}
<a class="eventlist-element__link" href="{{ post.url | prepend: site.baseurl }}">
{% endif %}
<time datetime="{{ post.date | date: "%Y-%m-%d" }}"class="eventlist-element__date">
<span class="eventlist-element__date-day">{{ post.date | date: "%d" }}</span>
<span class="eventlist-element__date-day-name">{{ post.date | date: "%a" }}</span>
</time>
<div class="eventlist-elemnt-infowrap">
<span class="eventlist-element__title">{{ post.title }}</span>
<span class="eventlist-element__venue">{{ post.venue }}</span><span class="eventlist-element__time">{{ post.time }}</span><span class="eventlist-element__ticket">{{ post.ticket }}</span>
</div>
<span class="eventlist-element__bullets"></span>
</a>
</li>
{% endfor %}
</ul>
I want to iterate over all posts and want to compare the post.date. If we have let's say 3 posts on the same day I want markup a for post 1 and for post 2 and 3 markup b. I've tried a few things but nothing worked so far.

Try something along these lines:
{% assign previousDate = site.date %}
{% for post in site.posts %}
{% if post.date == previousDate %}
// some other class
{% endif %}
{% assign previousDate = post.date %}
{% endfor %}
Basically you assign the date of the previous post to some variable and compare it with the current posts date. If they are the same you apply a different style. Not sure if you can use date as is, does it also contain hours, minutes,..? If so you will want to filter it to get out only the year, month, day.

Some code :
{% assign defaultStyle = 'defaultStyle' %}
{% assign alternateStyle = 'alternateStyle' %}
{% assign counter = 1 %}
{% for post in site.posts %}
{% if previousPostDate %}
{% if previousPostDate == post.date %}
{% capture counter %}{{ counter | plus: 1 }}{% endcapture %}
{% else %}
{% assign counter = 1 %}
{% endif %}
{% endif %}
{% if counter | to_number > 1 %}
{% assign defaultStyle = alternateStyle %}
{% endif %}
<div class="{{ defaultStyle }}">
<h3>{{ post.title }}</h3>
<p>{{ post.date }}</p>
</div>
{% assign previousPostDate = post.date %}
{% endfor %}
The code implements :
If I'm not the first post in a date group my style is different.
Your logic was
If I'm not the first post in a date group of three posts (or more) my style is different.
The part date group of three posts or more implies that you already know how many "same date posts" you have and makes the code more complex.

Related

Single Index Blog Post for Multiple Series Posts in Jekyll

I have several blog posts that fall under one umbrella blog post. For example, I have several posts about SQL Zoo tutorials, but I want to be able to link them all up to one "umbrella" post so that I only have one SQL Zoo post on the index page of my blog. I got this idea from: https://codeasashu.github.io/implement-series-post-jekyll-part-2/ and tried to follow the instructions, but now my series post does not show up on my index page. I have the following code in a file called post-series.html located in my _includes folder:
{% assign seriesarray = '|' | split : '|' %}
{% assign seriestitle = '' %}
{% assign serieslabel = '' %}
{% assign sortedposts = (site.posts | sort: 'date') %}
{% for post in sortedposts %}
{% if post.series and page.series_slug != nil and post.series == page.series_slug %}
{% capture postitem %} <li> {{ post.title }} </li> {% endcapture %}
{% assign seriesarray = seriesarray | push: postitem %}
{% assign seriestitle = 'Posts in this series' %}
{% assign serieslabel = 'Series Post' %}
{% elsif post.series != nil and page.series != nil and page.series == post.series %}
{% assign pageurl = page.url | split:'/' | last %}
{% assign posturl = post.url | split:'/' | last %}
{% if pageurl != posturl %}
{% capture postitem %} <li> {{ post.title }} </li> {% endcapture %}
{% else %}
{% capture postitem %} <li> {{ post.title }} </li> {% endcapture %}
{% endif %}
{% assign seriesarray = seriesarray | push: postitem %}
{% endif %}
{% if post.series_slug != nil and page.series != nil and page.series == post.series_slug %}
{% capture series_title %} {{ post.title }} {% endcapture %}
{% assign seriestitle = 'This posts is part of series - ' | append: series_title %}
{% assign serieslabel = 'This posts is part of series - ' | append: series_title %}
{% endif %}
{% endfor %}
{% capture serieslayout %}
{% if seriesarray.size > 0 %}
<hr />
<div class="panel">
<div class="panel-body">
<h4> {{ seriestitle }} </h4>
<ul id="post-series-list">
{% endif %}
{% for post in seriesarray %} {{ post }} {% endfor %}
{% if seriesarray.size > 0 %} </ul> </div> </div> {% endif %}
{% endcapture %}
and the following code from my index.html file in the root of my directory:
---
layout: index
---
<div id="home">
<h1>{{ site.title }}</h1>
<hr />
<ol class="posts">
{% for post in paginator.posts %}
{% assign seriesPost = nil %}
{% if post.series == nil %}
{% if post.series_slug != nil %} {% assign seriesPost = '(Series)' %} {% endif %}
<li class="post-listing">
<img class="post__image" src="/static/img/{{ post.cover_image}}" alt="{{ post.cover_alt }}" />
<div class="post__text">
<a class="post__title" href="{{ post.url }}">{{ post.title }}</a><br>
<span>
{{ post.date | date_to_string }} •
{% assign words = post.content | number_of_words %}
{% if words < 360 %}
1 min read
{% else %}
{{ words | divided_by:180 }} min read
{% endif %}
</span>
{{ post.excerpt }}
</div>
</li>
{% endif %}
{% endfor %}
</ol>
<!-- <div class="sidebar-right sidebar"></div> -->
<!-- <ul>
{% for post in paginator.posts %}
<li>
{{ post.title }}
{{ post.excerpt }}
</li>
{% endfor %}
</ul> -->
<!-- Pagination links -->
{% if paginator.total_pages > 1 %}
<ul class="pagination pagination-sm">
{% if paginator.previous_page %}
<li>«</li>
{% else %}
<li class="disabled"><span aria-hidden="true">«</span></li>
{% endif %}
<li>First</li>
{% for page in (1..paginator.total_pages) %}
{% if page == paginator.page %}
<li class="active"><a>{{ page }}<span class="sr-only">(current)</span></a></li>
{% elsif page == 1 %}
<li>{{ page }}</li>
{% else %}
<li>{{ page }}</li>
{% endif %}
{% endfor %}
<li>Last</li>
{% if paginator.next_page %}
<li>»</li>
{% else %}
<li class="disabled"><span>»</span></li>
{% endif %}
</ul>
{% endif %}
</div><!-- end #home -->
My full repo can be found here: https://github.com/thedatasleuth/thedatasleuth.github.io
In your index.html, {% if post.series == nil %} simply bares posts containing a series: someserie front matter variable to be printed.
For the second problem (note that on SO, you are supposed to ask one question at a time) :
Umbrella post always have series_slug: "My serie slug" in front
matter.
Serie's posts always have series: "My serie slug" in front
matter, and this must be strictly equal to umbrella page series_slug. (eg : you have a post with published: false and series: "SQL Zoology" that will not appear in SQL Zoo serie if you publish it.)
In _layouts/post.html remove {% include series.html %}.
In _includes/post-series.html replace all your code by the following :
{% comment %} #### On an umbrella page {% endcomment %}
{% if page.series_slug != nil %}
{% assign key = page.series_slug %}
{% assign title = page.title %}
{% assign url = page.url %}
{% assign sentence = "All posts in this serie :" %}
{% endif %}
{% comment %} #### On a serie page {% endcomment %}
{% if page.series != nil %}
{% assign key = page.series %}
{% assign umbrella_page = site.posts | where: 'series_slug', key | first %}
{% assign title = umbrella_page.title %}
{% assign url = umbrella_page.url %}
{% assign series_posts = site.posts | where: "series", key %}
{% for post in series_posts %}
{% if post.url == page.url %}
{% assign idx = forloop.index %}
{% endif %}
{% endfor %}
{% capture sentence %}
This article is <strong>Part {{ idx }}</strong> in a <strong>{{ series_posts.size }}-Part</strong> in {{ title }} serie
{% endcapture %}
{% endif %}
{% if page.series_slug != nil or page.series != nil %}
{% assign series_posts = site.posts | where: "series", key %}
<hr />
<div class="panel">
<div class="panel-body">
{% if page.series_slug != nil %}
{% assign key = page.series_slug %}
{% assign title = page.title %}
{% assign url = page.url %}
{% endif %}
<h4>{{ sentence }}</h4>
<ul id="post-series-list">
{% for post in series_posts %}
<li>
{% if page.url == post.url %}
This post : {{ post.title }} - part {{ forloop.index }}
{% else %}
{{ post.title }} - part {{ forloop.index }}
{% endif %}
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
The problem you are facing
You forgot to add the series and series_slug YML variables. That is why it is not working in your case. You seem to not fully understand how the code works, probably due to the complexity of the solution. Therefore, I added another (much simpler) solution, that might fit your needs equally well.
A better/simpler solution
Simply add a YML variable called group: groupname to each post you want in a group. Do NOT skip any of the posts during pagination. Next, list the posts with the same group on the footer of each post (in your page/post layout) with the code below. Finally, add 'part 1', 'part 2', etc. to the post names in the series.
{% if post.group != nil %}
<ul>
{% for post in site.posts %}
{% if post.group == page.group %}
<li>{{ post.title }}</li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
PS. If you really want to skip some posts in the pagination, I would create an universal solution. Add a skip_pagination boolean to the Front Matter (YML) and see if it is true in the pagination loop. This enables you to skip any post in the paginator.

jekyll/liquid - how to pass variable as an argument to post.title contains statement?

I'm trying to filter results of the loop based on hashtags included in titles. If done manually it works perfectly:
<article class="post-preview">
<a href="{{ post.url | prepend: site.baseurl | replace: '//', '/' }}">
{% if post.title contains "#hashtag" %}
<br>
<div class="float-left">
{% include read-time.html %}
</div>
<h2 class="post-title">{{ post.title }}</h2>
{% if post.subtitle %}
<h3 class="post-subtitle">{{ post.subtitle }}</h3>
{% else %}
<h3 class="post-subtitle">{{ post.excerpt | strip_html | truncatewords: 15 }}</h3>
{% endif %}
</a>
<p class="post-meta float-right">Posted by
{% if post.author %}
{{ post.author }}
{% else %}
{{ site.author }}
{% endif %}
on {{ post.date | date: '%B %d, %Y' }}</p><br>
{% endif %}
</article>
what I'd like to achieve is to be able to use variable instead of string:
e.g.
{% if post.title contains "{{ content }}" %} - (this way it doesn't work)
instead of
{% if post.title contains "#hashtag" %}
Is there a way to pass variable to post.title contains, or any other way to achieve my goal i.e. filtering results of for loop based on the hashtag in the title ?
Simply write the variable name after contains. Example:
{% assign a = "Sample text" %}
{% assign b = "text" %}
{% if a contains b %}
Variable 'a' contains the value of variable 'b'! :)
{% endif %}
In your case, a will be post.title and b will be content.

How to use Jekyll to sort posts by a custom YAML front matter variable?

I'm trying to create a page in my Jekyll site that will display a custom variable and list the posts that contain that custom variable.
I have created a movie review blog using a template Thiago Rossener created:
Thiago's Template: https://github.com/thiagorossener/jekflix-template
My Site: https://www.howdareyoureview.com/
in each post, I have defined custom variables in the YAML front matter that relate to the movie's details (i.e actor, director score, etc.)
for example:
---
layout: post
title: "Baby Driver"
image: 'https://res.cloudinary.com/how-dare-you-review/image/upload/c_fill,h_399,w_760/v1529865791/baby-driver.png'
tags:
- action
score: 72
director: Edgar Wright
written-by: Edgar Wright
staring:
- Ansel Elgort
- Lily James
- Eiza González
- Jon Hamm
- Jamie Foxx
---
I want to create pages exactly like the tags page that already exists in this template:
https://www.howdareyoureview.com/tags/
except I would want to sort by director, starring, etc. instead of by tags.
the tags page is created using the following code in a tags.html file:
---
layout: minimal
title: "#Tags"
permalink: /tags/index.html
description: "Procure por sua #tag favorita."
---
<div class="tags">
{% assign tags_list = site.tags %}
{% if tags_list.first[0] == null %}
{% for tag in tags_list %}
{{ tag }}
{% endfor %}
{% else %}
{% for tag in tags_list %}
{{ tag[0] }}
{% endfor %}
{% endif %}
{% assign tags_list = nil %}
</div>
{% for tag in site.tags %}
<div class="tag-wrapper">
<span class="tag-title" id="{{ tag[0] | slugify }}">{{ tag[0] }}</span>
<ul class="post-list">
{% assign pages_list = tag[1] %}
{% for post in pages_list reversed %}
{% if post.title != null %}
{% if group == null or group == post.group %}
<li>{{ post.title }}<span class="entry-date"><time datetime="{{ post.date | date_to_xmlschema }}" itemprop="datePublished">{{ post.date | date: "%m/%d/%Y" }}</time></li>
{% endif %}
{% endif %}
{% endfor %}
{% assign pages_list = nil %}
{% assign group = nil %}
</ul>
</span>
</div>
{% endfor %}
To achieve this for the custom variables I created I tried replacing "tag/tags" with director and saving the file out into to root directory as "directors.html" but the page is blank.
---
layout: minimal
title: "#Directors"
permalink: /directors/index.html
description: "Procure por sua director favorita."
---
<div class="directors">
{% assign directors_list = site.director %}
{% if directors_list.first[0] == null %}
{% for director in directors_list %}
{{ director }}
{% endfor %}
{% else %}
{% for director in directors_list %}
{{ director[0] }}
{% endfor %}
{% endif %}
{% assign directors_list = nil %}
</div>
{% for director in site.director %}
<div class="director-wrapper">
<span class="director-title" id="{{ tag[0] | slugify }}">{{ director[0] }}</span>
<ul class="post-list">
{% assign pages_list = director[1] %}
{% for post in pages_list reversed %}
{% if post.title != null %}
{% if group == null or group == post.group %}
<li>{{ post.title }}<span class="entry-date"><time datetime="{{ post.date | date_to_xmlschema }}" itemprop="datePublished">{{ post.date | date: "%m/%d/%Y" }}</time></li>
{% endif %}
{% endif %}
{% endfor %}
{% assign pages_list = nil %}
{% assign group = nil %}
</ul>
</span>
</div>
{% endfor %}
Since the code and the concept is exactly the same as the way tags are populated - I cannot understand why this doesn't work - I'm hoping someone can assist!
Here is my entire directory for reference:
https://github.com/howdareyoureview/howdareyoureview.github.io
Tags page uses site.tags, which is an array of site.posts grouped by tag, created by Jekyll at generation time.
You're trying to replicate by targeting site.directors but this expected array doesn't exist. But, you can use the group_by filter to achieve your goal.
<div class="directors">
{% assign directors = site.posts | group_by: 'director' | sort: "name" %}
{% for director in directors %}
{% if director.name == "" %}
{% assign name = "Anonymous" %}
{% else %}
{% assign name = director.name %}
{% endif %}
{{ name }}
{% endfor %}
</div>
{% for director in directors %}
<div class="director-wrapper">
{% if director.name == "" %}
{% assign name = "Anonymous" %}
{% else %}
{% assign name = director.name %}
{% endif %}
<span class="director-title" id="{{ name | slugify }}">{{ name | debug }}</span>
<ul class="post-list">
{% assign pages_list = director.items %}
{% for post in pages_list reversed %}
<li>{{ post.title }}<span class="entry-date"><time datetime="{{ post.date | date_to_xmlschema }}" itemprop="datePublished">{{ post.date | date: "%m/%d/%Y" }}</time></li>
{% endfor %}
</ul>
</span>
</div>
{% endfor %}
Tip : You can use the inspect filter to debug your vars. {{ myvar | inspect }}

Jekyll & Liquid: Output category list with post count?

I'm somehow stuck and don't find the right way to do it.
I want to output a category list with the number of posts in each categorie.
I got this far: https://paste.xinu.at/dOLtod/
but I never managed to get the real count. I tried so many ways and nothing worked, for example going through each post and checked on every category if it equals {{ category | first }}.
Here is the code without the count:
<ul class="dropdown-menu">
{% for category in site.categories %}
<li class="camelize_me">
<a href="/tags/{{category | first}}/">
{{category | first }}
<span class="badge">
<!-- Post count here -->
</span>
</a>
</li>
{% endfor %}
</ul>
Has anyone an idea to get this done?
I have written a code snippet that not only shows post count in each category, but also navigates to the posts of a specific category which got clicked. I hope you find it helpful:
<ul class="tag-box inline">
{% assign tags_list = site.categories %}
{% if tags_list.first[0] == null %}
{% for tag in tags_list %}
<li>{{ tag | capitalize }} <span>{{ site.tags[tag].size }}</span></li>
{% endfor %}
{% else %}
{% for tag in tags_list %}
<li>{{ tag[0] | capitalize }} <span>{{ tag[1].size }}</span></li>
{% endfor %}
{% endif %}
{% assign tags_list = nil %}
</ul>
{% for tag in site.categories %}
<h2 id="{{ tag[0] }}">{{ tag[0] | capitalize }}</h2>
<ul class="post-list">
{% assign pages_list = tag[1] %}
{% for post in pages_list %}
{% if post.title != null %}
{% if group == null or group == post.group %}
<li>{{ post.title }}<span class="entry-date"><time datetime="{{ post.date | date_to_xmlschema }}" itemprop="datePublished">{{ post.date | date: "%B %d, %Y" }}</time></li>
{% endif %}
{% endif %}
{% endfor %}
{% assign pages_list = nil %}
{% assign group = nil %}
</ul>
{% endfor %}
Solution: {{ category | last }} has all my posts, so {{ category | last | size }} displays the count. I got help on the IRC. :)
An incremental improvement over Hossain's answer, which sorts the categories. Tested with Jekyll 3.3.1:
<h1 class='tag'>Blog Posts Sorted By Category</h1>
{% assign sorted_categories = site.categories | sort %}
{% for tag in sorted_categories %}
<h2 class='tag' id="{{ tag[0] }}">{{ tag[0] | capitalize }}</h2>
<ul class="post-list">
{% assign pages_list = tag[1] %}
{% for post in pages_list %}
{% if post.title != null %}
{% if group == null or group == post.group %}
<li><a href="{{ site.url }}{{ post.url }}">
<span class="entry-date"><time datetime="{{ post.date | date_to_xmlschema }}" itemprop="datePublished">{{ post.date | date: "%B %d, %Y" }}</time></span>
•
{{ post.title }}
</a></li>
{% endif %}
{% endif %}
{% endfor %}
{% assign pages_list = nil %}
{% assign group = nil %}
</ul>
{% endfor %}

Jekyll/Liquid Templating: How to group blog posts by year?

I'm rewriting my blog to use Jekyll. Jekyll uses the Liquid templating language so it makes it a little more difficult to learn how to customize.
I'd like to group my list of blog posts by year. How would I write the Liquid code to be able to do this?
{% for post in site.posts %}
<li><!-- display post year here (but only once, per year) --></li>
<li>
{{ post.title }}
</li>
{% endfor %}
It can be done with much, much less Liquid code than in the existing answers:
{% for post in site.posts %}
{% assign currentdate = post.date | date: "%Y" %}
{% if currentdate != date %}
<li id="y{{currentdate}}">{{ currentdate }}</li>
{% assign date = currentdate %}
{% endif %}
<li>{{ post.title }}</li>
{% endfor %}
This will return exactly the HTML specified in your question:
<li id="y2013">2013</li>
<li>foo</li>
<li id="y2012">2012</li>
<li>bar</li>
<li>baz</li>
However, this is not the optimal solution, because the year numbers are "only" list items as well.
It's not much more Liquid code to put the year into a headline and to begin a new <ul> for each year's posts:
{% for post in site.posts %}
{% assign currentdate = post.date | date: "%Y" %}
{% if currentdate != date %}
{% unless forloop.first %}</ul>{% endunless %}
<h1 id="y{{post.date | date: "%Y"}}">{{ currentdate }}</h1>
<ul>
{% assign date = currentdate %}
{% endif %}
<li>{{ post.title }}</li>
{% if forloop.last %}</ul>{% endif %}
{% endfor %}
The generated HTML:
<h1 id="y2013">2013</h1>
<ul>
<li>foo</li>
</ul>
<h1 id="y2012">2012</h1>
<ul>
<li>bar</li>
<li>baz</li>
</ul>
You can also group by month and year instead (so that the headlines are February 2012, January 2012 and so on).
To do this, you just need to replace date: "%Y" (in the second line of both above examples) by date: "%B %Y".
(%B is the full month name, see the documentation)
These previous solutions are fantastic but luckily in late 2016, Jekyll added a group_by_exp filter that can do this much more cleanly.
{% assign postsByYear =
site.posts | group_by_exp:"post", "post.date | date: '%Y'" %}
{% for year in postsByYear %}
<h1>{{ year.name }}</h1>
<ul>
{% for post in year.items %}
<li>{{ post.title }}-{{ post.date }}</li>
{% endfor %}
</ul>
{% endfor %}
Documentation can be found on the Jekyll Templates page.
If you want to break it down by year, here's the code:
{% for post in site.posts %}
{% capture this_year %}{{ post.date | date: "%Y" }}{% endcapture %}
{% capture next_year %}{{ post.previous.date | date: "%Y" }}{% endcapture %}
{% if forloop.first %}
<h2 id="{{ this_year }}-ref">{{this_year}}</h2>
<ul>
{% endif %}
<li>{{ post.title }}</li>
{% if forloop.last %}
</ul>
{% else %}
{% if this_year != next_year %}
</ul>
<h2 id="{{ next_year }}-ref">{{next_year}}</h2>
<ul>
{% endif %}
{% endif %}
{% endfor %}
If you want to break it down to year and months it can be achieved like this:
{% for post in site.posts %}
{% capture this_year %}{{ post.date | date: "%Y" }}{% endcapture %}
{% capture this_month %}{{ post.date | date: "%B" }}{% endcapture %}
{% capture next_year %}{{ post.previous.date | date: "%Y" }}{% endcapture %}
{% capture next_month %}{{ post.previous.date | date: "%B" }}{% endcapture %}
{% if forloop.first %}
<h2 id="{{ this_year }}-ref">{{this_year}}</h2>
<h3 id="{{ this_year }}-{{ this_month }}-ref">{{ this_month }}</h3>
<ul>
{% endif %}
<li>{{ post.title }}</li>
{% if forloop.last %}
</ul>
{% else %}
{% if this_year != next_year %}
</ul>
<h2 id="{{ next_year }}-ref">{{next_year}}</h2>
<h3 id="{{ next_year }}-{{ next_month }}-ref">{{ next_month }}</h3>
<ul>
{% else %}
{% if this_month != next_month %}
</ul>
<h3 id="{{ this_year }}-{{ next_month }}-ref">{{ next_month }}</h3>
<ul>
{% endif %}
{% endif %}
{% endif %}
{% endfor %}
It is only a matter of where do you make the cut on the loop.
Some solutions above are very complex but then as #Trevor pointed out that we can levarage Jekyll's group_by_exp filter. Also I liked the solution but what I needed was grouped by Year and then inside that list grouped by Month. So, I tweaked it a little bit.
{% assign postsByYear = site.posts | group_by_exp:"post", "post.date | date: '%Y'" %}
{% for year in postsByYear %}
<h1>{{ year.name }}</h1>
{% assign postsByMonth = year.items | group_by_exp:"post", "post.date | date: '%B'" %}
{% for month in postsByMonth %}
<h2>{{ month.name }}</h2>
<ul>
{% for post in month.items %}
<li>{{ post.title }}-{{ post.date }}</li>
{% endfor %}
</ul>
{% endfor %}
{% endfor %}
Variation of Ankit R Gadiya's answer. The inner for loop was displaying the html code. I needed to de-indent it to get it to properly render the markup. I also added the post's excerpt:
{% assign postsByYear = site.posts | group_by_exp:"post", "post.date | date: '%Y'" %}
{% for year in postsByYear %}
<h1>{{ year.name }}</h1>
{% assign postsByMonth = year.items | group_by_exp:"post", "post.date | date: '%B'" %}
{% for month in postsByMonth %}
<h2>{{ month.name }}</h2>
<ul>
{% for post in month.items %}
<li>
{{ post.title }}
<br>{{ post.excerpt }}
</li>
{% endfor %}
</ul>
{% endfor %}
{% endfor %}
Example:
Try:
{% for post in site.posts %}
{% capture this_year %}{{ post.date | date: "%Y" }}{% endcapture %}
{% if forloop.first %}
<h2 id="{{ this_year }}-ref">{{this_year}}</h2>
<ul class="posts">
{% else %}
{% if this_year != last_year %}
</ul>
<h2 id="{{ this_year }}-ref">{{this_year}}</h2>
<ul class="posts">
{% endif %}
{% endif %}
<li>
<span class="post-date">{{ post.date | date_to_string }} »</span>
{{ post.title }}
</li>
{% if forloop.last %}
</ul>
{% endif %}
{% capture last_year %}{{ this_year }}{% endcapture %}
{% endfor %}
<ul>
{% for post in site.posts %}
{% assign year = post.date | date: "%Y" %}
{% if year != prev_year %}
<h3>{{year}}</h3>
{% endif %}
<li>
<span>{{ post.date | date: "%B %e, %Y" }}</span>
{{ post.title }}
</li>
{% assign prev_year = year %}
{% endfor %}
</ul>
Did not much like the other answer so here's an alternative for you. Basic logic: Display year/month only if it "new":
{% assign var currentYear = 0 %}
{% assign var currentMonth = 0 %}
{% for post in site.posts %}
{% capture year %}{{ post.date | date: "%Y" }}{% endcapture %}
{% capture month %}{{ post.date | date: "%B" }}{% endcapture %}
{% if currentYear != year %}
<div>
<h2>{{ year }}</h2>
</div>
{% assign var currentYear = year %}
{% endif %}
{% if currentMonth != month %}
<div>
<h3>{{ month }}</h3>
</div>
{% assign var currentMonth = month %}
{% endif %}
<p>{{ post.title }}</p>
{% endfor %}