Skip to content

Support content taxonomies #210

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

Closed
lucperkins opened this issue May 6, 2022 · 4 comments
Closed

Support content taxonomies #210

lucperkins opened this issue May 6, 2022 · 4 comments
Labels
feature New feature or request

Comments

@lucperkins
Copy link

lucperkins commented May 6, 2022

In line with #209, another Hugo feature I like is its built-in taxonomies, which enable you to group your content based on document metadata. When you configure a taxonomy, such as tags or categories, you can then template that taxonomized content in any number of ways. So with a tags taxonomy in place, for example, you can pretty easily template out a page at /blog/tags and /blog/tags/fashion would yield a templated page displaying all the blog posts that have the fashion tag. And so on.

Now, I suspect that some of this is already possible with Contentlayer using logic à la allDocs.filter(...). But I do think it may be worth considering adding a specific feature along these lines. Maybe something like this:

export const Post = defineDocumentType(() => ({
  name: 'Post',
  filePathPattern: `**/*.md`,
  fields: {
    title: {
      type: 'string',
      description: 'The title of the post',
      required: true,
    },
  },
  taxonomies: {
    tags: {
      name: 'tag'
    }
  }
}));

With this config, Contentlayer would look for documents with a tags field in the metadata and create a new sub-object for each value under tags that it encounters. Then you could do things like:

// "Query" documents
import { allPosts, Post} from "contentlayer/generated";

const gettingStartedPosts: Post[] = allPosts.taxonomies.tags.find(["getting-started"]);

// Iterate through tags
const allTags: string[] = allPosts.taxonomies.tags.all();
allTags.forEach(tag => ...);

// Iterate through all available taxonomies
allPosts.taxonomies.forEach(taxonomy => {
  console.log(taxonomy.name);
  taxonomy.groups.forEach(group => {
    console.log(group.name);
    console.log(group.items.length);
  });
});

This example is off the cuff but hopefully illustrates the kind of thing that I think would be nice to provide. Happy to help out with this if project leads think it's a good idea!

@schickling
Copy link
Collaborator

schickling commented May 10, 2022

Thanks for this feature suggestion. Looking forward to hearing more about this use case from other users!

In the meanwhile I hope you'll find a workaround for your situation along the lines of the code snippet you've shared above. If you need further help please feel free to reach out in Discord!

Possibly also related to #71

@schickling schickling added the feature New feature or request label May 10, 2022
@seancdavis
Copy link
Collaborator

@lucperkins I like where you're going with this in trying to streamline relationships. I'm curious why allDocs.filter(...) doesn't feel like a good approach to this scenario?

Although I haven't tested, I imagine that building these taxonomy arrays of objects wouldn't take much more JS than the example is showing. It feels like adding more rigidity and opinion into CL. That said, I'd love to hear if and why others would find this beneficial.

I'd rather see us focus first on issues like #71, #82, #86, #88. These all point toward adding more power to the content processing engine, without sacrificing flexibility. We could then showcase how you could easily achieve this taxonomies feature with an example.

@lucperkins
Copy link
Author

lucperkins commented May 10, 2022

@seancdavis I do think that filter would be a reasonable way to accomplish this, and in fact I'm already doing this in a pet project I'm using to learn CL 😄 I think I'm suggesting this as a feature because I'm drawn to a more "compile-time" approach whereby CL provides you the exact stuff you want at build time, but I can also see why extra API surface area is something you want to be vigilant about. One way or another I'll probably end up with my own personal helper library anyway 🤣 I think that your suggested approach of focusing on other work and displaying filter logic on configured fields in the docs/examples is probably the right call. Here's what I've essentially done:

// Config
const Post = defineDocumentType(() => ({
  name: 'Post',
  filePathPattern: '**/*.md',
  fields: {
    title: {
      type: 'string',
      description: 'The title of the post',
      required: true,
    },
    tags: {
      type: 'list',
      of: {
        type: 'string'
      },
      default: [],
      description: 'Blog post tags',
      required: false
    }
  },
}));

export default makeSource({
  contentDirPath: 'blog',
  documentTypes: [Post],
});

// Helper
type Tagged = { tags: string[] };

const hasTag = <T extends Tagged>(items: T[], tag: string): T[] => {
  return items.filter(item => item.tags.includes(tag));
}

// Code
import { allPosts, Post } from 'contentlayer/generated';

const productPosts: Post[] = hasTag(allPosts, 'product');

@lucperkins
Copy link
Author

I've provided a pretty thorough example of using this kind of logic in this PR:

contentlayerdev/next-contentlayer-example#12

I agree with the assessment that this is already possible in CL and will thus close this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants