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

Add next and previous aliases to collection items #529

Closed
thejohnfreeman opened this issue May 12, 2019 · 18 comments
Closed

Add next and previous aliases to collection items #529

thejohnfreeman opened this issue May 12, 2019 · 18 comments

Comments

@thejohnfreeman
Copy link

@thejohnfreeman thejohnfreeman commented May 12, 2019

It seems like pagination is the only way to get URLs to the "next" and "previous" items in a collection. Then I can create a template with this front-matter:

pagination:
  data: collections.posts
  size: 1

Here's the rub:

  • If I put my collection templates outside of my input directory (config.dir.input) then they don't create a collection.
  • If I put my collection templates in my input directory, then each one produces two .html documents: one for the template itself, and one for its page in the collection.

How can I paginate without the extra output documents?

@illvart

This comment has been minimized.

Copy link

@illvart illvart commented May 12, 2019

Try this one:

---
pagination:
  data: collections.posts
  size: 1
  alias: posts
  reverse: true
permalink: /posts/{% if pagination.pageNumber > 0 %}page/{{ pagination.pageNumber }}/{% endif %}index.html
---

<div class="pagination">
  {% if pagination.previousPageHref %}
  <a class="pagination-item prev" rel="prev" href="{{ pagination.previousPageHref }}">← Newer</a>
  {% endif %}

  {% if pagination.nextPageHref %}
  <a class="pagination-item next" rel="next" href="{{ pagination.nextPageHref }}">Older →</a>
  {% endif %}
</div>

You can read more about pagination on the pagination docs.

@thejohnfreeman

This comment has been minimized.

Copy link
Author

@thejohnfreeman thejohnfreeman commented May 12, 2019

Did you read the full question? The title doesn't tell the whole story. Pretty sure your answer is going to produce the double output I talked about.

@illvart

This comment has been minimized.

Copy link

@illvart illvart commented May 13, 2019

Oh no, I'm so sorry. I'm just reading the title and make comments, maybe sleepy. ✌️

@jevets

This comment has been minimized.

Copy link

@jevets jevets commented May 13, 2019

@thejohnfreeman

Have you tried using the filter pagination option? May be able to use it to exclude items to prevent doubling. (May not work in your case.)

Did you try defining your own collection if keeping the items outside of config.input.dir? This will create the collection, but they won't be built as final individual .html files. (You could then use pagination again to generate a template file for each item in the collection.)

eleventyConfig.addCollection('outsideOfInputDir', c => {
  return c.getFilteredByGlob('some/where/outside/of/input/dir/**/*.md');
});
@thejohnfreeman

This comment has been minimized.

Copy link
Author

@thejohnfreeman thejohnfreeman commented May 13, 2019

@jevets Yes, I forgot to mention that my attempt to put the files outside my input directory involved trying to build the collection in the configuration, but it came up empty. I think it's because only files in the input directory ever get added to the collections parameter, and getFilteredByGlob just filters files added to the collections parameter; it does not trigger a new search for more files outside of the input directory. I've put the broken example in the pagination branch of my repository.

@jevets

This comment has been minimized.

Copy link

@jevets jevets commented May 13, 2019

@thejohnfreeman

