11ty / eleventy

A simpler site generator. Transforms a directory of templates (of varying types) into HTML.
https://www.11ty.dev/
MIT License
17.19k stars 494 forks source link

Generating a post tag based on the post date #316

Closed danurbanowicz closed 5 years ago

danurbanowicz commented 5 years ago

I am happily using zero-maintenance tag pages but now I'm trying to additionally sort and paginate my posts (calendar events) based on an explicitly set date - and specifically by the month.

For example: I could just add tags: april to the post front matter, and it'd allow me to sort my posts by the april tag, but I was thinking it'd be better if Eleventy could just generate the tag from the post date filter I'm using. Something like:

tags: {{ event.date | monthName }} which would output tags: april

..but alas, we can't use template syntax in front matter data for anything other than the permalink field.

So, how could I generate a tag or array of tags for a post based on post data, such as the date? Or is anyone using a different method to organise their collections by date? 🤔

edwardhorsford commented 5 years ago

I'm doing essentially what you want using a custom collection. I'm doing it to group by months and by years. You could easily extend to group by day, etc.

My code below:

const moment = require("moment");

function generateDateSet(collection, format){
  let dateSet = new Set();

  collection.getAllSorted().forEach(function(item) {
    if( "date" in item.data ) {

      var tags = item.data.tags;
      if( typeof tags === "string" ) {
        tags = [tags];
      }
      if ( tags && ( tags.includes("_blog") || tags.includes("_photoblog") ) ){
        let itemDate = item.data.date;
        var date = moment(itemDate).format(format);
        dateSet.add(date);
      }     
    }
  });

  return Array.from(dateSet);
}

function getItemsByDate(collection, date, format){

  var result = {};
  result = collection.getAll().filter(function(item) {

      var tags = item.data.tags;

      if( typeof tags === "string" ) {
        tags = [tags];
      }

      if ( tags && ( tags.includes("_blog") || tags.includes("_photoblog") ) ){

        if( !item.data.date ){
          return false;
        }

        var itemDate = item.data.date;
        var itemShortDate = moment(itemDate).format(format);

        return (itemShortDate == date);
      };
      return false;
    });

  result = result.sort(function(a, b) {
    return b.date - a.date;
  });

  return result;
}

const contentByDateString = (collection, format) => {
  var dateSet = {};
  var newSet = new Set();

  dateSet = generateDateSet(collection, format);

  dateSet.forEach(function(date){
    var result = getItemsByDate(collection, date, format)
    newSet[date] = result;
  });

  return [{...newSet}];
}

exports.contentByMonth = collection => {
  return contentByDateString(collection, "YYYY/MM");
}

exports.contentByYear = collection => {
  return contentByDateString(collection, "YYYY");
}

Note, because of #277 I have to return the result wrapped in an array - so when you paginate on these collections you'll need to do something like:

pagination: 
 -data: collections.contentByMonth[0]
danurbanowicz commented 5 years ago

Thanks for this Ed, it's just given me an idea.

zachleat commented 5 years ago

Is this okay to close? I do think you want a custom collection for this :+1:

jvoros commented 5 years ago

@edwardhorsford I like your solution. How did you make your module exports available as a method on the collections object?

edwardhorsford commented 5 years ago

@jvoros it's not quite method on the collections object.

I'm using eleventy's addCollection method like this: eleventyConfig.addCollection("contentByMonth", require("./utils/collections/contentByDate").contentByMonth);

This creates a new collection using the function I posted above. I then use this collection to make pages / content.

darekkay commented 3 years ago

See other solutions in #1284