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

Feature: add injectable token/service that provides list of content w/frontmatter #222

Closed
2 tasks
brandonroberts opened this issue Jan 16, 2023 · 4 comments
Closed
2 tasks
Assignees
Labels
accepting PRs enhancement New feature or request

Comments

@brandonroberts
Copy link
Member

brandonroberts commented Jan 16, 2023

Which scope/s are relevant/related to the feature request?

content

Information

Define an injectable token or service that provides a list of the files markdown files scanned from the src/content folder in an array that includes frontmatter.

Example structure below

import { inject, InjectionToken } from '@angular/core';

export interface Frontmatter {
  title: string;
  description: string;
  publishedDate: string;
  slug: string;
  published: boolean;
  meta: string[];
  [name: string]: any; // extra metadata
}

export interface AnalogContentMetadata {
  filename: string;
  content: string;
  frontmatter: Frontmatter;
}

export const CONTENT_FILES_TOKEN = new InjectionToken<AnalogContentMetadata[]>('@analogjs/content Content Files', {
  providedIn: 'root',
  factory() {
    const rawContentFiles = import.meta.glob('/src/content/**/*.md', {
      eager: true,  // maybe support lazy loading here
      as: 'raw',
    });

    const contentFiles = Object.keys(rawContentFiles)
      .map((contentFile) => {
        const metadata = frontmatter<Frontmatter>(rawContentFiles[contentFile]);

        return {
          filename: contentFile,
          content: metadata.body, 
          frontmatter: {
            ...metadata.attributes,
            title: metadata.attributes.title,
            description: metadata.attributes.description,
            slug: metadata.attributes.slug,
            publishedDate: metadata.attributes.publishedDate,
            published: metadata.attributes.published,
          }
        };
      });

    return contentFiles;
  },
});

export function injectContentMetadata() {
  return inject(CONTENT_FILES_TOKEN);
}

Usage

import { Component } from '@angular/core';

import { injectContentMetadata } from '@analogjs/content';

@Component({
  selector: 'blog-posts',
  standalone: true,
  imports: [NgFor],
  template: `
    <div *ngFor="let post of posts">
      {{ post.frontmatter.title }} <br/>
    </div>
  `,
})
export default class BlogComponent {
  posts = injectContentMetadata(); // AnalogContentMetadata[]
}

This could be used to list blog posts for example and could be filtered further based on needs, such as only listing published posts as an example.

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No
@brandonroberts brandonroberts added the enhancement New feature or request label Jan 16, 2023
@markostanimirovic
Copy link
Member

@brandonroberts

What do you think about having a more flexible result, so attributes type can be provided based on the project needs? This will also provide the ability to set proper typing for plain markdown content without frontmatter.

export interface ContentFile<
  Attributes extends Record<string, any> = Record<string, any>
> {
  filename: string;
  content: string;
  attributes: Attributes;
}

const CONTENT_FILES_TOKEN = new InjectionToken<ContentFile[]>(
  '@analogjs/content Content Files',
  {
    providedIn: 'root',
    factory() {
      const rawContentFiles = import.meta.glob('/src/content/**/*.md', {
        eager: true,
        as: 'raw',
      });

      return Object.keys(rawContentFiles).map((filename) => {
        const { body, attributes } = fm<Record<string, any>>(
          rawContentFiles[filename]
        );

        return {
          filename,
          content: body,
          attributes,
        };
      });
    },
  }
);

export function injectContentFiles<
  Attributes extends Record<string, any>
>(): ContentFile<T>[] {
  return inject(CONTENT_FILES_TOKEN) as ContentFile<Attributes>[];
}

// usage:

interface PostAttributes {
  title: string;
  coverSrc: string;
  published: boolean;
}

@Component({
  selector: 'app-blog',
  standalone: true,
  imports: [NgFor],
  template: `
    <article *ngFor="let post of publishedPosts">
      <h2>{{ post.attributes.title }}</h2>
      <img [src]="post.attributes.coverSrc" [alt]="post.attributes.title" />
    </article>
  `,
})
export default class BlogComponent {
  private readonly posts = injectContentFiles<PostAttributes>();
  readonly publishedPosts = this.posts.filter(
    (post) => post.attributes.published
  );
}

Changes:

  • Function injectContentMetadata is renamed to injectContentFiles.
  • Property frontmatter is renamed to attributes.
  • Interface AnalogContentMetadata is renamed to ContentFile and Frontmatter is removed, so attributes can be typed via generic.

@brandonroberts
Copy link
Member Author

Definitely prefer the more flexible option with better types.

@goetzrobin
Copy link
Member

@markostanimirovic @brandonroberts I can implement as laid out here: #222 (comment)
If you guys are not already working on this and want to assign this issue to me 👍

@brandonroberts
Copy link
Member Author

@goetzrobin 👍

goetzrobin added a commit to goetzrobin/analog that referenced this issue Jan 18, 2023
…ate routes in blog-app

The new function is used to inject the content files parsed with
front-matter to the blog-app's blog page. Then, the attributes of each
file are used to dynamically create links to them.

Closes analogjs#222
Villanuevand pushed a commit to Villanuevand/analog that referenced this issue Sep 12, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
accepting PRs enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants