Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can we offer "Created" and "Last Modified" dates for each template? #869

Closed
mehtapratik opened this issue Jan 17, 2020 · 14 comments
Closed
Labels
enhancement needs-votes A feature request on the backlog that needs upvotes or downvotes. Remove this label when resolved.

Comments

@mehtapratik
Copy link

Let's say I want to put two dates on my blog. Date it was originally created and date it was last modified. How can I do this using 11ty?

I thought following could work. But, now I understand it won't because value of createdDate will not be transformed.

---
createdDate: Created
date: Last Modified
---

Am I missing something? Is there a better way of handling this scenario?

@brycewray
Copy link

According to https://www.11ty.dev/docs/dates/ the items Created and Last Modified will change dynamically, so you could try something like this:

---
Published: Created
Updated: Last Modified
---

@mehtapratik
Copy link
Author

Thanks for the response @brycewray. But, this doesn't work.

index.md

---
tags:
  - notPost
  - navItem
layout: site.njk
title: Home
Published: Created
Updated: Last Modified
---

site.njk

...
<p>This article was published on {{ Published }}.</p>
<p>Last updated on: {{ Updated }}</p>
...

It outputs following:

This article was published on: Created.

Last updated on: Last Modified

@brycewray
Copy link

brycewray commented Jan 26, 2020

@mehtapratik True, that's a different matter. Sorry; I misunderstood what you were trying to do. In my site's case, I use a more manual approach (which is probably what you want to avoid). For example, in a post's front matter I might have:

date: 2020-01-18T21:20:00-06:00
lastmod: 2020-01-20T08:15:00-06:00

And then, in the appropriate template (omitting some styling items irrelevant to this discussion):

      <p>
        Published: {{ page.date | htmlDateString }}<br />
        {% if lastmod %}
        Last modified: {{ lastmod | htmlDateString }}
        {% else %}
        &nbsp;
        {% endif %}
      </p>

As for htmlDateString, of course, it's defined in my .eleventy.js file following an earlier const { DateTime } = require("luxon"):

  eleventyConfig.addFilter('htmlDateString', dateObj => {
    return DateTime.fromJSDate(dateObj).toFormat('MMMM d, yyyy')
  })

@mehtapratik
Copy link
Author

Thanks @brycewray.

Hello @zachleat: Is this supported in current version or any plans to include it in future releases?

@vwkd
Copy link

vwkd commented Oct 8, 2020

I'd very much like to see this too.

Maybe more future proof than simply adding another pre-processed update field would be to allow the user to hook in "processing functions" for fields through the .eleventy.js config, such that they can choose to make arbitrary transformations on a field value. There could be pre-defined "processing functions" for common use cases, like date parsing.

A basic syntax could look like

// custom function
eleventyConfig.addKeyTransform("key-name", function(key) {});

// pre-defined function
eleventyConfig.addKeyTransform("key-name", "date");

@ki9us
Copy link

ki9us commented Jan 27, 2021

I want the data @brycewray put in his front matter:

date: 2020-01-18T21:20:00-06:00
lastmod: 2020-01-20T08:15:00-06:00

on all of my pages' data automatically. I've been fooling around with global computed data to try to find a way to extract the file modification time by running fs on the inputPath.

This is what I tried in _data/eleventyComputed.js:

const fs = require('fs')
module.exports = { 
  created: (data) => data.length?fs.statSync(data.inputPath).birthtime:undefined,
  modified: (data) => data.length?fs.statSync(data.inputPath).mtime:undefined,
}

I have to check data.length because some pages feed in data = {} and statSync errors out if you feed it undefined as an arg.

Next, in my nunjuck template:

<footer>
  date: {{eleventyComputed.modified}}
</footer>

And the html output is:

<footer>date: function () {
      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 &lt; _len2; _key2++) {
        args[_key2] = arguments[_key2];
      }

      return obj[val].apply(obj, args);
    }
</footer>

Which dumps all that weird javascript right out onto the footer. A spectacular failure. Absolute catastrophe.

I give up. I'm pretty sure this is possible with computed data, but I haven't figured it out.

@pdehaan
Copy link
Contributor

pdehaan commented Jan 27, 2021

@keith24 I think that's correct. Your eleventyComputed.js file's modified property is an anonymous function, so Eleventy is outputting the function definition.

I recreated your code in a new project and got the following:

---
title: Homepage
permalink: /
---

<footer>
  <p>created: {{ eleventyComputed.created }}</p>
  <p>created(): {{ eleventyComputed.created() }}</p>
  <p>created(page): {{ eleventyComputed.created(page) }}</p>

  <hr/>

  <p>modified: {{ eleventyComputed.modified }}</p>
  <p>modified(): {{ eleventyComputed.modified() }}</p>
  <p>modified(page): {{ eleventyComputed.modified(page) }}</p>
</footer>

And my output is:

<footer>
  <p>created: function () {
      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 &lt; _len2; _key2++) {
        args[_key2] = arguments[_key2];
      }

      return obj[val].apply(obj, args);
    }</p>
  <p>created(): </p>
  <p>created(page): Tue Jan 26 2021 23:50:44 GMT-0800 (Pacific Standard Time)</p>

  <hr/>

  <p>modified: function () {
      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 &lt; _len2; _key2++) {
        args[_key2] = arguments[_key2];
      }

      return obj[val].apply(obj, args);
    }</p>
  <p>modified(): </p>
  <p>modified(page): Wed Jan 27 2021 00:01:52 GMT-0800 (Pacific Standard Time)</p>
</footer>