(Assuming you checked out #211.)

Yeah, you're right that files only get added to collections if they're in config.dir.input. I forgot about that, and I think I've attempted that a few times in the past to no avail.

  • You do want each post to get its own .html file in the final build output, right? (i.e. _site/blog/2012-04-25-disqus-with-markdown/index.html)
  • And in each one of those .html files, you want to show next/prev post links (based on publish date or whatever your sorting is)
  • And you want a blog index template that shows, say 10 posts per (pagination) page and generates _site/blog/page/x/, right?

Gonna have to keep the post markdown files inside of config.dir.input, and if you do end up using pagination size=1 to generate the single post pages instead of using the markdown files directly as templates (as @zachleat mentions in #211), you could bypass permalinks in a posts/posts.json file to keep them from being output twice as html files (you'd rely on pagination w/ size=1 and dynamic permalinks instead).

I'm gonna clone your repo later on or setup a separate example to dig more into this. But for now, let me know if any above assumptions are incorrect or if any other info to consider.

@thejohnfreeman

This comment has been minimized.

Copy link
Author

@thejohnfreeman thejohnfreeman commented May 14, 2019

Those assumptions are correct. Right now, my index page just has all the posts since there are not many (page size 100, let's say).

I updated the pagination branch of my blog with your suggestions.

  • I have a _posts subdirectory of my input directory (config.dir.input) with all my posts as Markdown files.
  • In that _posts directory is a directory data file, _posts.json, with permalink: false to disable normal output for all the posts.
  • I moved the post layout from _includes/post.liquid to be the page template at blog/index.liquid.
  • I added pagination settings to the page template with page size 1.

What does this give me?

  • Single output for every post.
  • Each post is put at the right permalink.
  • Within the page template, I get URLs for the previous and next posts.

There are a couple remaining problems (in my opinion):

  • On my blog index page, I have to manually construct the URL to the "page" for each post.
  • Pagination does not give me the neighboring post data so that I can get their titles. I want to use the post title for my link text instead of "Next" or "Previous".
@jevets

This comment has been minimized.

Copy link

@jevets jevets commented May 14, 2019

Here's some code to solve the second remaining problem (post titles in next/prev post links). And a screenshot of it running locally.

Screen Shot 2019-05-14 at 11 41 32 AM

// blog/index.liquid

{% assign previousIndex = pagination.pageNumber | minus: 1 %}
{% assign previousItem = collections.posts[previousIndex] %}
{% assign nextIndex = pagination.pageNumber | plus: 1 %}
{% assign nextItem = collections.posts[nextIndex] %}

<pre>
  {% if previousItem.data.title %}
    Previous title: {{ previousItem.data.title }}
    Previous URL: {{ pagination.previousPageHref }}
  {% endif %}
  {% if nextItem.data.title %}
    Next title: {{ nextItem.data.title }}
    Next URL: {{ pagination.nextPageHref }}
  {% endif %}
</pre>
@jevets

This comment has been minimized.

Copy link

@jevets jevets commented May 14, 2019

Regarding the first problem:

On my blog index page, I have to manually construct the URL to the "page" for each post.

Are you referring to the the disqus config?

this.page.url = 'https://thejohnfreeman.com{{ page.url }}';
@jevets

This comment has been minimized.

Copy link

@jevets jevets commented May 14, 2019

@thejohnfreeman

A couple notes on the previous/next post titles and links.

  • Be careful about the sort order. If you ever change how you're sorting posts (asc/desc), you'll probably need to update in two places to ensure indexes match. (i.e. next item on blog index shows what the single blog post considers to be the previous item)
  • previousItem.url and nextItem.url won't work, since your _posts.json returns false for permalinks. You could probably change it up to make each post markdown files its own template, then use collections.posts to lookup the current item (maybe use fileSlug) and get back the previous and next items. Then you could use previousItem.data.title and previousItem.url (and nextItem). Not sure it's worth it to you.
@thejohnfreeman

This comment has been minimized.

Copy link
Author

@thejohnfreeman thejohnfreeman commented May 14, 2019

The first problem is regarding the URL of each post on the home page:

<table class="table">
  <tbody>
    {%- for post in collections.post reversed -%}
    <tr>
      <td>{{ post.date | utcDate: 'YYYY[&nbsp;]MMMM[&nbsp;]D' }}</td>
      <td><a href="{{ post.url }}">{{ post.data.title }}</a></td>
       <!-- this ^ url has to be constructed manually to match what I use in the page template -->
    </tr>
    {%- endfor -%}
  </tbody>
</table>

I've thought about trying to add some function or data to link within a collection without using pagination, but I haven't dug into it yet. I think I would prefer that approach considering I'd have to do essentially the same thing in pagination to get the neighboring titles.

@jevets

This comment has been minimized.

Copy link

@jevets jevets commented May 14, 2019

Yeah, if each post markdown file was its own template, its url would be built appropriately for you. That'd be enough for me to consider switching over.
Then you'd only need pagination for the home page, eventually (if ever).

@morgaan

This comment has been minimized.

Copy link

@morgaan morgaan commented Sep 17, 2019

@thejohnfreeman I do not know if this would be of any help for you, but here is what I came up with to bring the previous and next links to a portfolio where each photo is a post from a collection:

I created 2 shortcodes in my .eleventy.js file:

eleventyConfig.addShortcode('previous', (collections, [tag], {inputPath}) => {
  // Assumes the first tag to be the filter for the post to be "paginated".
  const collec = collections[tag];

  for (let i = 0; i <= collec.length; i++) {
    if (collec[i+1] && collec[i+1].data.page.inputPath === inputPath) {
      return `<a href="${ collec[i].data.page.url }">Previous</a>`;
    }
  }
});

eleventyConfig.addShortcode('next', (collections, [tag], {inputPath}) => {
  // Assumes the first tag to be the filter for the post to be "paginated".
  const collec = collections[tag];

  for (let i = 1; i <= collec.length-1; i++) {
    if (collec[i-1] && collec[i-1].data.page.inputPath === inputPath) {
      return `<a href="${ collec[i].data.page.url }">Next</a>`;
    }
  }
});

These 2 shortcodes are to be used this way in the template or layout:

<!-- POST goes here -->
{% previous collections, tags, page %}&nbsp;|&nbsp;{% next collections, tags, page %}
@brycewray

This comment has been minimized.

Copy link

@brycewray brycewray commented Dec 21, 2019

@morgaan Have tried a variation on this but can’t seem to handle what happens if there is no previous or next — always comes up as undefined which is, of course, to be expected but I can’t seem to if/else my way out of showing an undefined result. When I do the following...

  eleventyConfig.addShortcode('next', (collections, [tag], {inputPath}) => {
    // Assumes the first tag to be the filter for the post to be "paginated."
    const collec = collections[tag]
  
    for (let i = 1; i <= collec.length-1; i++) {
      if (collec[i-1] && collec[i-1].data.page.inputPath === inputPath) {
        return `<p class="ctr"><strong>Next</strong>: <a class="next" href="${ collec[i].data.page.url }">${ collec[i].data.title }</a></p>`
      } else {
        return `<p style="display: none;">No next</p>`
      }
    }
  })

  eleventyConfig.addShortcode('previous', (collections, [tag], {inputPath}, ) => {
    // Assumes the first tag to be the filter for the post to be "paginated."
    const collec = collections[tag]
  
    for (let i = 0; i <= collec.length; i++) {
      if (collec[i+1] && collec[i+1].data.page.inputPath === inputPath) {
        return `<p class="ctr"><strong>Previous</strong>: <a class="previous" href="${ collec[i].data.page.url }">${ collec[i].data.title }</a></p>`
      } else {
        return `<p style="display: none;">No previous</p>`
      }
    }
  })

...everything comes up blank (except on the very first post, which does indeed show a “Next” but no “Previous” as is desired in this case). In other words, they all fail the test. If I remove the else part from each, it works but, again, null values produce the expected undefined. I’m sure there’s something obvious that I’m missing. Any thoughts?

Update: Ah, I see now. The return is breaking the loop and so i never increments past 1 at most. (D’oh.) Will see if I can compensate for that.

@pascalw

This comment has been minimized.

Copy link

@pascalw pascalw commented Dec 22, 2019

@thejohnfreeman @brycewray maybe I don't fully understand what it is you're trying to achieve, but doesn't something like this work?

// .eleventy.js
eleventyConfig.addCollection("posts", function(collection) {
  const coll = collection.getFilteredByTag("posts");

  for(let i = 0; i < coll.length ; i++) {
    const prevPost = coll[i-1];
    const nextPost = coll[i + 1];

    coll[i].data["prevPost"] = prevPost;
    coll[i].data["nextPost"] = nextPost;
  }

  return coll;
});

This way you'll get access to the next and previous post anywhere you're using the collection:

{{ prevPost.url }} {{ prevPost.data.title }}
@brycewray

This comment has been minimized.

Copy link

@brycewray brycewray commented Dec 22, 2019

@pascalw Yes, was just about to sit down and try basically that exact approach (i.e., return outside the loop) before I have to head on an out-of-town trip. 👍 But I would not have had your incredibly elegant logic in there — just want to be clear on that. You win big-time on that.

Update: Works perfectly. You da man! 💯 Thanks very much!!

@xjussix

This comment has been minimized.

Copy link

@xjussix xjussix commented Jan 15, 2020

Can also confirm the solution by @pascalw works very nicely for me as well. Hats off to you!

@zachleat zachleat changed the title How to link previous and next blog post? Add next and previous aliases to collection items Jan 17, 2020
@zachleat

This comment has been minimized.

Copy link
Member

@zachleat zachleat commented Jan 17, 2020

Gonna swap this issue to the enhancement queue to adopt something like @pascalw’s solution into core #529 (comment)

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 👍!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
8 participants
You can’t perform that action at this time.