@analogjs/content 3.0.0-alpha.56 → 3.0.0-alpha.58

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -46,7 +46,7 @@ var DevToolsContentRenderer = class DevToolsContentRenderer extends ContentRende
46
46
  static {
47
47
  this.ɵfac = i0.ɵɵngDeclareFactory({
48
48
  minVersion: "12.0.0",
49
- version: "21.2.8",
49
+ version: "22.0.0",
50
50
  ngImport: i0,
51
51
  type: DevToolsContentRenderer,
52
52
  deps: null,
@@ -56,7 +56,7 @@ var DevToolsContentRenderer = class DevToolsContentRenderer extends ContentRende
56
56
  static {
57
57
  this.ɵprov = i0.ɵɵngDeclareInjectable({
58
58
  minVersion: "12.0.0",
59
- version: "21.2.8",
59
+ version: "22.0.0",
60
60
  ngImport: i0,
61
61
  type: DevToolsContentRenderer
62
62
  });
@@ -64,7 +64,7 @@ var DevToolsContentRenderer = class DevToolsContentRenderer extends ContentRende
64
64
  };
65
65
  i0.ɵɵngDeclareClassMetadata({
66
66
  minVersion: "12.0.0",
67
- version: "21.2.8",
67
+ version: "22.0.0",
68
68
  ngImport: i0,
69
69
  type: DevToolsContentRenderer,
70
70
  decorators: [{ type: Injectable }]
@@ -69,7 +69,7 @@ var Md4xContentRendererService = class Md4xContentRendererService extends Conten
69
69
  static {
70
70
  this.ɵfac = i0.ɵɵngDeclareFactory({
71
71
  minVersion: "12.0.0",
72
- version: "21.2.8",
72
+ version: "22.0.0",
73
73
  ngImport: i0,
74
74
  type: Md4xContentRendererService,
75
75
  deps: null,
@@ -79,7 +79,7 @@ var Md4xContentRendererService = class Md4xContentRendererService extends Conten
79
79
  static {
80
80
  this.ɵprov = i0.ɵɵngDeclareInjectable({
81
81
  minVersion: "12.0.0",
82
- version: "21.2.8",
82
+ version: "22.0.0",
83
83
  ngImport: i0,
84
84
  type: Md4xContentRendererService
85
85
  });
@@ -87,7 +87,7 @@ var Md4xContentRendererService = class Md4xContentRendererService extends Conten
87
87
  };
88
88
  i0.ɵɵngDeclareClassMetadata({
89
89
  minVersion: "12.0.0",
90
- version: "21.2.8",
90
+ version: "22.0.0",
91
91
  ngImport: i0,
92
92
  type: Md4xContentRendererService,
93
93
  decorators: [{ type: Injectable }]
@@ -162,7 +162,7 @@ var Md4xWasmContentRendererService = class Md4xWasmContentRendererService extend
162
162
  static {
163
163
  this.ɵfac = i0.ɵɵngDeclareFactory({
164
164
  minVersion: "12.0.0",
165
- version: "21.2.8",
165
+ version: "22.0.0",
166
166
  ngImport: i0,
167
167
  type: Md4xWasmContentRendererService,
168
168
  deps: null,
@@ -172,7 +172,7 @@ var Md4xWasmContentRendererService = class Md4xWasmContentRendererService extend
172
172
  static {
173
173
  this.ɵprov = i0.ɵɵngDeclareInjectable({
174
174
  minVersion: "12.0.0",
175
- version: "21.2.8",
175
+ version: "22.0.0",
176
176
  ngImport: i0,
177
177
  type: Md4xWasmContentRendererService
178
178
  });
@@ -180,7 +180,7 @@ var Md4xWasmContentRendererService = class Md4xWasmContentRendererService extend
180
180
  };
181
181
  i0.ɵɵngDeclareClassMetadata({
182
182
  minVersion: "12.0.0",
183
- version: "21.2.8",
183
+ version: "22.0.0",
184
184
  ngImport: i0,
185
185
  type: Md4xWasmContentRendererService,
186
186
  decorators: [{ type: Injectable }]
@@ -117,7 +117,7 @@ var MdcRendererDirective = class MdcRendererDirective {
117
117
  static {
118
118
  this.ɵfac = i0.ɵɵngDeclareFactory({
119
119
  minVersion: "12.0.0",
120
- version: "21.2.8",
120
+ version: "22.0.0",
121
121
  ngImport: i0,
122
122
  type: MdcRendererDirective,
123
123
  deps: [],
@@ -127,7 +127,7 @@ var MdcRendererDirective = class MdcRendererDirective {
127
127
  static {
128
128
  this.ɵdir = i0.ɵɵngDeclareDirective({
129
129
  minVersion: "17.1.0",
130
- version: "21.2.8",
130
+ version: "22.0.0",
131
131
  type: MdcRendererDirective,
132
132
  isStandalone: true,
133
133
  selector: "[mdcAst]",
@@ -144,7 +144,7 @@ var MdcRendererDirective = class MdcRendererDirective {
144
144
  };
145
145
  i0.ɵɵngDeclareClassMetadata({
146
146
  minVersion: "12.0.0",
147
- version: "21.2.8",
147
+ version: "22.0.0",
148
148
  ngImport: i0,
149
149
  type: MdcRendererDirective,
150
150
  decorators: [{
@@ -46,7 +46,7 @@ var PrismHighlighter = class PrismHighlighter extends MarkedContentHighlighter {
46
46
  static {
47
47
  this.ɵfac = i0.ɵɵngDeclareFactory({
48
48
  minVersion: "12.0.0",
49
- version: "21.2.8",
49
+ version: "22.0.0",
50
50
  ngImport: i0,
51
51
  type: PrismHighlighter,
52
52
  deps: null,
@@ -56,7 +56,7 @@ var PrismHighlighter = class PrismHighlighter extends MarkedContentHighlighter {
56
56
  static {
57
57
  this.ɵprov = i0.ɵɵngDeclareInjectable({
58
58
  minVersion: "12.0.0",
59
- version: "21.2.8",
59
+ version: "22.0.0",
60
60
  ngImport: i0,
61
61
  type: PrismHighlighter
62
62
  });
@@ -64,7 +64,7 @@ var PrismHighlighter = class PrismHighlighter extends MarkedContentHighlighter {
64
64
  };
65
65
  i0.ɵɵngDeclareClassMetadata({
66
66
  minVersion: "12.0.0",
67
- version: "21.2.8",
67
+ version: "22.0.0",
68
68
  ngImport: i0,
69
69
  type: PrismHighlighter,
70
70
  decorators: [{ type: Injectable }]
@@ -32,7 +32,7 @@ var AnchorNavigationDirective = class AnchorNavigationDirective {
32
32
  static {
33
33
  this.ɵfac = i0.ɵɵngDeclareFactory({
34
34
  minVersion: "12.0.0",
35
- version: "21.2.8",
35
+ version: "22.0.0",
36
36
  ngImport: i0,
37
37
  type: AnchorNavigationDirective,
38
38
  deps: [],
@@ -42,7 +42,7 @@ var AnchorNavigationDirective = class AnchorNavigationDirective {
42
42
  static {
43
43
  this.ɵdir = i0.ɵɵngDeclareDirective({
44
44
  minVersion: "14.0.0",
45
- version: "21.2.8",
45
+ version: "22.0.0",
46
46
  type: AnchorNavigationDirective,
47
47
  isStandalone: true,
48
48
  selector: "[analogAnchorNavigation]",
@@ -53,7 +53,7 @@ var AnchorNavigationDirective = class AnchorNavigationDirective {
53
53
  };
54
54
  i0.ɵɵngDeclareClassMetadata({
55
55
  minVersion: "12.0.0",
56
- version: "21.2.8",
56
+ version: "22.0.0",
57
57
  ngImport: i0,
58
58
  type: AnchorNavigationDirective,
59
59
  decorators: [{
@@ -194,7 +194,7 @@ var MarkedSetupService = class MarkedSetupService {
194
194
  static {
195
195
  this.ɵfac = i0.ɵɵngDeclareFactory({
196
196
  minVersion: "12.0.0",
197
- version: "21.2.8",
197
+ version: "22.0.0",
198
198
  ngImport: i0,
199
199
  type: MarkedSetupService,
200
200
  deps: [],
@@ -204,7 +204,7 @@ var MarkedSetupService = class MarkedSetupService {
204
204
  static {
205
205
  this.ɵprov = i0.ɵɵngDeclareInjectable({
206
206
  minVersion: "12.0.0",
207
- version: "21.2.8",
207
+ version: "22.0.0",
208
208
  ngImport: i0,
209
209
  type: MarkedSetupService
210
210
  });
@@ -212,7 +212,7 @@ var MarkedSetupService = class MarkedSetupService {
212
212
  };
213
213
  i0.ɵɵngDeclareClassMetadata({
214
214
  minVersion: "12.0.0",
215
- version: "21.2.8",
215
+ version: "22.0.0",
216
216
  ngImport: i0,
217
217
  type: MarkedSetupService,
218
218
  decorators: [{ type: Injectable }],
@@ -239,7 +239,7 @@ var MarkdownContentRendererService = class MarkdownContentRendererService {
239
239
  static {
240
240
  this.ɵfac = i0.ɵɵngDeclareFactory({
241
241
  minVersion: "12.0.0",
242
- version: "21.2.8",
242
+ version: "22.0.0",
243
243
  ngImport: i0,
244
244
  type: MarkdownContentRendererService,
245
245
  deps: [],
@@ -249,7 +249,7 @@ var MarkdownContentRendererService = class MarkdownContentRendererService {
249
249
  static {
250
250
  this.ɵprov = i0.ɵɵngDeclareInjectable({
251
251
  minVersion: "12.0.0",
252
- version: "21.2.8",
252
+ version: "22.0.0",
253
253
  ngImport: i0,
254
254
  type: MarkdownContentRendererService
255
255
  });
@@ -257,7 +257,7 @@ var MarkdownContentRendererService = class MarkdownContentRendererService {
257
257
  };
258
258
  i0.ɵɵngDeclareClassMetadata({
259
259
  minVersion: "12.0.0",
260
- version: "21.2.8",
260
+ version: "22.0.0",
261
261
  ngImport: i0,
262
262
  type: MarkdownContentRendererService,
263
263
  decorators: [{ type: Injectable }]
@@ -301,7 +301,7 @@ var AnalogMarkdownRouteComponent = class AnalogMarkdownRouteComponent {
301
301
  static {
302
302
  this.ɵfac = i0.ɵɵngDeclareFactory({
303
303
  minVersion: "12.0.0",
304
- version: "21.2.8",
304
+ version: "22.0.0",
305
305
  ngImport: i0,
306
306
  type: AnalogMarkdownRouteComponent,
307
307
  deps: [],
@@ -311,7 +311,7 @@ var AnalogMarkdownRouteComponent = class AnalogMarkdownRouteComponent {
311
311
  static {
312
312
  this.ɵcmp = i0.ɵɵngDeclareComponent({
313
313
  minVersion: "14.0.0",
314
- version: "21.2.8",
314
+ version: "22.0.0",
315
315
  type: AnalogMarkdownRouteComponent,
316
316
  isStandalone: true,
317
317
  selector: "analog-markdown-route",
@@ -327,7 +327,7 @@ var AnalogMarkdownRouteComponent = class AnalogMarkdownRouteComponent {
327
327
  };
328
328
  i0.ɵɵngDeclareClassMetadata({
329
329
  minVersion: "12.0.0",
330
- version: "21.2.8",
330
+ version: "22.0.0",
331
331
  ngImport: i0,
332
332
  type: AnalogMarkdownRouteComponent,
333
333
  decorators: [{
@@ -384,7 +384,7 @@ var AnalogMarkdownComponent = class AnalogMarkdownComponent {
384
384
  static {
385
385
  this.ɵfac = i0.ɵɵngDeclareFactory({
386
386
  minVersion: "12.0.0",
387
- version: "21.2.8",
387
+ version: "22.0.0",
388
388
  ngImport: i0,
389
389
  type: AnalogMarkdownComponent,
390
390
  deps: [],
@@ -394,7 +394,7 @@ var AnalogMarkdownComponent = class AnalogMarkdownComponent {
394
394
  static {
395
395
  this.ɵcmp = i0.ɵɵngDeclareComponent({
396
396
  minVersion: "17.1.0",
397
- version: "21.2.8",
397
+ version: "22.0.0",
398
398
  type: AnalogMarkdownComponent,
399
399
  isStandalone: true,
400
400
  selector: "analog-markdown",
@@ -425,7 +425,7 @@ var AnalogMarkdownComponent = class AnalogMarkdownComponent {
425
425
  };
426
426
  i0.ɵɵngDeclareClassMetadata({
427
427
  minVersion: "12.0.0",
428
- version: "21.2.8",
428
+ version: "22.0.0",
429
429
  ngImport: i0,
430
430
  type: AnalogMarkdownComponent,
431
431
  decorators: [{
@@ -1 +1 @@
1
- {"version":3,"file":"analogjs-content.mjs","names":["#marked"],"sources":["../../src/lib/anchor-navigation.directive.ts","../../src/lib/utils/zone-wait-for.ts","../../src/lib/content.ts","../../src/lib/marked-setup.service.ts","../../src/lib/markdown-content-renderer.service.ts","../../src/lib/provide-content.ts","../../src/lib/markdown-route.component.ts","../../src/lib/markdown.component.ts"],"sourcesContent":["import { Directive, HostListener, inject } from '@angular/core';\nimport { DOCUMENT, Location } from '@angular/common';\nimport { Router } from '@angular/router';\n\n@Directive({\n selector: '[analogAnchorNavigation]',\n standalone: true,\n})\nexport class AnchorNavigationDirective {\n private readonly document = inject(DOCUMENT);\n private readonly location = inject(Location);\n private readonly router = inject(Router);\n\n @HostListener('click', ['$event.target'])\n handleNavigation(element: EventTarget | null): boolean {\n if (\n element instanceof HTMLAnchorElement &&\n isInternalUrl(element, this.document) &&\n hasTargetSelf(element) &&\n !hasDownloadAttribute(element)\n ) {\n const { pathname, search, hash } = element;\n const url = this.location.normalize(`${pathname}${search}${hash}`);\n this.router.navigateByUrl(url);\n\n return false;\n }\n\n return true;\n }\n}\n\nfunction hasDownloadAttribute(anchorElement: HTMLAnchorElement): boolean {\n return anchorElement.getAttribute('download') !== null;\n}\n\nfunction hasTargetSelf(anchorElement: HTMLAnchorElement): boolean {\n return !anchorElement.target || anchorElement.target === '_self';\n}\n\nfunction isInternalUrl(\n anchorElement: HTMLAnchorElement,\n document: Document,\n): boolean {\n return (\n anchorElement.host === document.location.host &&\n anchorElement.protocol === document.location.protocol\n );\n}\n","import { firstValueFrom, isObservable, Observable } from 'rxjs';\n\ndeclare const Zone: any;\n\nexport async function waitFor<T>(prom: Promise<T> | Observable<T>): Promise<T> {\n if (isObservable(prom)) {\n prom = firstValueFrom(prom);\n }\n\n if (typeof Zone === 'undefined') {\n return prom;\n }\n\n const macroTask = Zone.current.scheduleMacroTask(\n `AnalogContentResolve-${Math.random()}`,\n () => {\n /* noop */\n },\n {},\n () => {\n /* noop */\n },\n );\n return prom.then((p: T) => {\n macroTask.invoke();\n return p;\n });\n}\n","/// <reference types=\"vite/client\" />\n\nimport { inject } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport { from, Observable, of } from 'rxjs';\nimport { map, switchMap, tap } from 'rxjs/operators';\n\nimport { ContentFile } from './content-file';\nimport { ContentRenderer } from './content-renderer';\nimport { CONTENT_LOCALE, withLocaleCandidates } from './content-locale';\nimport { CONTENT_FILES_TOKEN } from './content-files-token';\nimport { parseRawContentFile } from './parse-raw-content-file';\nimport { waitFor } from './utils/zone-wait-for';\nimport { RenderTaskService } from './render-task.service';\n\nfunction getContentFile<\n Attributes extends Record<string, any> = Record<string, any>,\n>(\n contentFiles: Record<string, () => Promise<string>>,\n prefix: string,\n slug: string,\n fallback: string,\n renderTaskService: RenderTaskService,\n contentRenderer: ContentRenderer,\n locale?: string | null,\n): Observable<ContentFile<Attributes | Record<string, never>>> {\n // Normalize file keys so both \"/src/content/...\" and \"/<project>/src/content/...\" resolve.\n const normalizedFiles: Record<string, () => Promise<string>> = {};\n for (const [key, resolver] of Object.entries(contentFiles)) {\n const normalizedKey = key\n .replace(/^(?:.*)\\/content/, '/src/content')\n .replace(/\\/{2,}/g, '/');\n normalizedFiles[normalizedKey] = resolver as () => Promise<string>;\n }\n\n const base = `/src/content/${prefix}${slug}`.replace(/\\/{2,}/g, '/');\n const candidates = [`${base}.md`, `${base}/index.md`];\n\n const allCandidates = withLocaleCandidates(candidates, locale);\n const matchKey = allCandidates.find((k) => k in normalizedFiles);\n const contentFile = matchKey ? normalizedFiles[matchKey] : undefined;\n const resolvedBase = (matchKey || `${base}.md`).replace(/\\.md$/, '');\n\n if (!contentFile) {\n return of({\n filename: resolvedBase,\n attributes: {},\n slug: '',\n content: fallback,\n toc: [],\n });\n }\n\n const contentTask = renderTaskService.addRenderTask();\n return new Observable<string | { default: string; metadata: Attributes }>(\n (observer) => {\n // `waitFor` is a no-op in non-Zone environments (browser without zone.js),\n // so this branch works for both SSR and client. Keeping a single path also\n // avoids Rolldown evaluating `import.meta.env.SSR` at library-build time\n // and eliminating the task-clearing branch, which caused SSR to hang once\n // the content map resolved a real lazy import.\n waitFor(contentFile()).then((content) => {\n observer.next(content);\n observer.complete();\n\n setTimeout(() => renderTaskService.clearRenderTask(contentTask), 10);\n });\n },\n ).pipe(\n switchMap((contentFile) => {\n if (typeof contentFile === 'string') {\n const { content, attributes } =\n parseRawContentFile<Attributes>(contentFile);\n return from(contentRenderer.render(content)).pipe(\n map((rendered) => ({\n filename: resolvedBase,\n slug,\n attributes,\n content,\n toc: rendered.toc ?? [],\n })),\n );\n }\n return of({\n filename: resolvedBase,\n slug,\n attributes: contentFile.metadata,\n content: contentFile.default,\n toc: [],\n });\n }),\n );\n}\n\n/**\n * Retrieves the static content using the provided param and/or prefix.\n *\n * @param param route parameter (default: 'slug')\n * @param fallback fallback text if content file is not found (default: 'No Content Found')\n */\nexport function injectContent<\n Attributes extends Record<string, any> = Record<string, any>,\n>(\n param:\n | string\n | {\n param: string;\n subdirectory: string;\n }\n | {\n customFilename: string;\n } = 'slug',\n fallback = 'No Content Found',\n): Observable<ContentFile<Attributes | Record<string, never>>> {\n const contentFiles = inject(CONTENT_FILES_TOKEN);\n const contentRenderer = inject(ContentRenderer);\n const renderTaskService = inject(RenderTaskService);\n const locale = inject(CONTENT_LOCALE, { optional: true });\n const task = renderTaskService.addRenderTask();\n\n if (typeof param === 'string' || 'param' in param) {\n const prefix = typeof param === 'string' ? '' : `${param.subdirectory}/`;\n const route = inject(ActivatedRoute);\n const paramKey = typeof param === 'string' ? param : param.param;\n return route.paramMap.pipe(\n map((params) => params.get(paramKey)),\n switchMap((slug) => {\n if (slug) {\n return getContentFile<Attributes>(\n contentFiles,\n prefix,\n slug,\n fallback,\n renderTaskService,\n contentRenderer,\n locale,\n );\n }\n return of({\n filename: '',\n slug: '',\n attributes: {},\n content: fallback,\n toc: [],\n });\n }),\n tap(() => renderTaskService.clearRenderTask(task)),\n );\n } else {\n return getContentFile<Attributes>(\n contentFiles,\n '',\n param.customFilename,\n fallback,\n renderTaskService,\n contentRenderer,\n locale,\n ).pipe(tap(() => renderTaskService.clearRenderTask(task)));\n }\n}\n","/**\n * Credit goes to Scully for original implementation\n * https://github.com/scullyio/scully/blob/main/libs/scully/src/lib/fileHanderPlugins/markdown.ts\n */\nimport { inject, Injectable } from '@angular/core';\nimport { marked } from 'marked';\nimport { gfmHeadingId } from 'marked-gfm-heading-id';\nimport { mangle } from 'marked-mangle';\nimport { MarkedContentHighlighter } from './marked-content-highlighter';\n\n@Injectable()\nexport class MarkedSetupService {\n private readonly marked: typeof marked;\n private readonly highlighter = inject(MarkedContentHighlighter, {\n optional: true,\n });\n\n constructor() {\n const renderer = new marked.Renderer();\n renderer.code = ({ text, lang }) => {\n // Let's do a language based detection like on GitHub\n // So we can still have non-interpreted mermaid code\n if (lang === 'mermaid') {\n return '<pre class=\"mermaid\">' + text + '</pre>';\n }\n\n if (!lang) {\n return '<pre><code>' + text + '</code></pre>';\n }\n\n if (this.highlighter?.augmentCodeBlock) {\n return this.highlighter?.augmentCodeBlock(text, lang);\n }\n\n return `<pre class=\"language-${lang}\"><code class=\"language-${lang}\">${text}</code></pre>`;\n };\n\n const extensions = [gfmHeadingId(), mangle()];\n\n if (this.highlighter) {\n extensions.push(this.highlighter.getHighlightExtension());\n }\n\n marked.use(...extensions, {\n renderer,\n pedantic: false,\n gfm: true,\n breaks: false,\n });\n\n this.marked = marked;\n }\n\n getMarkedInstance(): typeof marked {\n return this.marked;\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { getHeadingList } from 'marked-gfm-heading-id';\n\nimport {\n ContentRenderer,\n RenderedContent,\n TableOfContentItem,\n} from './content-renderer';\nimport { MarkedSetupService } from './marked-setup.service';\n\n@Injectable()\nexport class MarkdownContentRendererService implements ContentRenderer {\n #marked = inject(MarkedSetupService, { self: true });\n\n async render(content: string): Promise<RenderedContent> {\n const renderedContent = await this.#marked\n .getMarkedInstance()\n .parse(content);\n return {\n content: renderedContent,\n toc: getHeadingList(),\n };\n }\n\n getContentHeadings(content: string): TableOfContentItem[] {\n return [...content.matchAll(/^(#{1,6})\\s+(.+?)\\s*$/gm)].map((match) => ({\n id: match[2]\n .trim()\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/\\s+/g, '-'),\n level: match[1].length,\n text: match[2].trim(),\n }));\n }\n\n // eslint-disable-next-line\n enhance(): void {}\n}\n","import { Provider, InjectionToken } from '@angular/core';\nimport { ContentRenderer, NoopContentRenderer } from './content-renderer';\nimport { RenderTaskService } from './render-task.service';\nimport { withContentFileLoader } from './content-file-loader';\nimport { withContentListLoader } from './content-list-loader';\n\nexport interface MarkdownRendererOptions {\n loadMermaid?: () => Promise<typeof import('mermaid')>;\n}\n\nconst CONTENT_RENDERER_PROVIDERS: Provider[] = [\n {\n provide: ContentRenderer,\n useClass: NoopContentRenderer,\n },\n withContentFileLoader(),\n withContentListLoader(),\n];\n\nexport function withMarkdownRenderer(\n options?: MarkdownRendererOptions,\n): Provider {\n return [\n CONTENT_RENDERER_PROVIDERS,\n options?.loadMermaid\n ? [\n {\n provide: MERMAID_IMPORT_TOKEN,\n useFactory: options.loadMermaid,\n },\n ]\n : [],\n ];\n}\n\nexport function provideContent(...features: Provider[]): Provider[] {\n return [\n { provide: RenderTaskService, useClass: RenderTaskService },\n ...features,\n ];\n}\n\nexport const MERMAID_IMPORT_TOKEN: InjectionToken<\n Promise<typeof import('mermaid')>\n> = new InjectionToken<Promise<typeof import('mermaid')>>('mermaid_import');\n","import {\n AfterViewChecked,\n Component,\n inject,\n Input,\n ViewEncapsulation,\n} from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { ActivatedRoute } from '@angular/router';\n\nimport { ContentRenderer } from './content-renderer';\nimport { AnchorNavigationDirective } from './anchor-navigation.directive';\n\n@Component({\n selector: 'analog-markdown-route',\n standalone: true,\n imports: [],\n hostDirectives: [AnchorNavigationDirective],\n preserveWhitespaces: true,\n encapsulation: ViewEncapsulation.None,\n template: `<div [innerHTML]=\"content\" [class]=\"classes\"></div>`,\n})\nexport default class AnalogMarkdownRouteComponent implements AfterViewChecked {\n private sanitizer = inject(DomSanitizer);\n private route = inject(ActivatedRoute);\n contentRenderer: ContentRenderer = inject(ContentRenderer);\n\n protected content: SafeHtml = this.sanitizer.bypassSecurityTrustHtml(\n this.route.snapshot.data['renderedAnalogContent'],\n );\n\n @Input() classes = 'analog-markdown-route';\n\n ngAfterViewChecked(): void {\n this.contentRenderer.enhance();\n }\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n AfterViewChecked,\n Component,\n InputSignal,\n NgZone,\n PLATFORM_ID,\n Signal,\n ViewEncapsulation,\n computed,\n inject,\n input,\n} from '@angular/core';\nimport { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { ActivatedRoute, Data } from '@angular/router';\nimport { from, Observable, of } from 'rxjs';\nimport { catchError, map, switchMap } from 'rxjs/operators';\n\nimport { AnchorNavigationDirective } from './anchor-navigation.directive';\nimport { ContentRenderer } from './content-renderer';\nimport { MERMAID_IMPORT_TOKEN } from './provide-content';\n\n@Component({\n selector: 'analog-markdown',\n standalone: true,\n hostDirectives: [AnchorNavigationDirective],\n preserveWhitespaces: true,\n encapsulation: ViewEncapsulation.None,\n template: ` <div [innerHTML]=\"htmlContent()\" [class]=\"classes()\"></div> `,\n})\nexport default class AnalogMarkdownComponent implements AfterViewChecked {\n private sanitizer = inject(DomSanitizer);\n private route = inject(ActivatedRoute);\n private zone = inject(NgZone);\n private readonly platformId = inject(PLATFORM_ID);\n private readonly mermaidImport = inject(MERMAID_IMPORT_TOKEN, {\n optional: true,\n });\n private mermaid: typeof import('mermaid') | undefined;\n\n private contentSource: Signal<SafeHtml | string | undefined> = toSignal(\n this.getContentSource(),\n );\n readonly htmlContent: Signal<SafeHtml | string | undefined> = computed(() => {\n const inputContent = this.content();\n\n if (inputContent) {\n return this.sanitizer.bypassSecurityTrustHtml(inputContent as string);\n }\n\n return this.contentSource();\n });\n readonly content: InputSignal<string | object | null | undefined> = input<\n string | object | null\n >();\n readonly classes: InputSignal<string> = input('analog-markdown');\n\n contentRenderer: ContentRenderer = inject(ContentRenderer);\n\n constructor() {\n if (isPlatformBrowser(this.platformId) && this.mermaidImport) {\n // Mermaid can only be loaded on client side\n this.loadMermaid(this.mermaidImport);\n }\n }\n\n getContentSource(): Observable<SafeHtml | string> {\n return this.route.data.pipe(\n map<Data, string>((data) => data['_analogContent'] ?? ''),\n switchMap((contentString) => this.renderContent(contentString)),\n map((content) => this.sanitizer.bypassSecurityTrustHtml(content)),\n catchError((e) => of(`There was an error ${e}`)),\n );\n }\n\n async renderContent(content: string): Promise<string> {\n const rendered = await this.contentRenderer.render(content);\n return rendered.content;\n }\n\n ngAfterViewChecked(): void {\n this.contentRenderer.enhance();\n this.zone.runOutsideAngular(() => this.mermaid?.default.run());\n }\n\n private loadMermaid(mermaidImport: Promise<typeof import('mermaid')>) {\n this.zone.runOutsideAngular(() =>\n // Wrap into an observable to avoid redundant initialization once\n // the markdown component is destroyed before the promise is resolved.\n from(mermaidImport)\n .pipe(takeUntilDestroyed())\n .subscribe((mermaid) => {\n this.mermaid = mermaid;\n this.mermaid.default.initialize({ startOnLoad: false });\n // Explicitly running mermaid as ngAfterViewChecked\n // has probably already been called\n this.mermaid?.default.run();\n }),\n );\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"analogjs-content.mjs","names":["#marked"],"sources":["../../src/lib/anchor-navigation.directive.ts","../../src/lib/utils/zone-wait-for.ts","../../src/lib/content.ts","../../src/lib/marked-setup.service.ts","../../src/lib/markdown-content-renderer.service.ts","../../src/lib/provide-content.ts","../../src/lib/markdown-route.component.ts","../../src/lib/markdown.component.ts"],"sourcesContent":["import { Directive, HostListener, inject } from '@angular/core';\nimport { DOCUMENT, Location } from '@angular/common';\nimport { Router } from '@angular/router';\n\n@Directive({\n selector: '[analogAnchorNavigation]',\n standalone: true,\n})\nexport class AnchorNavigationDirective {\n private readonly document = inject(DOCUMENT);\n private readonly location = inject(Location);\n private readonly router = inject(Router);\n\n @HostListener('click', ['$event.target'])\n handleNavigation(element: EventTarget | null): boolean {\n if (\n element instanceof HTMLAnchorElement &&\n isInternalUrl(element, this.document) &&\n hasTargetSelf(element) &&\n !hasDownloadAttribute(element)\n ) {\n const { pathname, search, hash } = element;\n const url = this.location.normalize(`${pathname}${search}${hash}`);\n this.router.navigateByUrl(url);\n\n return false;\n }\n\n return true;\n }\n}\n\nfunction hasDownloadAttribute(anchorElement: HTMLAnchorElement): boolean {\n return anchorElement.getAttribute('download') !== null;\n}\n\nfunction hasTargetSelf(anchorElement: HTMLAnchorElement): boolean {\n return !anchorElement.target || anchorElement.target === '_self';\n}\n\nfunction isInternalUrl(\n anchorElement: HTMLAnchorElement,\n document: Document,\n): boolean {\n return (\n anchorElement.host === document.location.host &&\n anchorElement.protocol === document.location.protocol\n );\n}\n","import { firstValueFrom, isObservable, Observable } from 'rxjs';\n\ndeclare const Zone: any;\n\nexport async function waitFor<T>(prom: Promise<T> | Observable<T>): Promise<T> {\n if (isObservable(prom)) {\n prom = firstValueFrom(prom);\n }\n\n if (typeof Zone === 'undefined') {\n return prom;\n }\n\n const macroTask = Zone.current.scheduleMacroTask(\n `AnalogContentResolve-${Math.random()}`,\n () => {\n /* noop */\n },\n {},\n () => {\n /* noop */\n },\n );\n return prom.then((p: T) => {\n macroTask.invoke();\n return p;\n });\n}\n","/// <reference types=\"vite/client\" />\n\nimport { inject } from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\nimport { from, Observable, of } from 'rxjs';\nimport { map, switchMap, tap } from 'rxjs/operators';\n\nimport { ContentFile } from './content-file';\nimport { ContentRenderer } from './content-renderer';\nimport { CONTENT_LOCALE, withLocaleCandidates } from './content-locale';\nimport { CONTENT_FILES_TOKEN } from './content-files-token';\nimport { parseRawContentFile } from './parse-raw-content-file';\nimport { waitFor } from './utils/zone-wait-for';\nimport { RenderTaskService } from './render-task.service';\n\nfunction getContentFile<\n Attributes extends Record<string, any> = Record<string, any>,\n>(\n contentFiles: Record<string, () => Promise<string>>,\n prefix: string,\n slug: string,\n fallback: string,\n renderTaskService: RenderTaskService,\n contentRenderer: ContentRenderer,\n locale?: string | null,\n): Observable<ContentFile<Attributes | Record<string, never>>> {\n // Normalize file keys so both \"/src/content/...\" and \"/<project>/src/content/...\" resolve.\n const normalizedFiles: Record<string, () => Promise<string>> = {};\n for (const [key, resolver] of Object.entries(contentFiles)) {\n const normalizedKey = key\n .replace(/^(?:.*)\\/content/, '/src/content')\n .replace(/\\/{2,}/g, '/');\n normalizedFiles[normalizedKey] = resolver as () => Promise<string>;\n }\n\n const base = `/src/content/${prefix}${slug}`.replace(/\\/{2,}/g, '/');\n const candidates = [`${base}.md`, `${base}/index.md`];\n\n const allCandidates = withLocaleCandidates(candidates, locale);\n const matchKey = allCandidates.find((k) => k in normalizedFiles);\n const contentFile = matchKey ? normalizedFiles[matchKey] : undefined;\n const resolvedBase = (matchKey || `${base}.md`).replace(/\\.md$/, '');\n\n if (!contentFile) {\n return of({\n filename: resolvedBase,\n attributes: {},\n slug: '',\n content: fallback,\n toc: [],\n });\n }\n\n const contentTask = renderTaskService.addRenderTask();\n return new Observable<string | { default: string; metadata: Attributes }>(\n (observer) => {\n // `waitFor` is a no-op in non-Zone environments (browser without zone.js),\n // so this branch works for both SSR and client. Keeping a single path also\n // avoids Rolldown evaluating `import.meta.env.SSR` at library-build time\n // and eliminating the task-clearing branch, which caused SSR to hang once\n // the content map resolved a real lazy import.\n waitFor(contentFile()).then((content) => {\n observer.next(content);\n observer.complete();\n\n setTimeout(() => renderTaskService.clearRenderTask(contentTask), 10);\n });\n },\n ).pipe(\n switchMap((contentFile) => {\n if (typeof contentFile === 'string') {\n const { content, attributes } =\n parseRawContentFile<Attributes>(contentFile);\n return from(contentRenderer.render(content)).pipe(\n map((rendered) => ({\n filename: resolvedBase,\n slug,\n attributes,\n content,\n toc: rendered.toc ?? [],\n })),\n );\n }\n return of({\n filename: resolvedBase,\n slug,\n attributes: contentFile.metadata,\n content: contentFile.default,\n toc: [],\n });\n }),\n );\n}\n\n/**\n * Retrieves the static content using the provided param and/or prefix.\n *\n * @param param route parameter (default: 'slug')\n * @param fallback fallback text if content file is not found (default: 'No Content Found')\n */\nexport function injectContent<\n Attributes extends Record<string, any> = Record<string, any>,\n>(\n param:\n | string\n | {\n param: string;\n subdirectory: string;\n }\n | {\n customFilename: string;\n } = 'slug',\n fallback = 'No Content Found',\n): Observable<ContentFile<Attributes | Record<string, never>>> {\n const contentFiles = inject(CONTENT_FILES_TOKEN);\n const contentRenderer = inject(ContentRenderer);\n const renderTaskService = inject(RenderTaskService);\n const locale = inject(CONTENT_LOCALE, { optional: true });\n const task = renderTaskService.addRenderTask();\n\n if (typeof param === 'string' || 'param' in param) {\n const prefix = typeof param === 'string' ? '' : `${param.subdirectory}/`;\n const route = inject(ActivatedRoute);\n const paramKey = typeof param === 'string' ? param : param.param;\n return route.paramMap.pipe(\n map((params) => params.get(paramKey)),\n switchMap((slug) => {\n if (slug) {\n return getContentFile<Attributes>(\n contentFiles,\n prefix,\n slug,\n fallback,\n renderTaskService,\n contentRenderer,\n locale,\n );\n }\n return of({\n filename: '',\n slug: '',\n attributes: {},\n content: fallback,\n toc: [],\n });\n }),\n tap(() => renderTaskService.clearRenderTask(task)),\n );\n } else {\n return getContentFile<Attributes>(\n contentFiles,\n '',\n param.customFilename,\n fallback,\n renderTaskService,\n contentRenderer,\n locale,\n ).pipe(tap(() => renderTaskService.clearRenderTask(task)));\n }\n}\n","/**\n * Credit goes to Scully for original implementation\n * https://github.com/scullyio/scully/blob/main/libs/scully/src/lib/fileHanderPlugins/markdown.ts\n */\nimport { inject, Injectable } from '@angular/core';\nimport { marked } from 'marked';\nimport { gfmHeadingId } from 'marked-gfm-heading-id';\nimport { mangle } from 'marked-mangle';\nimport { MarkedContentHighlighter } from './marked-content-highlighter';\n\n@Injectable()\nexport class MarkedSetupService {\n private readonly marked: typeof marked;\n private readonly highlighter = inject(MarkedContentHighlighter, {\n optional: true,\n });\n\n constructor() {\n const renderer = new marked.Renderer();\n renderer.code = ({ text, lang }) => {\n // Let's do a language based detection like on GitHub\n // So we can still have non-interpreted mermaid code\n if (lang === 'mermaid') {\n return '<pre class=\"mermaid\">' + text + '</pre>';\n }\n\n if (!lang) {\n return '<pre><code>' + text + '</code></pre>';\n }\n\n if (this.highlighter?.augmentCodeBlock) {\n return this.highlighter?.augmentCodeBlock(text, lang);\n }\n\n return `<pre class=\"language-${lang}\"><code class=\"language-${lang}\">${text}</code></pre>`;\n };\n\n const extensions = [gfmHeadingId(), mangle()];\n\n if (this.highlighter) {\n extensions.push(this.highlighter.getHighlightExtension());\n }\n\n marked.use(...extensions, {\n renderer,\n pedantic: false,\n gfm: true,\n breaks: false,\n });\n\n this.marked = marked;\n }\n\n getMarkedInstance(): typeof marked {\n return this.marked;\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { getHeadingList } from 'marked-gfm-heading-id';\n\nimport {\n ContentRenderer,\n RenderedContent,\n TableOfContentItem,\n} from './content-renderer';\nimport { MarkedSetupService } from './marked-setup.service';\n\n@Injectable()\nexport class MarkdownContentRendererService implements ContentRenderer {\n #marked = inject(MarkedSetupService, { self: true });\n\n async render(content: string): Promise<RenderedContent> {\n const renderedContent = await this.#marked\n .getMarkedInstance()\n .parse(content);\n return {\n content: renderedContent,\n toc: getHeadingList(),\n };\n }\n\n getContentHeadings(content: string): TableOfContentItem[] {\n return [...content.matchAll(/^(#{1,6})\\s+(.+?)\\s*$/gm)].map((match) => ({\n id: match[2]\n .trim()\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/\\s+/g, '-'),\n level: match[1].length,\n text: match[2].trim(),\n }));\n }\n\n // eslint-disable-next-line\n enhance(): void {}\n}\n","import { Provider, InjectionToken } from '@angular/core';\nimport { ContentRenderer, NoopContentRenderer } from './content-renderer';\nimport { RenderTaskService } from './render-task.service';\nimport { withContentFileLoader } from './content-file-loader';\nimport { withContentListLoader } from './content-list-loader';\n\nexport interface MarkdownRendererOptions {\n loadMermaid?: () => Promise<typeof import('mermaid')>;\n}\n\nconst CONTENT_RENDERER_PROVIDERS: Provider[] = [\n {\n provide: ContentRenderer,\n useClass: NoopContentRenderer,\n },\n withContentFileLoader(),\n withContentListLoader(),\n];\n\nexport function withMarkdownRenderer(\n options?: MarkdownRendererOptions,\n): Provider {\n return [\n CONTENT_RENDERER_PROVIDERS,\n options?.loadMermaid\n ? [\n {\n provide: MERMAID_IMPORT_TOKEN,\n useFactory: options.loadMermaid,\n },\n ]\n : [],\n ];\n}\n\nexport function provideContent(...features: Provider[]): Provider[] {\n return [\n { provide: RenderTaskService, useClass: RenderTaskService },\n ...features,\n ];\n}\n\nexport const MERMAID_IMPORT_TOKEN: InjectionToken<\n Promise<typeof import('mermaid')>\n> = new InjectionToken<Promise<typeof import('mermaid')>>('mermaid_import');\n","import {\n AfterViewChecked,\n Component,\n inject,\n Input,\n ViewEncapsulation,\n} from '@angular/core';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { ActivatedRoute } from '@angular/router';\n\nimport { ContentRenderer } from './content-renderer';\nimport { AnchorNavigationDirective } from './anchor-navigation.directive';\n\n@Component({\n selector: 'analog-markdown-route',\n standalone: true,\n imports: [],\n hostDirectives: [AnchorNavigationDirective],\n preserveWhitespaces: true,\n encapsulation: ViewEncapsulation.None,\n template: `<div [innerHTML]=\"content\" [class]=\"classes\"></div>`,\n})\nexport default class AnalogMarkdownRouteComponent implements AfterViewChecked {\n private sanitizer = inject(DomSanitizer);\n private route = inject(ActivatedRoute);\n contentRenderer: ContentRenderer = inject(ContentRenderer);\n\n protected content: SafeHtml = this.sanitizer.bypassSecurityTrustHtml(\n this.route.snapshot.data['renderedAnalogContent'],\n );\n\n @Input() classes = 'analog-markdown-route';\n\n ngAfterViewChecked(): void {\n this.contentRenderer.enhance();\n }\n}\n","import { isPlatformBrowser } from '@angular/common';\nimport {\n AfterViewChecked,\n Component,\n InputSignal,\n NgZone,\n PLATFORM_ID,\n Signal,\n ViewEncapsulation,\n computed,\n inject,\n input,\n} from '@angular/core';\nimport { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';\nimport { DomSanitizer, SafeHtml } from '@angular/platform-browser';\nimport { ActivatedRoute, Data } from '@angular/router';\nimport { from, Observable, of } from 'rxjs';\nimport { catchError, map, switchMap } from 'rxjs/operators';\n\nimport { AnchorNavigationDirective } from './anchor-navigation.directive';\nimport { ContentRenderer } from './content-renderer';\nimport { MERMAID_IMPORT_TOKEN } from './provide-content';\n\n@Component({\n selector: 'analog-markdown',\n standalone: true,\n hostDirectives: [AnchorNavigationDirective],\n preserveWhitespaces: true,\n encapsulation: ViewEncapsulation.None,\n template: ` <div [innerHTML]=\"htmlContent()\" [class]=\"classes()\"></div> `,\n})\nexport default class AnalogMarkdownComponent implements AfterViewChecked {\n private sanitizer = inject(DomSanitizer);\n private route = inject(ActivatedRoute);\n private zone = inject(NgZone);\n private readonly platformId = inject(PLATFORM_ID);\n private readonly mermaidImport = inject(MERMAID_IMPORT_TOKEN, {\n optional: true,\n });\n private mermaid: typeof import('mermaid') | undefined;\n\n private contentSource: Signal<SafeHtml | string | undefined> = toSignal(\n this.getContentSource(),\n );\n readonly htmlContent: Signal<SafeHtml | string | undefined> = computed(() => {\n const inputContent = this.content();\n\n if (inputContent) {\n return this.sanitizer.bypassSecurityTrustHtml(inputContent as string);\n }\n\n return this.contentSource();\n });\n readonly content: InputSignal<string | object | null | undefined> = input<\n string | object | null\n >();\n readonly classes: InputSignal<string> = input('analog-markdown');\n\n contentRenderer: ContentRenderer = inject(ContentRenderer);\n\n constructor() {\n if (isPlatformBrowser(this.platformId) && this.mermaidImport) {\n // Mermaid can only be loaded on client side\n this.loadMermaid(this.mermaidImport);\n }\n }\n\n getContentSource(): Observable<SafeHtml | string> {\n return this.route.data.pipe(\n map<Data, string>((data) => data['_analogContent'] ?? ''),\n switchMap((contentString) => this.renderContent(contentString)),\n map((content) => this.sanitizer.bypassSecurityTrustHtml(content)),\n catchError((e) => of(`There was an error ${e}`)),\n );\n }\n\n async renderContent(content: string): Promise<string> {\n const rendered = await this.contentRenderer.render(content);\n return rendered.content;\n }\n\n ngAfterViewChecked(): void {\n this.contentRenderer.enhance();\n this.zone.runOutsideAngular(() => this.mermaid?.default.run());\n }\n\n private loadMermaid(mermaidImport: Promise<typeof import('mermaid')>) {\n this.zone.runOutsideAngular(() =>\n // Wrap into an observable to avoid redundant initialization once\n // the markdown component is destroyed before the promise is resolved.\n from(mermaidImport)\n .pipe(takeUntilDestroyed())\n .subscribe((mermaid) => {\n this.mermaid = mermaid;\n this.mermaid.default.initialize({ startOnLoad: false });\n // Explicitly running mermaid as ngAfterViewChecked\n // has probably already been called\n this.mermaid?.default.run();\n }),\n );\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;CAQO,cAAA;;kBACuB,OAAO,SAAS;gBAChB,OAAO,OAAS;;;AAG5C,MAAA,mBACuD,qBAEnD,cAAmB,SAAA,KAAA,SACnB,IAIQ,cAAU,QAAQ,IACpB,CAAA,qBAAoB,QAAa,EAAA;GAClC,MAAO,EAAA,UAAc,QAAI,SAAA;GAEvB,MAAA,MAAA,KAAA,SAAA,UAAA,GAAA,WAAA,SAAA,OAAA;;AAGF,UAAA;;;;CAfR;AAAA,OAAa,OAAU,GAAA,mBAAiB;GAAA,YAAA;GAAA,SAAA;GAAA,UAAA;GAAA,MAAA;GAAA,MAAA,EAAA;GAAA,QAAA,GAAA,gBAAA;GAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;GAR/B,UAAA;GACE,YAAA;GACZ,CAAA;;;EAyBO,MAAA;EACA,MAAc,CAAA,SAAA,CAAA,gBAAwB,CAAA;;;AAG/C,SAAS,qBAAc,eAA2C;AAChE,QAAQ,cAAc,aAAU,WAAc,KAAA;;AAGhD,SAAS,cACP,eACA;AAEA,QACE,CAAA,cAAc,UAAS,cAAkB,WACzC;;;;;;;AC1CJ,eAAsB,QAAW,MAA8C;AAC7E,KAAI,aAAa,KAAO,CACtB,QAAO,eAAoB,KAAA;AAG7B,KAAI,OAAO,SAAS,YAClB,QAAO;CAGT,MAAM,YAAY,KAAK,QAAQ,kBAC7B,wBAAwB,KAAK,QAAQ,UAC/B,IASN,EAAA,QAAU,GAEV;;;;;;;;SCCI,eAAA,cAA2D,QAAA,MAAA,UAAA,mBAAA,iBAAA,QAAA;CAE/D,MAAM,kBACH,EAAA;AAEH,MAAA,MAAA,CAAA,KAAgB,aAAA,OAAiB,QAAA,aAAA,EAAA;4BAG7B,QAAO,oBAAyB,eAAe,CAC/C,QAAc,WAAQ,IAAM;AAE5B,kBAAgB,iBAAA;;CAEtB,MAAM,OAAA,gBAAyB,SAAA,OAAgB,QAAA,WAAY,IAAA;CAIzD,MAAO,WADS,qBAFI,CAAA,GAAA,KAAA,MAAe,GAAA,KAAK,WAAc,EAEtC,OAAA,CACN,MAAA,MAAA,KAAA,gBAAA;CACR,MAAA,cAAU,WAAA,gBAAA,YAAA,KAAA;CACV,MAAA,gBAAc,YAAA,GAAA,KAAA,MAAA,QAAA,SAAA,GAAA;AACd,KAAA,CAAM,YACN,QAAS,GAAA;EACJ,UAAA;EACL,YAAA,EAAA;;EAGE,SAAc;EACT,KAAA,EAAA;EAOP,CAAQ;CAEN,MAAA,cAAmB,kBAAA,eAAA;AAEnB,QAAA,IAAA,YAAiB,aAAkB;AAU/B,UAAU,aAAA,CAAA,CAAA,MAAA,YAAA;AACV,YAAA,KAAA,QAAA;AACA,YAAA,UAAA;AACA,oBAAA,kBAAA,gBAAA,YAAA,EAAA,GAAA;IACK;GACJ,CACJ,KAAA,WAAA,gBAAA;;GAEO,MAAA,EAAA,SAAA,eAAA,oBAAA,YAAA;AACR,UAAU,KAAA,gBAAA,OAAA,QAAA,CAAA,CAAA,KAAA,KAAA,cAAA;IACV,UAAA;IACY;IACH;IACJ;IACL,KAAA,SAAA,OAAA,EAAA;IAEL,EAAA,CAAA;;;;;;;;GASI,CAAA;GAcL,CAAM;;;;;;;;SASE,cAAkB,QAAU,QAAA,WAAmB,oBAAM;CAC3D,MAAO,eAAe,OACpB,oBAAuB;CAErB,MAAI,kBAAM,OAAA,gBAAA;CACR,MAAA,oBACE,OAAA,kBAGA;;CAMJ,MAAO,OAAG,kBAAA,eAAA;AACR,KAAA,OAAU,UAAA,YAAA,WAAA,OAAA;EACV,MAAM,SAAA,OAAA,UAAA,WAAA,KAAA,GAAA,MAAA,aAAA;EACN,MAAA,QAAc,OAAA,eAAA;EACd,MAAS,WAAA,OAAA,UAAA,WAAA,QAAA,MAAA;AACT,SAAK,MAAA,SAAA,KAAA,KAAA,WAAA,OAAA,IAAA,SAAA,CAAA,EAAA,WAAA,SAAA;AACL,OAAA,KAEM,QAAA,eAAkB,cAAgB,QAC7C,MAAA,UAAA,mBAAA,iBAAA,OAAA;AAEM,UAAA,GAAA;;;;;;;;;;;;;;;;;CC1IJ,cAAA;AAML,OAAA,cAAc,OAAA,0BAAA,EAAA,UAJiB,MAKvB,CAAA;EACN,MAAS,WAAU,IAAM,OAAA,UAAW;AAG9B,WAAS,QAAA,EAAW,MAAA,WAAA;AAInB,OAAM,SAAA,UACF,QAAA,4BAAuB,OAAA;AAGvB,OAAA,CAAA,KACK,QAAA,gBAAa,OAAiB;AAGrC,OAAA,KAAA,aAAwB,iBAAA,QAAA,KAAA,aAAA,iBAAA,MAAA,KAAA;AAKxB,UAAA,wBAAa,KAAA,0BAAA,KAAA,IAAA,KAAA;;;AAItB,MAAO,KAAO,YACZ,YAAA,KAAA,KAAA,YAAA,uBAAA,CAAA;AAEK,SAAA,IAAA,GAAA,YAAA;GACG;GACR,UAAA;GAEG,KAAS;;GAGhB,CAAA;AACE,OAAO,SAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC3CT,IAAA,iCAAA,MAAA,+BAAM;CACX,UAAU,OAAO,oBAAsB,EAAA,MAAM,MAAO,CAAA;CAEpD,MAAM,OAAO,SAA2C;AAM/C,SAAA;GACN,SAN6B,MAAKA,MAAAA,OAG5B,mBAAA,CACI,MAAA,QAAA;;GAKb;;CAEI,mBAEG,SAAA;AAGH,SAAO,CAAM,GAAG,QAAA,SAAA,0BAAA,CAAA,CAAA,KAAA,WAAA;GACV,IAAM,MAAG,GACd,MAAA,CAAA,aAAA,CAIW,QAAA,aAAA,GAAA,CAAA,QAAA,QAAA,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC3BlB,IAAM,6BAAyC;CAC7C;EACE,SAAS;EACT,UAAU;EACX;CACD,uBAAuB;CACvB,uBAAA;CACD;AAED,SAAgB,qBACd,SACU;AACV,QACE,CAIQ,4BACA,SAAY,cAIrB,CAAA;EAGa,SAAA;EAEZ,YAAA,QAAA;EAAW,CAA6B,GACrC,EACJ,CAAA;;;;;;;;;;;ACjBY,IAAA,+BAAA,MAAA,6BAAM;;mBACC,OAAO,aAAa;eACxB,OAAO,eAAe;yBACH,OAAO,gBAAgB;iBAEvB,KAAA,UAAU,wBACtC,KAAM,MAAA,SAAc,KAAA,yBAC1B;iBAEkB;;CAEnB,qBAA2B;AACpB,OAAA,gBAAgB,SAAS;;;;;;;;;;;;;AAH/B,OAAO,OAAA,GAAA,qBAAA;GAAA,YAAA;GAAA,SAAA;GAAA,MAAA;GAAA,cAAA;GAAA,UAAA;GAAA,QAAA,EAAA,SAAA,WAAA;GAAA,gBAAA,CAAA,EAAA,WAAA,2BAAA,CAAA;GAAA,UAAA;GAAA,UAAA;GAAA,UAAA;GAAA,eAAA,GAAA,kBAAA;GAAA,qBAAA;GAAA,CAAA;;;GAjBR,yBAAU;CAAA,YAAA;CAAA,SAAA;CAAA,UAAA;CAAA,MAAA;CAAA,YAAA,CAAA;EACV,MAAY;EACD,MAAA,CAAA;GACM,UAAA;GACjB,YAAqB;GACN,SAAA,EAAA;GACL,gBAAA,CAAA,0BAAA;GACV,qBAAA;;;;;;;;;ACUa,IAAA,0BAAA,MAAA,wBAAM;CA6BnB,cAAc;mBA5BM,OAAO,aAAa;eACxB,OAAO,eAAe;cACvB,OAAO,OAAO;oBACC,OAAO,YAAY;uBAChB,OAAO,sBACtC,EAAA,UAAA,MAAA,CAAA;AAQA,OAAM,gBAAoB,SAAS,KAAA,kBAAA,CAAA;AAE/B,OAAA,cAAc,eAAA;GACT,MAAK,eAAU,KAAA,SAAA;oBAGZ,QAAA,KAAA,UAAe,wBAAA,aAAA;UAEuC,KAEjE,eAAA;QAGuD,EAAA,CAAA;AAGpD,OAAA,UAAA,MAEG,GAA+B,EAAA,CAAA;;AAIxC,OAAA,kBAAkD,OAAA,gBAAA;AAChD,MAAO,kBACL,KAAmB,WAAS,IAAK,KAAA,cAO/B,MAAA,YAAc,KAAkC,cAAA;;;AAKtD,SAAA,KAAA,MAA2B,KAAA,KAAA,KAAA,SAAA,KAAA,qBAAA,GAAA,EAAA,WAAA,kBAAA,KAAA,cAAA,cAAA,CAAA,EAAA,KAAA,YAAA,KAAA,UAAA,wBAAA,QAAA,CAAA,EAAA,YAAA,MAAA,GAAA,sBAAA,IAAA,CAAA,CAAA;;CAEzB,MAAK,cAAK,SAAA;AAGZ,UAAA,MAAA,KAAA,gBAAA,OAAA,QAAA,EAAoB;;CAOZ,qBAAe;AACV,OAAA,gBAAgB,SAAa;AAG7B,OAAA,KAAS,wBAAa,KAAA,SAAA,QAAA,KAAA,CAAA;;;oCAxEnC,KAAY,cAAA,CACZ,KAAiB,oBAAA,CAAA,CACjB,WAAqB,YAAA;AACrB,QAAe,UAAA;AACL,QAAA,QAAA,QAAA,WAAA,EAAA,aAAA,OAAA,CAAA"}
@@ -187,7 +187,7 @@ var RenderTaskService = class RenderTaskService {
187
187
  static {
188
188
  this.ɵfac = i0.ɵɵngDeclareFactory({
189
189
  minVersion: "12.0.0",
190
- version: "21.2.8",
190
+ version: "22.0.0",
191
191
  ngImport: i0,
192
192
  type: RenderTaskService,
193
193
  deps: [],
@@ -197,7 +197,7 @@ var RenderTaskService = class RenderTaskService {
197
197
  static {
198
198
  this.ɵprov = i0.ɵɵngDeclareInjectable({
199
199
  minVersion: "12.0.0",
200
- version: "21.2.8",
200
+ version: "22.0.0",
201
201
  ngImport: i0,
202
202
  type: RenderTaskService
203
203
  });
@@ -205,7 +205,7 @@ var RenderTaskService = class RenderTaskService {
205
205
  };
206
206
  i0.ɵɵngDeclareClassMetadata({
207
207
  minVersion: "12.0.0",
208
- version: "21.2.8",
208
+ version: "22.0.0",
209
209
  ngImport: i0,
210
210
  type: RenderTaskService,
211
211
  decorators: [{ type: Injectable }]
@@ -15,7 +15,7 @@ var ContentRenderer = class ContentRenderer {
15
15
  static {
16
16
  this.ɵfac = i0.ɵɵngDeclareFactory({
17
17
  minVersion: "12.0.0",
18
- version: "21.2.8",
18
+ version: "22.0.0",
19
19
  ngImport: i0,
20
20
  type: ContentRenderer,
21
21
  deps: [],
@@ -25,7 +25,7 @@ var ContentRenderer = class ContentRenderer {
25
25
  static {
26
26
  this.ɵprov = i0.ɵɵngDeclareInjectable({
27
27
  minVersion: "12.0.0",
28
- version: "21.2.8",
28
+ version: "22.0.0",
29
29
  ngImport: i0,
30
30
  type: ContentRenderer
31
31
  });
@@ -33,7 +33,7 @@ var ContentRenderer = class ContentRenderer {
33
33
  };
34
34
  i0.ɵɵngDeclareClassMetadata({
35
35
  minVersion: "12.0.0",
36
- version: "21.2.8",
36
+ version: "22.0.0",
37
37
  ngImport: i0,
38
38
  type: ContentRenderer,
39
39
  decorators: [{ type: Injectable }]
@@ -5,7 +5,7 @@ var MarkedContentHighlighter = class MarkedContentHighlighter {
5
5
  static {
6
6
  this.ɵfac = i0.ɵɵngDeclareFactory({
7
7
  minVersion: "12.0.0",
8
- version: "21.2.8",
8
+ version: "22.0.0",
9
9
  ngImport: i0,
10
10
  type: MarkedContentHighlighter,
11
11
  deps: [],
@@ -15,7 +15,7 @@ var MarkedContentHighlighter = class MarkedContentHighlighter {
15
15
  static {
16
16
  this.ɵprov = i0.ɵɵngDeclareInjectable({
17
17
  minVersion: "12.0.0",
18
- version: "21.2.8",
18
+ version: "22.0.0",
19
19
  ngImport: i0,
20
20
  type: MarkedContentHighlighter
21
21
  });
@@ -23,7 +23,7 @@ var MarkedContentHighlighter = class MarkedContentHighlighter {
23
23
  };
24
24
  i0.ɵɵngDeclareClassMetadata({
25
25
  minVersion: "12.0.0",
26
- version: "21.2.8",
26
+ version: "22.0.0",
27
27
  ngImport: i0,
28
28
  type: MarkedContentHighlighter,
29
29
  decorators: [{ type: Injectable }]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@analogjs/content",
3
- "version": "3.0.0-alpha.56",
3
+ "version": "3.0.0-alpha.58",
4
4
  "description": "Content Rendering for Analog",
5
5
  "type": "module",
6
6
  "author": "Brandon Roberts <robertsbt@gmail.com>",
@@ -23,10 +23,10 @@
23
23
  "url": "https://github.com/sponsors/brandonroberts"
24
24
  },
25
25
  "peerDependencies": {
26
- "@angular/common": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
27
- "@angular/core": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
28
- "@angular/platform-browser": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
29
- "@angular/router": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0",
26
+ "@angular/common": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
27
+ "@angular/core": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
28
+ "@angular/platform-browser": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
29
+ "@angular/router": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
30
30
  "@nx/devkit": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0 || ^22",
31
31
  "front-matter": "^4.0.2",
32
32
  "marked": "^18.0.0",
@@ -113,8 +113,8 @@
113
113
  "tslib": "^2.3.0"
114
114
  },
115
115
  "devDependencies": {
116
- "@analogjs/vite-plugin-angular": "3.0.0-alpha.56",
117
- "@analogjs/vitest-angular": "3.0.0-alpha.56"
116
+ "@analogjs/vite-plugin-angular": "3.0.0-alpha.58",
117
+ "@analogjs/vitest-angular": "3.0.0-alpha.58"
118
118
  },
119
119
  "ng-update": {
120
120
  "packageGroup": [