@analogjs/content
Version:
Content Rendering for Analog
236 lines (219 loc) • 10.2 kB
TypeScript
import * as i0 from '@angular/core';
import { InjectionToken, Provider, AfterViewChecked, Signal, Type, AbstractType, ProviderToken } from '@angular/core';
import * as rxjs from 'rxjs';
import { Observable } from 'rxjs';
import * as mermaid from 'mermaid';
import { SafeHtml } from '@angular/platform-browser';
import * as marked from 'marked';
import { marked as marked$1 } from 'marked';
declare class AnchorNavigationDirective {
private readonly document;
private readonly location;
private readonly router;
handleNavigation(element: EventTarget | null): boolean;
static ɵfac: i0.ɵɵFactoryDeclaration<AnchorNavigationDirective, never>;
static ɵdir: i0.ɵɵDirectiveDeclaration<AnchorNavigationDirective, "[analogAnchorNavigation]", never, {}, {}, never, never, true, never>;
}
type TableOfContentItem = {
id: string;
level: number;
text: string;
};
type RenderedContent = {
content: string;
toc: TableOfContentItem[];
};
declare abstract class ContentRenderer {
render(content: string): Promise<RenderedContent>;
getContentHeadings(_content: string): TableOfContentItem[];
enhance(): void;
static ɵfac: i0.ɵɵFactoryDeclaration<ContentRenderer, never>;
static ɵprov: i0.ɵɵInjectableDeclaration<ContentRenderer>;
}
declare class NoopContentRenderer implements ContentRenderer {
private readonly transferState;
private contentId;
/**
* Generates a hash from the content string
* to be used with the transfer state
*/
private generateHash;
render(content: string): Promise<RenderedContent>;
enhance(): void;
getContentHeadings(content: string): TableOfContentItem[];
private extractHeadings;
private extractHeadingsFromMarkdown;
private extractHeadingsFromHtml;
private makeSlug;
}
interface ContentFile<Attributes extends Record<string, any> = Record<string, any>> {
filename: string;
slug: string;
content?: string | object;
attributes: Attributes;
toc?: TableOfContentItem[];
}
/**
* Retrieves the static content using the provided param and/or prefix.
*
* @param param route parameter (default: 'slug')
* @param fallback fallback text if content file is not found (default: 'No Content Found')
*/
declare function injectContent<Attributes extends Record<string, any> = Record<string, any>>(param?: string | {
param: string;
subdirectory: string;
} | {
customFilename: string;
}, fallback?: string): Observable<ContentFile<Attributes | Record<string, never>>>;
declare function injectContentFiles<Attributes extends Record<string, any>>(filterFn?: InjectContentFilesFilterFunction<Attributes>): ContentFile<Attributes>[];
type InjectContentFilesFilterFunction<T extends Record<string, any>> = (value: ContentFile<T>, index: number, array: ContentFile<T>[]) => boolean;
declare function injectContentFilesMap(): Record<string, () => Promise<string>>;
declare class MarkdownContentRendererService implements ContentRenderer {
#private;
render(content: string): Promise<RenderedContent>;
getContentHeadings(content: string): TableOfContentItem[];
enhance(): void;
static ɵfac: i0.ɵɵFactoryDeclaration<MarkdownContentRendererService, never>;
static ɵprov: i0.ɵɵInjectableDeclaration<MarkdownContentRendererService>;
}
interface MarkdownRendererOptions {
loadMermaid?: () => Promise<typeof mermaid>;
}
declare function withMarkdownRenderer(options?: MarkdownRendererOptions): Provider;
declare function provideContent(...features: Provider[]): Provider[];
declare const MERMAID_IMPORT_TOKEN: InjectionToken<Promise<typeof mermaid>>;
declare class AnalogMarkdownRouteComponent implements AfterViewChecked {
private sanitizer;
private route;
contentRenderer: ContentRenderer;
protected content: SafeHtml;
classes: string;
ngAfterViewChecked(): void;
static ɵfac: i0.ɵɵFactoryDeclaration<AnalogMarkdownRouteComponent, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<AnalogMarkdownRouteComponent, "analog-markdown-route", never, { "classes": { "alias": "classes"; "required": false; }; }, {}, never, never, true, [{ directive: typeof AnchorNavigationDirective; inputs: {}; outputs: {}; }]>;
}
declare class AnalogMarkdownComponent implements AfterViewChecked {
private sanitizer;
private route;
private zone;
private readonly platformId;
private readonly mermaidImport;
private mermaid;
private contentSource;
readonly htmlContent: Signal<SafeHtml | undefined>;
readonly content: i0.InputSignal<string | object | null | undefined>;
readonly classes: i0.InputSignal<string>;
contentRenderer: ContentRenderer;
constructor();
getContentSource(): rxjs.Observable<string | SafeHtml>;
renderContent(content: string): Promise<string>;
ngAfterViewChecked(): void;
private loadMermaid;
static ɵfac: i0.ɵɵFactoryDeclaration<AnalogMarkdownComponent, never>;
static ɵcmp: i0.ɵɵComponentDeclaration<AnalogMarkdownComponent, "analog-markdown", never, { "content": { "alias": "content"; "required": false; "isSignal": true; }; "classes": { "alias": "classes"; "required": false; "isSignal": true; }; }, {}, never, never, true, [{ directive: typeof AnchorNavigationDirective; inputs: {}; outputs: {}; }]>;
}
declare function parseRawContentFile<Attributes extends Record<string, any>>(rawContentFile: string): {
content: string;
attributes: Attributes;
};
declare class MarkedSetupService {
private readonly marked;
private readonly highlighter;
constructor();
getMarkedInstance(): typeof marked$1;
static ɵfac: i0.ɵɵFactoryDeclaration<MarkedSetupService, never>;
static ɵprov: i0.ɵɵInjectableDeclaration<MarkedSetupService>;
}
interface MarkedContentHighlighter {
augmentCodeBlock?(code: string, lang: string): string;
}
declare abstract class MarkedContentHighlighter {
abstract getHighlightExtension(): marked.MarkedExtension;
static ɵfac: i0.ɵɵFactoryDeclaration<MarkedContentHighlighter, never>;
static ɵprov: i0.ɵɵInjectableDeclaration<MarkedContentHighlighter>;
}
declare function withHighlighter(provider: ({
useValue: MarkedContentHighlighter;
} | {
useClass: Type<MarkedContentHighlighter> | AbstractType<MarkedContentHighlighter>;
} | {
useFactory: (...deps: any[]) => MarkedContentHighlighter;
}) & {
deps?: ProviderToken<any>[];
}): Provider;
type ContentListLoaderFunction<Attributes extends Record<string, any>> = () => Promise<ContentFile<Attributes>[]>;
declare const CONTENT_LIST_LOADER: InjectionToken<ContentListLoaderFunction<any>>;
declare function injectContentListLoader<Attributes extends Record<string, any>>(): ContentListLoaderFunction<Attributes>;
declare function withContentListLoader(): Provider;
type ContentFileLoaderFunction = () => Promise<Record<string, () => Promise<string>>>;
declare const CONTENT_FILE_LOADER: InjectionToken<ContentFileLoaderFunction>;
declare function injectContentFileLoader(): ContentFileLoaderFunction;
declare function withContentFileLoader(): Provider;
/**
* Token for the active content locale.
* Provided via `withLocale()` in `provideContent()`.
*
* When set, `injectContentFiles()` filters to content matching this locale,
* and `injectContent()` resolves locale-prefixed content paths first.
*/
declare const CONTENT_LOCALE: InjectionToken<string>;
/**
* Injects the content locale, returning null if not configured.
*/
declare function injectContentLocale(): string | null;
interface ContentLocaleOptions {
/**
* Function that returns the active locale.
* Runs in injection context so `inject()` can be used to read
* from other tokens (e.g., a LOCALE token from a router package).
*
* ```typescript
* withLocale({ loadLocale: injectLocale })
* withLocale({ loadLocale: () => navigator.language.split('-')[0] })
* ```
*/
loadLocale: () => string | null;
}
/**
* Content feature that sets the active locale for content resolution.
*
* When provided, content APIs become locale-aware:
* - `injectContentFiles()` filters to files in the locale subdirectory
* or with a matching `locale` frontmatter attribute.
* - `injectContent()` tries locale-prefixed paths first
* (e.g., `content/fr/blog/post.md` before `content/blog/post.md`).
*
* Usage:
* ```typescript
* // With loader — runs in injection context
* provideContent(
* withMarkdownRenderer(),
* withLocale({ loadLocale: injectLocale }),
* )
*
* // Static locale
* provideContent(
* withMarkdownRenderer(),
* withLocale('fr'),
* )
* ```
*/
declare function withLocale(locale: string | ContentLocaleOptions): Provider;
/**
* Filters content files by locale using map-based key lookup.
*
* Matching rules:
* 1. Frontmatter `locale` attribute matches the active locale.
* 2. File is in the active locale subdirectory (e.g., `/content/fr/blog/post`).
* 3. File has no locale marker and no localized variant exists — included as universal content.
*
* Files in a different locale's subdirectory are always excluded.
*/
declare function filterByLocale<T extends Record<string, any>>(files: ContentFile<T>[], locale: string): ContentFile<T>[];
/**
* Prepends locale-prefixed candidates before the standard candidates
* for content file map key lookup.
*/
declare function withLocaleCandidates(candidates: string[], locale: string | null | undefined): string[];
export { AnchorNavigationDirective, CONTENT_FILE_LOADER, CONTENT_LIST_LOADER, CONTENT_LOCALE, ContentRenderer, MERMAID_IMPORT_TOKEN, AnalogMarkdownComponent as MarkdownComponent, MarkdownContentRendererService, AnalogMarkdownRouteComponent as MarkdownRouteComponent, MarkedContentHighlighter, MarkedSetupService, NoopContentRenderer, filterByLocale, injectContent, injectContentFileLoader, injectContentFiles, injectContentFilesMap, injectContentListLoader, injectContentLocale, parseRawContentFile, provideContent, withContentFileLoader, withContentListLoader, withHighlighter, withLocale, withLocaleCandidates, withMarkdownRenderer };
export type { ContentFile, InjectContentFilesFilterFunction };