But I also had to revise the eleventyComputed.js file slightly, since the data argument is expecting an object and I don't think you can use data.length to check for an empty object. So instead I used optional chaining (?.) to check if the data argument has an inputPath property before trying to use it w/ fs.statSync:

const fs = require('fs')

module.exports = {
  created: (data) => data?.inputPath ? fs.statSync(data.inputPath).birthtime : undefined,
  modified: (data) => data?.inputPath ? fs.statSync(data.inputPath).mtime : undefined,
};

TL:DR: I think your code [mostly] works, but you'll need to pass in the current page object to your custom method in order to get the page's current context.

@pdehaan
Copy link
Contributor

pdehaan commented Jan 27, 2021

If you don't want to pass in the page context every time (can't blame you), another option might be shortcodes, which have access to the current page context via this.page (assuming you aren't using arrow functions):

  // in your .eleventy.js config file:
  eleventyConfig.addShortcode("created", function () {
    return this.page?.inputPath ? fs.statSync(this.page.inputPath).birthtime : undefined;
  });
  eleventyConfig.addShortcode("modified", function () {
    return this.page?.inputPath ? fs.statSync(this.page.inputPath).mtime : undefined;
  });

Now, in my one random Nunjucks test file, I can add this:

<p>created (shortcode): {% created %}</p>
<p>modified (shortcode): {% modified %}</p>

OUTPUT

<p>created (shortcode): Tue Jan 26 2021 23:50:44 GMT-0800 (Pacific Standard Time)</p>
<p>modified (shortcode): Wed Jan 27 2021 00:21:09 GMT-0800 (Pacific Standard Time)</p>

It doesn't look like this.page works w/ filters yet; ref: #1047. But, I think we can hack it, although it feels kind of messy:

<p>created (filter): {{ page.inputPath | fileDate }}</p>
<p>created (filter): {{ page.inputPath | fileDate("birthtime") }}</p>
<p>modified (filter): {{ page.inputPath | fileDate("mtime") }}</p>
  // somewhere in your .eleventy.js config file:
  eleventyConfig.addFilter("fileDate", (inputPath, key="birthtime") => {
    return inputPath ? fs.statSync(inputPath)[key] : undefined;
  });

OUTPUT

<p>created (filter): Tue Jan 26 2021 23:50:44 GMT-0800 (Pacific Standard Time)</p>
<p>created (filter): Tue Jan 26 2021 23:50:44 GMT-0800 (Pacific Standard Time)</p>
<p>modified (filter): Wed Jan 27 2021 00:32:07 GMT-0800 (Pacific Standard Time)</p>

Last one... an async Nunjucks shortcode, since you mentioned a Nunjucks template:

  eleventyConfig.addNunjucksAsyncShortcode("fileDateShortcode", async function (label="", key="birthtime") {
    const inputPath = this.page?.inputPath;
    if (!inputPath) {
      return "";
    }
    const stats = await fs.promises.stat(inputPath);
    const date = new Date(stats[key]);
    return `${label} ${date?.toLocaleDateString()}`.trim();
  });
<p>{% fileDateShortcode "CrEaTeD:" %}</p>
<p>{% fileDateShortcode "MoDiFiEd:", "mtime" %}</p>
<p>CrEaTeD: 2021-01-26</p>
<p>MoDiFiEd: 2021-01-27</p>

@cat-a-flame
Copy link

Any update on this?

I tried @brycewray's idea, but it doesn't seem to work. In the rendered version I get "Wed Apr 14 2021 22:00:00 GMT+0200 (Central European Summer Time)" :(

@ki9us
Copy link

ki9us commented Apr 18, 2021

I got it to work. Note that only the last example brycewray gave actually converts to a readable string. I think it's done by the .toLocaleDateString() method in the return function. There are lots of similar methods to convert a Date object to readable text. You can find a good list at the MDN Javascript Date Object docs.

@zachleat
Copy link
Member

zachleat commented Aug 6, 2021

I am not really a fan of additions like this because of the unreliability of file creation and modified times. See also https://www.11ty.dev/docs/dates/#collections-out-of-order-when-you-run-eleventy-on-your-server

I do think a better way forward is #142

But I’ll put this in the enhancement queue and let folks vote on it

@zachleat zachleat added the needs-votes A feature request on the backlog that needs upvotes or downvotes. Remove this label when resolved. label Aug 6, 2021
@zachleat
Copy link
Member

zachleat commented Aug 6, 2021

This repository is now using lodash style issue management for enhancements. This means enhancement issues will now be closed instead of leaving them open.

View the enhancement backlog here. Don’t forget to upvote the top comment with 👍!

@miklb
Copy link

miklb commented Apr 26, 2023

I'm not following how #142 helps with the original issue. The question was how can I display both the created and last modified dates without manually editing them. Currently it seems it is an either/or situation for date front matter.

@BPowell76
Copy link

BPowell76 commented Mar 31, 2024

I'm not following how #142 helps with the original issue. The question was how can I display both the created and last modified dates without manually editing them. Currently it seems it is an either/or situation for date front matter.

Yes, this is something I'm working through right now as well. For the purpose of a blog site I'm working I want to be able to use git Created and git Last Modified to show Date Created and Date Updated, respectively; however, date is the only front matter tag recognized. Knowing little-to-no JS my current option is to just hard type one of these values. I'd like to be able to use the git date values, though, since I'm working on this with git source control.

For the purpose of using the existing setup to having a working sitemap page that updates automatically, I just use date: git Last Modified and use a filter code snippet I found to convert it to ISO from JSDate. The Date Created will have to be hard coded.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement needs-votes A feature request on the backlog that needs upvotes or downvotes. Remove this label when resolved.
Projects
None yet
Development

No branches or pull requests

9 participants