Skip to content

Commit

Permalink
feat(content): add agx support for injectContent (#1012)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuamorony authored Apr 8, 2024
1 parent 7815bb1 commit 62f0f86
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 73 deletions.
37 changes: 37 additions & 0 deletions apps/ng-app/src/app/pages/posts.[slug].page.analog
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script lang="ts">
import {
ContentRenderer,
injectContent,
} from '@analogjs/content';
import { MarkdownComponent } from '@analogjs/content' with { analog: 'imports' }
import { RouteMeta } from '@analogjs/router';
import { AsyncPipe, JsonPipe, NgFor, NgIf } from '@angular/common' with { analog: 'imports'};
import { Component, inject } from '@angular/core';
import { map } from 'rxjs';

import { PostAttributes } from './models';

const renderer = inject(ContentRenderer);
const post$ = injectContent<PostAttributes>();

const toc$ = this.post$.pipe(
map(() => {
return this.renderer.getContentHeadings();
})
);
</script>

<template>
<ng-container *ngIf="post$ | async as post">
<h1>{{ post.attributes.title }}</h1>
<div *ngIf="toc$ | async as toc">
<ul>
<li *ngFor="let item of toc">
<a href="#{{ item.id }}">{{ item.text }}</a>
</li>
</ul>
</div>

<analog-markdown [content]="post.content"></analog-markdown>
</ng-container>
</template>
42 changes: 14 additions & 28 deletions apps/ng-app/src/app/pages/posts.page.analog
Original file line number Diff line number Diff line change
@@ -1,44 +1,32 @@
<script lang="ts">
import {
inject,
ViewChild,
ElementRef,
afterNextRender,
ViewContainerRef,
ChangeDetectorRef,
signal
signal,
effect,
} from '@angular/core';
import { injectContentFiles } from '@analogjs/content';
import { toSignal } from '@angular/core/rxjs-interop';
import { MarkdownComponent } from '@analogjs/content' with { analog: 'imports'};
import { injectContentFiles, injectContent } from '@analogjs/content';

defineMetadata({
queries: {
divPost: new ViewChild('post', { static: true, read: ViewContainerRef }),
}
});

let divPost: ElementRef<HTMLDivElement>;
const cdr = inject(ChangeDetectorRef);
let title = signal('');

afterNextRender(() => {
console.log('the post', divPost);

const postId = 'post';
import(`../../content/${postId}.agx`).then(m => {
divPost.createComponent(m.default);
title.set(m.metadata.title);
cdr.detectChanges();
});
});

const posts = injectContentFiles();

onInit(() => {
console.log('posts', posts);
});

const post$ = injectContent({
customFilename: 'hello'
});
const post = toSignal(post$)
</script>

<template>
@if(post()){
<analog-markdown [content]="post().content"></analog-markdown>
}

Posts

@for (post of posts; track post) {
Expand All @@ -50,7 +38,5 @@
<h2>Post</h2>

<h3>{{ title() }}</h3>

<div #post></div>
</template>

3 changes: 3 additions & 0 deletions apps/ng-app/src/content/post.agx
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
---
title: Hello World
slug: 'hello'
description: 'a description'
coverImage: ''
---

<script lang="ts">
Expand Down
2 changes: 1 addition & 1 deletion packages/content/src/lib/content-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ export interface ContentFile<
> {
filename: string;
slug: string;
content?: string;
content?: string | object;
attributes: Attributes;
}
11 changes: 8 additions & 3 deletions packages/content/src/lib/content-files-token.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { InjectionToken, inject } from '@angular/core';

import { getContentFiles } from './get-content-files';
import { getAgxFiles, getContentFiles } from './get-content-files';
import { CONTENT_FILES_LIST_TOKEN } from './content-files-list-token';

export const CONTENT_FILES_TOKEN = new InjectionToken<
Expand All @@ -9,17 +9,22 @@ export const CONTENT_FILES_TOKEN = new InjectionToken<
providedIn: 'root',
factory() {
const contentFiles = getContentFiles();
const agxFiles = getAgxFiles();
const allFiles = { ...contentFiles, ...agxFiles };
const contentFilesList = inject(CONTENT_FILES_LIST_TOKEN);

const lookup: Record<string, string> = {};
contentFilesList.forEach((item) => {
const fileParts = item.filename.split('/');
const filePath = fileParts.slice(0, fileParts.length - 1).join('/');
lookup[item.filename] = `${filePath}/${item.slug}.md`;
const fileNameParts = fileParts[fileParts.length - 1].split('.');
lookup[item.filename] = `${filePath}/${item.slug}.${
fileNameParts[fileNameParts.length - 1]
}`;
});

const objectUsingSlugAttribute: Record<string, () => Promise<string>> = {};
Object.entries(contentFiles).forEach((entry) => {
Object.entries(allFiles).forEach((entry) => {
const filename = entry[0];
const value = entry[1];

Expand Down
70 changes: 63 additions & 7 deletions packages/content/src/lib/content.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('injectContent', () => {
injectContent().subscribe((c) => {
expect(c.content).toMatch('No Content Found');
expect(c.attributes).toEqual({});
expect(c.filename).toEqual('/src/content/test.md');
expect(c.filename).toEqual('/src/content/test');
});
flushMicrotasks();
flush();
Expand All @@ -41,7 +41,7 @@ describe('injectContent', () => {
injectContent().subscribe((c) => {
expect(c.content).toMatch(customFallback);
expect(c.attributes).toEqual({});
expect(c.filename).toEqual('/src/content/test.md');
expect(c.filename).toEqual('/src/content/test');
});
flushMicrotasks();
flush();
Expand All @@ -68,7 +68,7 @@ Test Content`),
injectContent().subscribe((c) => {
expect(c.content).toMatch('Test Content');
expect(c.attributes).toEqual({ slug: 'test' });
expect(c.filename).toEqual('/src/content/test.md');
expect(c.filename).toEqual('/src/content/test');
expect(c.slug).toEqual('test');
});
flushMicrotasks();
Expand Down Expand Up @@ -98,7 +98,7 @@ Test Content`),
injectContent().subscribe((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');
expect(c.filename).toEqual('/src/content/custom-slug-test');
expect(c.slug).toEqual('custom-slug-test');
});
flushMicrotasks();
Expand Down Expand Up @@ -129,7 +129,7 @@ Test Content`),
expect(c.content).toMatch('Test Content');
expect(c.attributes).toEqual({ slug: 'custom-prefix-slug-test' });
expect(c.filename).toEqual(
'/src/content/customPrefix/custom-prefix-slug-test.md'
'/src/content/customPrefix/custom-prefix-slug-test'
);
expect(c.slug).toEqual('custom-prefix-slug-test');
});
Expand Down Expand Up @@ -160,13 +160,66 @@ Test Content`),
injectContent().subscribe((c) => {
expect(c.content).toMatch('Test Content');
expect(c.attributes).toEqual({ slug: 'custom-filename-test-slug' });
expect(c.filename).toEqual('/src/content/custom-filename-test.md');
expect(c.filename).toEqual('/src/content/custom-filename-test');
expect(c.slug).toEqual('custom-filename-test');
});
flushMicrotasks();
flush();
}));

it('should return ContentFile object matching md file if both md and agx available', fakeAsync(() => {
const routeParams = { slug: 'test' };
const contentFiles = {
'/src/content/test.md': () =>
Promise.resolve(`---
slug: 'test'
---
Test md Content`),
'/src/content/test.agx': () =>
Promise.resolve(`---
slug: 'test'
---
Test agx Content`),
};
const { injectContent } = setup({
routeParams,
contentFiles,
});
injectContent().subscribe((c) => {
expect(c.content).toMatch('Test md Content');
expect(c.attributes).toEqual({ slug: 'test' });
expect(c.filename).toEqual('/src/content/test');
expect(c.slug).toEqual('test');
});
flushMicrotasks();
flush();
}));

it('should return ContentFile object with data set to exports for non-string contentFiles', fakeAsync(() => {
const routeParams = { slug: 'test' };
const metadataExport = { title: 'test' };
const defaultExport = 'default';
const contentFiles = {
'/src/content/test.agx': () =>
Promise.resolve({
default: defaultExport,
metadata: metadataExport,
}),
};
const { injectContent } = setup({
routeParams,
contentFiles,
});
injectContent().subscribe((c) => {
expect(c.content).toMatch(defaultExport);
expect(c.attributes).toEqual(metadataExport);
expect(c.filename).toEqual('/src/content/test');
expect(c.slug).toEqual('test');
});
flushMicrotasks();
flush();
}));

function setup(
args: Partial<{
customParam:
Expand All @@ -175,7 +228,10 @@ Test Content`),
| { customFilename: string };
customFallback: string;
routeParams: { [key: string]: any };
contentFiles: Record<string, () => Promise<string>>;
contentFiles: Record<
string,
() => Promise<string | { default: any; metadata: any }>
>;
}>
) {
TestBed.configureTestingModule({
Expand Down
60 changes: 35 additions & 25 deletions packages/content/src/lib/content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ function getContentFile<
slug: string,
fallback: string
): Observable<ContentFile<Attributes | Record<string, never>>> {
const filePath = `/src/content/${prefix}${slug}.md`;
const contentFile = contentFiles[filePath];
const filePath = `/src/content/${prefix}${slug}`;
const contentFile =
contentFiles[`${filePath}.md`] ?? contentFiles[`${filePath}.agx`];
if (!contentFile) {
return of({
filename: filePath,
Expand All @@ -29,28 +30,38 @@ function getContentFile<
});
}

return new Observable<string>((observer) => {
const contentResolver = contentFile();
return new Observable<string | { default: any; metadata: any }>(
(observer) => {
const contentResolver = contentFile();

if (import.meta.env.SSR === true) {
waitFor(contentResolver).then((content) => {
observer.next(content);
});
} else {
contentResolver.then((content) => {
observer.next(content);
});
if (import.meta.env.SSR === true) {
waitFor(contentResolver).then((content) => {
observer.next(content);
});
} else {
contentResolver.then((content) => {
observer.next(content);
});
}
}
}).pipe(
map((rawContentFile) => {
const { content, attributes } =
parseRawContentFile<Attributes>(rawContentFile);
).pipe(
map((contentFile) => {
if (typeof contentFile === 'string') {
const { content, attributes } =
parseRawContentFile<Attributes>(contentFile);

return {
filename: filePath,
slug,
attributes,
content,
};
}
return {
filename: filePath,
slug,
attributes,
content,
attributes: contentFile.metadata,
content: contentFile.default,
};
})
);
Expand Down Expand Up @@ -92,14 +103,13 @@ export function injectContent<
slug,
fallback
);
} else {
return of({
filename: '',
slug: '',
attributes: {},
content: fallback,
});
}
return of({
filename: '',
slug: '',
attributes: {},
content: fallback,
});
})
);
} else {
Expand Down
4 changes: 3 additions & 1 deletion packages/content/src/lib/get-content-files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ export const getContentFilesList = () =>
* @returns
*/
export const getContentFiles = () =>
import.meta.glob(['/src/content/**/*.md', '/src/content/**/*.agx'], {
import.meta.glob(['/src/content/**/*.md'], {
query: '?raw',
import: 'default',
});

export const getAgxFiles = () => import.meta.glob(['/src/content/**/*.agx']);
Loading

0 comments on commit 62f0f86

Please sign in to comment.