@analogjs/content
Version:
Content Rendering for Analog
152 lines (147 loc) • 5.66 kB
JavaScript
import { resource, inject, computed } from '@angular/core';
import { injectContentListLoader, injectContentLocale, filterByLocale, parseRawContentFile, injectContentFileLoader, ContentRenderer } from '@analogjs/content';
import { ActivatedRoute } from '@angular/router';
import { toSignal } from '@angular/core/rxjs-interop';
import { from } from 'rxjs';
import { map } from 'rxjs/operators';
function contentFilesResource(filterFn) {
const contentListLoader = injectContentListLoader();
const locale = injectContentLocale();
const contentList = contentListLoader().then((items) => {
let results = locale ? filterByLocale(items, locale) : items;
if (filterFn) {
results = results.filter(filterFn);
}
return results;
});
return resource({
loader: () => contentList,
});
}
async function getContentFile(contentFiles, slug, fallback, locale) {
// Normalize file keys so both "/src/content/..." and "/<project>/src/content/..." resolve.
// This mirrors normalization used elsewhere in the content pipeline.
const normalizedFiles = {};
const stemToKey = {};
for (const [key, resolver] of Object.entries(contentFiles)) {
const normalizedKey = key
// replace any prefix up to the content directory with /src/content
// use a non-greedy match so nested paths containing "/content" are preserved
.replace(/^(?:.*?)\/content(?=\/)/, '/src/content')
// normalize duplicate slashes
.replace(/\/{2,}/g, '/');
normalizedFiles[normalizedKey] = resolver;
// Index by bare filename stem so slug-only lookups work
const stem = normalizedKey
.split('/')
.pop()
?.replace(/\.[^.]+$/, '');
if (stem && !stemToKey[stem]) {
stemToKey[stem] = normalizedKey;
}
}
// Try direct file first, then directory index variants, then bare slug via stem
const base = `/src/content/${slug}`.replace(/\/{2,}/g, '/');
const candidates = [
`${base}.md`,
`${base}.agx`,
`${base}/index.md`,
`${base}/index.agx`,
];
// Try locale-prefixed paths first, then fall back to unprefixed, then bare slug via stem
const localeCandidates = locale
? candidates.map((c) => c.replace('/src/content/', `/src/content/${locale}/`))
: [];
const allCandidates = [...localeCandidates, ...candidates];
const matchKey = allCandidates.find((k) => k in normalizedFiles) ?? stemToKey[slug];
const contentFile = matchKey ? normalizedFiles[matchKey] : undefined;
if (!contentFile) {
return {
filename: base,
attributes: {},
slug: '',
content: fallback,
};
}
const resolvedBase = matchKey.replace(/\.(md|agx)$/, '');
return contentFile().then((contentFile) => {
if (typeof contentFile === 'string') {
const { content, attributes } = parseRawContentFile(contentFile);
return {
filename: resolvedBase,
slug,
attributes,
content,
};
}
return {
filename: resolvedBase,
slug,
attributes: contentFile.metadata,
content: contentFile.default,
};
});
}
/**
* Resource for requesting an individual content file
*
* @param params
* @param fallback
* @returns
*/
function contentFileResource(params, fallback = 'No Content Found') {
const loaderPromise = injectContentFileLoader();
const contentRenderer = inject(ContentRenderer);
const locale = injectContentLocale();
const contentFilesMap = toSignal(from(loaderPromise()));
const input = params ||
toSignal(inject(ActivatedRoute).paramMap.pipe(map((params) => params.get('slug'))), { requireSync: true });
return resource({
params: computed(() => ({ input: input(), files: contentFilesMap() })),
loader: async ({ params }) => {
const { input: param, files } = params;
if (typeof param === 'string') {
if (param) {
const file = await getContentFile(files, param, fallback, locale);
if (typeof file.content === 'string') {
const rendered = (await contentRenderer.render(file.content));
return {
...file,
toc: rendered.toc ?? [],
};
}
return {
...file,
toc: [],
};
}
return {
filename: '',
slug: '',
attributes: {},
content: fallback,
toc: [],
};
}
else {
const file = await getContentFile(files, param.customFilename, fallback, locale);
if (typeof file.content === 'string') {
const rendered = (await contentRenderer.render(file.content));
return {
...file,
toc: rendered.toc ?? [],
};
}
return {
...file,
toc: [],
};
}
},
});
}
/**
* Generated bundle index. Do not edit.
*/
export { contentFileResource, contentFilesResource };
//# sourceMappingURL=analogjs-content-resources.mjs.map