analogjs / analog

The fullstack meta-framework for Angular. Powered by Vite and Nitro
https://analogjs.org
MIT License
2.48k stars 234 forks source link

Feature: Support providing content summaries for each post #705

Open brandonroberts opened 10 months ago

brandonroberts commented 10 months ago

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

content

Information

Hugo supports providing content summaries for each content file

https://gohugo.io/content-management/summaries/

We currently only support using the frontmatter for the summary, but this could be a configurable option in the @analogjs/platform plugin that's passed to the internal contentPlugin that provides the list of parsed frontmatter.

Example:

import { defineConfig } from 'vite';
import analog from '@analogjs/platform';

// https://vitejs.dev/config/
export default defineConfig(({ mode }) => ({
  plugins: [
    analog({
      content: {
        summaryLength: number, // default 70
        summaryDelimiter: string // default <!--more-->
      }
    }),
  ],
}));

The summary property would also be added to the ContentFile interface. It would be opt-in initially.

Describe any alternatives/workarounds you're currently using

No response

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

Dyqmin commented 10 months ago

Hi @brandonroberts, I can work on this. There are a few things I wanna discuss.

Currently ContentFile interface is used for both injectors - injectContentFiles and injectContent.

If we want to create additional attributes (like the summary) in the content plugin, we would need to change the flow since parseRawContentFile uses its own mechanism and has no access to the plugin data at this point.

We can modify CONTENT_FILES_TOKEN to produce content wrapped in a promise, along with attributes, by simply attaching them in the factory function. The other way is to inject the CONTENT_FILES_LIST_TOKEN into the injectContent and apply attributes from there.

The goal in both solutions is to pass previously generated attributes by the content plugin into parseRawContentFile, so we get consistent results.

Also, I'm wondering about creating two separate interfaces, one for the files list and the other for the file. I want to point out that injectContentFiles returns a ContentFile with an optional content field, but we are 100% sure that it's not there.

export interface ContentFileMeta<
  Attributes extends Record<string, any> = Record<string, any>
> {
  filename: string;
  slug: string;
  attributes: Attributes;
  summary?: string;
}

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

This allows us to use them accordingly:

export function injectContentFiles<Attributes extends Record<string, any>>(
...
): ContentFileMeta<Attributes>[]

export function injectContent<
  Attributes extends Record<string, any> = Record<string, any>
>(
...
): Observable<ContentFile<Attributes | Record<string, never>>> 

We may also don't need to have summary in the injectContent, so the interfaces would look slightly different and we would not change the flow.

brandonroberts commented 10 months ago

Thanks @Dyqmin, good poins.

@mhartington had the initial idea and already said they would take a go at it, so I'll leave it to them first before you start any work there.