@refrakt-md/astro 0.9.2

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.
@@ -0,0 +1,17 @@
1
+ import type { RendererNode } from '@refrakt-md/types';
2
+ /**
3
+ * Check whether a rendered page tree contains any interactive runes
4
+ * that require behavior initialization scripts.
5
+ *
6
+ * Used to conditionally include the behavior `<script>` tag,
7
+ * shipping zero JS for static-only pages.
8
+ */
9
+ export declare function hasInteractiveRunes(node: RendererNode): boolean;
10
+ /**
11
+ * Generate the inline behavior initialization script for an Astro page.
12
+ *
13
+ * Supports both standard page loads and Astro View Transitions
14
+ * via the `astro:page-load` event.
15
+ */
16
+ export declare function behaviorScript(pages: unknown[], currentUrl: string): string;
17
+ //# sourceMappingURL=behaviors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"behaviors.d.ts","sourceRoot":"","sources":["../src/behaviors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAItD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAE/D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAyB3E"}
@@ -0,0 +1,45 @@
1
+ import { hasMatchingRunes } from '@refrakt-md/transform';
2
+ import { getBehaviorNames } from '@refrakt-md/behaviors';
3
+ /**
4
+ * Check whether a rendered page tree contains any interactive runes
5
+ * that require behavior initialization scripts.
6
+ *
7
+ * Used to conditionally include the behavior `<script>` tag,
8
+ * shipping zero JS for static-only pages.
9
+ */
10
+ export function hasInteractiveRunes(node) {
11
+ return hasMatchingRunes(node, getBehaviorNames());
12
+ }
13
+ /**
14
+ * Generate the inline behavior initialization script for an Astro page.
15
+ *
16
+ * Supports both standard page loads and Astro View Transitions
17
+ * via the `astro:page-load` event.
18
+ */
19
+ export function behaviorScript(pages, currentUrl) {
20
+ const contextData = JSON.stringify({ pages, currentUrl });
21
+ return `
22
+ import { registerElements, RfContext, initRuneBehaviors, initLayoutBehaviors } from '@refrakt-md/behaviors';
23
+
24
+ function init() {
25
+ const contextEl = document.getElementById('rf-context');
26
+ if (contextEl) {
27
+ try {
28
+ const ctx = JSON.parse(contextEl.textContent || '{}');
29
+ RfContext.pages = ctx.pages;
30
+ RfContext.currentUrl = ctx.currentUrl;
31
+ } catch {}
32
+ }
33
+ registerElements();
34
+ initRuneBehaviors();
35
+ initLayoutBehaviors();
36
+ }
37
+
38
+ // Standard page load
39
+ init();
40
+
41
+ // View Transitions support — re-init after each navigation
42
+ document.addEventListener('astro:page-load', () => init());
43
+ `;
44
+ }
45
+ //# sourceMappingURL=behaviors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"behaviors.js","sourceRoot":"","sources":["../src/behaviors.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAEzD;;;;;;GAMG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAkB;IACrD,OAAO,gBAAgB,CAAC,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC;AACnD,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,KAAgB,EAAE,UAAkB;IAClE,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;IAC1D,OAAO;;;;;;;;;;;;;;;;;;;;;;CAsBP,CAAC;AACF,CAAC"}
@@ -0,0 +1,10 @@
1
+ export { refrakt } from './integration.js';
2
+ export { renderPage } from './render.js';
3
+ export type { RenderPageInput } from './render.js';
4
+ export { buildSeoHead } from './seo.js';
5
+ export type { SeoHeadProps } from './seo.js';
6
+ export { hasInteractiveRunes, behaviorScript } from './behaviors.js';
7
+ export { registry } from './registry.js';
8
+ export type { ComponentRegistry } from './registry.js';
9
+ export type { AstroTheme, RefraktAstroOptions } from './types.js';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAG3C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGrE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,YAAY,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAGvD,YAAY,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,11 @@
1
+ // Integration
2
+ export { refrakt } from './integration.js';
3
+ // Rendering
4
+ export { renderPage } from './render.js';
5
+ // SEO
6
+ export { buildSeoHead } from './seo.js';
7
+ // Behaviors
8
+ export { hasInteractiveRunes, behaviorScript } from './behaviors.js';
9
+ // Component registry (ADR-008)
10
+ export { registry } from './registry.js';
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc;AACd,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE3C,YAAY;AACZ,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,MAAM;AACN,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAGxC,YAAY;AACZ,OAAO,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErE,+BAA+B;AAC/B,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { AstroIntegration } from 'astro';
2
+ import type { RefraktAstroOptions } from './types.js';
3
+ /**
4
+ * Astro integration for refrakt.
5
+ *
6
+ * Reads `refrakt.config.json`, configures SSR noExternal,
7
+ * and sets up content HMR in dev mode.
8
+ */
9
+ export declare function refrakt(options?: RefraktAstroOptions): AstroIntegration;
10
+ //# sourceMappingURL=integration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAG9C,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEtD;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,OAAO,GAAE,mBAAwB,GAAG,gBAAgB,CAiC3E"}
@@ -0,0 +1,40 @@
1
+ import { resolve } from 'node:path';
2
+ import { CORE_PACKAGES } from '@refrakt-md/transform';
3
+ import { loadRefraktConfig } from '@refrakt-md/transform/node';
4
+ /**
5
+ * Astro integration for refrakt.
6
+ *
7
+ * Reads `refrakt.config.json`, configures SSR noExternal,
8
+ * and sets up content HMR in dev mode.
9
+ */
10
+ export function refrakt(options = {}) {
11
+ const configPath = options.configPath ?? './refrakt.config.json';
12
+ return {
13
+ name: '@refrakt-md/astro',
14
+ hooks: {
15
+ 'astro:config:setup'({ config, updateConfig, addWatchFile }) {
16
+ const refraktConfig = loadRefraktConfig(configPath);
17
+ const noExternal = [
18
+ ...CORE_PACKAGES,
19
+ '@refrakt-md/astro',
20
+ refraktConfig.theme,
21
+ ...(refraktConfig.packages ?? []),
22
+ ];
23
+ updateConfig({
24
+ vite: {
25
+ ssr: {
26
+ noExternal,
27
+ optimizeDeps: {
28
+ include: ['@markdoc/markdoc'],
29
+ },
30
+ },
31
+ },
32
+ });
33
+ // Watch content directory for changes in dev mode
34
+ const contentDir = resolve(config.root.pathname, refraktConfig.contentDir);
35
+ addWatchFile(contentDir);
36
+ },
37
+ },
38
+ };
39
+ }
40
+ //# sourceMappingURL=integration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.js","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAG/D;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,UAA+B,EAAE;IACxD,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,uBAAuB,CAAC;IAEjE,OAAO;QACN,IAAI,EAAE,mBAAmB;QACzB,KAAK,EAAE;YACN,oBAAoB,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE;gBAC1D,MAAM,aAAa,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;gBAEpD,MAAM,UAAU,GAAG;oBAClB,GAAG,aAAa;oBAChB,mBAAmB;oBACnB,aAAa,CAAC,KAAK;oBACnB,GAAG,CAAC,aAAa,CAAC,QAAQ,IAAI,EAAE,CAAC;iBACjC,CAAC;gBAEF,YAAY,CAAC;oBACZ,IAAI,EAAE;wBACL,GAAG,EAAE;4BACJ,UAAU;4BACV,YAAY,EAAE;gCACb,OAAO,EAAE,CAAC,kBAAkB,CAAC;6BAC7B;yBACD;qBACD;iBACD,CAAC,CAAC;gBAEH,kDAAkD;gBAClD,MAAM,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,UAAU,CAAC,CAAC;gBAC3E,YAAY,CAAC,UAAU,CAAC,CAAC;YAC1B,CAAC;SACD;KACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Component registry for Astro component overrides.
3
+ *
4
+ * Maps rune type names (the `data-rune` attribute value) to Astro components.
5
+ * Components receive extracted properties as props, named refs as named slots,
6
+ * anonymous content as the default slot, and the original `tag` as a prop.
7
+ *
8
+ * Usage in an .astro page:
9
+ * ```astro
10
+ * ---
11
+ * import MyRecipe from '../components/MyRecipe.astro';
12
+ * const components = { recipe: MyRecipe };
13
+ * ---
14
+ * <RfRenderer node={renderable} components={components} />
15
+ * ```
16
+ */
17
+ export type ComponentRegistry = Record<string, any>;
18
+ /**
19
+ * Default component registry — empty.
20
+ *
21
+ * All runes render through the identity transform + @refrakt-md/behaviors.
22
+ * Theme authors or site authors register .astro component overrides for runes
23
+ * that need custom rendering.
24
+ */
25
+ export declare const registry: ComponentRegistry;
26
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAEpD;;;;;;GAMG;AACH,eAAO,MAAM,QAAQ,EAAE,iBAAsB,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Default component registry — empty.
3
+ *
4
+ * All runes render through the identity transform + @refrakt-md/behaviors.
5
+ * Theme authors or site authors register .astro component overrides for runes
6
+ * that need custom rendering.
7
+ */
8
+ export const registry = {};
9
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAkBA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAsB,EAAE,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { renderPage } from '@refrakt-md/transform';
2
+ export type { RenderPageInput } from '@refrakt-md/transform';
3
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,YAAY,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/render.js ADDED
@@ -0,0 +1,2 @@
1
+ export { renderPage } from '@refrakt-md/transform';
2
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC"}
package/dist/seo.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import type { SeoInput } from '@refrakt-md/transform';
2
+ export type SeoHeadProps = SeoInput;
3
+ /**
4
+ * Build HTML strings for SEO meta tags suitable for injection into `<head>`.
5
+ *
6
+ * Returns an object with `title`, `metaTags` (OG + description), and
7
+ * `jsonLd` (structured data script tags).
8
+ */
9
+ export declare function buildSeoHead(props: SeoHeadProps): {
10
+ title: string;
11
+ metaTags: string;
12
+ jsonLd: string;
13
+ };
14
+ //# sourceMappingURL=seo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seo.d.ts","sourceRoot":"","sources":["../src/seo.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD,MAAM,MAAM,YAAY,GAAG,QAAQ,CAAC;AAEpC;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,GAAG;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CACf,CAEA"}
package/dist/seo.js ADDED
@@ -0,0 +1,11 @@
1
+ import { extractSeoData, seoToHtml } from '@refrakt-md/transform';
2
+ /**
3
+ * Build HTML strings for SEO meta tags suitable for injection into `<head>`.
4
+ *
5
+ * Returns an object with `title`, `metaTags` (OG + description), and
6
+ * `jsonLd` (structured data script tags).
7
+ */
8
+ export function buildSeoHead(props) {
9
+ return seoToHtml(extractSeoData(props));
10
+ }
11
+ //# sourceMappingURL=seo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seo.js","sourceRoot":"","sources":["../src/seo.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAKlE;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,KAAmB;IAK/C,OAAO,SAAS,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;AACzC,CAAC"}
@@ -0,0 +1,14 @@
1
+ import type { AdapterTheme } from '@refrakt-md/transform';
2
+ /**
3
+ * Theme definition for the Astro adapter.
4
+ *
5
+ * Alias for the shared `AdapterTheme` — all non-Svelte adapters
6
+ * use the same shape (manifest + layouts).
7
+ */
8
+ export type AstroTheme = AdapterTheme;
9
+ /** Options for the refrakt Astro integration */
10
+ export interface RefraktAstroOptions {
11
+ /** Path to refrakt.config.json (default: './refrakt.config.json') */
12
+ configPath?: string;
13
+ }
14
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAE1D;;;;;GAKG;AACH,MAAM,MAAM,UAAU,GAAG,YAAY,CAAC;AAEtC,gDAAgD;AAChD,MAAM,WAAW,mBAAmB;IACnC,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@refrakt-md/astro",
3
+ "description": "Astro integration for refrakt.md — render Markdoc runes in Astro projects",
4
+ "version": "0.9.2",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/refrakt-md/refrakt.git",
10
+ "directory": "packages/astro"
11
+ },
12
+ "bugs": "https://github.com/refrakt-md/refrakt/issues",
13
+ "homepage": "https://github.com/refrakt-md/refrakt",
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "main": "dist/index.js",
18
+ "types": "dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "./BaseLayout.astro": "./src/BaseLayout.astro",
25
+ "./RfRenderer.astro": "./src/RfRenderer.astro",
26
+ "./elements/Table.astro": "./src/elements/Table.astro",
27
+ "./elements/Pre.astro": "./src/elements/Pre.astro"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "src/BaseLayout.astro",
32
+ "src/RfRenderer.astro",
33
+ "src/elements"
34
+ ],
35
+ "scripts": {
36
+ "build": "tsc"
37
+ },
38
+ "dependencies": {
39
+ "@refrakt-md/behaviors": "0.9.2",
40
+ "@refrakt-md/content": "0.9.2",
41
+ "@refrakt-md/transform": "0.9.2",
42
+ "@refrakt-md/types": "0.9.2"
43
+ },
44
+ "devDependencies": {
45
+ "astro": "^5.0.0"
46
+ },
47
+ "peerDependencies": {
48
+ "astro": "^5.0.0"
49
+ }
50
+ }
@@ -0,0 +1,59 @@
1
+ ---
2
+ import type { LayoutPageData } from '@refrakt-md/transform';
3
+ import type { AstroTheme } from './types.js';
4
+ import { renderPage } from './render.js';
5
+ import { buildSeoHead } from './seo.js';
6
+ import { hasInteractiveRunes } from './behaviors.js';
7
+
8
+ interface Props {
9
+ theme: AstroTheme;
10
+ page: LayoutPageData;
11
+ seo?: { jsonLd: object[]; og: { title?: string; description?: string; image?: string; type?: string; url?: string } };
12
+ lang?: string;
13
+ }
14
+
15
+ const { theme, page, seo, lang = 'en' } = Astro.props;
16
+
17
+ const html = renderPage({ theme, page });
18
+ const head = buildSeoHead({ title: page.title, description: undefined, frontmatter: page.frontmatter, seo });
19
+ const needsBehaviors = hasInteractiveRunes(page.renderable);
20
+ const contextData = JSON.stringify({ pages: page.pages, currentUrl: page.url });
21
+ ---
22
+
23
+ <html lang={lang}>
24
+ <head>
25
+ <meta charset="utf-8" />
26
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
27
+ {head.title && <title>{head.title}</title>}
28
+ <Fragment set:html={head.metaTags} />
29
+ <Fragment set:html={head.jsonLd} />
30
+ <slot name="head" />
31
+ </head>
32
+ <body>
33
+ <Fragment set:html={html} />
34
+ <script type="application/json" id="rf-context" set:html={contextData} />
35
+ {needsBehaviors && (
36
+ <script>
37
+ import { registerElements, RfContext, initRuneBehaviors, initLayoutBehaviors } from '@refrakt-md/behaviors';
38
+
39
+ function init() {
40
+ const contextEl = document.getElementById('rf-context');
41
+ if (contextEl) {
42
+ try {
43
+ const ctx = JSON.parse(contextEl.textContent || '{}');
44
+ RfContext.pages = ctx.pages;
45
+ RfContext.currentUrl = ctx.currentUrl;
46
+ } catch {}
47
+ }
48
+ registerElements();
49
+ initRuneBehaviors();
50
+ initLayoutBehaviors();
51
+ }
52
+
53
+ init();
54
+ document.addEventListener('astro:page-load', () => init());
55
+ </script>
56
+ )}
57
+ <slot name="body-end" />
58
+ </body>
59
+ </html>
@@ -0,0 +1,91 @@
1
+ ---
2
+ /**
3
+ * Recursive Astro renderer for refrakt.md content (ADR-008).
4
+ *
5
+ * Renders a RendererNode tree, dispatching to registered component overrides
6
+ * for runes that have them, falling back to HTML for everything else.
7
+ *
8
+ * Component overrides receive:
9
+ * - Extracted properties as named props (e.g., prepTime, difficulty)
10
+ * - Named refs as named slots (e.g., <slot name="headline" />)
11
+ * - Anonymous content as the default slot
12
+ * - The original `tag` object as a prop for escape-hatch access
13
+ */
14
+ import type { SerializedTag, RendererNode } from '@refrakt-md/types';
15
+ import { isTag, extractComponentInterface, renderToHtml } from '@refrakt-md/transform';
16
+ import RfRenderer from './RfRenderer.astro';
17
+
18
+ interface Props {
19
+ node: RendererNode;
20
+ /** typeof name → Astro component (component overrides) */
21
+ components?: Record<string, any>;
22
+ /** HTML element name → Astro component (element-level overrides) */
23
+ elements?: Record<string, any>;
24
+ }
25
+
26
+ const { node, components, elements } = Astro.props;
27
+
28
+ // Determine what kind of node we have
29
+ const isArray = Array.isArray(node);
30
+ const isString = typeof node === 'string';
31
+ const isNumber = typeof node === 'number';
32
+ const isNull = node === null || node === undefined;
33
+ const isTagNode = !isArray && !isString && !isNumber && !isNull && isTag(node);
34
+
35
+ // For tag nodes, determine the rendering path
36
+ let Component: any = undefined;
37
+ let ElementOverride: any = undefined;
38
+ let iface: { properties: Record<string, string>; refs: Record<string, SerializedTag[]>; children: RendererNode[] } | undefined;
39
+ let refHtml: Record<string, string> = {};
40
+ let tagNode: SerializedTag | undefined;
41
+
42
+ if (isTagNode) {
43
+ tagNode = node as SerializedTag;
44
+ const runeType = tagNode.attributes?.['data-rune'];
45
+
46
+ if (runeType && components?.[runeType]) {
47
+ Component = components[runeType];
48
+ iface = extractComponentInterface(tagNode);
49
+
50
+ // Pre-render refs to HTML for named slots
51
+ for (const [name, tags] of Object.entries(iface.refs)) {
52
+ refHtml[name] = tags.map(t => renderToHtml(t)).join('');
53
+ }
54
+ } else if (elements?.[tagNode.name]) {
55
+ ElementOverride = elements[tagNode.name];
56
+ }
57
+ }
58
+ ---
59
+
60
+ {isNull && null}
61
+
62
+ {isString && node}
63
+
64
+ {isNumber && String(node)}
65
+
66
+ {isArray && (node as RendererNode[]).map((child: RendererNode) => (
67
+ <RfRenderer node={child} components={components} elements={elements} />
68
+ ))}
69
+
70
+ {isTagNode && Component && iface && (
71
+ <Component {...iface.properties} tag={tagNode}>
72
+ {Object.entries(refHtml).map(([name, html]: [string, string]) => (
73
+ <Fragment slot={name} set:html={html} />
74
+ ))}
75
+ {iface.children.map((child: RendererNode) => (
76
+ <RfRenderer node={child} components={components} elements={elements} />
77
+ ))}
78
+ </Component>
79
+ )}
80
+
81
+ {isTagNode && ElementOverride && tagNode && (
82
+ <ElementOverride tag={tagNode}>
83
+ {tagNode.children.map((child: RendererNode) => (
84
+ <RfRenderer node={child} components={components} elements={elements} />
85
+ ))}
86
+ </ElementOverride>
87
+ )}
88
+
89
+ {isTagNode && !Component && !ElementOverride && tagNode && (
90
+ <Fragment set:html={renderToHtml(tagNode)} />
91
+ )}
@@ -0,0 +1,22 @@
1
+ ---
2
+ /**
3
+ * Pre element override — wraps code blocks in the rf-codeblock structure
4
+ * that @refrakt-md/behaviors enhances with a copy button.
5
+ */
6
+ import type { SerializedTag } from '@refrakt-md/types';
7
+
8
+ interface Props {
9
+ tag: SerializedTag;
10
+ }
11
+
12
+ const { tag } = Astro.props;
13
+ const isCodeBlock = 'data-language' in (tag.attributes || {});
14
+ ---
15
+
16
+ {isCodeBlock ? (
17
+ <div class="rf-codeblock">
18
+ <pre {...tag.attributes}><slot /></pre>
19
+ </div>
20
+ ) : (
21
+ <pre {...tag.attributes}><slot /></pre>
22
+ )}
@@ -0,0 +1,24 @@
1
+ ---
2
+ /**
3
+ * Table element override — wraps <table> in a scrollable container.
4
+ */
5
+ import type { SerializedTag } from '@refrakt-md/types';
6
+
7
+ interface Props {
8
+ tag: SerializedTag;
9
+ }
10
+
11
+ const { tag } = Astro.props;
12
+
13
+ // Filter attributes for HTML output
14
+ const attrs = Object.entries(tag.attributes)
15
+ .filter(([k, v]) => k !== '$$mdtype' && v !== undefined && v !== null && v !== false)
16
+ .map(([k, v]) => `${k}="${String(v === true ? '' : v).replace(/"/g, '&quot;')}"`)
17
+ .join(' ');
18
+ ---
19
+
20
+ <div class="rf-table-wrapper">
21
+ <table {...tag.attributes}>
22
+ <slot />
23
+ </table>
24
+ </div>