@analogjs/content 3.0.0-alpha.5 → 3.0.0-alpha.51
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.
- package/devtools/package.json +4 -0
- package/fesm2022/analogjs-content-devtools.mjs +162 -0
- package/fesm2022/analogjs-content-devtools.mjs.map +1 -0
- package/fesm2022/analogjs-content-md4x.mjs +291 -0
- package/fesm2022/analogjs-content-md4x.mjs.map +1 -0
- package/fesm2022/analogjs-content-mdc.mjs +170 -0
- package/fesm2022/analogjs-content-mdc.mjs.map +1 -0
- package/fesm2022/analogjs-content-og.mjs.map +1 -0
- package/fesm2022/analogjs-content-prism-highlighter.mjs +5 -4
- package/fesm2022/analogjs-content-prism-highlighter.mjs.map +1 -0
- package/fesm2022/analogjs-content-resources.mjs +39 -25
- package/fesm2022/analogjs-content-resources.mjs.map +1 -0
- package/fesm2022/analogjs-content-shiki-highlighter.mjs +1 -1
- package/fesm2022/analogjs-content-shiki-highlighter.mjs.map +1 -0
- package/fesm2022/analogjs-content.mjs +42 -354
- package/fesm2022/analogjs-content.mjs.map +1 -0
- package/fesm2022/content-list-loader.mjs +260 -0
- package/fesm2022/content-list-loader.mjs.map +1 -0
- package/fesm2022/content-renderer.mjs +128 -0
- package/fesm2022/content-renderer.mjs.map +1 -0
- package/fesm2022/marked-content-highlighter.mjs +40 -0
- package/fesm2022/marked-content-highlighter.mjs.map +1 -0
- package/fesm2022/parse-raw-content-file.mjs +45 -0
- package/fesm2022/parse-raw-content-file.mjs.map +1 -0
- package/md4x/package.json +4 -0
- package/mdc/package.json +4 -0
- package/package.json +71 -36
- package/plugin/migrations.json +1 -1
- package/plugin/package.json +2 -21
- package/plugin/src/index.d.ts +3 -1
- package/plugin/src/index.d.ts.map +1 -0
- package/plugin/src/index.js +5 -4
- package/plugin/src/index.js.map +1 -0
- package/plugin/src/migrations/update-markdown-version/compat.d.ts +5 -2
- package/plugin/src/migrations/update-markdown-version/compat.d.ts.map +1 -0
- package/plugin/src/migrations/update-markdown-version/compat.js +8 -7
- package/plugin/src/migrations/update-markdown-version/compat.js.map +1 -0
- package/plugin/src/migrations/update-markdown-version/update-markdown-version.d.ts +6 -2
- package/plugin/src/migrations/update-markdown-version/update-markdown-version.d.ts.map +1 -0
- package/plugin/src/migrations/update-markdown-version/update-markdown-version.js +18 -20
- package/plugin/src/migrations/update-markdown-version/update-markdown-version.js.map +1 -0
- package/src/lib/devtools/content-devtools-client.ts +215 -0
- package/src/lib/devtools/content-devtools.styles.css +194 -0
- package/types/devtools/src/index.d.ts +1 -0
- package/types/md4x/src/index.d.ts +5 -0
- package/types/md4x/src/lib/md4x-content-renderer.service.d.ts +33 -0
- package/types/md4x/src/lib/md4x-wasm-content-renderer.service.d.ts +16 -0
- package/types/md4x/src/lib/provide-md4x.d.ts +26 -0
- package/types/md4x/src/lib/streaming-markdown-renderer.d.ts +21 -0
- package/types/mdc/src/index.d.ts +2 -0
- package/types/mdc/src/lib/mdc-component-registry.d.ts +25 -0
- package/types/mdc/src/lib/mdc-renderer.directive.d.ts +33 -0
- package/types/prism-highlighter/src/lib/prism-highlighter.d.ts +1 -1
- package/types/resources/src/content-file-resource.d.ts +32 -7
- package/types/resources/src/content-files-resource.d.ts +2 -1
- package/types/src/index.d.ts +6 -3
- package/types/src/lib/content-locale.d.ts +68 -0
- package/types/src/lib/devtools/content-devtools-plugin.d.ts +23 -0
- package/types/src/lib/devtools/content-devtools-renderer.d.ts +23 -0
- package/types/src/lib/devtools/index.d.ts +23 -0
- package/types/src/lib/get-content-files.d.ts +19 -4
- package/types/src/lib/parse-raw-content-file.d.ts +15 -1
- package/plugin/README.md +0 -11
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { t as ContentRenderer } from "./content-renderer.mjs";
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
import { Injectable, InjectionToken, inject } from "@angular/core";
|
|
4
|
+
import { transformWithOxc } from "vite";
|
|
5
|
+
//#region \0rolldown/runtime.js
|
|
6
|
+
var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region packages/content/src/lib/devtools/content-devtools-renderer.ts
|
|
9
|
+
/**
|
|
10
|
+
* Token for the wrapped renderer that DevTools delegates to.
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
var DEVTOOLS_INNER_RENDERER = new InjectionToken("devtools_inner_renderer");
|
|
14
|
+
/**
|
|
15
|
+
* Wraps an existing ContentRenderer to collect timing and metadata for the
|
|
16
|
+
* Content DevTools panel. Dispatches a custom event on the window after
|
|
17
|
+
* each render so the devtools client can update.
|
|
18
|
+
*
|
|
19
|
+
* @experimental Content DevTools is experimental and may change in future releases.
|
|
20
|
+
*/
|
|
21
|
+
var DevToolsContentRenderer = class DevToolsContentRenderer extends ContentRenderer {
|
|
22
|
+
constructor() {
|
|
23
|
+
super(...arguments);
|
|
24
|
+
this.inner = inject(DEVTOOLS_INNER_RENDERER);
|
|
25
|
+
}
|
|
26
|
+
async render(content) {
|
|
27
|
+
const start = performance.now();
|
|
28
|
+
const result = await this.inner.render(content);
|
|
29
|
+
const elapsed = performance.now() - start;
|
|
30
|
+
if (typeof window !== "undefined") window.dispatchEvent(new CustomEvent("analog-content-devtools-data", { detail: {
|
|
31
|
+
renderer: this.inner.constructor.name,
|
|
32
|
+
renderTimeMs: elapsed,
|
|
33
|
+
toc: result.toc,
|
|
34
|
+
contentLength: content.length,
|
|
35
|
+
headingCount: result.toc.length,
|
|
36
|
+
frontmatter: {}
|
|
37
|
+
} }));
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
getContentHeadings(content) {
|
|
41
|
+
return this.inner.getContentHeadings(content);
|
|
42
|
+
}
|
|
43
|
+
enhance() {
|
|
44
|
+
this.inner.enhance();
|
|
45
|
+
}
|
|
46
|
+
static {
|
|
47
|
+
this.ɵfac = i0.ɵɵngDeclareFactory({
|
|
48
|
+
minVersion: "12.0.0",
|
|
49
|
+
version: "21.2.8",
|
|
50
|
+
ngImport: i0,
|
|
51
|
+
type: DevToolsContentRenderer,
|
|
52
|
+
deps: null,
|
|
53
|
+
target: i0.ɵɵFactoryTarget.Injectable
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
static {
|
|
57
|
+
this.ɵprov = i0.ɵɵngDeclareInjectable({
|
|
58
|
+
minVersion: "12.0.0",
|
|
59
|
+
version: "21.2.8",
|
|
60
|
+
ngImport: i0,
|
|
61
|
+
type: DevToolsContentRenderer
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
66
|
+
minVersion: "12.0.0",
|
|
67
|
+
version: "21.2.8",
|
|
68
|
+
ngImport: i0,
|
|
69
|
+
type: DevToolsContentRenderer,
|
|
70
|
+
decorators: [{ type: Injectable }]
|
|
71
|
+
});
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region packages/content/src/lib/devtools/content-devtools-plugin.ts
|
|
74
|
+
var import___vite_browser_external = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
75
|
+
module.exports = {};
|
|
76
|
+
})))();
|
|
77
|
+
/**
|
|
78
|
+
* Vite plugin that injects the Analog Content DevTools panel in dev mode.
|
|
79
|
+
*
|
|
80
|
+
* Shows render time, frontmatter data, TOC, and content stats in a floating
|
|
81
|
+
* panel. Dev-only — completely stripped from production builds.
|
|
82
|
+
*
|
|
83
|
+
* @experimental Content DevTools is experimental and may change in future releases.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* // vite.config.ts
|
|
88
|
+
* import { contentDevToolsPlugin } from '@analogjs/content/devtools';
|
|
89
|
+
*
|
|
90
|
+
* export default defineConfig({
|
|
91
|
+
* plugins: [
|
|
92
|
+
* analog({ ... }),
|
|
93
|
+
* contentDevToolsPlugin(),
|
|
94
|
+
* ],
|
|
95
|
+
* });
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
function contentDevToolsPlugin() {
|
|
99
|
+
let isDev = false;
|
|
100
|
+
return {
|
|
101
|
+
name: "analog-content-devtools",
|
|
102
|
+
apply: "serve",
|
|
103
|
+
configResolved(config) {
|
|
104
|
+
isDev = config.command === "serve";
|
|
105
|
+
},
|
|
106
|
+
transformIndexHtml: {
|
|
107
|
+
order: "post",
|
|
108
|
+
async handler(html) {
|
|
109
|
+
if (!isDev) return html;
|
|
110
|
+
const pluginDir = (0, import___vite_browser_external.dirname)((0, import___vite_browser_external.fileURLToPath)(import.meta.url));
|
|
111
|
+
const cssPath = (0, import___vite_browser_external.resolve)(pluginDir, "content-devtools.styles.css");
|
|
112
|
+
const clientPath = (0, import___vite_browser_external.resolve)(pluginDir, "content-devtools-client.ts");
|
|
113
|
+
let css;
|
|
114
|
+
let clientCode;
|
|
115
|
+
try {
|
|
116
|
+
css = (0, import___vite_browser_external.readFileSync)(cssPath, "utf-8");
|
|
117
|
+
clientCode = (0, import___vite_browser_external.readFileSync)(clientPath, "utf-8");
|
|
118
|
+
} catch {
|
|
119
|
+
return html;
|
|
120
|
+
}
|
|
121
|
+
const transformResult = await transformWithOxc(clientCode, "content-devtools-client.ts", { lang: "ts" });
|
|
122
|
+
const injection = `
|
|
123
|
+
<style>${css}</style>
|
|
124
|
+
<script type="module">${transformResult.code}<\/script>`;
|
|
125
|
+
return html.replace("</body>", `${injection}\n</body>`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//#endregion
|
|
131
|
+
//#region packages/content/src/lib/devtools/index.ts
|
|
132
|
+
/**
|
|
133
|
+
* Wraps the given ContentRenderer with DevTools instrumentation.
|
|
134
|
+
*
|
|
135
|
+
* The supplied renderer class is provided under DEVTOOLS_INNER_RENDERER and
|
|
136
|
+
* DevToolsContentRenderer becomes the new ContentRenderer, delegating
|
|
137
|
+
* all calls and collecting timing data.
|
|
138
|
+
*
|
|
139
|
+
* @param innerRenderer The renderer class to wrap with devtools instrumentation.
|
|
140
|
+
*
|
|
141
|
+
* @experimental Content DevTools is experimental and may change in future releases.
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```typescript
|
|
145
|
+
* provideContent(
|
|
146
|
+
* withContentDevTools(NoopContentRenderer),
|
|
147
|
+
* );
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
function withContentDevTools(innerRenderer) {
|
|
151
|
+
return [{
|
|
152
|
+
provide: DEVTOOLS_INNER_RENDERER,
|
|
153
|
+
useClass: innerRenderer
|
|
154
|
+
}, {
|
|
155
|
+
provide: ContentRenderer,
|
|
156
|
+
useClass: DevToolsContentRenderer
|
|
157
|
+
}];
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
export { DevToolsContentRenderer, contentDevToolsPlugin, withContentDevTools };
|
|
161
|
+
|
|
162
|
+
//# sourceMappingURL=analogjs-content-devtools.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analogjs-content-devtools.mjs","names":[],"sources":["../../src/lib/devtools/content-devtools-renderer.ts","../../../../__vite-browser-external","../../src/lib/devtools/content-devtools-plugin.ts","../../src/lib/devtools/index.ts"],"sourcesContent":["import { Injectable, InjectionToken, inject } from '@angular/core';\n\nimport {\n ContentRenderer,\n RenderedContent,\n TableOfContentItem,\n} from '../content-renderer';\n\n/**\n * Token for the wrapped renderer that DevTools delegates to.\n * @internal\n */\nexport const DEVTOOLS_INNER_RENDERER: InjectionToken<ContentRenderer> =\n new InjectionToken<ContentRenderer>('devtools_inner_renderer');\n\n/**\n * Wraps an existing ContentRenderer to collect timing and metadata for the\n * Content DevTools panel. Dispatches a custom event on the window after\n * each render so the devtools client can update.\n *\n * @experimental Content DevTools is experimental and may change in future releases.\n */\n@Injectable()\nexport class DevToolsContentRenderer extends ContentRenderer {\n private readonly inner = inject(DEVTOOLS_INNER_RENDERER);\n\n override async render(content: string): Promise<RenderedContent> {\n const start = performance.now();\n const result = await this.inner.render(content);\n const elapsed = performance.now() - start;\n\n if (typeof window !== 'undefined') {\n window.dispatchEvent(\n new CustomEvent('analog-content-devtools-data', {\n detail: {\n renderer: this.inner.constructor.name,\n renderTimeMs: elapsed,\n toc: result.toc,\n contentLength: content.length,\n headingCount: result.toc.length,\n frontmatter: {},\n },\n }),\n );\n }\n\n return result;\n }\n\n override getContentHeadings(content: string): TableOfContentItem[] {\n return this.inner.getContentHeadings(content);\n }\n\n override enhance(): void {\n this.inner.enhance();\n }\n}\n","module.exports = {}","import { readFileSync } from 'node:fs';\nimport { resolve, dirname } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport type { Plugin } from 'vite';\nimport { transformWithOxc } from 'vite';\n\n/**\n * Vite plugin that injects the Analog Content DevTools panel in dev mode.\n *\n * Shows render time, frontmatter data, TOC, and content stats in a floating\n * panel. Dev-only — completely stripped from production builds.\n *\n * @experimental Content DevTools is experimental and may change in future releases.\n *\n * @example\n * ```typescript\n * // vite.config.ts\n * import { contentDevToolsPlugin } from '@analogjs/content/devtools';\n *\n * export default defineConfig({\n * plugins: [\n * analog({ ... }),\n * contentDevToolsPlugin(),\n * ],\n * });\n * ```\n */\nexport function contentDevToolsPlugin(): Plugin {\n let isDev = false;\n\n return {\n name: 'analog-content-devtools',\n apply: 'serve',\n\n configResolved(config) {\n isDev = config.command === 'serve';\n },\n\n transformIndexHtml: {\n order: 'post',\n async handler(html) {\n if (!isDev) return html;\n\n const pluginDir = dirname(fileURLToPath(import.meta.url));\n const cssPath = resolve(pluginDir, 'content-devtools.styles.css');\n const clientPath = resolve(pluginDir, 'content-devtools-client.ts');\n\n let css: string;\n let clientCode: string;\n try {\n css = readFileSync(cssPath, 'utf-8');\n clientCode = readFileSync(clientPath, 'utf-8');\n } catch {\n // Fallback: files may not exist if running from compiled output.\n // The plugin silently degrades — no devtools panel.\n return html;\n }\n\n const transformResult = await transformWithOxc(\n clientCode,\n 'content-devtools-client.ts',\n { lang: 'ts' },\n );\n\n const injection = `\n<style>${css}</style>\n<script type=\"module\">${transformResult.code}</script>`;\n\n return html.replace('</body>', `${injection}\\n</body>`);\n },\n },\n };\n}\n","import { Provider, Type } from '@angular/core';\nimport { ContentRenderer } from '../content-renderer';\nimport {\n DevToolsContentRenderer,\n DEVTOOLS_INNER_RENDERER,\n} from './content-devtools-renderer';\n\nexport { contentDevToolsPlugin } from './content-devtools-plugin';\nexport {\n DevToolsContentRenderer,\n DEVTOOLS_INNER_RENDERER,\n} from './content-devtools-renderer';\n\n/**\n * Wraps the given ContentRenderer with DevTools instrumentation.\n *\n * The supplied renderer class is provided under DEVTOOLS_INNER_RENDERER and\n * DevToolsContentRenderer becomes the new ContentRenderer, delegating\n * all calls and collecting timing data.\n *\n * @param innerRenderer The renderer class to wrap with devtools instrumentation.\n *\n * @experimental Content DevTools is experimental and may change in future releases.\n *\n * @example\n * ```typescript\n * provideContent(\n * withContentDevTools(NoopContentRenderer),\n * );\n * ```\n */\nexport function withContentDevTools(\n innerRenderer: Type<ContentRenderer>,\n): Provider {\n return [\n {\n provide: DEVTOOLS_INNER_RENDERER,\n useClass: innerRenderer,\n },\n {\n provide: ContentRenderer,\n useClass: DevToolsContentRenderer,\n },\n ];\n}\n"],"mappings":";;;;;;;;;;;;AAYA,IAAa,0BACX,IAAI,eAAgC,0BAA0B;;;;;;;;IAetD,0BAAA,MAAA,gCAAiC,gBAAQ;CAC/C,cAAgB;AAEZ,QAAO,GAAA,UAAW;AACpB,OAAO,QAAA,OACD,wBAAY;;CAGZ,MAAA,OAAA,SAAc;EACd,MAAK,QAAO,YAAA,KAAA;EACZ,MAAA,SAAe,MAAQ,KAAA,MAAA,OAAA,QAAA;EACvB,MAAA,UAAc,YAAW,KAAA,GAAA;AACzB,MAAA,OAAa,WAAA,YAGlB,QAAA,cAAA,IAAA,YAAA,gCAAA,EAAA,QAAA;GAGI,UAAA,KAAA,MAAA,YAAA;;GAGmB,KAAA,OAAuC;GAC/C,eAAA,QAAmB;;GAGd,aAAA,EAAA;GACZ,EAAA,CAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACtDf,QAAO,UAAU,EAAA;;;;;;;;;;;;;;;;;;;;;;;AC4BjB,SAAgB,wBAAgC;CAC9C,IAAI,QAAQ;AAEZ,QAAO;EACC,MAAA;EACN,OAAO;EAEP,eAAe,QAAQ;AACb,WAAO,OAAA,YAAY;;EAG7B,oBAAoB;GACX,OAAA;GACD,MAAQ,QAAM,MAAA;AACN,QAAA,CAAA,MAEN,QAAY;IACZ,MAAU,aAAA,GAAA,+BAAA,UAAA,GAAA,+BAAA,eAAmB,OAAA,KAAA,IAA8B,CAAA;IAC3D,MAAA,WAAA,GAAA,+BAAA,SAAqB,WAAW,8BAA6B;IAE/D,MAAA,cAAA,GAAA,+BAAA,SAAA,WAAA,6BAAA;IACA,IAAA;IACA,IAAA;AACI,QAAA;AACO,YAAA,GAAA,+BAAA,cAAa,SAAY,QAAQ;AACxC,mBAAA,GAAA,+BAAA,cAAA,YAAA,QAAA;;AAaH,YAAA;;IAGO,MAAA,kBAAsB,MAAA,iBAAqB,YAAA,8BAAA,EAAA,MAAA,MAAA,CAAA;;;wBAG5D,gBAAA,KAAA;;;;;;;;;;;;;;;;;;;;;;;;;;ACzCH,SAAgB,oBACd,eACU;AACV,QACE,CACE;EACU,SAAA;EAEZ,UAAA;EACE,EACA;EAEH,SAAA"}
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { t as ContentRenderer } from "./content-renderer.mjs";
|
|
2
|
+
import { o as withContentFileLoader, r as withContentListLoader } from "./content-list-loader.mjs";
|
|
3
|
+
import * as i0 from "@angular/core";
|
|
4
|
+
import { Injectable, InjectionToken, inject } from "@angular/core";
|
|
5
|
+
//#region packages/content/md4x/src/lib/md4x-content-renderer.service.ts
|
|
6
|
+
var MD4X_RENDERER_OPTIONS = new InjectionToken("md4x_renderer_options");
|
|
7
|
+
function makeSlug$1(text, slugCounts) {
|
|
8
|
+
const baseSlug = text.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-");
|
|
9
|
+
const count = slugCounts.get(baseSlug) ?? 0;
|
|
10
|
+
slugCounts.set(baseSlug, count + 1);
|
|
11
|
+
return count === 0 ? baseSlug : `${baseSlug}-${count}`;
|
|
12
|
+
}
|
|
13
|
+
function headingsToToc$1(headings) {
|
|
14
|
+
const slugCounts = /* @__PURE__ */ new Map();
|
|
15
|
+
return headings.map((h) => ({
|
|
16
|
+
id: makeSlug$1(h.text, slugCounts),
|
|
17
|
+
level: h.level,
|
|
18
|
+
text: h.text
|
|
19
|
+
}));
|
|
20
|
+
}
|
|
21
|
+
function injectHeadingIds$1(html, toc) {
|
|
22
|
+
let tocIndex = 0;
|
|
23
|
+
return html.replace(/<h([1-6])>/g, (match, level) => {
|
|
24
|
+
const item = toc[tocIndex++];
|
|
25
|
+
return item ? `<h${level} id="${item.id}">` : match;
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Content renderer backed by md4x (C-based CommonMark parser compiled with Zig).
|
|
30
|
+
* 50-70x faster than marked for complex documents.
|
|
31
|
+
*
|
|
32
|
+
* @experimental md4x integration is experimental and may change in future releases.
|
|
33
|
+
*/
|
|
34
|
+
var Md4xContentRendererService = class Md4xContentRendererService extends ContentRenderer {
|
|
35
|
+
constructor() {
|
|
36
|
+
super(...arguments);
|
|
37
|
+
this.options = inject(MD4X_RENDERER_OPTIONS, { optional: true });
|
|
38
|
+
}
|
|
39
|
+
async render(content) {
|
|
40
|
+
const { renderToHtml, parseMeta } = await import("md4x/napi");
|
|
41
|
+
const html = renderToHtml(content, {
|
|
42
|
+
heal: this.options?.heal,
|
|
43
|
+
highlighter: this.options?.highlighter
|
|
44
|
+
});
|
|
45
|
+
const toc = headingsToToc$1(parseMeta(content).headings);
|
|
46
|
+
return {
|
|
47
|
+
content: injectHeadingIds$1(html, toc),
|
|
48
|
+
toc
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
getContentHeadings(content) {
|
|
52
|
+
const lines = content.split("\n");
|
|
53
|
+
const toc = [];
|
|
54
|
+
const slugCounts = /* @__PURE__ */ new Map();
|
|
55
|
+
for (const line of lines) {
|
|
56
|
+
const match = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
|
|
57
|
+
if (!match) continue;
|
|
58
|
+
const level = match[1].length;
|
|
59
|
+
const text = match[2].trim();
|
|
60
|
+
if (!text) continue;
|
|
61
|
+
toc.push({
|
|
62
|
+
id: makeSlug$1(text, slugCounts),
|
|
63
|
+
level,
|
|
64
|
+
text
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return toc;
|
|
68
|
+
}
|
|
69
|
+
static {
|
|
70
|
+
this.ɵfac = i0.ɵɵngDeclareFactory({
|
|
71
|
+
minVersion: "12.0.0",
|
|
72
|
+
version: "21.2.8",
|
|
73
|
+
ngImport: i0,
|
|
74
|
+
type: Md4xContentRendererService,
|
|
75
|
+
deps: null,
|
|
76
|
+
target: i0.ɵɵFactoryTarget.Injectable
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
static {
|
|
80
|
+
this.ɵprov = i0.ɵɵngDeclareInjectable({
|
|
81
|
+
minVersion: "12.0.0",
|
|
82
|
+
version: "21.2.8",
|
|
83
|
+
ngImport: i0,
|
|
84
|
+
type: Md4xContentRendererService
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
89
|
+
minVersion: "12.0.0",
|
|
90
|
+
version: "21.2.8",
|
|
91
|
+
ngImport: i0,
|
|
92
|
+
type: Md4xContentRendererService,
|
|
93
|
+
decorators: [{ type: Injectable }]
|
|
94
|
+
});
|
|
95
|
+
//#endregion
|
|
96
|
+
//#region packages/content/md4x/src/lib/md4x-wasm-content-renderer.service.ts
|
|
97
|
+
function makeSlug(text, slugCounts) {
|
|
98
|
+
const baseSlug = text.toLowerCase().replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-");
|
|
99
|
+
const count = slugCounts.get(baseSlug) ?? 0;
|
|
100
|
+
slugCounts.set(baseSlug, count + 1);
|
|
101
|
+
return count === 0 ? baseSlug : `${baseSlug}-${count}`;
|
|
102
|
+
}
|
|
103
|
+
function headingsToToc(headings) {
|
|
104
|
+
const slugCounts = /* @__PURE__ */ new Map();
|
|
105
|
+
return headings.map((h) => ({
|
|
106
|
+
id: makeSlug(h.text, slugCounts),
|
|
107
|
+
level: h.level,
|
|
108
|
+
text: h.text
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
function injectHeadingIds(html, toc) {
|
|
112
|
+
let tocIndex = 0;
|
|
113
|
+
return html.replace(/<h([1-6])>/g, (match, level) => {
|
|
114
|
+
const item = toc[tocIndex++];
|
|
115
|
+
return item ? `<h${level} id="${item.id}">` : match;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Content renderer backed by md4x/wasm for client-side (browser) rendering.
|
|
120
|
+
* ~100KB gzip, 3-6x faster than marked in the browser.
|
|
121
|
+
*
|
|
122
|
+
* @experimental md4x integration is experimental and may change in future releases.
|
|
123
|
+
*/
|
|
124
|
+
var Md4xWasmContentRendererService = class Md4xWasmContentRendererService extends ContentRenderer {
|
|
125
|
+
constructor() {
|
|
126
|
+
super(...arguments);
|
|
127
|
+
this.options = inject(MD4X_RENDERER_OPTIONS, { optional: true });
|
|
128
|
+
this.initPromise = null;
|
|
129
|
+
}
|
|
130
|
+
async render(content) {
|
|
131
|
+
const wasm = await import("md4x/wasm");
|
|
132
|
+
if (!this.initPromise) this.initPromise = wasm.init();
|
|
133
|
+
await this.initPromise;
|
|
134
|
+
const html = wasm.renderToHtml(content, {
|
|
135
|
+
heal: this.options?.heal,
|
|
136
|
+
highlighter: this.options?.highlighter
|
|
137
|
+
});
|
|
138
|
+
const toc = headingsToToc(wasm.parseMeta(content).headings);
|
|
139
|
+
return {
|
|
140
|
+
content: injectHeadingIds(html, toc),
|
|
141
|
+
toc
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
getContentHeadings(content) {
|
|
145
|
+
const lines = content.split("\n");
|
|
146
|
+
const toc = [];
|
|
147
|
+
const slugCounts = /* @__PURE__ */ new Map();
|
|
148
|
+
for (const line of lines) {
|
|
149
|
+
const match = /^(#{1,6})\s+(.+?)\s*$/.exec(line);
|
|
150
|
+
if (!match) continue;
|
|
151
|
+
const level = match[1].length;
|
|
152
|
+
const text = match[2].trim();
|
|
153
|
+
if (!text) continue;
|
|
154
|
+
toc.push({
|
|
155
|
+
id: makeSlug(text, slugCounts),
|
|
156
|
+
level,
|
|
157
|
+
text
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return toc;
|
|
161
|
+
}
|
|
162
|
+
static {
|
|
163
|
+
this.ɵfac = i0.ɵɵngDeclareFactory({
|
|
164
|
+
minVersion: "12.0.0",
|
|
165
|
+
version: "21.2.8",
|
|
166
|
+
ngImport: i0,
|
|
167
|
+
type: Md4xWasmContentRendererService,
|
|
168
|
+
deps: null,
|
|
169
|
+
target: i0.ɵɵFactoryTarget.Injectable
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
static {
|
|
173
|
+
this.ɵprov = i0.ɵɵngDeclareInjectable({
|
|
174
|
+
minVersion: "12.0.0",
|
|
175
|
+
version: "21.2.8",
|
|
176
|
+
ngImport: i0,
|
|
177
|
+
type: Md4xWasmContentRendererService
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
182
|
+
minVersion: "12.0.0",
|
|
183
|
+
version: "21.2.8",
|
|
184
|
+
ngImport: i0,
|
|
185
|
+
type: Md4xWasmContentRendererService,
|
|
186
|
+
decorators: [{ type: Injectable }]
|
|
187
|
+
});
|
|
188
|
+
//#endregion
|
|
189
|
+
//#region packages/content/md4x/src/lib/provide-md4x.ts
|
|
190
|
+
/**
|
|
191
|
+
* Provides the experimental md4x-based content renderer (NAPI, server/build-time).
|
|
192
|
+
*
|
|
193
|
+
* @experimental md4x integration is experimental and may change in future releases.
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```typescript
|
|
197
|
+
* provideContent(withMd4xRenderer());
|
|
198
|
+
* provideContent(withMd4xRenderer({ heal: true }));
|
|
199
|
+
* ```
|
|
200
|
+
*/
|
|
201
|
+
function withMd4xRenderer(options) {
|
|
202
|
+
return [
|
|
203
|
+
{
|
|
204
|
+
provide: ContentRenderer,
|
|
205
|
+
useClass: Md4xContentRendererService
|
|
206
|
+
},
|
|
207
|
+
options ? {
|
|
208
|
+
provide: MD4X_RENDERER_OPTIONS,
|
|
209
|
+
useValue: options
|
|
210
|
+
} : [],
|
|
211
|
+
withContentFileLoader(),
|
|
212
|
+
withContentListLoader()
|
|
213
|
+
];
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Provides the experimental md4x WASM content renderer (browser/CSR).
|
|
217
|
+
* ~100KB gzip, 3-6x faster than marked in the browser.
|
|
218
|
+
*
|
|
219
|
+
* @experimental md4x integration is experimental and may change in future releases.
|
|
220
|
+
*
|
|
221
|
+
* @example
|
|
222
|
+
* ```typescript
|
|
223
|
+
* provideContent(withMd4xWasmRenderer());
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
function withMd4xWasmRenderer(options) {
|
|
227
|
+
return [
|
|
228
|
+
{
|
|
229
|
+
provide: ContentRenderer,
|
|
230
|
+
useClass: Md4xWasmContentRendererService
|
|
231
|
+
},
|
|
232
|
+
options ? {
|
|
233
|
+
provide: MD4X_RENDERER_OPTIONS,
|
|
234
|
+
useValue: options
|
|
235
|
+
} : [],
|
|
236
|
+
withContentFileLoader(),
|
|
237
|
+
withContentListLoader()
|
|
238
|
+
];
|
|
239
|
+
}
|
|
240
|
+
//#endregion
|
|
241
|
+
//#region packages/content/md4x/src/lib/streaming-markdown-renderer.ts
|
|
242
|
+
/**
|
|
243
|
+
* Transforms a stream of markdown chunks into a stream of rendered HTML.
|
|
244
|
+
* Uses md4x's `heal()` to fix incomplete markdown from streaming sources
|
|
245
|
+
* (LLMs, collaborative editing) so each emitted HTML chunk is valid.
|
|
246
|
+
*
|
|
247
|
+
* @experimental Streaming markdown support is experimental and may change in future releases.
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```typescript
|
|
251
|
+
* // In a Nitro API route
|
|
252
|
+
* import { streamMarkdown } from '@analogjs/content';
|
|
253
|
+
*
|
|
254
|
+
* export default defineEventHandler(async (event) => {
|
|
255
|
+
* const llmStream = getAIStream(prompt);
|
|
256
|
+
* return streamMarkdown(llmStream, { heal: true });
|
|
257
|
+
* });
|
|
258
|
+
* ```
|
|
259
|
+
*/
|
|
260
|
+
async function streamMarkdown(input, options) {
|
|
261
|
+
const { renderToHtml, heal } = await import("md4x/napi");
|
|
262
|
+
let buffer = "";
|
|
263
|
+
const reader = input.getReader();
|
|
264
|
+
return new ReadableStream({
|
|
265
|
+
async pull(controller) {
|
|
266
|
+
try {
|
|
267
|
+
const { done, value } = await reader.read();
|
|
268
|
+
if (done) {
|
|
269
|
+
if (buffer) {
|
|
270
|
+
const source = options?.heal ? heal(buffer) : buffer;
|
|
271
|
+
controller.enqueue(renderToHtml(source));
|
|
272
|
+
}
|
|
273
|
+
controller.close();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
buffer += value;
|
|
277
|
+
const source = options?.heal ? heal(buffer) : buffer;
|
|
278
|
+
controller.enqueue(renderToHtml(source));
|
|
279
|
+
} catch (error) {
|
|
280
|
+
controller.error(error);
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
cancel() {
|
|
284
|
+
reader.cancel();
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
//#endregion
|
|
289
|
+
export { MD4X_RENDERER_OPTIONS, Md4xContentRendererService, Md4xWasmContentRendererService, streamMarkdown, withMd4xRenderer, withMd4xWasmRenderer };
|
|
290
|
+
|
|
291
|
+
//# sourceMappingURL=analogjs-content-md4x.mjs.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import * as i0 from "@angular/core";
|
|
2
|
+
import { Directive, ElementRef, InjectionToken, Renderer2, ViewContainerRef, effect, inject, input } from "@angular/core";
|
|
3
|
+
//#region packages/content/mdc/src/lib/mdc-component-registry.ts
|
|
4
|
+
/**
|
|
5
|
+
* Registry mapping MDC component names to lazy-loaded Angular components.
|
|
6
|
+
*
|
|
7
|
+
* @experimental MDC component support is experimental and may change in future releases.
|
|
8
|
+
*/
|
|
9
|
+
var MDC_COMPONENTS = new InjectionToken("mdc_components");
|
|
10
|
+
/**
|
|
11
|
+
* Provides a registry of Angular components that can be used in MDC
|
|
12
|
+
* (Markdown Components) syntax within markdown content.
|
|
13
|
+
*
|
|
14
|
+
* @experimental MDC component support is experimental and may change in future releases.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* provideContent(
|
|
19
|
+
* withMd4xRenderer(),
|
|
20
|
+
* withMdcComponents({
|
|
21
|
+
* alert: () => import('./components/alert.component').then(m => m.AlertComponent),
|
|
22
|
+
* card: () => import('./components/card.component').then(m => m.CardComponent),
|
|
23
|
+
* }),
|
|
24
|
+
* );
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
function withMdcComponents(components) {
|
|
28
|
+
return {
|
|
29
|
+
provide: MDC_COMPONENTS,
|
|
30
|
+
useValue: new Map(Object.entries(components))
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region packages/content/mdc/src/lib/mdc-renderer.directive.ts
|
|
35
|
+
/**
|
|
36
|
+
* Directive that renders MDC (Markdown Components) AST nodes as Angular components.
|
|
37
|
+
*
|
|
38
|
+
* Walks the ComarkTree AST from md4x's `parseAST()`, matches component nodes
|
|
39
|
+
* to the registered MDC_COMPONENTS map, and instantiates them via
|
|
40
|
+
* `ViewContainerRef.createComponent()` with MDC attributes bound as inputs.
|
|
41
|
+
*
|
|
42
|
+
* @experimental MDC component support is experimental and may change in future releases.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```html
|
|
46
|
+
* <div [mdcAst]="parsedAst"></div>
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
var MdcRendererDirective = class MdcRendererDirective {
|
|
50
|
+
constructor() {
|
|
51
|
+
this.ast = input(null, { alias: "mdcAst" });
|
|
52
|
+
this.viewContainer = inject(ViewContainerRef);
|
|
53
|
+
this.renderer = inject(Renderer2);
|
|
54
|
+
this.el = inject(ElementRef);
|
|
55
|
+
this.components = inject(MDC_COMPONENTS, { optional: true });
|
|
56
|
+
this.renderId = 0;
|
|
57
|
+
effect(() => {
|
|
58
|
+
const ast = this.ast();
|
|
59
|
+
const currentRenderId = ++this.renderId;
|
|
60
|
+
this.viewContainer.clear();
|
|
61
|
+
const host = this.el.nativeElement;
|
|
62
|
+
host.innerHTML = "";
|
|
63
|
+
if (!ast?.nodes) return;
|
|
64
|
+
this.renderNodes(ast.nodes, host, currentRenderId);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async renderNodes(nodes, parent, renderId) {
|
|
68
|
+
for (const node of nodes) {
|
|
69
|
+
if (this.renderId !== renderId) return;
|
|
70
|
+
await this.renderNode(node, parent, renderId);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async renderNode(node, parent, renderId) {
|
|
74
|
+
if (this.renderId !== renderId) return;
|
|
75
|
+
if (typeof node === "string") {
|
|
76
|
+
const text = this.renderer.createText(node);
|
|
77
|
+
this.renderer.appendChild(parent, text);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const [tag, props, ...children] = node;
|
|
81
|
+
if (!tag) {
|
|
82
|
+
for (const child of children) {
|
|
83
|
+
if (this.renderId !== renderId) return;
|
|
84
|
+
await this.renderNode(child, parent, renderId);
|
|
85
|
+
}
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const componentLoader = this.components?.get(tag);
|
|
89
|
+
if (componentLoader) {
|
|
90
|
+
try {
|
|
91
|
+
const componentType = await componentLoader();
|
|
92
|
+
if (this.renderId !== renderId) return;
|
|
93
|
+
const tempContainer = this.renderer.createElement("div");
|
|
94
|
+
for (const child of children) {
|
|
95
|
+
if (this.renderId !== renderId) return;
|
|
96
|
+
await this.renderNode(child, tempContainer, renderId);
|
|
97
|
+
}
|
|
98
|
+
if (this.renderId !== renderId) return;
|
|
99
|
+
const projectableNodes = [Array.from(tempContainer.childNodes)];
|
|
100
|
+
const componentRef = this.viewContainer.createComponent(componentType, { projectableNodes });
|
|
101
|
+
for (const [key, value] of Object.entries(props)) componentRef.setInput(key, value);
|
|
102
|
+
const componentEl = componentRef.location.nativeElement;
|
|
103
|
+
this.renderer.appendChild(parent, componentEl);
|
|
104
|
+
} catch (e) {
|
|
105
|
+
console.error(`[MdcRenderer] Failed to load component "${tag}":`, e);
|
|
106
|
+
}
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
const el = this.renderer.createElement(tag);
|
|
110
|
+
for (const [key, value] of Object.entries(props)) this.renderer.setAttribute(el, key, String(value));
|
|
111
|
+
for (const child of children) {
|
|
112
|
+
if (this.renderId !== renderId) return;
|
|
113
|
+
await this.renderNode(child, el, renderId);
|
|
114
|
+
}
|
|
115
|
+
this.renderer.appendChild(parent, el);
|
|
116
|
+
}
|
|
117
|
+
static {
|
|
118
|
+
this.ɵfac = i0.ɵɵngDeclareFactory({
|
|
119
|
+
minVersion: "12.0.0",
|
|
120
|
+
version: "21.2.8",
|
|
121
|
+
ngImport: i0,
|
|
122
|
+
type: MdcRendererDirective,
|
|
123
|
+
deps: [],
|
|
124
|
+
target: i0.ɵɵFactoryTarget.Directive
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
static {
|
|
128
|
+
this.ɵdir = i0.ɵɵngDeclareDirective({
|
|
129
|
+
minVersion: "17.1.0",
|
|
130
|
+
version: "21.2.8",
|
|
131
|
+
type: MdcRendererDirective,
|
|
132
|
+
isStandalone: true,
|
|
133
|
+
selector: "[mdcAst]",
|
|
134
|
+
inputs: { ast: {
|
|
135
|
+
classPropertyName: "ast",
|
|
136
|
+
publicName: "mdcAst",
|
|
137
|
+
isSignal: true,
|
|
138
|
+
isRequired: false,
|
|
139
|
+
transformFunction: null
|
|
140
|
+
} },
|
|
141
|
+
ngImport: i0
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
i0.ɵɵngDeclareClassMetadata({
|
|
146
|
+
minVersion: "12.0.0",
|
|
147
|
+
version: "21.2.8",
|
|
148
|
+
ngImport: i0,
|
|
149
|
+
type: MdcRendererDirective,
|
|
150
|
+
decorators: [{
|
|
151
|
+
type: Directive,
|
|
152
|
+
args: [{
|
|
153
|
+
selector: "[mdcAst]",
|
|
154
|
+
standalone: true
|
|
155
|
+
}]
|
|
156
|
+
}],
|
|
157
|
+
ctorParameters: () => [],
|
|
158
|
+
propDecorators: { ast: [{
|
|
159
|
+
type: i0.Input,
|
|
160
|
+
args: [{
|
|
161
|
+
isSignal: true,
|
|
162
|
+
alias: "mdcAst",
|
|
163
|
+
required: false
|
|
164
|
+
}]
|
|
165
|
+
}] }
|
|
166
|
+
});
|
|
167
|
+
//#endregion
|
|
168
|
+
export { MDC_COMPONENTS, MdcRendererDirective, withMdcComponents };
|
|
169
|
+
|
|
170
|
+
//# sourceMappingURL=analogjs-content-mdc.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analogjs-content-mdc.mjs","names":[],"sources":["../../mdc/src/lib/mdc-component-registry.ts","../../mdc/src/lib/mdc-renderer.directive.ts"],"sourcesContent":["import { InjectionToken, Type, Provider } from '@angular/core';\n\n/**\n * Registry mapping MDC component names to lazy-loaded Angular components.\n *\n * @experimental MDC component support is experimental and may change in future releases.\n */\nexport const MDC_COMPONENTS: InjectionToken<\n Map<string, () => Promise<Type<unknown>>>\n> = new InjectionToken('mdc_components');\n\n/**\n * Provides a registry of Angular components that can be used in MDC\n * (Markdown Components) syntax within markdown content.\n *\n * @experimental MDC component support is experimental and may change in future releases.\n *\n * @example\n * ```typescript\n * provideContent(\n * withMd4xRenderer(),\n * withMdcComponents({\n * alert: () => import('./components/alert.component').then(m => m.AlertComponent),\n * card: () => import('./components/card.component').then(m => m.CardComponent),\n * }),\n * );\n * ```\n */\nexport function withMdcComponents(\n components: Record<string, () => Promise<Type<unknown>>>,\n): Provider {\n return {\n provide: MDC_COMPONENTS,\n useValue: new Map(Object.entries(components)),\n };\n}\n","import {\n Directive,\n ElementRef,\n effect,\n inject,\n input,\n InputSignal,\n Renderer2,\n ViewContainerRef,\n} from '@angular/core';\n\nimport { MDC_COMPONENTS } from './mdc-component-registry';\n\ntype ComarkNode =\n | string\n | [string | null, Record<string, unknown>, ...ComarkNode[]];\n\n/**\n * Directive that renders MDC (Markdown Components) AST nodes as Angular components.\n *\n * Walks the ComarkTree AST from md4x's `parseAST()`, matches component nodes\n * to the registered MDC_COMPONENTS map, and instantiates them via\n * `ViewContainerRef.createComponent()` with MDC attributes bound as inputs.\n *\n * @experimental MDC component support is experimental and may change in future releases.\n *\n * @example\n * ```html\n * <div [mdcAst]=\"parsedAst\"></div>\n * ```\n */\n@Directive({\n // eslint-disable-next-line @angular-eslint/directive-selector\n selector: '[mdcAst]',\n standalone: true,\n})\nexport class MdcRendererDirective {\n readonly ast: InputSignal<{ nodes: ComarkNode[] } | null> = input<{\n nodes: ComarkNode[];\n } | null>(null, {\n alias: 'mdcAst',\n });\n\n private readonly viewContainer = inject(ViewContainerRef);\n private readonly renderer = inject(Renderer2);\n private readonly el = inject(ElementRef);\n private readonly components = inject(MDC_COMPONENTS, { optional: true });\n private renderId = 0;\n\n constructor() {\n effect(() => {\n const ast = this.ast();\n const currentRenderId = ++this.renderId;\n\n this.viewContainer.clear();\n const host = this.el.nativeElement as HTMLElement;\n host.innerHTML = '';\n\n if (!ast?.nodes) return;\n\n // Fire-and-forget: Angular doesn't await effects, so we schedule\n // the async rendering and let it complete in the background.\n void this.renderNodes(ast.nodes, host, currentRenderId);\n });\n }\n\n private async renderNodes(\n nodes: ComarkNode[],\n parent: HTMLElement,\n renderId: number,\n ): Promise<void> {\n for (const node of nodes) {\n if (this.renderId !== renderId) return;\n await this.renderNode(node, parent, renderId);\n }\n }\n\n private async renderNode(\n node: ComarkNode,\n parent: HTMLElement,\n renderId: number,\n ): Promise<void> {\n if (this.renderId !== renderId) return;\n\n if (typeof node === 'string') {\n const text = this.renderer.createText(node);\n this.renderer.appendChild(parent, text);\n return;\n }\n\n const [tag, props, ...children] = node;\n\n if (!tag) {\n for (const child of children) {\n if (this.renderId !== renderId) return;\n await this.renderNode(child, parent, renderId);\n }\n return;\n }\n\n // Check if this tag is a registered MDC component\n const componentLoader = this.components?.get(tag);\n if (componentLoader) {\n try {\n const componentType = await componentLoader();\n if (this.renderId !== renderId) return;\n\n const tempContainer = this.renderer.createElement('div');\n for (const child of children) {\n if (this.renderId !== renderId) return;\n await this.renderNode(child, tempContainer, renderId);\n }\n if (this.renderId !== renderId) return;\n\n const projectableNodes = [\n Array.from((tempContainer as HTMLElement).childNodes),\n ];\n const componentRef = this.viewContainer.createComponent(componentType, {\n projectableNodes,\n });\n\n // Bind MDC attributes as component inputs\n for (const [key, value] of Object.entries(props)) {\n componentRef.setInput(key, value);\n }\n\n const componentEl = componentRef.location.nativeElement as HTMLElement;\n this.renderer.appendChild(parent, componentEl);\n } catch (e) {\n console.error(`[MdcRenderer] Failed to load component \"${tag}\":`, e);\n }\n return;\n }\n\n // Fall back to rendering as a standard HTML element\n const el = this.renderer.createElement(tag);\n for (const [key, value] of Object.entries(props)) {\n this.renderer.setAttribute(el, key, String(value));\n }\n for (const child of children) {\n if (this.renderId !== renderId) return;\n await this.renderNode(child, el, renderId);\n }\n this.renderer.appendChild(parent, el);\n }\n}\n"],"mappings":";;;;;;;;AAOA,IAAa,iBAET,IAAI,eAAe,iBAAiB;;;;;;;;;;;;;;;;;;AAmBxC,SAAgB,kBACd,YACU;AACV,QAAO;EACL,SAAS;EACT,UAAc,IAAI,IAAA,OAAO,QAAQ,WAAW,CAAA;EAC7C;;;;;;;;;;;;;;;;;;ACsBG,IAAK,uBAAL,MAAK,qBAAY;CAEjB,cAAU;AAIL,OAAK,MAAA,MAAY,MAAI,EAA6B,OAAA,UAAA,CAAA;AACvD,OAAA,gBAAA,OAAA,iBAAA;;AAGU,OAAA,KAAA,OACZ,WAEA;AAEK,OAAM,aAAQ,OAAO,gBAAA,EAAA,UAAA,MAAA,CAAA;AACpB,OAAK,WAAA;AACT,eAAW;;;AAID,QAAA,cAEZ,OACA;GAES,MAAA,OAAa,KAAA,GAAU;AAE5B,QAAO,YAAS;AACZ,OAAO,CAAA,KAAK,MACb;AAIK,QAAO,YAAG,IAAY,OAAA,MAAA,gBAAA;IAE7B;;CAED,MAAI,YAAK,OAAa,QAAU,UAAA;AAChC,OAAM,MAAK,QAAW,OAAO;kCAE/B;;;;CAMA,MAAI,WAAA,MAAA,QAAA,UAAA;AACF,MAAM,KAAA,aAAgB,SAClB;AAEJ,MAAM,OAAA,SAAgB,UAAK;GACtB,MAAM,OAAS,KAAA,SAAU,WAAA,KAAA;AACxB,QAAK,SAAA,YAAuB,QAAA,KAAA;AAC1B;;EAEJ,MAAK,CAAA,KAAA,OAAa,GAAA,YAAU;AAEhC,MAAM,CAAA,KAAA;AAGA,QAAA,MAAA,SAAoB,UAAA;AAKd,QAAA,KAAK,aAAiB,SACnB;;;AAIV;;;AAIP,MAAA,iBAAA;;IAIS,MAAK,gBAAS,MAAc,iBAAI;AAC/B,QAAK,KAAA,aAAiB,SAClB;;AAEL,SAAA,MAAS,SAAU,UAAA;AACnB,SAAA,KAAa,aAAU,SACrB;;;;6BA9GhB,CAEW,MAAA,KAAA,cAAA,WAAA,CACE;IACZ,MAAA,eAAA,KAAA,cAAA,gBAAA,eAAA,EAAA,kBAAA,CAAA"}
|