notro-loader 0.0.1 → 0.0.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.
- package/LICENSE +21 -21
- package/README.md +276 -276
- package/image-service.ts +45 -45
- package/index.ts +21 -21
- package/integration.ts +13 -13
- package/package.json +4 -3
- package/src/components/NotroContent.astro +37 -37
- package/src/loader/live-loader.ts +181 -181
- package/src/loader/loader.ts +268 -268
- package/src/loader/schema.ts +837 -837
- package/src/utils/HtmlElements.ts +27 -27
- package/src/utils/compile-mdx.ts +159 -159
- package/src/utils/default-components.ts +62 -62
- package/src/utils/mdx-pipeline.ts +501 -501
- package/src/utils/notion-url.ts +49 -49
- package/src/utils/notion.ts +127 -127
- package/src/utils/notro-config.ts +35 -35
- package/utils.ts +11 -11
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Passthrough components for standard HTML elements.
|
|
3
|
-
*
|
|
4
|
-
* Uses astro/jsx-runtime to create lightweight wrappers that inject
|
|
5
|
-
* an optional class string without requiring individual .astro files.
|
|
6
|
-
*
|
|
7
|
-
* makeHtmlElement(tag, cls?) bakes the class in at creation time.
|
|
8
|
-
* The headless notro package uses this with no default classes;
|
|
9
|
-
* notro-ui's NotionMarkdownRenderer passes Tailwind classes at creation time.
|
|
10
|
-
*
|
|
11
|
-
* Note on <code>:
|
|
12
|
-
* The `code` element is intentionally omitted here. Both inline `code` and
|
|
13
|
-
* code-block `pre > code` share the same element, so distinguishing them
|
|
14
|
-
* requires the `:not(pre) > code` CSS selector — handled in notro-theme.css.
|
|
15
|
-
*/
|
|
16
|
-
import { jsx } from 'astro/jsx-runtime';
|
|
17
|
-
import { __astro_tag_component__ } from 'astro/runtime/server/index.js';
|
|
18
|
-
|
|
19
|
-
export function makeHtmlElement(tag: string, cls?: string) {
|
|
20
|
-
function HtmlElement({ class: className, ...rest }: Record<string, unknown>) {
|
|
21
|
-
const combined = [cls, className].filter(Boolean).join(' ') || undefined;
|
|
22
|
-
return jsx(tag, combined !== undefined ? { ...rest, class: combined } : rest);
|
|
23
|
-
}
|
|
24
|
-
__astro_tag_component__(HtmlElement, 'astro:jsx');
|
|
25
|
-
return HtmlElement;
|
|
26
|
-
}
|
|
27
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Passthrough components for standard HTML elements.
|
|
3
|
+
*
|
|
4
|
+
* Uses astro/jsx-runtime to create lightweight wrappers that inject
|
|
5
|
+
* an optional class string without requiring individual .astro files.
|
|
6
|
+
*
|
|
7
|
+
* makeHtmlElement(tag, cls?) bakes the class in at creation time.
|
|
8
|
+
* The headless notro package uses this with no default classes;
|
|
9
|
+
* notro-ui's NotionMarkdownRenderer passes Tailwind classes at creation time.
|
|
10
|
+
*
|
|
11
|
+
* Note on <code>:
|
|
12
|
+
* The `code` element is intentionally omitted here. Both inline `code` and
|
|
13
|
+
* code-block `pre > code` share the same element, so distinguishing them
|
|
14
|
+
* requires the `:not(pre) > code` CSS selector — handled in notro-theme.css.
|
|
15
|
+
*/
|
|
16
|
+
import { jsx } from 'astro/jsx-runtime';
|
|
17
|
+
import { __astro_tag_component__ } from 'astro/runtime/server/index.js';
|
|
18
|
+
|
|
19
|
+
export function makeHtmlElement(tag: string, cls?: string) {
|
|
20
|
+
function HtmlElement({ class: className, ...rest }: Record<string, unknown>) {
|
|
21
|
+
const combined = [cls, className].filter(Boolean).join(' ') || undefined;
|
|
22
|
+
return jsx(tag, combined !== undefined ? { ...rest, class: combined } : rest);
|
|
23
|
+
}
|
|
24
|
+
__astro_tag_component__(HtmlElement, 'astro:jsx');
|
|
25
|
+
return HtmlElement;
|
|
26
|
+
}
|
|
27
|
+
|
package/src/utils/compile-mdx.ts
CHANGED
|
@@ -1,159 +1,159 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MDX → Astro component compiler.
|
|
3
|
-
*
|
|
4
|
-
* Astro integration layer: wires the MDX plugin pipeline from mdx-pipeline.ts
|
|
5
|
-
* into Astro's jsx-runtime, registers the result with Astro's component
|
|
6
|
-
* renderer, and caches compiled output by content hash.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { evaluate } from '@mdx-js/mdx';
|
|
10
|
-
import { createHash } from 'node:crypto';
|
|
11
|
-
import { buildMdxPlugins } from './mdx-pipeline.ts';
|
|
12
|
-
import type { LinkToPages } from '../types.ts';
|
|
13
|
-
|
|
14
|
-
// Import Astro's jsx-runtime so evaluate() produces Astro VNodes.
|
|
15
|
-
// (astro/src/jsx-runtime/index.ts:94)
|
|
16
|
-
import { Fragment, jsx, jsxs } from 'astro/jsx-runtime';
|
|
17
|
-
|
|
18
|
-
// Required to register Content as an 'astro:jsx' renderer.
|
|
19
|
-
// Equivalent to annotateContentExport() in vite-plugin-mdx-postprocess.ts.
|
|
20
|
-
import { __astro_tag_component__ } from 'astro/runtime/server/index.js';
|
|
21
|
-
|
|
22
|
-
// ── Core compile function ──────────────────────────────────────────────────
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Compiles a preprocessed Notion markdown string into an Astro component
|
|
26
|
-
* that can be rendered with <Content components={notionComponents} />.
|
|
27
|
-
*
|
|
28
|
-
* @param mdxSource - Preprocessed markdown string (from loader store)
|
|
29
|
-
* @param options.linkToPages - Optional map for resolving Notion page URLs
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* ```astro
|
|
33
|
-
* ---
|
|
34
|
-
* const Content = await compileMdxForAstro(entry.data.markdown, { linkToPages });
|
|
35
|
-
* ---
|
|
36
|
-
* <Content components={notionComponents} />
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
export async function compileMdxForAstro(
|
|
40
|
-
mdxSource: string,
|
|
41
|
-
options: { linkToPages?: LinkToPages } = {},
|
|
42
|
-
) {
|
|
43
|
-
const { linkToPages = {} } = options;
|
|
44
|
-
const { remarkPlugins, rehypePlugins } = buildMdxPlugins(linkToPages);
|
|
45
|
-
|
|
46
|
-
// evaluate() compiles + executes the MDX using Astro's jsx-runtime,
|
|
47
|
-
// producing a function that returns Astro VNodes.
|
|
48
|
-
// Wrapped in try-catch so a broken page does not crash the entire build.
|
|
49
|
-
let mod: Awaited<ReturnType<typeof evaluate>>;
|
|
50
|
-
try {
|
|
51
|
-
mod = await evaluate(mdxSource, {
|
|
52
|
-
jsx,
|
|
53
|
-
jsxs,
|
|
54
|
-
Fragment,
|
|
55
|
-
remarkPlugins,
|
|
56
|
-
rehypePlugins,
|
|
57
|
-
});
|
|
58
|
-
} catch (error) {
|
|
59
|
-
console.warn(
|
|
60
|
-
`[notro] MDX compilation failed for markdown (${mdxSource.length} chars):`,
|
|
61
|
-
error,
|
|
62
|
-
);
|
|
63
|
-
// Return a fallback component that renders an error message so the build
|
|
64
|
-
// continues and the problem is visible in the output without a 500 error.
|
|
65
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
66
|
-
const FallbackContent = (props: Record<string, unknown> = {}) => {
|
|
67
|
-
void props;
|
|
68
|
-
return jsx('div', {
|
|
69
|
-
style: 'border:2px solid red;padding:1em;color:red;white-space:pre-wrap',
|
|
70
|
-
children: `[notro] MDX compilation error:\n${errorMessage}`,
|
|
71
|
-
});
|
|
72
|
-
};
|
|
73
|
-
FallbackContent[Symbol.for('astro.needsHeadRendering')] = true;
|
|
74
|
-
__astro_tag_component__(FallbackContent, 'astro:jsx');
|
|
75
|
-
return FallbackContent;
|
|
76
|
-
}
|
|
77
|
-
const MDXContent = mod.default;
|
|
78
|
-
|
|
79
|
-
// Pick up any `export const components = {...}` defined inside the MDX source.
|
|
80
|
-
const mdxInternalComponents = (mod as Record<string, unknown>).components ?? {};
|
|
81
|
-
|
|
82
|
-
// Wraps MDXContent so props.components are merged in priority order:
|
|
83
|
-
// 1. Caller's <Content components={{...}} />
|
|
84
|
-
// 2. MDX-internal `export const components`
|
|
85
|
-
// 3. Fragment (required)
|
|
86
|
-
const Content = (props: Record<string, unknown> = {}) =>
|
|
87
|
-
MDXContent({
|
|
88
|
-
...props,
|
|
89
|
-
components: {
|
|
90
|
-
Fragment,
|
|
91
|
-
...(mdxInternalComponents as Record<string, unknown>),
|
|
92
|
-
...(props.components as Record<string, unknown> | undefined),
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// Tag the component so Astro's rendering pipeline handles it correctly.
|
|
97
|
-
// Symbol.for("astro.needsHeadRendering") is checked by Astro's component renderer.
|
|
98
|
-
// __astro_tag_component__ sets Symbol.for("astro:renderer") = 'astro:jsx',
|
|
99
|
-
// which routes rendering through Astro's built-in JSX renderer.
|
|
100
|
-
Content[Symbol.for('astro.needsHeadRendering')] = true;
|
|
101
|
-
__astro_tag_component__(Content, 'astro:jsx');
|
|
102
|
-
|
|
103
|
-
return Content;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// ── Cached variant ─────────────────────────────────────────────────────────
|
|
107
|
-
|
|
108
|
-
// In-memory promise cache: keyed by SHA-256(mdxSource + linkToPages JSON).
|
|
109
|
-
// Stores the Promise itself (not the resolved value) so that concurrent calls
|
|
110
|
-
// with the same key share the in-flight compilation instead of launching
|
|
111
|
-
// duplicate evaluate() calls. The cache is intentionally module-scoped and
|
|
112
|
-
// lives for the duration of the build process.
|
|
113
|
-
//
|
|
114
|
-
// Known limitation: JSON.stringify(linkToPages) is insertion-order dependent.
|
|
115
|
-
// Two objects with the same key/value pairs but different insertion order
|
|
116
|
-
// produce different cache keys. In practice this is not a problem because
|
|
117
|
-
// `buildLinkToPages()` always produces a consistent insertion order, but
|
|
118
|
-
// custom linkToPages objects constructed in different orders would create
|
|
119
|
-
// redundant cache entries rather than sharing results.
|
|
120
|
-
|
|
121
|
-
// Maximum number of entries kept in compilationCache.
|
|
122
|
-
// When the limit is reached, the oldest entry (first inserted) is evicted
|
|
123
|
-
// using Map's guaranteed insertion-order iteration (FIFO eviction).
|
|
124
|
-
const MAX_CACHE_SIZE = 500;
|
|
125
|
-
const compilationCache = new Map<string, ReturnType<typeof compileMdxForAstro>>();
|
|
126
|
-
|
|
127
|
-
export async function compileMdxCached(
|
|
128
|
-
mdxSource: string,
|
|
129
|
-
options: { linkToPages?: LinkToPages } = {},
|
|
130
|
-
) {
|
|
131
|
-
const linkToPages = options.linkToPages ?? {};
|
|
132
|
-
const key = createHash('sha256')
|
|
133
|
-
.update(mdxSource)
|
|
134
|
-
.update(JSON.stringify(linkToPages))
|
|
135
|
-
.digest('hex');
|
|
136
|
-
|
|
137
|
-
let entry = compilationCache.get(key);
|
|
138
|
-
if (!entry) {
|
|
139
|
-
// Evict the oldest entry (FIFO) before inserting a new one to keep
|
|
140
|
-
// memory usage bounded. Eviction is skipped when the current key is
|
|
141
|
-
// already present (cache hit), but a cache miss always inserts a new
|
|
142
|
-
// entry, so we check the size here before that insertion.
|
|
143
|
-
if (compilationCache.size >= MAX_CACHE_SIZE) {
|
|
144
|
-
const oldestKey = compilationCache.keys().next().value;
|
|
145
|
-
if (oldestKey !== undefined) {
|
|
146
|
-
compilationCache.delete(oldestKey);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Store the promise immediately so concurrent callers share the same
|
|
151
|
-
// in-flight compilation. On error, evict the cache entry so the next
|
|
152
|
-
// request retries compilation rather than replaying the failure.
|
|
153
|
-
const promise = compileMdxForAstro(mdxSource, options);
|
|
154
|
-
compilationCache.set(key, promise);
|
|
155
|
-
promise.catch(() => compilationCache.delete(key));
|
|
156
|
-
entry = promise;
|
|
157
|
-
}
|
|
158
|
-
return entry;
|
|
159
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* MDX → Astro component compiler.
|
|
3
|
+
*
|
|
4
|
+
* Astro integration layer: wires the MDX plugin pipeline from mdx-pipeline.ts
|
|
5
|
+
* into Astro's jsx-runtime, registers the result with Astro's component
|
|
6
|
+
* renderer, and caches compiled output by content hash.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { evaluate } from '@mdx-js/mdx';
|
|
10
|
+
import { createHash } from 'node:crypto';
|
|
11
|
+
import { buildMdxPlugins } from './mdx-pipeline.ts';
|
|
12
|
+
import type { LinkToPages } from '../types.ts';
|
|
13
|
+
|
|
14
|
+
// Import Astro's jsx-runtime so evaluate() produces Astro VNodes.
|
|
15
|
+
// (astro/src/jsx-runtime/index.ts:94)
|
|
16
|
+
import { Fragment, jsx, jsxs } from 'astro/jsx-runtime';
|
|
17
|
+
|
|
18
|
+
// Required to register Content as an 'astro:jsx' renderer.
|
|
19
|
+
// Equivalent to annotateContentExport() in vite-plugin-mdx-postprocess.ts.
|
|
20
|
+
import { __astro_tag_component__ } from 'astro/runtime/server/index.js';
|
|
21
|
+
|
|
22
|
+
// ── Core compile function ──────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Compiles a preprocessed Notion markdown string into an Astro component
|
|
26
|
+
* that can be rendered with <Content components={notionComponents} />.
|
|
27
|
+
*
|
|
28
|
+
* @param mdxSource - Preprocessed markdown string (from loader store)
|
|
29
|
+
* @param options.linkToPages - Optional map for resolving Notion page URLs
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```astro
|
|
33
|
+
* ---
|
|
34
|
+
* const Content = await compileMdxForAstro(entry.data.markdown, { linkToPages });
|
|
35
|
+
* ---
|
|
36
|
+
* <Content components={notionComponents} />
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export async function compileMdxForAstro(
|
|
40
|
+
mdxSource: string,
|
|
41
|
+
options: { linkToPages?: LinkToPages } = {},
|
|
42
|
+
) {
|
|
43
|
+
const { linkToPages = {} } = options;
|
|
44
|
+
const { remarkPlugins, rehypePlugins } = buildMdxPlugins(linkToPages);
|
|
45
|
+
|
|
46
|
+
// evaluate() compiles + executes the MDX using Astro's jsx-runtime,
|
|
47
|
+
// producing a function that returns Astro VNodes.
|
|
48
|
+
// Wrapped in try-catch so a broken page does not crash the entire build.
|
|
49
|
+
let mod: Awaited<ReturnType<typeof evaluate>>;
|
|
50
|
+
try {
|
|
51
|
+
mod = await evaluate(mdxSource, {
|
|
52
|
+
jsx,
|
|
53
|
+
jsxs,
|
|
54
|
+
Fragment,
|
|
55
|
+
remarkPlugins,
|
|
56
|
+
rehypePlugins,
|
|
57
|
+
});
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.warn(
|
|
60
|
+
`[notro] MDX compilation failed for markdown (${mdxSource.length} chars):`,
|
|
61
|
+
error,
|
|
62
|
+
);
|
|
63
|
+
// Return a fallback component that renders an error message so the build
|
|
64
|
+
// continues and the problem is visible in the output without a 500 error.
|
|
65
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
66
|
+
const FallbackContent = (props: Record<string, unknown> = {}) => {
|
|
67
|
+
void props;
|
|
68
|
+
return jsx('div', {
|
|
69
|
+
style: 'border:2px solid red;padding:1em;color:red;white-space:pre-wrap',
|
|
70
|
+
children: `[notro] MDX compilation error:\n${errorMessage}`,
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
FallbackContent[Symbol.for('astro.needsHeadRendering')] = true;
|
|
74
|
+
__astro_tag_component__(FallbackContent, 'astro:jsx');
|
|
75
|
+
return FallbackContent;
|
|
76
|
+
}
|
|
77
|
+
const MDXContent = mod.default;
|
|
78
|
+
|
|
79
|
+
// Pick up any `export const components = {...}` defined inside the MDX source.
|
|
80
|
+
const mdxInternalComponents = (mod as Record<string, unknown>).components ?? {};
|
|
81
|
+
|
|
82
|
+
// Wraps MDXContent so props.components are merged in priority order:
|
|
83
|
+
// 1. Caller's <Content components={{...}} />
|
|
84
|
+
// 2. MDX-internal `export const components`
|
|
85
|
+
// 3. Fragment (required)
|
|
86
|
+
const Content = (props: Record<string, unknown> = {}) =>
|
|
87
|
+
MDXContent({
|
|
88
|
+
...props,
|
|
89
|
+
components: {
|
|
90
|
+
Fragment,
|
|
91
|
+
...(mdxInternalComponents as Record<string, unknown>),
|
|
92
|
+
...(props.components as Record<string, unknown> | undefined),
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Tag the component so Astro's rendering pipeline handles it correctly.
|
|
97
|
+
// Symbol.for("astro.needsHeadRendering") is checked by Astro's component renderer.
|
|
98
|
+
// __astro_tag_component__ sets Symbol.for("astro:renderer") = 'astro:jsx',
|
|
99
|
+
// which routes rendering through Astro's built-in JSX renderer.
|
|
100
|
+
Content[Symbol.for('astro.needsHeadRendering')] = true;
|
|
101
|
+
__astro_tag_component__(Content, 'astro:jsx');
|
|
102
|
+
|
|
103
|
+
return Content;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ── Cached variant ─────────────────────────────────────────────────────────
|
|
107
|
+
|
|
108
|
+
// In-memory promise cache: keyed by SHA-256(mdxSource + linkToPages JSON).
|
|
109
|
+
// Stores the Promise itself (not the resolved value) so that concurrent calls
|
|
110
|
+
// with the same key share the in-flight compilation instead of launching
|
|
111
|
+
// duplicate evaluate() calls. The cache is intentionally module-scoped and
|
|
112
|
+
// lives for the duration of the build process.
|
|
113
|
+
//
|
|
114
|
+
// Known limitation: JSON.stringify(linkToPages) is insertion-order dependent.
|
|
115
|
+
// Two objects with the same key/value pairs but different insertion order
|
|
116
|
+
// produce different cache keys. In practice this is not a problem because
|
|
117
|
+
// `buildLinkToPages()` always produces a consistent insertion order, but
|
|
118
|
+
// custom linkToPages objects constructed in different orders would create
|
|
119
|
+
// redundant cache entries rather than sharing results.
|
|
120
|
+
|
|
121
|
+
// Maximum number of entries kept in compilationCache.
|
|
122
|
+
// When the limit is reached, the oldest entry (first inserted) is evicted
|
|
123
|
+
// using Map's guaranteed insertion-order iteration (FIFO eviction).
|
|
124
|
+
const MAX_CACHE_SIZE = 500;
|
|
125
|
+
const compilationCache = new Map<string, ReturnType<typeof compileMdxForAstro>>();
|
|
126
|
+
|
|
127
|
+
export async function compileMdxCached(
|
|
128
|
+
mdxSource: string,
|
|
129
|
+
options: { linkToPages?: LinkToPages } = {},
|
|
130
|
+
) {
|
|
131
|
+
const linkToPages = options.linkToPages ?? {};
|
|
132
|
+
const key = createHash('sha256')
|
|
133
|
+
.update(mdxSource)
|
|
134
|
+
.update(JSON.stringify(linkToPages))
|
|
135
|
+
.digest('hex');
|
|
136
|
+
|
|
137
|
+
let entry = compilationCache.get(key);
|
|
138
|
+
if (!entry) {
|
|
139
|
+
// Evict the oldest entry (FIFO) before inserting a new one to keep
|
|
140
|
+
// memory usage bounded. Eviction is skipped when the current key is
|
|
141
|
+
// already present (cache hit), but a cache miss always inserts a new
|
|
142
|
+
// entry, so we check the size here before that insertion.
|
|
143
|
+
if (compilationCache.size >= MAX_CACHE_SIZE) {
|
|
144
|
+
const oldestKey = compilationCache.keys().next().value;
|
|
145
|
+
if (oldestKey !== undefined) {
|
|
146
|
+
compilationCache.delete(oldestKey);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Store the promise immediately so concurrent callers share the same
|
|
151
|
+
// in-flight compilation. On error, evict the cache entry so the next
|
|
152
|
+
// request retries compilation rather than replaying the failure.
|
|
153
|
+
const promise = compileMdxForAstro(mdxSource, options);
|
|
154
|
+
compilationCache.set(key, promise);
|
|
155
|
+
promise.catch(() => compilationCache.delete(key));
|
|
156
|
+
entry = promise;
|
|
157
|
+
}
|
|
158
|
+
return entry;
|
|
159
|
+
}
|
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
import { makeHtmlElement } from "./HtmlElements.ts";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Default headless component map for NotroContent.
|
|
5
|
-
*
|
|
6
|
-
* Maps all Notion block types and standard HTML elements to semantic HTML
|
|
7
|
-
* equivalents with no Tailwind classes. Spread and override to customize:
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```ts
|
|
11
|
-
* import { defaultComponents, NotroContent } from 'notro';
|
|
12
|
-
* // Use as-is for unstyled output:
|
|
13
|
-
* <NotroContent markdown={md} components={defaultComponents} />
|
|
14
|
-
* // Or extend with your own components:
|
|
15
|
-
* <NotroContent markdown={md} components={{ ...defaultComponents, callout: MyCallout }} />
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
|
-
export const defaultComponents = {
|
|
19
|
-
// ── Notion block elements (PascalCase) ────────────────────────────────────
|
|
20
|
-
// These use PascalCase keys because MDX only generates a components-map
|
|
21
|
-
// lookup (_jsx(Video, ...)) for PascalCase names. Lowercase names compile
|
|
22
|
-
// as plain HTML string literals (_jsx("video", ...)), which bypass the
|
|
23
|
-
// `components` prop entirely. rehypeBlockElementsPlugin renames the
|
|
24
|
-
// mdxJsxFlowElement nodes to these PascalCase names before MDX compiles.
|
|
25
|
-
TableOfContents: makeHtmlElement("nav"),
|
|
26
|
-
Video: makeHtmlElement("figure"),
|
|
27
|
-
Audio: makeHtmlElement("figure"),
|
|
28
|
-
FileBlock: makeHtmlElement("div"),
|
|
29
|
-
PdfBlock: makeHtmlElement("figure"),
|
|
30
|
-
Columns: makeHtmlElement("div"),
|
|
31
|
-
Column: makeHtmlElement("div"),
|
|
32
|
-
PageRef: makeHtmlElement("a"),
|
|
33
|
-
DatabaseRef: makeHtmlElement("a"),
|
|
34
|
-
Details: makeHtmlElement("details"),
|
|
35
|
-
Summary: makeHtmlElement("summary"),
|
|
36
|
-
EmptyBlock: makeHtmlElement("div"),
|
|
37
|
-
// callout is created by remarkNfm (a remark-level plugin via data.hName),
|
|
38
|
-
// not from raw HTML, so MDX tracks it in _components and lowercase works.
|
|
39
|
-
callout: makeHtmlElement("aside"),
|
|
40
|
-
// ── Inline mention components (PascalCase) ────────────────────────────────
|
|
41
|
-
// Same PascalCase requirement — rehypeInlineMentionsPlugin renames these.
|
|
42
|
-
MentionUser: makeHtmlElement("span"),
|
|
43
|
-
MentionPage: makeHtmlElement("span"),
|
|
44
|
-
MentionDatabase: makeHtmlElement("span"),
|
|
45
|
-
MentionDataSource: makeHtmlElement("span"),
|
|
46
|
-
MentionAgent: makeHtmlElement("span"),
|
|
47
|
-
MentionDate: makeHtmlElement("time"),
|
|
48
|
-
|
|
49
|
-
// ── Standard HTML element pass-throughs ──────────────────────────────────
|
|
50
|
-
span: makeHtmlElement("span"),
|
|
51
|
-
p: makeHtmlElement("p"),
|
|
52
|
-
ul: makeHtmlElement("ul"),
|
|
53
|
-
ol: makeHtmlElement("ol"),
|
|
54
|
-
li: makeHtmlElement("li"),
|
|
55
|
-
pre: makeHtmlElement("pre"),
|
|
56
|
-
hr: makeHtmlElement("hr"),
|
|
57
|
-
th: makeHtmlElement("th"),
|
|
58
|
-
a: makeHtmlElement("a"),
|
|
59
|
-
strong: makeHtmlElement("strong"),
|
|
60
|
-
em: makeHtmlElement("em"),
|
|
61
|
-
del: makeHtmlElement("del"),
|
|
62
|
-
} as const;
|
|
1
|
+
import { makeHtmlElement } from "./HtmlElements.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Default headless component map for NotroContent.
|
|
5
|
+
*
|
|
6
|
+
* Maps all Notion block types and standard HTML elements to semantic HTML
|
|
7
|
+
* equivalents with no Tailwind classes. Spread and override to customize:
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { defaultComponents, NotroContent } from 'notro';
|
|
12
|
+
* // Use as-is for unstyled output:
|
|
13
|
+
* <NotroContent markdown={md} components={defaultComponents} />
|
|
14
|
+
* // Or extend with your own components:
|
|
15
|
+
* <NotroContent markdown={md} components={{ ...defaultComponents, callout: MyCallout }} />
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export const defaultComponents = {
|
|
19
|
+
// ── Notion block elements (PascalCase) ────────────────────────────────────
|
|
20
|
+
// These use PascalCase keys because MDX only generates a components-map
|
|
21
|
+
// lookup (_jsx(Video, ...)) for PascalCase names. Lowercase names compile
|
|
22
|
+
// as plain HTML string literals (_jsx("video", ...)), which bypass the
|
|
23
|
+
// `components` prop entirely. rehypeBlockElementsPlugin renames the
|
|
24
|
+
// mdxJsxFlowElement nodes to these PascalCase names before MDX compiles.
|
|
25
|
+
TableOfContents: makeHtmlElement("nav"),
|
|
26
|
+
Video: makeHtmlElement("figure"),
|
|
27
|
+
Audio: makeHtmlElement("figure"),
|
|
28
|
+
FileBlock: makeHtmlElement("div"),
|
|
29
|
+
PdfBlock: makeHtmlElement("figure"),
|
|
30
|
+
Columns: makeHtmlElement("div"),
|
|
31
|
+
Column: makeHtmlElement("div"),
|
|
32
|
+
PageRef: makeHtmlElement("a"),
|
|
33
|
+
DatabaseRef: makeHtmlElement("a"),
|
|
34
|
+
Details: makeHtmlElement("details"),
|
|
35
|
+
Summary: makeHtmlElement("summary"),
|
|
36
|
+
EmptyBlock: makeHtmlElement("div"),
|
|
37
|
+
// callout is created by remarkNfm (a remark-level plugin via data.hName),
|
|
38
|
+
// not from raw HTML, so MDX tracks it in _components and lowercase works.
|
|
39
|
+
callout: makeHtmlElement("aside"),
|
|
40
|
+
// ── Inline mention components (PascalCase) ────────────────────────────────
|
|
41
|
+
// Same PascalCase requirement — rehypeInlineMentionsPlugin renames these.
|
|
42
|
+
MentionUser: makeHtmlElement("span"),
|
|
43
|
+
MentionPage: makeHtmlElement("span"),
|
|
44
|
+
MentionDatabase: makeHtmlElement("span"),
|
|
45
|
+
MentionDataSource: makeHtmlElement("span"),
|
|
46
|
+
MentionAgent: makeHtmlElement("span"),
|
|
47
|
+
MentionDate: makeHtmlElement("time"),
|
|
48
|
+
|
|
49
|
+
// ── Standard HTML element pass-throughs ──────────────────────────────────
|
|
50
|
+
span: makeHtmlElement("span"),
|
|
51
|
+
p: makeHtmlElement("p"),
|
|
52
|
+
ul: makeHtmlElement("ul"),
|
|
53
|
+
ol: makeHtmlElement("ol"),
|
|
54
|
+
li: makeHtmlElement("li"),
|
|
55
|
+
pre: makeHtmlElement("pre"),
|
|
56
|
+
hr: makeHtmlElement("hr"),
|
|
57
|
+
th: makeHtmlElement("th"),
|
|
58
|
+
a: makeHtmlElement("a"),
|
|
59
|
+
strong: makeHtmlElement("strong"),
|
|
60
|
+
em: makeHtmlElement("em"),
|
|
61
|
+
del: makeHtmlElement("del"),
|
|
62
|
+
} as const;
|