@analogjs/content 3.0.0-alpha.95 → 3.0.0-alpha.96

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.
Files changed (37) hide show
  1. package/fesm2022/analogjs-content-md4x.mjs +1 -2
  2. package/fesm2022/analogjs-content-md4x.mjs.map +1 -1
  3. package/fesm2022/analogjs-content-resources.mjs +1 -2
  4. package/fesm2022/analogjs-content-resources.mjs.map +1 -1
  5. package/fesm2022/analogjs-content.mjs +3 -122
  6. package/fesm2022/analogjs-content.mjs.map +1 -1
  7. package/fesm2022/content-list-loader.mjs +125 -2
  8. package/fesm2022/content-list-loader.mjs.map +1 -1
  9. package/package.json +3 -21
  10. package/plugin/migrations.json +1 -71
  11. package/types/src/index.d.ts +0 -3
  12. package/fesm2022/analogjs-content-prism-highlighter.mjs +0 -99
  13. package/fesm2022/analogjs-content-prism-highlighter.mjs.map +0 -1
  14. package/fesm2022/analogjs-content-shiki-highlighter.mjs +0 -12
  15. package/fesm2022/analogjs-content-shiki-highlighter.mjs.map +0 -1
  16. package/fesm2022/content-renderer.mjs +0 -128
  17. package/fesm2022/content-renderer.mjs.map +0 -1
  18. package/fesm2022/marked-content-highlighter.mjs +0 -40
  19. package/fesm2022/marked-content-highlighter.mjs.map +0 -1
  20. package/plugin/src/migrations/update-markdown-version/compat.d.ts +0 -6
  21. package/plugin/src/migrations/update-markdown-version/compat.d.ts.map +0 -1
  22. package/plugin/src/migrations/update-markdown-version/compat.js +0 -9
  23. package/plugin/src/migrations/update-markdown-version/compat.js.map +0 -1
  24. package/plugin/src/migrations/update-markdown-version/update-markdown-version.d.ts +0 -6
  25. package/plugin/src/migrations/update-markdown-version/update-markdown-version.d.ts.map +0 -1
  26. package/plugin/src/migrations/update-markdown-version/update-markdown-version.js +0 -21
  27. package/plugin/src/migrations/update-markdown-version/update-markdown-version.js.map +0 -1
  28. package/prism-highlighter/package.json +0 -4
  29. package/shiki-highlighter/package.json +0 -4
  30. package/types/prism-highlighter/src/index.d.ts +0 -8
  31. package/types/prism-highlighter/src/lib/prism/angular.d.ts +0 -1
  32. package/types/prism-highlighter/src/lib/prism-highlighter.d.ts +0 -8
  33. package/types/shiki-highlighter/src/index.d.ts +0 -7
  34. package/types/src/lib/markdown-content-renderer.service.d.ts +0 -10
  35. package/types/src/lib/marked-content-highlighter.d.ts +0 -17
  36. package/types/src/lib/marked-setup.service.d.ts +0 -10
  37. package/types/src/lib/utils/escape-html.d.ts +0 -7
@@ -1,5 +1,4 @@
1
- import { t as ContentRenderer } from "./content-renderer.mjs";
2
- import { o as withContentFileLoader, r as withContentListLoader } from "./content-list-loader.mjs";
1
+ import { g as ContentRenderer, o as withContentFileLoader, r as withContentListLoader } from "./content-list-loader.mjs";
3
2
  import * as i0 from "@angular/core";
4
3
  import { Injectable, InjectionToken, inject } from "@angular/core";
5
4
  //#region packages/content/md4x/src/lib/md4x-content-renderer.service.ts
