Skip to content

Commit

Permalink
feat(content): update injectContent function to return content object… (
Browse files Browse the repository at this point in the history
#229)

Closes #228

BREAKING CHANGES:

The signature of the `injectContent` function has changed.

BEFORE:

injectContent now returns an observable of a string from the rendered markdown.

AFTER:

injectContent now ContentFile<Attributes | Record<string, never>> which is an object that includes the rendered markdown as the `content` property.
  • Loading branch information
goetzrobin committed Jan 22, 2023
1 parent d740a5c commit 19beed1
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 39 deletions.
12 changes: 8 additions & 4 deletions apps/blog-app/src/app/routes/blog/[slug].ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { injectContent, MarkdownComponent } from '@analogjs/content';
import { AsyncPipe } from '@angular/common';
import { AsyncPipe, NgIf } from '@angular/common';
import { Component } from '@angular/core';
import { BlogAttributes } from '../../../lib/blog-attributes';

@Component({
selector: 'blog-post',
standalone: true,
imports: [MarkdownComponent, AsyncPipe],
imports: [MarkdownComponent, AsyncPipe, NgIf],
template: `
<analog-markdown [content]="content$ | async"></analog-markdown>
<ng-container *ngIf="contentFile$ | async as cf">
<h1>{{ cf.attributes.title }}</h1>
<analog-markdown [content]="cf.content"></analog-markdown>
</ng-container>
`,
})
export default class BlogPostComponent {
content$ = injectContent();
public contentFile$ = injectContent<BlogAttributes>();
}
2 changes: 0 additions & 2 deletions apps/blog-app/src/content/2022-12-27-my-first-post.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,4 @@ title: My First Post
slug: 2022-12-27-my-first-post
---

## My First Post

Hello
2 changes: 0 additions & 2 deletions apps/blog-app/src/content/2022-12-31-my-second-post.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ title: My Second Post
slug: 2022-12-31-my-second-post
---

## My Second Post

- [Home](/)
- [Blog](http://localhost:3000/blog)
- [About](/about)
Expand Down
4 changes: 4 additions & 0 deletions apps/blog-app/src/lib/blog-attributes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface BlogAttributes {
title: string;
slug: string;
}
67 changes: 40 additions & 27 deletions packages/content/src/lib/content.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,73 +2,84 @@ import { fakeAsync, flushMicrotasks, TestBed } from '@angular/core/testing';
import { ActivatedRoute, convertToParamMap } from '@angular/router';
import { expect } from 'vitest';
import { injectContent } from './content';
import { of } from 'rxjs';
import { Observable, of } from 'rxjs';
import { CONTENT_FILES_TOKEN } from './content-files-token';
import { ContentFile } from './content-file';

describe('injectContent', () => {
it("should provide the fallback 'No Content Found' message when no match between slug and files and no custom fallback provided", fakeAsync(() => {
type TestAttributes = {
slug: string;
};
it("should return ContentFile object with empty filename, empty attributes, and default fallback 'No Content Found' as content when no match between slug and files and no custom fallback provided", fakeAsync(() => {
const { injectContent } = setup({
routeParams: { slug: 'test' },
});
let content;
injectContent().subscribe((c) => {
content = c;
expect(c.content).toMatch('No Content Found');
expect(c.attributes).toEqual({});
expect(c.filename).toEqual('');
});
flushMicrotasks();
expect(content).toMatch('No Content Found');
}));

it("should provide the custom fallback 'Custom Fallback' message when no match between slug and files and custom fallback 'Custom Fallback' provided", fakeAsync(() => {
it("should return ContentFile object with empty filename, empty attributes, and the custom fallback 'Custom Fallback' as content when no match between slug and files and custom fallback 'Custom Fallback' provided", fakeAsync(() => {
const customFallback = 'Custom Fallback';
const routeParams = { slug: 'test' };
const { injectContent } = setup({ routeParams, customFallback });
let content;
injectContent().subscribe((c) => {
content = c;
expect(c.content).toMatch(customFallback);
expect(c.attributes).toEqual({});
expect(c.filename).toEqual('');
});
flushMicrotasks();
expect(content).toMatch(customFallback);
}));

it('should provide the content of the file when match between slug and files', fakeAsync(() => {
it('should return ContentFile object with correct filename, correct attributes, and the correct content of the file when match between slug and files', fakeAsync(() => {
const routeParams = { slug: 'test' };
const contentFiles = [
{
filename: '/src/content/dont-match.md',
attributes: {},
attributes: {
slug: 'dont-match',
},
content: 'Dont Match',
},
{
filename: '/src/content/test.md',
attributes: {},
attributes: {
slug: 'test',
},
content: 'Test Content',
},
];
const { injectContent } = setup({
routeParams,
contentFiles,
});
let content;
injectContent().subscribe((c) => {
content = c;
expect(c.content).toMatch('Test Content');
expect(c.attributes).toEqual({ slug: 'test' });
expect(c.filename).toEqual('/src/content/test.md');
});
flushMicrotasks();
expect(content).toMatch('Test Content');
}));

it('should provide the content of the file when match between custom param and files', fakeAsync(() => {
it('should return ContentFile object with correct filename, correct attributes, and the correct content of the file when match between custom param and files', fakeAsync(() => {
const customParam = 'customSlug';
const routeParams = { customSlug: 'custom-test' };
const contentFiles = [
const routeParams = { customSlug: 'custom-slug-test' };
const contentFiles: ContentFile<TestAttributes>[] = [
{
filename: '/src/content/dont-match.md',
attributes: {},
attributes: {
slug: 'dont-match',
},
content: 'Dont Match',
},
{
filename: '/src/content/custom-test.md',
attributes: {},
filename: '/src/content/custom-slug-test.md',
attributes: {
slug: 'custom-slug-test',
},
content: 'Test Content',
},
];
Expand All @@ -77,20 +88,20 @@ describe('injectContent', () => {
routeParams,
contentFiles,
});
let content;
injectContent().subscribe((c) => {
content = c;
expect(c.content).toMatch('Test Content');
expect(c.attributes).toEqual({ slug: 'custom-slug-test' });
expect(c.filename).toEqual('/src/content/custom-slug-test.md');
});
flushMicrotasks();
expect(content).toMatch('Test Content');
}));

function setup(
args: Partial<{
customParam: string;
customFallback: string;
routeParams: { [key: string]: any };
contentFiles: ContentFile[];
contentFiles: ContentFile<TestAttributes>[];
}>
) {
TestBed.configureTestingModule({
Expand All @@ -109,9 +120,11 @@ describe('injectContent', () => {
useValue: args.contentFiles ?? [],
});
return {
injectContent: () =>
injectContent: (): Observable<
ContentFile<TestAttributes | Record<string, never>>
> =>
TestBed.runInInjectionContext(() =>
injectContent(args.customParam, args.customFallback)
injectContent<TestAttributes>(args.customParam, args.customFallback)
),
};
}
Expand Down
20 changes: 16 additions & 4 deletions packages/content/src/lib/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,34 @@ import { inject } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { map } from 'rxjs/operators';
import { injectContentFiles } from './inject-content-files';
import { Observable } from 'rxjs';
import { ContentFile } from './content-file';

/**
* Retrieves the static content using the provided param
*
* @param param route parameter (default: 'slug')
* @param fallback fallback text if content file is not found (default: 'No Content Found')
*/
export function injectContent(param = 'slug', fallback = 'No Content Found') {
export function injectContent<
Attributes extends Record<string, any> = Record<string, any>
>(
param = 'slug',
fallback = 'No Content Found'
): Observable<ContentFile<Attributes | Record<string, never>>> {
const route = inject(ActivatedRoute);
const contentFiles = injectContentFiles();
const contentFiles = injectContentFiles<Attributes | Record<string, never>>();
return route.paramMap.pipe(
map((params) => params.get(param)),
map((slug) => {
return (
contentFiles.find((file) => file.filename === `/src/content/${slug}.md`)
?.content || fallback
contentFiles.find(
(file) => file.filename === `/src/content/${slug}.md`
) || {
attributes: {},
filename: '',
content: fallback,
}
);
})
);
Expand Down

0 comments on commit 19beed1

Please sign in to comment.