@@ -1 +1 @@
1
- {"version":3,"file":"analogjs-content-md4x.mjs","names":[],"sources":["../../md4x/src/lib/md4x-content-renderer.service.ts","../../md4x/src/lib/md4x-wasm-content-renderer.service.ts","../../md4x/src/lib/provide-md4x.ts","../../md4x/src/lib/streaming-markdown-renderer.ts"],"sourcesContent":["import { Injectable, inject, InjectionToken } from '@angular/core';\n\nimport {\n ContentRenderer,\n RenderedContent,\n TableOfContentItem,\n} from '../../../src/lib/content-renderer';\n\n/**\n * Options for the experimental md4x-based content renderer.\n *\n * @experimental md4x integration is experimental and may change in future releases.\n */\nexport interface Md4xRendererOptions {\n /** Heal incomplete markdown (useful for streaming/LLM content). */\n heal?: boolean;\n /** Custom code block highlighter. Receives raw code and block metadata,\n * returns highlighted HTML or undefined to keep default rendering. */\n highlighter?: (\n code: string,\n block: { lang: string; filename?: string; highlights?: number[] },\n ) => string | undefined;\n}\n\nexport const MD4X_RENDERER_OPTIONS: InjectionToken<Md4xRendererOptions> =\n new InjectionToken<Md4xRendererOptions>('md4x_renderer_options');\n\nfunction makeSlug(text: string, slugCounts: Map<string, number>): string {\n const baseSlug = text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .trim()\n .replace(/\\s+/g, '-');\n const count = slugCounts.get(baseSlug) ?? 0;\n slugCounts.set(baseSlug, count + 1);\n return count === 0 ? baseSlug : `${baseSlug}-${count}`;\n}\n\nfunction headingsToToc(\n headings: Array<{ level: number; text: string }>,\n): TableOfContentItem[] {\n const slugCounts = new Map<string, number>();\n return headings.map((h) => ({\n id: makeSlug(h.text, slugCounts),\n level: h.level,\n text: h.text,\n }));\n}\n\nfunction injectHeadingIds(html: string, toc: TableOfContentItem[]): string {\n let tocIndex = 0;\n return html.replace(/<h([1-6])>/g, (match, level) => {\n const item = toc[tocIndex++];\n return item ? `<h${level} id=\"${item.id}\">` : match;\n });\n}\n\n/**\n * Content renderer backed by md4x (C-based CommonMark parser compiled with Zig).\n * 50-70x faster than marked for complex documents.\n *\n * @experimental md4x integration is experimental and may change in future releases.\n */\n@Injectable()\nexport class Md4xContentRendererService extends ContentRenderer {\n private options = inject(MD4X_RENDERER_OPTIONS, { optional: true });\n\n override async render(content: string): Promise<RenderedContent> {\n const { renderToHtml, parseMeta } = await import('md4x/napi');\n const html = renderToHtml(content, {\n heal: this.options?.heal,\n highlighter: this.options?.highlighter,\n });\n const meta = parseMeta(content);\n const toc = headingsToToc(meta.headings);\n return {\n content: injectHeadingIds(html, toc),\n toc,\n };\n }\n\n override getContentHeadings(content: string): TableOfContentItem[] {\n // Synchronous fallback — md4x is async-imported so we use regex extraction\n // matching NoopContentRenderer's algorithm for consistency.\n const lines = content.split('\\n');\n const toc: TableOfContentItem[] = [];\n const slugCounts = new Map<string, number>();\n\n for (const line of lines) {\n const match = /^(#{1,6})\\s+(.+?)\\s*$/.exec(line);\n if (!match) continue;\n const level = match[1].length;\n const text = match[2].trim();\n if (!text) continue;\n toc.push({ id: makeSlug(text, slugCounts), level, text });\n }\n\n return toc;\n }\n}\n","import { Injectable, inject } from '@angular/core';\n\nimport {\n ContentRenderer,\n RenderedContent,\n TableOfContentItem,\n} from '../../../src/lib/content-renderer';\nimport {\n MD4X_RENDERER_OPTIONS,\n Md4xRendererOptions,\n} from './md4x-content-renderer.service';\n\nfunction makeSlug(text: string, slugCounts: Map<string, number>): string {\n const baseSlug = text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .trim()\n .replace(/\\s+/g, '-');\n const count = slugCounts.get(baseSlug) ?? 0;\n slugCounts.set(baseSlug, count + 1);\n return count === 0 ? baseSlug : `${baseSlug}-${count}`;\n}\n\nfunction headingsToToc(\n headings: Array<{ level: number; text: string }>,\n): TableOfContentItem[] {\n const slugCounts = new Map<string, number>();\n return headings.map((h) => ({\n id: makeSlug(h.text, slugCounts),\n level: h.level,\n text: h.text,\n }));\n}\n\nfunction injectHeadingIds(html: string, toc: TableOfContentItem[]): string {\n let tocIndex = 0;\n return html.replace(/<h([1-6])>/g, (match, level) => {\n const item = toc[tocIndex++];\n return item ? `<h${level} id=\"${item.id}\">` : match;\n });\n}\n\n/**\n * Content renderer backed by md4x/wasm for client-side (browser) rendering.\n * ~100KB gzip, 3-6x faster than marked in the browser.\n *\n * @experimental md4x integration is experimental and may change in future releases.\n */\n@Injectable()\nexport class Md4xWasmContentRendererService extends ContentRenderer {\n private options = inject(MD4X_RENDERER_OPTIONS, { optional: true });\n private initPromise: Promise<void> | null = null;\n\n override async render(content: string): Promise<RenderedContent> {\n const wasm = await import('md4x/wasm');\n if (!this.initPromise) {\n this.initPromise = wasm.init();\n }\n await this.initPromise;\n\n const html = wasm.renderToHtml(content, {\n heal: this.options?.heal,\n highlighter: this.options?.highlighter,\n });\n const meta = wasm.parseMeta(content);\n const toc = headingsToToc(meta.headings);\n return {\n content: injectHeadingIds(html, toc),\n toc,\n };\n }\n\n override getContentHeadings(content: string): TableOfContentItem[] {\n const lines = content.split('\\n');\n const toc: TableOfContentItem[] = [];\n const slugCounts = new Map<string, number>();\n\n for (const line of lines) {\n const match = /^(#{1,6})\\s+(.+?)\\s*$/.exec(line);\n if (!match) continue;\n const level = match[1].length;\n const text = match[2].trim();\n if (!text) continue;\n toc.push({ id: makeSlug(text, slugCounts), level, text });\n }\n\n return toc;\n }\n}\n","import { Provider } from '@angular/core';\n\nimport { ContentRenderer } from '../../../src/lib/content-renderer';\nimport { withContentFileLoader } from '../../../src/lib/content-file-loader';\nimport { withContentListLoader } from '../../../src/lib/content-list-loader';\nimport {\n Md4xContentRendererService,\n MD4X_RENDERER_OPTIONS,\n} from './md4x-content-renderer.service';\nimport { Md4xWasmContentRendererService } from './md4x-wasm-content-renderer.service';\nimport type { Md4xRendererOptions } from './md4x-content-renderer.service';\n\n/**\n * Provides the experimental md4x-based content renderer (NAPI, server/build-time).\n *\n * @experimental md4x integration is experimental and may change in future releases.\n *\n * @example\n * ```typescript\n * provideContent(withMd4xRenderer());\n * provideContent(withMd4xRenderer({ heal: true }));\n * ```\n */\nexport function withMd4xRenderer(options?: Md4xRendererOptions): Provider {\n return [\n { provide: ContentRenderer, useClass: Md4xContentRendererService },\n options ? { provide: MD4X_RENDERER_OPTIONS, useValue: options } : [],\n withContentFileLoader(),\n withContentListLoader(),\n ];\n}\n\n/**\n * Provides the experimental md4x WASM content renderer (browser/CSR).\n * ~100KB gzip, 3-6x faster than marked in the browser.\n *\n * @experimental md4x integration is experimental and may change in future releases.\n *\n * @example\n * ```typescript\n * provideContent(withMd4xWasmRenderer());\n * ```\n */\nexport function withMd4xWasmRenderer(options?: Md4xRendererOptions): Provider {\n return [\n { provide: ContentRenderer, useClass: Md4xWasmContentRendererService },\n options ? { provide: MD4X_RENDERER_OPTIONS, useValue: options } : [],\n withContentFileLoader(),\n withContentListLoader(),\n ];\n}\n","/**\n * Transforms a stream of markdown chunks into a stream of rendered HTML.\n * Uses md4x's `heal()` to fix incomplete markdown from streaming sources\n * (LLMs, collaborative editing) so each emitted HTML chunk is valid.\n *\n * @experimental Streaming markdown support is experimental and may change in future releases.\n *\n * @example\n * ```typescript\n * // In a Nitro API route\n * import { streamMarkdown } from '@analogjs/content';\n *\n * export default defineEventHandler(async (event) => {\n * const llmStream = getAIStream(prompt);\n * return streamMarkdown(llmStream, { heal: true });\n * });\n * ```\n */\nexport async function streamMarkdown(\n input: ReadableStream<string>,\n options?: { heal?: boolean },\n): Promise<ReadableStream<string>> {\n const { renderToHtml, heal } = await import('md4x/napi');\n\n let buffer = '';\n const reader = input.getReader();\n\n return new ReadableStream<string>({\n async pull(controller) {\n try {\n const { done, value } = await reader.read();\n\n if (done) {\n if (buffer) {\n const source = options?.heal ? heal(buffer) : buffer;\n controller.enqueue(renderToHtml(source));\n }\n controller.close();\n return;\n }\n\n buffer += value;\n const source = options?.heal ? heal(buffer) : buffer;\n controller.enqueue(renderToHtml(source));\n } catch (error) {\n controller.error(error);\n }\n },\n cancel() {\n reader.cancel();\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"analogjs-content-md4x.mjs","names":[],"sources":["../../md4x/src/lib/md4x-content-renderer.service.ts","../../md4x/src/lib/md4x-wasm-content-renderer.service.ts","../../md4x/src/lib/provide-md4x.ts","../../md4x/src/lib/streaming-markdown-renderer.ts"],"sourcesContent":["import { Injectable, inject, InjectionToken } from '@angular/core';\n\nimport {\n ContentRenderer,\n RenderedContent,\n TableOfContentItem,\n} from '../../../src/lib/content-renderer';\n\n/**\n * Options for the experimental md4x-based content renderer.\n *\n * @experimental md4x integration is experimental and may change in future releases.\n */\nexport interface Md4xRendererOptions {\n /** Heal incomplete markdown (useful for streaming/LLM content). */\n heal?: boolean;\n /** Custom code block highlighter. Receives raw code and block metadata,\n * returns highlighted HTML or undefined to keep default rendering. */\n highlighter?: (\n code: string,\n block: { lang: string; filename?: string; highlights?: number[] },\n ) => string | undefined;\n}\n\nexport const MD4X_RENDERER_OPTIONS: InjectionToken<Md4xRendererOptions> =\n new InjectionToken<Md4xRendererOptions>('md4x_renderer_options');\n\nfunction makeSlug(text: string, slugCounts: Map<string, number>): string {\n const baseSlug = text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .trim()\n .replace(/\\s+/g, '-');\n const count = slugCounts.get(baseSlug) ?? 0;\n slugCounts.set(baseSlug, count + 1);\n return count === 0 ? baseSlug : `${baseSlug}-${count}`;\n}\n\nfunction headingsToToc(\n headings: Array<{ level: number; text: string }>,\n): TableOfContentItem[] {\n const slugCounts = new Map<string, number>();\n return headings.map((h) => ({\n id: makeSlug(h.text, slugCounts),\n level: h.level,\n text: h.text,\n }));\n}\n\nfunction injectHeadingIds(html: string, toc: TableOfContentItem[]): string {\n let tocIndex = 0;\n return html.replace(/<h([1-6])>/g, (match, level) => {\n const item = toc[tocIndex++];\n return item ? `<h${level} id=\"${item.id}\">` : match;\n });\n}\n\n/**\n * Content renderer backed by md4x (C-based CommonMark parser compiled with Zig).\n * 50-70x faster than marked for complex documents.\n *\n * @experimental md4x integration is experimental and may change in future releases.\n */\n@Injectable()\nexport class Md4xContentRendererService extends ContentRenderer {\n private options = inject(MD4X_RENDERER_OPTIONS, { optional: true });\n\n override async render(content: string): Promise<RenderedContent> {\n const { renderToHtml, parseMeta } = await import('md4x/napi');\n const html = renderToHtml(content, {\n heal: this.options?.heal,\n highlighter: this.options?.highlighter,\n });\n const meta = parseMeta(content);\n const toc = headingsToToc(meta.headings);\n return {\n content: injectHeadingIds(html, toc),\n toc,\n };\n }\n\n override getContentHeadings(content: string): TableOfContentItem[] {\n // Synchronous fallback — md4x is async-imported so we use regex extraction\n // matching NoopContentRenderer's algorithm for consistency.\n const lines = content.split('\\n');\n const toc: TableOfContentItem[] = [];\n const slugCounts = new Map<string, number>();\n\n for (const line of lines) {\n const match = /^(#{1,6})\\s+(.+?)\\s*$/.exec(line);\n if (!match) continue;\n const level = match[1].length;\n const text = match[2].trim();\n if (!text) continue;\n toc.push({ id: makeSlug(text, slugCounts), level, text });\n }\n\n return toc;\n }\n}\n","import { Injectable, inject } from '@angular/core';\n\nimport {\n ContentRenderer,\n RenderedContent,\n TableOfContentItem,\n} from '../../../src/lib/content-renderer';\nimport {\n MD4X_RENDERER_OPTIONS,\n Md4xRendererOptions,\n} from './md4x-content-renderer.service';\n\nfunction makeSlug(text: string, slugCounts: Map<string, number>): string {\n const baseSlug = text\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .trim()\n .replace(/\\s+/g, '-');\n const count = slugCounts.get(baseSlug) ?? 0;\n slugCounts.set(baseSlug, count + 1);\n return count === 0 ? baseSlug : `${baseSlug}-${count}`;\n}\n\nfunction headingsToToc(\n headings: Array<{ level: number; text: string }>,\n): TableOfContentItem[] {\n const slugCounts = new Map<string, number>();\n return headings.map((h) => ({\n id: makeSlug(h.text, slugCounts),\n level: h.level,\n text: h.text,\n }));\n}\n\nfunction injectHeadingIds(html: string, toc: TableOfContentItem[]): string {\n let tocIndex = 0;\n return html.replace(/<h([1-6])>/g, (match, level) => {\n const item = toc[tocIndex++];\n return item ? `<h${level} id=\"${item.id}\">` : match;\n });\n}\n\n/**\n * Content renderer backed by md4x/wasm for client-side (browser) rendering.\n * ~100KB gzip, 3-6x faster than marked in the browser.\n *\n * @experimental md4x integration is experimental and may change in future releases.\n */\n@Injectable()\nexport class Md4xWasmContentRendererService extends ContentRenderer {\n private options = inject(MD4X_RENDERER_OPTIONS, { optional: true });\n private initPromise: Promise<void> | null = null;\n\n override async render(content: string): Promise<RenderedContent> {\n const wasm = await import('md4x/wasm');\n if (!this.initPromise) {\n this.initPromise = wasm.init();\n }\n await this.initPromise;\n\n const html = wasm.renderToHtml(content, {\n heal: this.options?.heal,\n highlighter: this.options?.highlighter,\n });\n const meta = wasm.parseMeta(content);\n const toc = headingsToToc(meta.headings);\n return {\n content: injectHeadingIds(html, toc),\n toc,\n };\n }\n\n override getContentHeadings(content: string): TableOfContentItem[] {\n const lines = content.split('\\n');\n const toc: TableOfContentItem[] = [];\n const slugCounts = new Map<string, number>();\n\n for (const line of lines) {\n const match = /^(#{1,6})\\s+(.+?)\\s*$/.exec(line);\n if (!match) continue;\n const level = match[1].length;\n const text = match[2].trim();\n if (!text) continue;\n toc.push({ id: makeSlug(text, slugCounts), level, text });\n }\n\n return toc;\n }\n}\n","import { Provider } from '@angular/core';\n\nimport { ContentRenderer } from '../../../src/lib/content-renderer';\nimport { withContentFileLoader } from '../../../src/lib/content-file-loader';\nimport { withContentListLoader } from '../../../src/lib/content-list-loader';\nimport {\n Md4xContentRendererService,\n MD4X_RENDERER_OPTIONS,\n} from './md4x-content-renderer.service';\nimport { Md4xWasmContentRendererService } from './md4x-wasm-content-renderer.service';\nimport type { Md4xRendererOptions } from './md4x-content-renderer.service';\n\n/**\n * Provides the experimental md4x-based content renderer (NAPI, server/build-time).\n *\n * @experimental md4x integration is experimental and may change in future releases.\n *\n * @example\n * ```typescript\n * provideContent(withMd4xRenderer());\n * provideContent(withMd4xRenderer({ heal: true }));\n * ```\n */\nexport function withMd4xRenderer(options?: Md4xRendererOptions): Provider {\n return [\n { provide: ContentRenderer, useClass: Md4xContentRendererService },\n options ? { provide: MD4X_RENDERER_OPTIONS, useValue: options } : [],\n withContentFileLoader(),\n withContentListLoader(),\n ];\n}\n\n/**\n * Provides the experimental md4x WASM content renderer (browser/CSR).\n * ~100KB gzip, 3-6x faster than marked in the browser.\n *\n * @experimental md4x integration is experimental and may change in future releases.\n *\n * @example\n * ```typescript\n * provideContent(withMd4xWasmRenderer());\n * ```\n */\nexport function withMd4xWasmRenderer(options?: Md4xRendererOptions): Provider {\n return [\n { provide: ContentRenderer, useClass: Md4xWasmContentRendererService },\n options ? { provide: MD4X_RENDERER_OPTIONS, useValue: options } : [],\n withContentFileLoader(),\n withContentListLoader(),\n ];\n}\n","/**\n * Transforms a stream of markdown chunks into a stream of rendered HTML.\n * Uses md4x's `heal()` to fix incomplete markdown from streaming sources\n * (LLMs, collaborative editing) so each emitted HTML chunk is valid.\n *\n * @experimental Streaming markdown support is experimental and may change in future releases.\n *\n * @example\n * ```typescript\n * // In a Nitro API route\n * import { streamMarkdown } from '@analogjs/content';\n *\n * export default defineEventHandler(async (event) => {\n * const llmStream = getAIStream(prompt);\n * return streamMarkdown(llmStream, { heal: true });\n * });\n * ```\n */\nexport async function streamMarkdown(\n input: ReadableStream<string>,\n options?: { heal?: boolean },\n): Promise<ReadableStream<string>> {\n const { renderToHtml, heal } = await import('md4x/napi');\n\n let buffer = '';\n const reader = input.getReader();\n\n return new ReadableStream<string>({\n async pull(controller) {\n try {\n const { done, value } = await reader.read();\n\n if (done) {\n if (buffer) {\n const source = options?.heal ? heal(buffer) : buffer;\n controller.enqueue(renderToHtml(source));\n }\n controller.close();\n return;\n }\n\n buffer += value;\n const source = options?.heal ? heal(buffer) : buffer;\n controller.enqueue(renderToHtml(source));\n } catch (error) {\n controller.error(error);\n }\n },\n cancel() {\n reader.cancel();\n },\n });\n}\n"],"mappings":"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"}
@@ -1,5 +1,4 @@
1
- import { t as ContentRenderer } from "./content-renderer.mjs";
2
- import { a as injectContentFileLoader, f as filterByLocale, n as injectContentListLoader, p as injectContentLocale } from "./content-list-loader.mjs";
1
+ import { a as injectContentFileLoader, f as filterByLocale, g as ContentRenderer, n as injectContentListLoader, p as injectContentLocale } from "./content-list-loader.mjs";
3
2
  import { n as parseRawContentFile, r as parseRawContentFileAsync, t as FrontmatterValidationError } from "./parse-raw-content-file.mjs";
4
3
  import { computed, inject, resource } from "@angular/core";
5
4
  import { ActivatedRoute } from "@angular/router";
@@ -1 +1 @@
1
- {"version":3,"file":"analogjs-content-resources.mjs","names":[],"sources":["../../resources/src/content-files-resource.ts","../../resources/src/content-file-resource.ts"],"sourcesContent":["import { resource, ResourceRef } from '@angular/core';\nimport type { ContentFile } from '../../src/lib/content-file';\nimport {\n filterByLocale,\n injectContentLocale,\n} from '../../src/lib/content-locale';\nimport type { InjectContentFilesFilterFunction } from '../../src/lib/inject-content-files';\nimport { injectContentListLoader } from '../../src/lib/content-list-loader';\n\nexport function contentFilesResource<Attributes extends Record<string, any>>(\n filterFn?: InjectContentFilesFilterFunction<Attributes> | undefined,\n): ResourceRef<ContentFile<Attributes>[] | undefined> {\n const contentListLoader = injectContentListLoader<Attributes>();\n const locale = injectContentLocale();\n const contentList = contentListLoader().then((items) => {\n let results = locale ? filterByLocale(items, locale) : items;\n if (filterFn) {\n results = results.filter(filterFn);\n }\n return results;\n });\n\n return resource({\n loader: () => contentList,\n });\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec';\nimport {\n computed,\n inject,\n resource,\n Signal,\n type ResourceRef,\n} from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\n\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { from } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport type { ContentFile } from '../../src/lib/content-file';\nimport { injectContentLocale } from '../../src/lib/content-locale';\nimport { ContentRenderer } from '../../src/lib/content-renderer';\nimport {\n FrontmatterValidationError,\n parseRawContentFile,\n parseRawContentFileAsync,\n} from '../../src/lib/parse-raw-content-file';\nimport { injectContentFileLoader } from '../../src/lib/content-file-loader';\n\nexport interface ContentFileResourceResult<\n Attributes extends Record<string, any> = Record<string, any>,\n> extends ContentFile<Attributes | Record<string, never>> {\n toc: Array<{ id: string; level: number; text: string }>;\n}\n\ntype ContentFileParams =\n | Signal<string | { customFilename: string }>\n | Signal<string>\n | Signal<{ customFilename: string }>;\n\nasync function validateAttributes<TSchema extends StandardSchemaV1>(\n schema: TSchema,\n attributes: unknown,\n filename?: string,\n) {\n const result = await schema['~standard'].validate(attributes);\n if (result.issues) {\n throw new FrontmatterValidationError(result.issues, filename);\n }\n\n return result.value;\n}\n\nfunction getValidationFilename(filename: string): string {\n return filename.replace(/^\\/src\\/content\\//, '');\n}\n\nasync function getContentFile<\n Attributes extends Record<string, any> = Record<string, any>,\n>(\n contentFiles: Record<string, () => Promise<string>>,\n slug: string,\n fallback: string,\n locale?: string | null,\n schema?: StandardSchemaV1,\n): Promise<ContentFile<Attributes | Record<string, never>>> {\n // Normalize file keys so both \"/src/content/...\" and \"/<project>/src/content/...\" resolve.\n // This mirrors normalization used elsewhere in the content pipeline.\n const normalizedFiles: Record<string, () => Promise<string>> = {};\n const stemToKey: Record<string, string> = {};\n for (const [key, resolver] of Object.entries(contentFiles)) {\n const normalizedKey = key\n // replace any prefix up to the content directory with /src/content\n // use a non-greedy match so nested paths containing \"/content\" are preserved\n .replace(/^(?:.*?)\\/content(?=\\/)/, '/src/content')\n // normalize duplicate slashes\n .replace(/\\/{2,}/g, '/');\n normalizedFiles[normalizedKey] = resolver;\n // Index by bare filename stem so slug-only lookups work\n const stem = normalizedKey\n .split('/')\n .pop()\n ?.replace(/\\.[^.]+$/, '');\n if (stem && !stemToKey[stem]) {\n stemToKey[stem] = normalizedKey;\n }\n }\n\n // Try direct file first, then directory index variants, then bare slug via stem\n const base = `/src/content/${slug}`.replace(/\\/{2,}/g, '/');\n const candidates = [`${base}.md`, `${base}/index.md`];\n\n // Try locale-prefixed paths first, then fall back to unprefixed, then bare slug via stem\n const localeCandidates = locale\n ? candidates.map((c) =>\n c.replace('/src/content/', `/src/content/${locale}/`),\n )\n : [];\n const allCandidates = [...localeCandidates, ...candidates];\n const matchKey =\n allCandidates.find((k) => k in normalizedFiles) ?? stemToKey[slug];\n const contentFile = matchKey ? normalizedFiles[matchKey] : undefined;\n\n if (!contentFile) {\n return {\n filename: base,\n attributes: {},\n slug: '',\n content: fallback,\n } as ContentFile<Attributes | Record<string, never>>;\n }\n\n const resolvedBase = matchKey!.replace(/\\.md$/, '');\n const validationFilename = getValidationFilename(matchKey!);\n\n return contentFile().then(\n async (contentFile: string | { default: any; metadata: any }) => {\n if (typeof contentFile === 'string') {\n const { content, attributes } = schema\n ? await parseRawContentFileAsync(\n contentFile,\n schema,\n validationFilename,\n )\n : parseRawContentFile<Attributes>(contentFile);\n\n return {\n filename: resolvedBase,\n slug,\n attributes,\n content,\n } as ContentFile<Attributes | Record<string, never>>;\n }\n\n const attributes = schema\n ? await validateAttributes(\n schema,\n contentFile.metadata,\n validationFilename,\n )\n : contentFile.metadata;\n\n return {\n filename: resolvedBase,\n slug,\n attributes,\n content: contentFile.default,\n } as ContentFile<Attributes | Record<string, never>>;\n },\n );\n}\n\n/**\n * Resource for requesting an individual content file.\n *\n * @example\n * ```typescript\n * // Without schema (existing behavior)\n * const post = contentFileResource<BlogAttributes>();\n *\n * // With schema validation\n * import * as v from 'valibot';\n * const BlogSchema = v.object({\n * title: v.string(),\n * date: v.pipe(v.string(), v.isoDate()),\n * });\n * const post = contentFileResource({ schema: BlogSchema });\n * ```\n */\nexport function contentFileResource<\n Attributes extends Record<string, any> = Record<string, any>,\n>(\n params?: ContentFileParams,\n fallback?: string,\n): ResourceRef<ContentFileResourceResult<Attributes> | undefined>;\n\nexport function contentFileResource<TSchema extends StandardSchemaV1>(options: {\n params?: ContentFileParams;\n fallback?: string;\n schema: TSchema;\n}): ResourceRef<\n | ContentFileResourceResult<\n StandardSchemaV1.InferOutput<TSchema> & Record<string, any>\n >\n | undefined\n>;\n\nexport function contentFileResource(\n paramsOrOptions?:\n | ContentFileParams\n | {\n params?: ContentFileParams;\n fallback?: string;\n schema?: StandardSchemaV1;\n },\n fallbackArg = 'No Content Found',\n) {\n // Detect options-object form vs legacy positional form\n const isOptionsObject =\n paramsOrOptions &&\n typeof paramsOrOptions === 'object' &&\n !('set' in paramsOrOptions) && // not a Signal\n ('schema' in paramsOrOptions ||\n 'params' in paramsOrOptions ||\n 'fallback' in paramsOrOptions);\n\n const params: ContentFileParams | undefined = isOptionsObject\n ? (paramsOrOptions as { params?: ContentFileParams }).params\n : (paramsOrOptions as ContentFileParams | undefined);\n const fallback: string = isOptionsObject\n ? ((paramsOrOptions as { fallback?: string }).fallback ??\n 'No Content Found')\n : fallbackArg;\n const schema: StandardSchemaV1 | undefined = isOptionsObject\n ? (paramsOrOptions as { schema?: StandardSchemaV1 }).schema\n : undefined;\n\n const loaderPromise = injectContentFileLoader();\n const contentRenderer = inject(ContentRenderer);\n const locale = injectContentLocale();\n const contentFilesMap = toSignal(from(loaderPromise()));\n const input =\n params ||\n toSignal(\n inject(ActivatedRoute).paramMap.pipe(\n map((params) => params.get('slug') as string),\n ),\n { requireSync: true },\n );\n\n return resource({\n params: computed(() => ({ input: input(), files: contentFilesMap() })),\n loader: async ({ params: resourceParams }) => {\n const { input: param, files } = resourceParams;\n\n if (typeof param === 'string') {\n if (param) {\n const file = await getContentFile(\n files!,\n param,\n fallback,\n locale,\n schema,\n );\n if (typeof file.content === 'string') {\n const rendered = (await contentRenderer.render(file.content)) as {\n toc?: Array<{ id: string; level: number; text: string }>;\n };\n return {\n ...file,\n toc: rendered.toc ?? [],\n };\n }\n return {\n ...file,\n toc: [],\n };\n }\n\n return {\n filename: '',\n slug: '',\n attributes: {},\n content: fallback,\n toc: [],\n };\n } else {\n const file = await getContentFile(\n files!,\n param.customFilename,\n fallback,\n locale,\n schema,\n );\n if (typeof file.content === 'string') {\n const rendered = (await contentRenderer.render(file.content)) as {\n toc?: Array<{ id: string; level: number; text: string }>;\n };\n return {\n ...file,\n toc: rendered.toc ?? [],\n };\n }\n return {\n ...file,\n toc: [],\n };\n }\n },\n });\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,qBACd,UAAmE;CAEnE,MAAM,oBAAoB,yBAAqC;CAC/D,MAAM,SAAS,qBAAqB;CACpC,MAAM,cAAc,mBAAmB,CAAC,MAAM,UAAS;EACrD,IAAI,UAAU,SAAS,eAAe,OAAO,OAAO,GAAG;AACvD,MAAI,SACF,WAAU,QAAQ,OAAO,SAAS;AAEpC,SAAO;GACP;AAEF,QAAO,SAAS,EACd,cAAc,aACf,CAAC;;;;ACUJ,eAAe,mBACb,QACA,YACA,UAAiB;CAEjB,MAAM,SAAS,MAAM,OAAO,aAAa,SAAS,WAAW;AAC7D,KAAI,OAAO,OACT,OAAM,IAAI,2BAA2B,OAAO,QAAQ,SAAS;AAG/D,QAAO,OAAO;;AAGhB,SAAS,sBAAsB,UAAgB;AAC7C,QAAO,SAAS,QAAQ,qBAAqB,GAAG;;AAGlD,eAAe,eAGb,cACA,MACA,UACA,QACA,QAAyB;CAIzB,MAAM,kBAAyD,EAAE;CACjE,MAAM,YAAoC,EAAE;AAC5C,MAAK,MAAM,CAAC,KAAK,aAAa,OAAO,QAAQ,aAAa,EAAE;EAC1D,MAAM,gBAAgB,IAGnB,QAAQ,2BAA2B,eAAe,CAElD,QAAQ,WAAW,IAAI;AAC1B,kBAAgB,iBAAiB;EAEjC,MAAM,OAAO,cACV,MAAM,IAAI,CACV,KAAK,EACJ,QAAQ,YAAY,GAAG;AAC3B,MAAI,QAAQ,CAAC,UAAU,MACrB,WAAU,QAAQ;;CAKtB,MAAM,OAAO,gBAAgB,OAAO,QAAQ,WAAW,IAAI;CAC3D,MAAM,aAAa,CAAC,GAAG,KAAI,MAAO,GAAG,KAAI,WAAY;CASrD,MAAM,WADgB,CAAC,GALE,SACrB,WAAW,KAAK,MACd,EAAE,QAAQ,iBAAiB,gBAAgB,OAAM,GAAI,CACtD,GACD,EAAE,EACsC,GAAG,WAAW,CAE1C,MAAM,MAAM,KAAK,gBAAgB,IAAI,UAAU;CAC/D,MAAM,cAAc,WAAW,gBAAgB,YAAY,KAAA;AAE3D,KAAI,CAAC,YACH,QAAO;EACL,UAAU;EACV,YAAY,EAAE;EACd,MAAM;EACN,SAAS;EACyC;CAGtD,MAAM,eAAe,SAAU,QAAQ,SAAS,GAAG;CACnD,MAAM,qBAAqB,sBAAsB,SAAU;AAE3D,QAAO,aAAa,CAAC,KACnB,OAAO,gBAAyD;AAC9D,MAAI,OAAO,gBAAgB,UAAU;GACnC,MAAM,EAAE,SAAS,eAAe,SAC5B,MAAM,yBACJ,aACA,QACA,mBACD,GACD,oBAAgC,YAAY;AAEhD,UAAO;IACL,UAAU;IACV;IACA;IACA;IACkD;;AAWtD,SAAO;GACL,UAAU;GACV;GACA,YAXiB,SACf,MAAM,mBACJ,QACA,YAAY,UACZ,mBACD,GACD,YAAY;GAMd,SAAS,YAAY;GAC6B;GAEvD;;AAsCH,SAAgB,oBACd,iBAOA,cAAc,oBAAkB;CAGhC,MAAM,kBACJ,mBACA,OAAO,oBAAoB,YAC3B,EAAE,SAAS,qBACV,YAAY,mBACX,YAAY,mBACZ,cAAc;CAElB,MAAM,SAAwC,kBACzC,gBAAmD,SACnD;CACL,MAAM,WAAmB,kBACnB,gBAA0C,YAC5C,qBACA;CACJ,MAAM,SAAuC,kBACxC,gBAAkD,SACnD,KAAA;CAEJ,MAAM,gBAAgB,yBAAyB;CAC/C,MAAM,kBAAkB,OAAO,gBAAgB;CAC/C,MAAM,SAAS,qBAAqB;CACpC,MAAM,kBAAkB,SAAS,KAAK,eAAe,CAAC,CAAC;CACvD,MAAM,QACJ,UACA,SACE,OAAO,eAAe,CAAC,SAAS,KAC9B,KAAK,WAAW,OAAO,IAAI,OAAiB,CAAC,CAC9C,EACD,EAAE,aAAa,MAAM,CACtB;AAEH,QAAO,SAAS;EACd,QAAQ,gBAAgB;GAAE,OAAO,OAAO;GAAE,OAAO,iBAAiB;GAAE,EAAE;EACtE,QAAQ,OAAO,EAAE,QAAQ,qBAAoB;GAC3C,MAAM,EAAE,OAAO,OAAO,UAAU;AAEhC,OAAI,OAAO,UAAU,UAAU;AAC7B,QAAI,OAAO;KACT,MAAM,OAAO,MAAM,eACjB,OACA,OACA,UACA,QACA,OACD;AACD,SAAI,OAAO,KAAK,YAAY,UAAU;MACpC,MAAM,WAAY,MAAM,gBAAgB,OAAO,KAAK,QAAQ;AAG5D,aAAO;OACL,GAAG;OACH,KAAK,SAAS,OAAO,EAAE;OACxB;;AAEH,YAAO;MACL,GAAG;MACH,KAAK,EAAE;MACR;;AAGH,WAAO;KACL,UAAU;KACV,MAAM;KACN,YAAY,EAAE;KACd,SAAS;KACT,KAAK,EAAE;KACR;UACI;IACL,MAAM,OAAO,MAAM,eACjB,OACA,MAAM,gBACN,UACA,QACA,OACD;AACD,QAAI,OAAO,KAAK,YAAY,UAAU;KACpC,MAAM,WAAY,MAAM,gBAAgB,OAAO,KAAK,QAAQ;AAG5D,YAAO;MACL,GAAG;MACH,KAAK,SAAS,OAAO,EAAE;MACxB;;AAEH,WAAO;KACL,GAAG;KACH,KAAK,EAAE;KACR;;;EAGN,CAAC"}
1
+ {"version":3,"file":"analogjs-content-resources.mjs","names":[],"sources":["../../resources/src/content-files-resource.ts","../../resources/src/content-file-resource.ts"],"sourcesContent":["import { resource, ResourceRef } from '@angular/core';\nimport type { ContentFile } from '../../src/lib/content-file';\nimport {\n filterByLocale,\n injectContentLocale,\n} from '../../src/lib/content-locale';\nimport type { InjectContentFilesFilterFunction } from '../../src/lib/inject-content-files';\nimport { injectContentListLoader } from '../../src/lib/content-list-loader';\n\nexport function contentFilesResource<Attributes extends Record<string, any>>(\n filterFn?: InjectContentFilesFilterFunction<Attributes> | undefined,\n): ResourceRef<ContentFile<Attributes>[] | undefined> {\n const contentListLoader = injectContentListLoader<Attributes>();\n const locale = injectContentLocale();\n const contentList = contentListLoader().then((items) => {\n let results = locale ? filterByLocale(items, locale) : items;\n if (filterFn) {\n results = results.filter(filterFn);\n }\n return results;\n });\n\n return resource({\n loader: () => contentList,\n });\n}\n","import type { StandardSchemaV1 } from '@standard-schema/spec';\nimport {\n computed,\n inject,\n resource,\n Signal,\n type ResourceRef,\n} from '@angular/core';\nimport { ActivatedRoute } from '@angular/router';\n\nimport { toSignal } from '@angular/core/rxjs-interop';\nimport { from } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport type { ContentFile } from '../../src/lib/content-file';\nimport { injectContentLocale } from '../../src/lib/content-locale';\nimport { ContentRenderer } from '../../src/lib/content-renderer';\nimport {\n FrontmatterValidationError,\n parseRawContentFile,\n parseRawContentFileAsync,\n} from '../../src/lib/parse-raw-content-file';\nimport { injectContentFileLoader } from '../../src/lib/content-file-loader';\n\nexport interface ContentFileResourceResult<\n Attributes extends Record<string, any> = Record<string, any>,\n> extends ContentFile<Attributes | Record<string, never>> {\n toc: Array<{ id: string; level: number; text: string }>;\n}\n\ntype ContentFileParams =\n | Signal<string | { customFilename: string }>\n | Signal<string>\n | Signal<{ customFilename: string }>;\n\nasync function validateAttributes<TSchema extends StandardSchemaV1>(\n schema: TSchema,\n attributes: unknown,\n filename?: string,\n) {\n const result = await schema['~standard'].validate(attributes);\n if (result.issues) {\n throw new FrontmatterValidationError(result.issues, filename);\n }\n\n return result.value;\n}\n\nfunction getValidationFilename(filename: string): string {\n return filename.replace(/^\\/src\\/content\\//, '');\n}\n\nasync function getContentFile<\n Attributes extends Record<string, any> = Record<string, any>,\n>(\n contentFiles: Record<string, () => Promise<string>>,\n slug: string,\n fallback: string,\n locale?: string | null,\n schema?: StandardSchemaV1,\n): Promise<ContentFile<Attributes | Record<string, never>>> {\n // Normalize file keys so both \"/src/content/...\" and \"/<project>/src/content/...\" resolve.\n // This mirrors normalization used elsewhere in the content pipeline.\n const normalizedFiles: Record<string, () => Promise<string>> = {};\n const stemToKey: Record<string, string> = {};\n for (const [key, resolver] of Object.entries(contentFiles)) {\n const normalizedKey = key\n // replace any prefix up to the content directory with /src/content\n // use a non-greedy match so nested paths containing \"/content\" are preserved\n .replace(/^(?:.*?)\\/content(?=\\/)/, '/src/content')\n // normalize duplicate slashes\n .replace(/\\/{2,}/g, '/');\n normalizedFiles[normalizedKey] = resolver;\n // Index by bare filename stem so slug-only lookups work\n const stem = normalizedKey\n .split('/')\n .pop()\n ?.replace(/\\.[^.]+$/, '');\n if (stem && !stemToKey[stem]) {\n stemToKey[stem] = normalizedKey;\n }\n }\n\n // Try direct file first, then directory index variants, then bare slug via stem\n const base = `/src/content/${slug}`.replace(/\\/{2,}/g, '/');\n const candidates = [`${base}.md`, `${base}/index.md`];\n\n // Try locale-prefixed paths first, then fall back to unprefixed, then bare slug via stem\n const localeCandidates = locale\n ? candidates.map((c) =>\n c.replace('/src/content/', `/src/content/${locale}/`),\n )\n : [];\n const allCandidates = [...localeCandidates, ...candidates];\n const matchKey =\n allCandidates.find((k) => k in normalizedFiles) ?? stemToKey[slug];\n const contentFile = matchKey ? normalizedFiles[matchKey] : undefined;\n\n if (!contentFile) {\n return {\n filename: base,\n attributes: {},\n slug: '',\n content: fallback,\n } as ContentFile<Attributes | Record<string, never>>;\n }\n\n const resolvedBase = matchKey!.replace(/\\.md$/, '');\n const validationFilename = getValidationFilename(matchKey!);\n\n return contentFile().then(\n async (contentFile: string | { default: any; metadata: any }) => {\n if (typeof contentFile === 'string') {\n const { content, attributes } = schema\n ? await parseRawContentFileAsync(\n contentFile,\n schema,\n validationFilename,\n )\n : parseRawContentFile<Attributes>(contentFile);\n\n return {\n filename: resolvedBase,\n slug,\n attributes,\n content,\n } as ContentFile<Attributes | Record<string, never>>;\n }\n\n const attributes = schema\n ? await validateAttributes(\n schema,\n contentFile.metadata,\n validationFilename,\n )\n : contentFile.metadata;\n\n return {\n filename: resolvedBase,\n slug,\n attributes,\n content: contentFile.default,\n } as ContentFile<Attributes | Record<string, never>>;\n },\n );\n}\n\n/**\n * Resource for requesting an individual content file.\n *\n * @example\n * ```typescript\n * // Without schema (existing behavior)\n * const post = contentFileResource<BlogAttributes>();\n *\n * // With schema validation\n * import * as v from 'valibot';\n * const BlogSchema = v.object({\n * title: v.string(),\n * date: v.pipe(v.string(), v.isoDate()),\n * });\n * const post = contentFileResource({ schema: BlogSchema });\n * ```\n */\nexport function contentFileResource<\n Attributes extends Record<string, any> = Record<string, any>,\n>(\n params?: ContentFileParams,\n fallback?: string,\n): ResourceRef<ContentFileResourceResult<Attributes> | undefined>;\n\nexport function contentFileResource<TSchema extends StandardSchemaV1>(options: {\n params?: ContentFileParams;\n fallback?: string;\n schema: TSchema;\n}): ResourceRef<\n | ContentFileResourceResult<\n StandardSchemaV1.InferOutput<TSchema> & Record<string, any>\n >\n | undefined\n>;\n\nexport function contentFileResource(\n paramsOrOptions?:\n | ContentFileParams\n | {\n params?: ContentFileParams;\n fallback?: string;\n schema?: StandardSchemaV1;\n },\n fallbackArg = 'No Content Found',\n) {\n // Detect options-object form vs legacy positional form\n const isOptionsObject =\n paramsOrOptions &&\n typeof paramsOrOptions === 'object' &&\n !('set' in paramsOrOptions) && // not a Signal\n ('schema' in paramsOrOptions ||\n 'params' in paramsOrOptions ||\n 'fallback' in paramsOrOptions);\n\n const params: ContentFileParams | undefined = isOptionsObject\n ? (paramsOrOptions as { params?: ContentFileParams }).params\n : (paramsOrOptions as ContentFileParams | undefined);\n const fallback: string = isOptionsObject\n ? ((paramsOrOptions as { fallback?: string }).fallback ??\n 'No Content Found')\n : fallbackArg;\n const schema: StandardSchemaV1 | undefined = isOptionsObject\n ? (paramsOrOptions as { schema?: StandardSchemaV1 }).schema\n : undefined;\n\n const loaderPromise = injectContentFileLoader();\n const contentRenderer = inject(ContentRenderer);\n const locale = injectContentLocale();\n const contentFilesMap = toSignal(from(loaderPromise()));\n const input =\n params ||\n toSignal(\n inject(ActivatedRoute).paramMap.pipe(\n map((params) => params.get('slug') as string),\n ),\n { requireSync: true },\n );\n\n return resource({\n params: computed(() => ({ input: input(), files: contentFilesMap() })),\n loader: async ({ params: resourceParams }) => {\n const { input: param, files } = resourceParams;\n\n if (typeof param === 'string') {\n if (param) {\n const file = await getContentFile(\n files!,\n param,\n fallback,\n locale,\n schema,\n );\n if (typeof file.content === 'string') {\n const rendered = (await contentRenderer.render(file.content)) as {\n toc?: Array<{ id: string; level: number; text: string }>;\n };\n return {\n ...file,\n toc: rendered.toc ?? [],\n };\n }\n return {\n ...file,\n toc: [],\n };\n }\n\n return {\n filename: '',\n slug: '',\n attributes: {},\n content: fallback,\n toc: [],\n };\n } else {\n const file = await getContentFile(\n files!,\n param.customFilename,\n fallback,\n locale,\n schema,\n );\n if (typeof file.content === 'string') {\n const rendered = (await contentRenderer.render(file.content)) as {\n toc?: Array<{ id: string; level: number; text: string }>;\n };\n return {\n ...file,\n toc: rendered.toc ?? [],\n };\n }\n return {\n ...file,\n toc: [],\n };\n }\n },\n });\n}\n"],"mappings":"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"}
@@ -1,16 +1,11 @@
1
- import { n as NoopContentRenderer, t as ContentRenderer } from "./content-renderer.mjs";
2
- import { a as injectContentFileLoader, c as injectContentFilesMap, d as CONTENT_LOCALE, f as filterByLocale, h as withLocaleCandidates, i as CONTENT_FILE_LOADER, l as RenderTaskService, m as withLocale, n as injectContentListLoader, o as withContentFileLoader, p as injectContentLocale, r as withContentListLoader, s as injectContentFiles, t as CONTENT_LIST_LOADER, u as CONTENT_FILES_TOKEN } from "./content-list-loader.mjs";
1
+ import { _ as NoopContentRenderer, a as injectContentFileLoader, c as injectContentFilesMap, d as CONTENT_LOCALE, f as filterByLocale, g as ContentRenderer, h as withLocaleCandidates, i as CONTENT_FILE_LOADER, l as RenderTaskService, m as withLocale, n as injectContentListLoader, o as withContentFileLoader, p as injectContentLocale, r as withContentListLoader, s as injectContentFiles, t as CONTENT_LIST_LOADER, u as CONTENT_FILES_TOKEN } from "./content-list-loader.mjs";
3
2
  import { n as parseRawContentFile, r as parseRawContentFileAsync, t as FrontmatterValidationError } from "./parse-raw-content-file.mjs";
4
- import { n as withHighlighter, t as MarkedContentHighlighter } from "./marked-content-highlighter.mjs";
5
3
  import * as i0 from "@angular/core";
6
- import { Component, Directive, HostListener, Injectable, InjectionToken, Input, NgZone, PLATFORM_ID, ViewEncapsulation, computed, inject, input } from "@angular/core";
4
+ import { Component, Directive, HostListener, InjectionToken, Input, NgZone, PLATFORM_ID, ViewEncapsulation, computed, inject, input } from "@angular/core";
7
5
  import { DOCUMENT, Location, isPlatformBrowser } from "@angular/common";
8
6
  import { ActivatedRoute, Router } from "@angular/router";
9
7
  import { Observable, firstValueFrom, from, isObservable, of } from "rxjs";
10
8
  import { catchError, finalize, map, switchMap, tap } from "rxjs/operators";
11
- import { getHeadingList, gfmHeadingId } from "marked-gfm-heading-id";
12
- import { marked } from "marked";
13
- import { mangle } from "marked-mangle";
14
9
  import { DomSanitizer } from "@angular/platform-browser";
15
10
  import { takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop";
16
11
  //#region packages/content/src/lib/anchor-navigation.directive.ts
@@ -168,120 +163,6 @@ function injectContent(param = "slug", fallback = "No Content Found") {
168
163
  } else return getContentFile(contentFiles, "", param.customFilename, fallback, renderTaskService, contentRenderer, locale).pipe(tap(() => renderTaskService.clearRenderTask(task)));
169
164
  }
170
165
  //#endregion
171
- //#region packages/content/src/lib/utils/escape-html.ts
172
- function escapeHtml(value) {
173
- return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("\"", "&quot;").replaceAll("'", "&#39;");
174
- }
175
- /**
176
- * Reduces a fenced code-block info string to a safe language identifier so it
177
- * cannot break out of the `class="language-..."` attribute it is interpolated
178
- * into.
179
- */
180
- function sanitizeLanguage(lang) {
181
- return (lang ?? "").match(/^[a-zA-Z0-9-+#.]*/)?.[0] ?? "";
182
- }
183
- //#endregion
184
- //#region packages/content/src/lib/marked-setup.service.ts
185
- /**
186
- * Credit goes to Scully for original implementation
187
- * https://github.com/scullyio/scully/blob/main/libs/scully/src/lib/fileHanderPlugins/markdown.ts
188
- */
189
- var MarkedSetupService = class MarkedSetupService {
190
- constructor() {
191
- this.highlighter = inject(MarkedContentHighlighter, { optional: true });
192
- const renderer = new marked.Renderer();
193
- renderer.code = ({ text, lang }) => {
194
- const language = sanitizeLanguage(lang);
195
- if (language === "mermaid") return "<pre class=\"mermaid\">" + escapeHtml(text) + "</pre>";
196
- if (!language) return "<pre><code>" + escapeHtml(text) + "</code></pre>";
197
- if (this.highlighter?.augmentCodeBlock) return this.highlighter?.augmentCodeBlock(text, language);
198
- return `<pre class="language-${language}"><code class="language-${language}">${escapeHtml(text)}</code></pre>`;
199
- };
200
- const extensions = [gfmHeadingId(), mangle()];
201
- if (this.highlighter) extensions.push(this.highlighter.getHighlightExtension());
202
- marked.use(...extensions, {
203
- renderer,
204
- pedantic: false,
205
- gfm: true,
206
- breaks: false
207
- });
208
- this.marked = marked;
209
- }
210
- getMarkedInstance() {
211
- return this.marked;
212
- }
213
- static {
214
- this.ɵfac = i0.ɵɵngDeclareFactory({
215
- minVersion: "12.0.0",
216
- version: "22.2.0",
217
- ngImport: i0,
218
- type: MarkedSetupService,
219
- deps: [],
220
- target: i0.ɵɵFactoryTarget.Injectable
221
- });
222
- }
223
- static {
224
- this.ɵprov = i0.ɵɵngDeclareInjectable({
225
- minVersion: "12.0.0",
226
- version: "22.2.0",
227
- ngImport: i0,
228
- type: MarkedSetupService
229
- });
230
- }
231
- };
232
- i0.ɵɵngDeclareClassMetadata({
233
- minVersion: "12.0.0",
234
- version: "22.2.0",
235
- ngImport: i0,
236
- type: MarkedSetupService,
237
- decorators: [{ type: Injectable }],
238
- ctorParameters: () => []
239
- });
240
- //#endregion
241
- //#region packages/content/src/lib/markdown-content-renderer.service.ts
242
- var MarkdownContentRendererService = class MarkdownContentRendererService {
243
- #marked = inject(MarkedSetupService, { self: true });
244
- async render(content) {
245
- return {
246
- content: await this.#marked.getMarkedInstance().parse(content),
247
- toc: getHeadingList()
248
- };
249
- }
250
- getContentHeadings(content) {
251
- return [...content.matchAll(/^(#{1,6})\s+(.+?)\s*$/gm)].map((match) => ({
252
- id: match[2].trim().toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-"),
253
- level: match[1].length,
254
- text: match[2].trim()
255
- }));
256
- }
257
- enhance() {}
258
- static {
259
- this.ɵfac = i0.ɵɵngDeclareFactory({
260
- minVersion: "12.0.0",
261
- version: "22.2.0",
262
- ngImport: i0,
263
- type: MarkdownContentRendererService,
264
- deps: [],
265
- target: i0.ɵɵFactoryTarget.Injectable
266
- });
267
- }
268
- static {
269
- this.ɵprov = i0.ɵɵngDeclareInjectable({
270
- minVersion: "12.0.0",
271
- version: "22.2.0",
272
- ngImport: i0,
273
- type: MarkdownContentRendererService
274
- });
275
- }
276
- };
277
- i0.ɵɵngDeclareClassMetadata({
278
- minVersion: "12.0.0",
279
- version: "22.2.0",
280
- ngImport: i0,
281
- type: MarkdownContentRendererService,
282
- decorators: [{ type: Injectable }]
283
- });
284
- //#endregion
285
166
  //#region packages/content/src/lib/provide-content.ts
286
167
  var CONTENT_RENDERER_PROVIDERS = [
287
168
  {
@@ -479,6 +360,6 @@ i0.ɵɵngDeclareClassMetadata({
479
360
  }
480
361
  });
481
362
  //#endregion
482
- export { AnchorNavigationDirective, CONTENT_FILE_LOADER, CONTENT_LIST_LOADER, CONTENT_LOCALE, ContentRenderer, FrontmatterValidationError, MERMAID_IMPORT_TOKEN, AnalogMarkdownComponent as MarkdownComponent, MarkdownContentRendererService, AnalogMarkdownRouteComponent as MarkdownRouteComponent, MarkedContentHighlighter, MarkedSetupService, NoopContentRenderer, filterByLocale, injectContent, injectContentFileLoader, injectContentFiles, injectContentFilesMap, injectContentListLoader, injectContentLocale, parseRawContentFile, parseRawContentFileAsync, provideContent, withContentFileLoader, withContentListLoader, withHighlighter, withLocale, withLocaleCandidates, withMarkdownRenderer };
363
+ export { AnchorNavigationDirective, CONTENT_FILE_LOADER, CONTENT_LIST_LOADER, CONTENT_LOCALE, ContentRenderer, FrontmatterValidationError, MERMAID_IMPORT_TOKEN, AnalogMarkdownComponent as MarkdownComponent, AnalogMarkdownRouteComponent as MarkdownRouteComponent, NoopContentRenderer, filterByLocale, injectContent, injectContentFileLoader, injectContentFiles, injectContentFilesMap, injectContentListLoader, injectContentLocale, parseRawContentFile, parseRawContentFileAsync, provideContent, withContentFileLoader, withContentListLoader, withLocale, withLocaleCandidates, withMarkdownRenderer };
483
364
 
484
365
  //# sourceMappingURL=analogjs-content.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"analogjs-content.mjs","names":[],"sources":["../../src/lib/anchor-navigation.directive.ts","../../src/lib/utils/zone-wait-for.ts","../../src/lib/content.ts","../../src/lib/utils/escape-html.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 // A `<base>` element makes the browser resolve fragment-only hrefs\n // (e.g. `href=\"#some-id\"`) against the base URL rather than the\n // current document, so `pathname`/`search` come back as the base's\n // rather than the current page's. Fall back to the document's own\n // location in that case, or an in-page anchor link would navigate\n // to the base route and lose the fragment there.\n const isFragmentOnly = element.getAttribute('href')?.startsWith('#');\n const resolvedPathname = isFragmentOnly\n ? this.document.location.pathname\n : pathname;\n const resolvedSearch = isFragmentOnly\n ? this.document.location.search\n : search;\n const url = this.location.normalize(\n `${resolvedPathname}${resolvedSearch}${hash}`,\n );\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 { finalize, 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 finalize(() => {\n if (import.meta.env.SSR !== true) {\n renderTaskService.clearRenderTask(contentTask);\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","export function escapeHtml(value: string): string {\n return value\n .replaceAll('&', '&amp;')\n .replaceAll('<', '&lt;')\n .replaceAll('>', '&gt;')\n .replaceAll('\"', '&quot;')\n .replaceAll(\"'\", '&#39;');\n}\n\n/**\n * Reduces a fenced code-block info string to a safe language identifier so it\n * cannot break out of the `class=\"language-...\"` attribute it is interpolated\n * into.\n */\nexport function sanitizeLanguage(lang: string | undefined): string {\n return (lang ?? '').match(/^[a-zA-Z0-9-+#.]*/)?.[0] ?? '';\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';\nimport { escapeHtml, sanitizeLanguage } from './utils/escape-html';\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 const language = sanitizeLanguage(lang);\n if (language === 'mermaid') {\n return '<pre class=\"mermaid\">' + escapeHtml(text) + '</pre>';\n }\n\n if (!language) {\n return '<pre><code>' + escapeHtml(text) + '</code></pre>';\n }\n\n if (this.highlighter?.augmentCodeBlock) {\n return this.highlighter?.augmentCodeBlock(text, language);\n }\n\n return `<pre class=\"language-${language}\"><code class=\"language-${language}\">${escapeHtml(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":[],"sources":["../../src/lib/anchor-navigation.directive.ts","../../src/lib/utils/zone-wait-for.ts","../../src/lib/content.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 // A `<base>` element makes the browser resolve fragment-only hrefs\n // (e.g. `href=\"#some-id\"`) against the base URL rather than the\n // current document, so `pathname`/`search` come back as the base's\n // rather than the current page's. Fall back to the document's own\n // location in that case, or an in-page anchor link would navigate\n // to the base route and lose the fragment there.\n const isFragmentOnly = element.getAttribute('href')?.startsWith('#');\n const resolvedPathname = isFragmentOnly\n ? this.document.location.pathname\n : pathname;\n const resolvedSearch = isFragmentOnly\n ? this.document.location.search\n : search;\n const url = this.location.normalize(\n `${resolvedPathname}${resolvedSearch}${hash}`,\n );\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 { finalize, 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 finalize(() => {\n if (import.meta.env.SSR !== true) {\n renderTaskService.clearRenderTask(contentTask);\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","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,5 +1,128 @@
1
1
  import * as i0 from "@angular/core";
2
- import { Injectable, InjectionToken, inject, signal, ɵPendingTasksInternal } from "@angular/core";
2
+ import { Injectable, InjectionToken, TransferState, inject, makeStateKey, signal, ɵPendingTasksInternal } from "@angular/core";
3
+ //#region packages/content/src/lib/content-renderer.ts
4
+ var ContentRenderer = class ContentRenderer {
5
+ async render(content) {
6
+ return {
7
+ content,
8
+ toc: []
9
+ };
10
+ }
11
+ getContentHeadings(_content) {
12
+ return [];
13
+ }
14
+ enhance() {}
15
+ static {
16
+ this.ɵfac = i0.ɵɵngDeclareFactory({
17
+ minVersion: "12.0.0",
18
+ version: "22.2.0",
19
+ ngImport: i0,
20
+ type: ContentRenderer,
21
+ deps: [],
22
+ target: i0.ɵɵFactoryTarget.Injectable
23
+ });
24
+ }
25
+ static {
26
+ this.ɵprov = i0.ɵɵngDeclareInjectable({
27
+ minVersion: "12.0.0",
28
+ version: "22.2.0",
29
+ ngImport: i0,
30
+ type: ContentRenderer
31
+ });
32
+ }
33
+ };
34
+ i0.ɵɵngDeclareClassMetadata({
35
+ minVersion: "12.0.0",
36
+ version: "22.2.0",
37
+ ngImport: i0,
38
+ type: ContentRenderer,
39
+ decorators: [{ type: Injectable }]
40
+ });
41
+ var NoopContentRenderer = class {
42
+ constructor() {
43
+ this.transferState = inject(TransferState);
44
+ this.contentId = 0;
45
+ }
46
+ /**
47
+ * Generates a hash from the content string
48
+ * to be used with the transfer state
49
+ */
50
+ generateHash(str) {
51
+ let hash = 0;
52
+ for (let i = 0, len = str.length; i < len; i++) {
53
+ const chr = str.charCodeAt(i);
54
+ hash = (hash << 5) - hash + chr;
55
+ hash |= 0;
56
+ }
57
+ return hash;
58
+ }
59
+ async render(content) {
60
+ this.contentId = this.generateHash(content);
61
+ const toc = this.getContentHeadings(content);
62
+ const key = makeStateKey(`content-headings-${this.contentId}`);
63
+ return {
64
+ content,
65
+ toc: this.transferState.get(key, toc)
66
+ };
67
+ }
68
+ enhance() {}
69
+ getContentHeadings(content) {
70
+ return this.extractHeadings(content);
71
+ }
72
+ extractHeadings(content) {
73
+ const markdownHeadings = this.extractHeadingsFromMarkdown(content);
74
+ if (markdownHeadings.length > 0) return markdownHeadings;
75
+ return this.extractHeadingsFromHtml(content);
76
+ }
77
+ extractHeadingsFromMarkdown(content) {
78
+ const lines = content.split("\n");
79
+ const toc = [];
80
+ const slugCounts = /* @__PURE__ */ new Map();
81
+ for (const line of lines) {
82
+ const match = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
83
+ if (!match) continue;
84
+ const level = match[1].length;
85
+ const text = match[2].trim();
86
+ if (!text) continue;
87
+ const baseSlug = text.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-");
88
+ const count = slugCounts.get(baseSlug) ?? 0;
89
+ slugCounts.set(baseSlug, count + 1);
90
+ const id = count === 0 ? baseSlug : `${baseSlug}-${count}`;
91
+ toc.push({
92
+ id,
93
+ level,
94
+ text
95
+ });
96
+ }
97
+ return toc;
98
+ }
99
+ extractHeadingsFromHtml(content) {
100
+ const toc = [];
101
+ const slugCounts = /* @__PURE__ */ new Map();
102
+ for (const match of content.matchAll(/<h([1-6])([^>]*)>([\s\S]*?)<\/h\1>/gi)) {
103
+ const level = Number(match[1]);
104
+ const attrs = match[2] ?? "";
105
+ const text = (match[3] ?? "").replace(/<[^>]+>/g, "").trim();
106
+ if (!text) continue;
107
+ const idMatch = /\sid=(['"])(.*?)\1/i.exec(attrs) ?? /\sid=([^\s>]+)/i.exec(attrs);
108
+ let id = idMatch?.[2] ?? idMatch?.[1] ?? "";
109
+ if (!id) id = this.makeSlug(text, slugCounts);
110
+ toc.push({
111
+ id,
112
+ level,
113
+ text
114
+ });
115
+ }
116
+ return toc;
117
+ }
118
+ makeSlug(text, slugCounts) {
119
+ const baseSlug = text.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-");
120
+ const count = slugCounts.get(baseSlug) ?? 0;
121
+ slugCounts.set(baseSlug, count + 1);
122
+ return count === 0 ? baseSlug : `${baseSlug}-${count}`;
123
+ }
124
+ };
125
+ //#endregion
3
126
  //#region packages/content/src/lib/content-locale.ts
4
127
  /**
5
128
  * Token for the active content locale.
@@ -255,6 +378,6 @@ function withContentListLoader() {
255
378
  };
256
379
  }
257
380
  //#endregion
258
- export { injectContentFileLoader as a, injectContentFilesMap as c, CONTENT_LOCALE as d, filterByLocale as f, withLocaleCandidates as h, CONTENT_FILE_LOADER as i, RenderTaskService as l, withLocale as m, injectContentListLoader as n, withContentFileLoader as o, injectContentLocale as p, withContentListLoader as r, injectContentFiles as s, CONTENT_LIST_LOADER as t, CONTENT_FILES_TOKEN as u };
381
+ export { NoopContentRenderer as _, injectContentFileLoader as a, injectContentFilesMap as c, CONTENT_LOCALE as d, filterByLocale as f, ContentRenderer as g, withLocaleCandidates as h, CONTENT_FILE_LOADER as i, RenderTaskService as l, withLocale as m, injectContentListLoader as n, withContentFileLoader as o, injectContentLocale as p, withContentListLoader as r, injectContentFiles as s, CONTENT_LIST_LOADER as t, CONTENT_FILES_TOKEN as u };
259
382
 
260
383
  //# sourceMappingURL=content-list-loader.mjs.map