@notion-headless-cms/fetch-markdown 0.0.1

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,374 @@
1
+ import { ContentExtension, ContentFetcher } from "@notion-headless-cms/notion-orm";
2
+ import { RendererFn } from "@notion-headless-cms/markdown-html";
3
+
4
+ //#region ../../node_modules/.pnpm/@types+unist@3.0.3/node_modules/@types/unist/index.d.ts
5
+ // ## Interfaces
6
+ /**
7
+ * Info associated with nodes by the ecosystem.
8
+ *
9
+ * This space is guaranteed to never be specified by unist or specifications
10
+ * implementing unist.
11
+ * But you can use it in utilities and plugins to store data.
12
+ *
13
+ * This type can be augmented to register custom data.
14
+ * For example:
15
+ *
16
+ * ```ts
17
+ * declare module 'unist' {
18
+ * interface Data {
19
+ * // `someNode.data.myId` is typed as `number | undefined`
20
+ * myId?: number | undefined
21
+ * }
22
+ * }
23
+ * ```
24
+ */
25
+ interface Data$1 {}
26
+ /**
27
+ * One place in a source file.
28
+ */
29
+ interface Point {
30
+ /**
31
+ * Line in a source file (1-indexed integer).
32
+ */
33
+ line: number;
34
+ /**
35
+ * Column in a source file (1-indexed integer).
36
+ */
37
+ column: number;
38
+ /**
39
+ * Character in a source file (0-indexed integer).
40
+ */
41
+ offset?: number | undefined;
42
+ }
43
+ /**
44
+ * Position of a node in a source document.
45
+ *
46
+ * A position is a range between two points.
47
+ */
48
+ interface Position {
49
+ /**
50
+ * Place of the first character of the parsed source region.
51
+ */
52
+ start: Point;
53
+ /**
54
+ * Place of the first character after the parsed source region.
55
+ */
56
+ end: Point;
57
+ }
58
+ /**
59
+ * Abstract unist node.
60
+ *
61
+ * The syntactic unit in unist syntax trees are called nodes.
62
+ *
63
+ * This interface is supposed to be extended.
64
+ * If you can use {@link Literal} or {@link Parent}, you should.
65
+ * But for example in markdown, a `thematicBreak` (`***`), is neither literal
66
+ * nor parent, but still a node.
67
+ */
68
+ interface Node$1 {
69
+ /**
70
+ * Node type.
71
+ */
72
+ type: string;
73
+ /**
74
+ * Info from the ecosystem.
75
+ */
76
+ data?: Data$1 | undefined;
77
+ /**
78
+ * Position of a node in a source document.
79
+ *
80
+ * Nodes that are generated (not in the original source document) must not
81
+ * have a position.
82
+ */
83
+ position?: Position | undefined;
84
+ }
85
+ //#endregion
86
+ //#region ../../node_modules/.pnpm/@types+hast@3.0.4/node_modules/@types/hast/index.d.ts
87
+ // ## Interfaces
88
+ /**
89
+ * Info associated with hast nodes by the ecosystem.
90
+ *
91
+ * This space is guaranteed to never be specified by unist or hast.
92
+ * But you can use it in utilities and plugins to store data.
93
+ *
94
+ * This type can be augmented to register custom data.
95
+ * For example:
96
+ *
97
+ * ```ts
98
+ * declare module 'hast' {
99
+ * interface Data {
100
+ * // `someNode.data.myId` is typed as `number | undefined`
101
+ * myId?: number | undefined
102
+ * }
103
+ * }
104
+ * ```
105
+ */
106
+ interface Data extends Data$1 {}
107
+ /**
108
+ * Info associated with an element.
109
+ */
110
+ interface Properties {
111
+ [PropertyName: string]: boolean | number | string | null | undefined | Array<string | number>;
112
+ }
113
+ // ## Content maps
114
+ /**
115
+ * Union of registered hast nodes that can occur in {@link Element}.
116
+ *
117
+ * To register mote custom hast nodes, add them to {@link ElementContentMap}.
118
+ * They will be automatically added here.
119
+ */
120
+ type ElementContent = ElementContentMap[keyof ElementContentMap];
121
+ /**
122
+ * Registry of all hast nodes that can occur as children of {@link Element}.
123
+ *
124
+ * For a union of all {@link Element} children, see {@link ElementContent}.
125
+ */
126
+ interface ElementContentMap {
127
+ comment: Comment;
128
+ element: Element;
129
+ text: Text;
130
+ }
131
+ /**
132
+ * Union of registered hast nodes that can occur in {@link Root}.
133
+ *
134
+ * To register custom hast nodes, add them to {@link RootContentMap}.
135
+ * They will be automatically added here.
136
+ */
137
+ type RootContent = RootContentMap[keyof RootContentMap];
138
+ /**
139
+ * Registry of all hast nodes that can occur as children of {@link Root}.
140
+ *
141
+ * > 👉 **Note**: {@link Root} does not need to be an entire document.
142
+ * > it can also be a fragment.
143
+ *
144
+ * For a union of all {@link Root} children, see {@link RootContent}.
145
+ */
146
+ interface RootContentMap {
147
+ comment: Comment;
148
+ doctype: Doctype;
149
+ element: Element;
150
+ text: Text;
151
+ }
152
+ // ## Abstract nodes
153
+ /**
154
+ * Abstract hast node.
155
+ *
156
+ * This interface is supposed to be extended.
157
+ * If you can use {@link Literal} or {@link Parent}, you should.
158
+ * But for example in HTML, a `Doctype` is neither literal nor parent, but
159
+ * still a node.
160
+ *
161
+ * To register custom hast nodes, add them to {@link RootContentMap} and other
162
+ * places where relevant (such as {@link ElementContentMap}).
163
+ *
164
+ * For a union of all registered hast nodes, see {@link Nodes}.
165
+ */
166
+ interface Node extends Node$1 {
167
+ /**
168
+ * Info from the ecosystem.
169
+ */
170
+ data?: Data | undefined;
171
+ }
172
+ /**
173
+ * Abstract hast node that contains the smallest possible value.
174
+ *
175
+ * This interface is supposed to be extended if you make custom hast nodes.
176
+ *
177
+ * For a union of all registered hast literals, see {@link Literals}.
178
+ */
179
+ interface Literal extends Node {
180
+ /**
181
+ * Plain-text value.
182
+ */
183
+ value: string;
184
+ }
185
+ /**
186
+ * Abstract hast node that contains other hast nodes (*children*).
187
+ *
188
+ * This interface is supposed to be extended if you make custom hast nodes.
189
+ *
190
+ * For a union of all registered hast parents, see {@link Parents}.
191
+ */
192
+ interface Parent extends Node {
193
+ /**
194
+ * List of children.
195
+ */
196
+ children: RootContent[];
197
+ }
198
+ // ## Concrete nodes
199
+ /**
200
+ * HTML comment.
201
+ */
202
+ interface Comment extends Literal {
203
+ /**
204
+ * Node type of HTML comments in hast.
205
+ */
206
+ type: "comment";
207
+ /**
208
+ * Data associated with the comment.
209
+ */
210
+ data?: CommentData | undefined;
211
+ }
212
+ /**
213
+ * Info associated with hast comments by the ecosystem.
214
+ */
215
+ interface CommentData extends Data {}
216
+ /**
217
+ * HTML document type.
218
+ */
219
+ interface Doctype extends Node$1 {
220
+ /**
221
+ * Node type of HTML document types in hast.
222
+ */
223
+ type: "doctype";
224
+ /**
225
+ * Data associated with the doctype.
226
+ */
227
+ data?: DoctypeData | undefined;
228
+ }
229
+ /**
230
+ * Info associated with hast doctypes by the ecosystem.
231
+ */
232
+ interface DoctypeData extends Data {}
233
+ /**
234
+ * HTML element.
235
+ */
236
+ interface Element extends Parent {
237
+ /**
238
+ * Node type of elements.
239
+ */
240
+ type: "element";
241
+ /**
242
+ * Tag name (such as `'body'`) of the element.
243
+ */
244
+ tagName: string;
245
+ /**
246
+ * Info associated with the element.
247
+ */
248
+ properties: Properties;
249
+ /**
250
+ * Children of element.
251
+ */
252
+ children: ElementContent[];
253
+ /**
254
+ * When the `tagName` field is `'template'`, a `content` field can be
255
+ * present.
256
+ */
257
+ content?: Root | undefined;
258
+ /**
259
+ * Data associated with the element.
260
+ */
261
+ data?: ElementData | undefined;
262
+ }
263
+ /**
264
+ * Info associated with hast elements by the ecosystem.
265
+ */
266
+ interface ElementData extends Data {}
267
+ /**
268
+ * Document fragment or a whole document.
269
+ *
270
+ * Should be used as the root of a tree and must not be used as a child.
271
+ *
272
+ * Can also be used as the value for the content field on a `'template'` element.
273
+ */
274
+ interface Root extends Parent {
275
+ /**
276
+ * Node type of hast root.
277
+ */
278
+ type: "root";
279
+ /**
280
+ * Children of root.
281
+ */
282
+ children: RootContent[];
283
+ /**
284
+ * Data associated with the hast root.
285
+ */
286
+ data?: RootData | undefined;
287
+ }
288
+ /**
289
+ * Info associated with hast root nodes by the ecosystem.
290
+ */
291
+ interface RootData extends Data {}
292
+ /**
293
+ * HTML character data (plain text).
294
+ */
295
+ interface Text extends Literal {
296
+ /**
297
+ * Node type of HTML character data (plain text) in hast.
298
+ */
299
+ type: "text";
300
+ /**
301
+ * Data associated with the text.
302
+ */
303
+ data?: TextData | undefined;
304
+ }
305
+ /**
306
+ * Info associated with hast texts by the ecosystem.
307
+ */
308
+ interface TextData extends Data {}
309
+ //#endregion
310
+ //#region src/rehype-notion-tags.d.ts
311
+ /**
312
+ * Notion enhanced markdown が含む独自タグを標準的な HTML 要素に変換する rehype プラグイン。
313
+ *
314
+ * 対象タグ (Notion API `GET /v1/pages/{id}/markdown` が出力するもの):
315
+ * - `<callout icon color>` → `<div class="nhc-callout nhc-color-…">`
316
+ * - `<span color underline>` → `<span class="nhc-color-…" data-underline>` (color 属性はブラウザ非対応)
317
+ * - `<mention-page url>` → `<a href data-mention="page">`
318
+ * - `<mention-date start [end]/>` → `<time datetime>` (range なら from–to を結合表示)
319
+ * - `<page url>` / `<database url>` → `<a href data-link-type>` (リンク表現に統一)
320
+ * - `<columns>` → `<div class="nhc-columns">`、`<column>` → `<div class="nhc-column">`
321
+ * - `<file src>` → `<a href download>` (画像/動画系の判定はファイル拡張子で行う v1 では一律リンク)
322
+ * - `<table_of_contents/>` → `<nav class="nhc-toc" data-placeholder>` (TOC 生成は別途)
323
+ *
324
+ * 未知の独自タグは class を付けるだけにしておく (除去すると内容が消えるため)。
325
+ */
326
+ declare function rehypeNotionTags(): (tree: Root) => void;
327
+ //#endregion
328
+ //#region src/renderer.d.ts
329
+ /**
330
+ * Notion `GET /v1/pages/{id}/markdown` が返す **enhanced markdown** を HTML に変換する。
331
+ * `@notion-headless-cms/fetch-markdown` の `markdownFetcher()` を使う時の標準 renderer。
332
+ *
333
+ * 標準 markdown 部分 (`#`, `**`, lists, links, GFM テーブル等) に加え、
334
+ * Notion 独自タグ (`<callout>` / `<span color>` / `<mention-page>` / `<mention-date>` /
335
+ * `<page>` / `<database>` / `<columns>` / `<column>` / `<file>` / `<table_of_contents/>`)
336
+ * を `nhc-*` クラス付きの素直な HTML に変換する。スタイリングは利用側で当てる。
337
+ *
338
+ * インライン数式 `$\`...\`$` は Notion 固有の記法 (バックティックでエスケープ) のため、
339
+ * パイプライン前に `$...$` に正規化してから remark-math が処理する。
340
+ *
341
+ * 画像 URL を `imageProxyBase` でプロキシ化したい場合は `cacheImage` を渡す。
342
+ * 内部で `markdown-html` の `rehypeImageCache` を経由する。
343
+ */
344
+ declare const notionMarkdownRenderer: RendererFn;
345
+ /**
346
+ * `ContentExtension` の `getMarkdownPlugins()` をパイプラインへ注入した `RendererFn` を生成する。
347
+ * katex / shiki など非同期プラグインも含む拡張を CMS の `renderer` オプションで使う場合に利用する。
348
+ *
349
+ * @example
350
+ * ```ts
351
+ * import { createNotionMarkdownRenderer } from "@notion-headless-cms/fetch-markdown";
352
+ * import { notionKatex } from "@notion-headless-cms/notion-katex";
353
+ *
354
+ * createClient({ renderer: createNotionMarkdownRenderer([notionKatex()]) });
355
+ * ```
356
+ */
357
+ declare function createNotionMarkdownRenderer(extensions: ContentExtension[]): RendererFn;
358
+ //#endregion
359
+ //#region src/index.d.ts
360
+ type MarkdownFetcherOptions = Record<string, never>;
361
+ /**
362
+ * Notion Markdown 取得 API (`GET /v1/pages/{id}/markdown`) を公式 SDK 経由で
363
+ * 1 リクエストで叩く取得戦略。
364
+ * Cloudflare Workers Free プランの 50 subrequest/invocation 上限に引っかからない。
365
+ *
366
+ * - `loadMarkdown`: 公式 SDK の `client.pages.retrieveMarkdown` を使う。
367
+ * リトライ・レート制限・Notion-Version は notion-orm の Client 構築側で固定。
368
+ * - `loadNotionBlocks`: 実装しない (`source/blocks_unsupported`)。
369
+ * React 描画には `@notion-headless-cms/fetch-markdown/react` の `<Renderer />` を使う。
370
+ */
371
+ declare function markdownFetcher(_opts?: MarkdownFetcherOptions): ContentFetcher;
372
+ //#endregion
373
+ export { MarkdownFetcherOptions, createNotionMarkdownRenderer, markdownFetcher, notionMarkdownRenderer, rehypeNotionTags };
374
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,235 @@
1
+ import { t as preprocessNotionMarkdown } from "./preprocess-DVyL8D0Q.mjs";
2
+ import { fetchPageMarkdown } from "@notion-headless-cms/notion-orm";
3
+ import { visit } from "unist-util-visit";
4
+ import { rehypeImageCache } from "@notion-headless-cms/markdown-html";
5
+ import rehypeRaw from "rehype-raw";
6
+ import rehypeStringify from "rehype-stringify";
7
+ import remarkGfm from "remark-gfm";
8
+ import remarkMath from "remark-math";
9
+ import remarkParse from "remark-parse";
10
+ import remarkRehype from "remark-rehype";
11
+ import { unified } from "unified";
12
+ //#region src/rehype-notion-tags.ts
13
+ /**
14
+ * Notion enhanced markdown が含む独自タグを標準的な HTML 要素に変換する rehype プラグイン。
15
+ *
16
+ * 対象タグ (Notion API `GET /v1/pages/{id}/markdown` が出力するもの):
17
+ * - `<callout icon color>` → `<div class="nhc-callout nhc-color-…">`
18
+ * - `<span color underline>` → `<span class="nhc-color-…" data-underline>` (color 属性はブラウザ非対応)
19
+ * - `<mention-page url>` → `<a href data-mention="page">`
20
+ * - `<mention-date start [end]/>` → `<time datetime>` (range なら from–to を結合表示)
21
+ * - `<page url>` / `<database url>` → `<a href data-link-type>` (リンク表現に統一)
22
+ * - `<columns>` → `<div class="nhc-columns">`、`<column>` → `<div class="nhc-column">`
23
+ * - `<file src>` → `<a href download>` (画像/動画系の判定はファイル拡張子で行う v1 では一律リンク)
24
+ * - `<table_of_contents/>` → `<nav class="nhc-toc" data-placeholder>` (TOC 生成は別途)
25
+ *
26
+ * 未知の独自タグは class を付けるだけにしておく (除去すると内容が消えるため)。
27
+ */
28
+ function rehypeNotionTags() {
29
+ return (tree) => {
30
+ visit(tree, "element", (node) => {
31
+ const name = node.tagName;
32
+ switch (name) {
33
+ case "callout":
34
+ transformCallout(node);
35
+ break;
36
+ case "span":
37
+ transformSpan(node);
38
+ break;
39
+ case "mention-page":
40
+ transformMentionPage(node);
41
+ break;
42
+ case "mention-date":
43
+ transformMentionDate(node);
44
+ break;
45
+ case "page":
46
+ transformPageLink(node, "page");
47
+ break;
48
+ case "database":
49
+ transformPageLink(node, "database");
50
+ break;
51
+ case "columns":
52
+ node.tagName = "div";
53
+ addClass(node, "nhc-columns");
54
+ break;
55
+ case "column":
56
+ node.tagName = "div";
57
+ addClass(node, "nhc-column");
58
+ break;
59
+ case "file":
60
+ transformFile(node);
61
+ break;
62
+ case "table_of_contents":
63
+ node.tagName = "nav";
64
+ addClass(node, "nhc-toc");
65
+ setAttr(node, "data-placeholder", "true");
66
+ node.children = [];
67
+ break;
68
+ default:
69
+ if (name.includes("-")) addClass(node, `nhc-${name}`);
70
+ break;
71
+ }
72
+ });
73
+ };
74
+ }
75
+ function transformCallout(node) {
76
+ const icon = getAttr(node, "icon");
77
+ const color = getAttr(node, "color");
78
+ node.tagName = "div";
79
+ node.properties = { ...node.properties };
80
+ delete node.properties.icon;
81
+ delete node.properties.color;
82
+ addClass(node, "nhc-callout");
83
+ if (color) addClass(node, `nhc-color-${color}`);
84
+ if (icon) node.children = [{
85
+ type: "element",
86
+ tagName: "span",
87
+ properties: { className: ["nhc-callout-icon"] },
88
+ children: [{
89
+ type: "text",
90
+ value: icon
91
+ }]
92
+ }, {
93
+ type: "element",
94
+ tagName: "div",
95
+ properties: { className: ["nhc-callout-body"] },
96
+ children: node.children
97
+ }];
98
+ }
99
+ function transformSpan(node) {
100
+ const color = getAttr(node, "color");
101
+ const underline = getAttr(node, "underline");
102
+ if (node.properties) {
103
+ delete node.properties.color;
104
+ delete node.properties.underline;
105
+ }
106
+ if (color) addClass(node, `nhc-color-${color}`);
107
+ if (underline === "true") addClass(node, "nhc-underline");
108
+ }
109
+ function transformMentionPage(node) {
110
+ const url = getAttr(node, "url");
111
+ node.tagName = "a";
112
+ node.properties = { className: ["nhc-mention", "nhc-mention-page"] };
113
+ if (url) node.properties.href = url;
114
+ setAttr(node, "data-mention", "page");
115
+ }
116
+ function transformMentionDate(node) {
117
+ const start = getAttr(node, "start");
118
+ const end = getAttr(node, "end");
119
+ node.tagName = "time";
120
+ node.properties = { className: ["nhc-mention", "nhc-mention-date"] };
121
+ if (start) node.properties.dateTime = start;
122
+ node.children = [{
123
+ type: "text",
124
+ value: end != null ? `${start} – ${end}` : start ?? ""
125
+ }];
126
+ }
127
+ function transformPageLink(node, kind) {
128
+ const url = getAttr(node, "url");
129
+ node.tagName = "a";
130
+ node.properties = { className: ["nhc-link", `nhc-link-${kind}`] };
131
+ if (url) node.properties.href = url;
132
+ setAttr(node, "data-link-type", kind);
133
+ }
134
+ function transformFile(node) {
135
+ const src = getAttr(node, "src");
136
+ node.tagName = "a";
137
+ node.properties = { className: ["nhc-file"] };
138
+ if (src) {
139
+ node.properties.href = src;
140
+ node.properties.download = true;
141
+ }
142
+ }
143
+ function getAttr(node, name) {
144
+ const v = node.properties?.[name];
145
+ if (v == null) return void 0;
146
+ if (typeof v === "string") return v;
147
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
148
+ if (Array.isArray(v)) return v.join(" ");
149
+ }
150
+ function setAttr(node, name, value) {
151
+ node.properties = {
152
+ ...node.properties ?? {},
153
+ [name]: value
154
+ };
155
+ }
156
+ function addClass(node, cls) {
157
+ if (!node.properties) node.properties = {};
158
+ const props = node.properties;
159
+ const current = props.className;
160
+ if (Array.isArray(current)) {
161
+ if (!current.includes(cls)) current.push(cls);
162
+ } else if (typeof current === "string") props.className = current ? `${current} ${cls}` : cls;
163
+ else props.className = [cls];
164
+ }
165
+ //#endregion
166
+ //#region src/renderer.ts
167
+ /**
168
+ * Notion `GET /v1/pages/{id}/markdown` が返す **enhanced markdown** を HTML に変換する。
169
+ * `@notion-headless-cms/fetch-markdown` の `markdownFetcher()` を使う時の標準 renderer。
170
+ *
171
+ * 標準 markdown 部分 (`#`, `**`, lists, links, GFM テーブル等) に加え、
172
+ * Notion 独自タグ (`<callout>` / `<span color>` / `<mention-page>` / `<mention-date>` /
173
+ * `<page>` / `<database>` / `<columns>` / `<column>` / `<file>` / `<table_of_contents/>`)
174
+ * を `nhc-*` クラス付きの素直な HTML に変換する。スタイリングは利用側で当てる。
175
+ *
176
+ * インライン数式 `$\`...\`$` は Notion 固有の記法 (バックティックでエスケープ) のため、
177
+ * パイプライン前に `$...$` に正規化してから remark-math が処理する。
178
+ *
179
+ * 画像 URL を `imageProxyBase` でプロキシ化したい場合は `cacheImage` を渡す。
180
+ * 内部で `markdown-html` の `rehypeImageCache` を経由する。
181
+ */
182
+ const notionMarkdownRenderer = async (markdown, options = {}) => {
183
+ const { imageProxyBase = "/api/images", cacheImage, remarkPlugins = [], rehypePlugins = [] } = options;
184
+ const normalized = preprocessNotionMarkdown(markdown);
185
+ const file = await unified().use(remarkParse).use(remarkGfm).use(remarkMath).use(remarkPlugins).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeRaw).use(rehypeNotionTags).use(rehypeImageCache, {
186
+ imageProxyBase,
187
+ cacheImage
188
+ }).use(rehypePlugins).use(rehypeStringify).process(normalized);
189
+ return String(file);
190
+ };
191
+ /**
192
+ * `ContentExtension` の `getMarkdownPlugins()` をパイプラインへ注入した `RendererFn` を生成する。
193
+ * katex / shiki など非同期プラグインも含む拡張を CMS の `renderer` オプションで使う場合に利用する。
194
+ *
195
+ * @example
196
+ * ```ts
197
+ * import { createNotionMarkdownRenderer } from "@notion-headless-cms/fetch-markdown";
198
+ * import { notionKatex } from "@notion-headless-cms/notion-katex";
199
+ *
200
+ * createClient({ renderer: createNotionMarkdownRenderer([notionKatex()]) });
201
+ * ```
202
+ */
203
+ function createNotionMarkdownRenderer(extensions) {
204
+ const extraRemark = extensions.flatMap((e) => e.getMarkdownPlugins?.()?.remarkPlugins ?? []);
205
+ const extraRehype = extensions.flatMap((e) => e.getMarkdownPlugins?.()?.rehypePlugins ?? []);
206
+ return (markdown, options = {}) => notionMarkdownRenderer(markdown, {
207
+ ...options,
208
+ remarkPlugins: [...options.remarkPlugins ?? [], ...extraRemark],
209
+ rehypePlugins: [...options.rehypePlugins ?? [], ...extraRehype]
210
+ });
211
+ }
212
+ //#endregion
213
+ //#region src/index.ts
214
+ /**
215
+ * Notion Markdown 取得 API (`GET /v1/pages/{id}/markdown`) を公式 SDK 経由で
216
+ * 1 リクエストで叩く取得戦略。
217
+ * Cloudflare Workers Free プランの 50 subrequest/invocation 上限に引っかからない。
218
+ *
219
+ * - `loadMarkdown`: 公式 SDK の `client.pages.retrieveMarkdown` を使う。
220
+ * リトライ・レート制限・Notion-Version は notion-orm の Client 構築側で固定。
221
+ * - `loadNotionBlocks`: 実装しない (`source/blocks_unsupported`)。
222
+ * React 描画には `@notion-headless-cms/fetch-markdown/react` の `<Renderer />` を使う。
223
+ */
224
+ function markdownFetcher(_opts = {}) {
225
+ return {
226
+ kind: "markdown",
227
+ async loadMarkdown(client, pageId) {
228
+ return fetchPageMarkdown(client, pageId);
229
+ }
230
+ };
231
+ }
232
+ //#endregion
233
+ export { createNotionMarkdownRenderer, markdownFetcher, notionMarkdownRenderer, rehypeNotionTags };
234
+
235
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/rehype-notion-tags.ts","../src/renderer.ts","../src/index.ts"],"sourcesContent":["import type { Element, ElementContent, Root, Text } from \"hast\";\nimport { visit } from \"unist-util-visit\";\n\n/**\n * Notion enhanced markdown が含む独自タグを標準的な HTML 要素に変換する rehype プラグイン。\n *\n * 対象タグ (Notion API `GET /v1/pages/{id}/markdown` が出力するもの):\n * - `<callout icon color>` → `<div class=\"nhc-callout nhc-color-…\">`\n * - `<span color underline>` → `<span class=\"nhc-color-…\" data-underline>` (color 属性はブラウザ非対応)\n * - `<mention-page url>` → `<a href data-mention=\"page\">`\n * - `<mention-date start [end]/>` → `<time datetime>` (range なら from–to を結合表示)\n * - `<page url>` / `<database url>` → `<a href data-link-type>` (リンク表現に統一)\n * - `<columns>` → `<div class=\"nhc-columns\">`、`<column>` → `<div class=\"nhc-column\">`\n * - `<file src>` → `<a href download>` (画像/動画系の判定はファイル拡張子で行う v1 では一律リンク)\n * - `<table_of_contents/>` → `<nav class=\"nhc-toc\" data-placeholder>` (TOC 生成は別途)\n *\n * 未知の独自タグは class を付けるだけにしておく (除去すると内容が消えるため)。\n */\nexport function rehypeNotionTags() {\n return (tree: Root) => {\n visit(tree, \"element\", (node: Element) => {\n const name = node.tagName;\n switch (name) {\n case \"callout\":\n transformCallout(node);\n break;\n case \"span\":\n transformSpan(node);\n break;\n case \"mention-page\":\n transformMentionPage(node);\n break;\n case \"mention-date\":\n transformMentionDate(node);\n break;\n case \"page\":\n transformPageLink(node, \"page\");\n break;\n case \"database\":\n transformPageLink(node, \"database\");\n break;\n case \"columns\":\n node.tagName = \"div\";\n addClass(node, \"nhc-columns\");\n break;\n case \"column\":\n node.tagName = \"div\";\n addClass(node, \"nhc-column\");\n break;\n case \"file\":\n transformFile(node);\n break;\n case \"table_of_contents\":\n node.tagName = \"nav\";\n addClass(node, \"nhc-toc\");\n setAttr(node, \"data-placeholder\", \"true\");\n node.children = [];\n break;\n default:\n // 他の独自タグはそのまま (将来の Notion 拡張に備える)。\n if (name.includes(\"-\")) addClass(node, `nhc-${name}`);\n break;\n }\n });\n };\n}\n\nfunction transformCallout(node: Element): void {\n const icon = getAttr(node, \"icon\");\n const color = getAttr(node, \"color\");\n node.tagName = \"div\";\n node.properties = { ...node.properties };\n delete node.properties.icon;\n delete node.properties.color;\n addClass(node, \"nhc-callout\");\n if (color) addClass(node, `nhc-color-${color}`);\n if (icon) {\n const iconEl: Element = {\n type: \"element\",\n tagName: \"span\",\n properties: { className: [\"nhc-callout-icon\"] },\n children: [{ type: \"text\", value: icon } satisfies Text],\n };\n const body: Element = {\n type: \"element\",\n tagName: \"div\",\n properties: { className: [\"nhc-callout-body\"] },\n children: node.children as ElementContent[],\n };\n node.children = [iconEl, body];\n }\n}\n\nfunction transformSpan(node: Element): void {\n const color = getAttr(node, \"color\");\n const underline = getAttr(node, \"underline\");\n // 標準でない属性は剥がしておかないと React/HTML 警告が出る。\n if (node.properties) {\n delete node.properties.color;\n delete node.properties.underline;\n }\n if (color) addClass(node, `nhc-color-${color}`);\n if (underline === \"true\") addClass(node, \"nhc-underline\");\n}\n\nfunction transformMentionPage(node: Element): void {\n const url = getAttr(node, \"url\");\n node.tagName = \"a\";\n node.properties = { className: [\"nhc-mention\", \"nhc-mention-page\"] };\n if (url) (node.properties as Record<string, unknown>).href = url;\n setAttr(node, \"data-mention\", \"page\");\n}\n\nfunction transformMentionDate(node: Element): void {\n const start = getAttr(node, \"start\");\n const end = getAttr(node, \"end\");\n node.tagName = \"time\";\n node.properties = { className: [\"nhc-mention\", \"nhc-mention-date\"] };\n if (start) (node.properties as Record<string, unknown>).dateTime = start;\n const label = end != null ? `${start} – ${end}` : (start ?? \"\");\n node.children = [{ type: \"text\", value: label } satisfies Text];\n}\n\nfunction transformPageLink(node: Element, kind: \"page\" | \"database\"): void {\n const url = getAttr(node, \"url\");\n node.tagName = \"a\";\n // children を保持して見出し相当のリンクにする。\n node.properties = { className: [\"nhc-link\", `nhc-link-${kind}`] };\n if (url) (node.properties as Record<string, unknown>).href = url;\n setAttr(node, \"data-link-type\", kind);\n}\n\nfunction transformFile(node: Element): void {\n const src = getAttr(node, \"src\");\n node.tagName = \"a\";\n node.properties = { className: [\"nhc-file\"] };\n if (src) {\n (node.properties as Record<string, unknown>).href = src;\n (node.properties as Record<string, unknown>).download = true;\n }\n}\n\nfunction getAttr(node: Element, name: string): string | undefined {\n const v = node.properties?.[name];\n if (v == null) return undefined;\n if (typeof v === \"string\") return v;\n if (typeof v === \"number\" || typeof v === \"boolean\") return String(v);\n if (Array.isArray(v)) return v.join(\" \");\n return undefined;\n}\n\nfunction setAttr(node: Element, name: string, value: string): void {\n node.properties = { ...(node.properties ?? {}), [name]: value };\n}\n\nfunction addClass(node: Element, cls: string): void {\n if (!node.properties) node.properties = {};\n const props = node.properties;\n const current = props.className;\n if (Array.isArray(current)) {\n if (!current.includes(cls)) current.push(cls);\n } else if (typeof current === \"string\") {\n props.className = current ? `${current} ${cls}` : cls;\n } else {\n props.className = [cls];\n }\n}\n","import {\n type RendererFn,\n rehypeImageCache,\n} from \"@notion-headless-cms/markdown-html\";\nimport type { ContentExtension } from \"@notion-headless-cms/notion-orm\";\nimport rehypeRaw from \"rehype-raw\";\nimport rehypeStringify from \"rehype-stringify\";\nimport remarkGfm from \"remark-gfm\";\nimport remarkMath from \"remark-math\";\nimport remarkParse from \"remark-parse\";\nimport remarkRehype from \"remark-rehype\";\nimport type { PluggableList } from \"unified\";\nimport { unified } from \"unified\";\nimport { preprocessNotionMarkdown } from \"./preprocess\";\nimport { rehypeNotionTags } from \"./rehype-notion-tags\";\n\n/**\n * Notion `GET /v1/pages/{id}/markdown` が返す **enhanced markdown** を HTML に変換する。\n * `@notion-headless-cms/fetch-markdown` の `markdownFetcher()` を使う時の標準 renderer。\n *\n * 標準 markdown 部分 (`#`, `**`, lists, links, GFM テーブル等) に加え、\n * Notion 独自タグ (`<callout>` / `<span color>` / `<mention-page>` / `<mention-date>` /\n * `<page>` / `<database>` / `<columns>` / `<column>` / `<file>` / `<table_of_contents/>`)\n * を `nhc-*` クラス付きの素直な HTML に変換する。スタイリングは利用側で当てる。\n *\n * インライン数式 `$\\`...\\`$` は Notion 固有の記法 (バックティックでエスケープ) のため、\n * パイプライン前に `$...$` に正規化してから remark-math が処理する。\n *\n * 画像 URL を `imageProxyBase` でプロキシ化したい場合は `cacheImage` を渡す。\n * 内部で `markdown-html` の `rehypeImageCache` を経由する。\n */\nexport const notionMarkdownRenderer: RendererFn = async (\n markdown,\n options = {},\n) => {\n const {\n imageProxyBase = \"/api/images\",\n cacheImage,\n remarkPlugins = [],\n rehypePlugins = [],\n } = options;\n\n const normalized = preprocessNotionMarkdown(markdown);\n\n const processor = unified()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkMath)\n .use(remarkPlugins as PluggableList)\n // Notion 独自タグは raw HTML として markdown に紛れ込んでくるので、\n // allowDangerousHtml + rehype-raw でちゃんと hast に展開してから変換する。\n .use(remarkRehype, { allowDangerousHtml: true })\n .use(rehypeRaw)\n .use(rehypeNotionTags)\n .use(rehypeImageCache, { imageProxyBase, cacheImage })\n .use(rehypePlugins as PluggableList)\n .use(rehypeStringify);\n\n const file = await processor.process(normalized);\n return String(file);\n};\n\n/**\n * `ContentExtension` の `getMarkdownPlugins()` をパイプラインへ注入した `RendererFn` を生成する。\n * katex / shiki など非同期プラグインも含む拡張を CMS の `renderer` オプションで使う場合に利用する。\n *\n * @example\n * ```ts\n * import { createNotionMarkdownRenderer } from \"@notion-headless-cms/fetch-markdown\";\n * import { notionKatex } from \"@notion-headless-cms/notion-katex\";\n *\n * createClient({ renderer: createNotionMarkdownRenderer([notionKatex()]) });\n * ```\n */\nexport function createNotionMarkdownRenderer(\n extensions: ContentExtension[],\n): RendererFn {\n const extraRemark = extensions.flatMap(\n (e) => e.getMarkdownPlugins?.()?.remarkPlugins ?? [],\n );\n const extraRehype = extensions.flatMap(\n (e) => e.getMarkdownPlugins?.()?.rehypePlugins ?? [],\n );\n return (markdown, options = {}) =>\n notionMarkdownRenderer(markdown, {\n ...options,\n remarkPlugins: [\n ...(options.remarkPlugins ?? []),\n ...(extraRemark as PluggableList),\n ],\n rehypePlugins: [\n ...(options.rehypePlugins ?? []),\n ...(extraRehype as PluggableList),\n ],\n });\n}\n","import type { ContentFetcher } from \"@notion-headless-cms/notion-orm\";\nimport { fetchPageMarkdown } from \"@notion-headless-cms/notion-orm\";\n\nexport { rehypeNotionTags } from \"./rehype-notion-tags\";\nexport {\n createNotionMarkdownRenderer,\n notionMarkdownRenderer,\n} from \"./renderer\";\n\nexport type MarkdownFetcherOptions = Record<string, never>;\n\n/**\n * Notion Markdown 取得 API (`GET /v1/pages/{id}/markdown`) を公式 SDK 経由で\n * 1 リクエストで叩く取得戦略。\n * Cloudflare Workers Free プランの 50 subrequest/invocation 上限に引っかからない。\n *\n * - `loadMarkdown`: 公式 SDK の `client.pages.retrieveMarkdown` を使う。\n * リトライ・レート制限・Notion-Version は notion-orm の Client 構築側で固定。\n * - `loadNotionBlocks`: 実装しない (`source/blocks_unsupported`)。\n * React 描画には `@notion-headless-cms/fetch-markdown/react` の `<Renderer />` を使う。\n */\nexport function markdownFetcher(\n _opts: MarkdownFetcherOptions = {},\n): ContentFetcher {\n return {\n kind: \"markdown\",\n async loadMarkdown(client, pageId) {\n return fetchPageMarkdown(client, pageId);\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ //#region src/preprocess.ts
2
+ /**
3
+ * Notion enhanced markdown を remark に通す前に整える。
4
+ *
5
+ * 1. インライン数式 `` $`expression`$ `` → `$expression$` に正規化 (remark-math 互換)
6
+ * 2. ブロックレベル Notion タグ (`<callout>` / `<details>` / `<columns>` / `<column>` /
7
+ * `<summary>` / `<table_of_contents/>`) の前後に空行を確保する。
8
+ * CommonMark は「閉じタグ直後に空行が無いと以降を HTML ブロック扱い」にするため、
9
+ * Notion API が出力する `</callout>\n# 見出し` の形だと heading が変換されない。
10
+ * 3. `</table>` の後に空行を確保する。
11
+ * Notion API が `<table>` タグ形式(CommonMark type-6 HTML ブロック)でテーブルを出力するため、
12
+ * `</table>` の直後に空行がないと後続の見出し等が HTML ブロックの一部として扱われる。
13
+ * テーブル内部に空行を入れると HTML ブロックが途切れてしまうため後ろのみに追加する。
14
+ * 4. `<unknown .../>` の前後に空行を確保する。
15
+ * 未対応ブロック型(bookmark / embed 等)を表すタグが CommonMark type-7 HTML ブロックとして
16
+ * 解釈され、後続の見出しを取り込んでしまうことを防ぐ。
17
+ */
18
+ function preprocessNotionMarkdown(md) {
19
+ let out = md.replace(/\$`([^`]+)`\$/g, (_, expr) => `$${expr}$`);
20
+ for (const tag of [
21
+ "callout",
22
+ "details",
23
+ "summary",
24
+ "columns",
25
+ "column"
26
+ ]) {
27
+ out = out.replace(new RegExp(`(<${tag}(?:\\s[^>]*)?>)`, "g"), "\n\n$1\n\n");
28
+ out = out.replace(new RegExp(`(</${tag}>)`, "g"), "\n\n$1\n\n");
29
+ }
30
+ out = out.replace(/(<table_of_contents\s*\/>)/g, "\n\n$1\n\n");
31
+ out = out.replace(/(<\/table>)/g, "$1\n\n");
32
+ out = out.replace(/(<unknown\s[^>]*\/>)/g, "\n\n$1\n\n");
33
+ return out;
34
+ }
35
+ //#endregion
36
+ export { preprocessNotionMarkdown as t };
37
+
38
+ //# sourceMappingURL=preprocess-DVyL8D0Q.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preprocess-DVyL8D0Q.mjs","names":[],"sources":["../src/preprocess.ts"],"sourcesContent":["/**\n * Notion enhanced markdown を remark に通す前に整える。\n *\n * 1. インライン数式 `` $`expression`$ `` → `$expression$` に正規化 (remark-math 互換)\n * 2. ブロックレベル Notion タグ (`<callout>` / `<details>` / `<columns>` / `<column>` /\n * `<summary>` / `<table_of_contents/>`) の前後に空行を確保する。\n * CommonMark は「閉じタグ直後に空行が無いと以降を HTML ブロック扱い」にするため、\n * Notion API が出力する `</callout>\\n# 見出し` の形だと heading が変換されない。\n * 3. `</table>` の後に空行を確保する。\n * Notion API が `<table>` タグ形式(CommonMark type-6 HTML ブロック)でテーブルを出力するため、\n * `</table>` の直後に空行がないと後続の見出し等が HTML ブロックの一部として扱われる。\n * テーブル内部に空行を入れると HTML ブロックが途切れてしまうため後ろのみに追加する。\n * 4. `<unknown .../>` の前後に空行を確保する。\n * 未対応ブロック型(bookmark / embed 等)を表すタグが CommonMark type-7 HTML ブロックとして\n * 解釈され、後続の見出しを取り込んでしまうことを防ぐ。\n */\nexport function preprocessNotionMarkdown(md: string): string {\n let out = md.replace(/\\$`([^`]+)`\\$/g, (_, expr) => `$${expr}$`);\n const BLOCK_TAGS = [\n \"callout\",\n \"details\",\n \"summary\",\n \"columns\",\n \"column\",\n ] as const;\n for (const tag of BLOCK_TAGS) {\n // 開きタグ: <tag ...> or <tag>\n out = out.replace(new RegExp(`(<${tag}(?:\\\\s[^>]*)?>)`, \"g\"), \"\\n\\n$1\\n\\n\");\n // 閉じタグ\n out = out.replace(new RegExp(`(</${tag}>)`, \"g\"), \"\\n\\n$1\\n\\n\");\n }\n // 自己閉じタグ\n out = out.replace(/(<table_of_contents\\s*\\/>)/g, \"\\n\\n$1\\n\\n\");\n\n // </table> の後にのみ空行を追加(前に入れるとテーブル HTML ブロックが途中で切れる)\n out = out.replace(/(<\\/table>)/g, \"$1\\n\\n\");\n\n // <unknown .../> 前後に空行を追加\n out = out.replace(/(<unknown\\s[^>]*\\/>)/g, \"\\n\\n$1\\n\\n\");\n\n return out;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,SAAgB,yBAAyB,IAAoB;CAC3D,IAAI,MAAM,GAAG,QAAQ,mBAAmB,GAAG,SAAS,IAAI,KAAK,EAAE;CAQ/D,KAAK,MAAM,OAAO;EANhB;EACA;EACA;EACA;EACA;CAEyB,GAAG;EAE5B,MAAM,IAAI,QAAQ,IAAI,OAAO,KAAK,IAAI,kBAAkB,GAAG,GAAG,YAAY;EAE1E,MAAM,IAAI,QAAQ,IAAI,OAAO,MAAM,IAAI,KAAK,GAAG,GAAG,YAAY;CAChE;CAEA,MAAM,IAAI,QAAQ,+BAA+B,YAAY;CAG7D,MAAM,IAAI,QAAQ,gBAAgB,QAAQ;CAG1C,MAAM,IAAI,QAAQ,yBAAyB,YAAY;CAEvD,OAAO;AACT"}
@@ -0,0 +1,45 @@
1
+ import { ContentExtension } from "@notion-headless-cms/notion-orm";
2
+ import { ComponentType, ReactNode } from "react";
3
+
4
+ //#region src/react.d.ts
5
+ /**
6
+ * `<Renderer />` の `components` で指定できるコンポーネントマップ。
7
+ * - 標準 HTML 要素 (`a`, `h1`, `code`, ...) を独自実装に差し替え可能
8
+ * - Notion 独自タグ (`callout`, `mention-page`, `mention-date`, `page`, `database`,
9
+ * `columns`, `column`, `file`, `table_of_contents`) も同じ枠組みで差し替え可能
10
+ *
11
+ * 未指定の Notion 独自タグはこのパッケージ同梱の既定コンポーネントが使われる。
12
+ */
13
+ type RendererComponents = Record<string, ComponentType<Record<string, unknown>>>;
14
+ interface RendererProps {
15
+ /**
16
+ * Notion から取得した markdown 文字列、または
17
+ * `cms.posts.find(...)` の content オブジェクト (`{ markdown }`)。
18
+ */
19
+ content: {
20
+ markdown: string;
21
+ } | string;
22
+ /** 独自コンポーネントで標準タグや Notion タグを差し替える。 */
23
+ components?: RendererComponents;
24
+ /** ラッパー `<div>` に付ける className。 */
25
+ className?: string;
26
+ /**
27
+ * `getMarkdownPlugins()` で unified プラグインを提供する拡張のリスト。
28
+ * 同期プラグイン(`rehype-katex` など)に対応。非同期プラグイン(shiki など)は
29
+ * サーバーサイドで `createNotionMarkdownRenderer` を使うこと。
30
+ */
31
+ extensions?: ContentExtension[];
32
+ }
33
+ /**
34
+ * Notion `pages/{id}/markdown` の enhanced markdown を React 要素として描画する。
35
+ * パイプライン全体が同期で完結するため SSR (renderToString) でもそのまま動く。
36
+ *
37
+ * - 標準 markdown (heading / list / GFM table / fenced code / inline code / links) は変換
38
+ * - Notion 独自タグ (`<callout>`, `<mention-page>`, ...) は既定の React コンポーネントへマップ
39
+ * - `<span color="red">` など属性ベースの装飾は既定コンポーネントが `data-*` 経由で受け取る
40
+ */
41
+ declare function Renderer(props: RendererProps): ReactNode;
42
+ declare const defaultNotionComponents: RendererComponents;
43
+ //#endregion
44
+ export { Renderer, RendererComponents, RendererProps, defaultNotionComponents };
45
+ //# sourceMappingURL=react.d.mts.map
package/dist/react.mjs ADDED
@@ -0,0 +1,129 @@
1
+ "use client";
2
+ import { t as preprocessNotionMarkdown } from "./preprocess-DVyL8D0Q.mjs";
3
+ import rehypeRaw from "rehype-raw";
4
+ import remarkGfm from "remark-gfm";
5
+ import remarkMath from "remark-math";
6
+ import remarkParse from "remark-parse";
7
+ import remarkRehype from "remark-rehype";
8
+ import { unified } from "unified";
9
+ import { Fragment, createElement } from "react";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
11
+ import rehypeReact from "rehype-react";
12
+ //#region src/react.tsx
13
+ /**
14
+ * Notion `pages/{id}/markdown` の enhanced markdown を React 要素として描画する。
15
+ * パイプライン全体が同期で完結するため SSR (renderToString) でもそのまま動く。
16
+ *
17
+ * - 標準 markdown (heading / list / GFM table / fenced code / inline code / links) は変換
18
+ * - Notion 独自タグ (`<callout>`, `<mention-page>`, ...) は既定の React コンポーネントへマップ
19
+ * - `<span color="red">` など属性ベースの装飾は既定コンポーネントが `data-*` 経由で受け取る
20
+ */
21
+ function Renderer(props) {
22
+ const normalized = preprocessNotionMarkdown(typeof props.content === "string" ? props.content : props.content.markdown);
23
+ const components = {
24
+ ...defaultNotionComponents,
25
+ ...props.components ?? {}
26
+ };
27
+ const extraRemark = (props.extensions ?? []).flatMap((e) => e.getMarkdownPlugins?.()?.remarkPlugins ?? []);
28
+ const extraRehype = (props.extensions ?? []).flatMap((e) => e.getMarkdownPlugins?.()?.rehypePlugins ?? []);
29
+ const tree = unified().use(remarkParse).use(remarkGfm).use(remarkMath).use(extraRemark).use(remarkRehype, { allowDangerousHtml: true }).use(rehypeRaw).use(extraRehype).use(rehypeReact, {
30
+ Fragment,
31
+ jsx,
32
+ jsxs,
33
+ components
34
+ }).processSync(normalized).result;
35
+ return props.className ? /* @__PURE__ */ jsx("div", {
36
+ className: props.className,
37
+ children: tree
38
+ }) : tree;
39
+ }
40
+ function NotionCallout(props) {
41
+ const { icon, color, children, ...rest } = props;
42
+ const cls = ["nhc-callout"];
43
+ if (typeof color === "string") cls.push(`nhc-color-${color}`);
44
+ return createElement("div", {
45
+ ...rest,
46
+ className: cls.join(" ")
47
+ }, typeof icon === "string" ? createElement("span", { className: "nhc-callout-icon" }, icon) : null, createElement("div", { className: "nhc-callout-body" }, children));
48
+ }
49
+ function NotionMentionPage(props) {
50
+ const { url, children } = props;
51
+ return createElement("a", {
52
+ href: typeof url === "string" ? url : void 0,
53
+ className: "nhc-mention nhc-mention-page",
54
+ "data-mention": "page"
55
+ }, children);
56
+ }
57
+ function NotionMentionDate(props) {
58
+ const { start, end } = props;
59
+ const startStr = typeof start === "string" ? start : "";
60
+ const endStr = typeof end === "string" ? end : void 0;
61
+ const label = endStr ? `${startStr} – ${endStr}` : startStr;
62
+ return createElement("time", {
63
+ className: "nhc-mention nhc-mention-date",
64
+ dateTime: startStr || void 0
65
+ }, label);
66
+ }
67
+ function NotionPageLink(props) {
68
+ const { url, children } = props;
69
+ return createElement("a", {
70
+ href: typeof url === "string" ? url : void 0,
71
+ className: "nhc-link nhc-link-page",
72
+ "data-link-type": "page"
73
+ }, children);
74
+ }
75
+ function NotionDatabaseLink(props) {
76
+ const { url, children } = props;
77
+ return createElement("a", {
78
+ href: typeof url === "string" ? url : void 0,
79
+ className: "nhc-link nhc-link-database",
80
+ "data-link-type": "database"
81
+ }, children);
82
+ }
83
+ function NotionColumns(props) {
84
+ return createElement("div", { className: "nhc-columns" }, props.children);
85
+ }
86
+ function NotionColumn(props) {
87
+ return createElement("div", { className: "nhc-column" }, props.children);
88
+ }
89
+ function NotionFile(props) {
90
+ const { src, children } = props;
91
+ return createElement("a", {
92
+ href: typeof src === "string" ? src : void 0,
93
+ className: "nhc-file",
94
+ download: true
95
+ }, children);
96
+ }
97
+ function NotionTOC() {
98
+ return createElement("nav", {
99
+ className: "nhc-toc",
100
+ "data-placeholder": "true"
101
+ });
102
+ }
103
+ function NotionSpan(props) {
104
+ const { color, underline, children, ...rest } = props;
105
+ const cls = [];
106
+ if (typeof color === "string") cls.push(`nhc-color-${color}`);
107
+ if (underline === "true" || underline === true) cls.push("nhc-underline");
108
+ if (cls.length === 0) return createElement("span", rest, children);
109
+ return createElement("span", {
110
+ ...rest,
111
+ className: cls.join(" ")
112
+ }, children);
113
+ }
114
+ const defaultNotionComponents = {
115
+ callout: NotionCallout,
116
+ "mention-page": NotionMentionPage,
117
+ "mention-date": NotionMentionDate,
118
+ page: NotionPageLink,
119
+ database: NotionDatabaseLink,
120
+ columns: NotionColumns,
121
+ column: NotionColumn,
122
+ file: NotionFile,
123
+ table_of_contents: NotionTOC,
124
+ span: NotionSpan
125
+ };
126
+ //#endregion
127
+ export { Renderer, defaultNotionComponents };
128
+
129
+ //# sourceMappingURL=react.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.mjs","names":[],"sources":["../src/react.tsx"],"sourcesContent":["\"use client\";\n\nimport type { ContentExtension } from \"@notion-headless-cms/notion-orm\";\nimport type { ComponentType, ReactNode } from \"react\";\nimport { createElement, Fragment } from \"react\";\nimport { jsx, jsxs } from \"react/jsx-runtime\";\nimport rehypeRaw from \"rehype-raw\";\nimport rehypeReact from \"rehype-react\";\nimport remarkGfm from \"remark-gfm\";\nimport remarkMath from \"remark-math\";\nimport remarkParse from \"remark-parse\";\nimport remarkRehype from \"remark-rehype\";\nimport type { PluggableList } from \"unified\";\nimport { unified } from \"unified\";\nimport { preprocessNotionMarkdown } from \"./preprocess\";\n\n/**\n * `<Renderer />` の `components` で指定できるコンポーネントマップ。\n * - 標準 HTML 要素 (`a`, `h1`, `code`, ...) を独自実装に差し替え可能\n * - Notion 独自タグ (`callout`, `mention-page`, `mention-date`, `page`, `database`,\n * `columns`, `column`, `file`, `table_of_contents`) も同じ枠組みで差し替え可能\n *\n * 未指定の Notion 独自タグはこのパッケージ同梱の既定コンポーネントが使われる。\n */\nexport type RendererComponents = Record<\n string,\n ComponentType<Record<string, unknown>>\n>;\n\nexport interface RendererProps {\n /**\n * Notion から取得した markdown 文字列、または\n * `cms.posts.find(...)` の content オブジェクト (`{ markdown }`)。\n */\n content: { markdown: string } | string;\n /** 独自コンポーネントで標準タグや Notion タグを差し替える。 */\n components?: RendererComponents;\n /** ラッパー `<div>` に付ける className。 */\n className?: string;\n /**\n * `getMarkdownPlugins()` で unified プラグインを提供する拡張のリスト。\n * 同期プラグイン(`rehype-katex` など)に対応。非同期プラグイン(shiki など)は\n * サーバーサイドで `createNotionMarkdownRenderer` を使うこと。\n */\n extensions?: ContentExtension[];\n}\n\n/**\n * Notion `pages/{id}/markdown` の enhanced markdown を React 要素として描画する。\n * パイプライン全体が同期で完結するため SSR (renderToString) でもそのまま動く。\n *\n * - 標準 markdown (heading / list / GFM table / fenced code / inline code / links) は変換\n * - Notion 独自タグ (`<callout>`, `<mention-page>`, ...) は既定の React コンポーネントへマップ\n * - `<span color=\"red\">` など属性ベースの装飾は既定コンポーネントが `data-*` 経由で受け取る\n */\nexport function Renderer(props: RendererProps): ReactNode {\n const markdown =\n typeof props.content === \"string\" ? props.content : props.content.markdown;\n const normalized = preprocessNotionMarkdown(markdown);\n\n const components: RendererComponents = {\n ...defaultNotionComponents,\n ...(props.components ?? {}),\n };\n\n const extraRemark = (props.extensions ?? []).flatMap(\n (e) => e.getMarkdownPlugins?.()?.remarkPlugins ?? [],\n ) as PluggableList;\n const extraRehype = (props.extensions ?? []).flatMap(\n (e) => e.getMarkdownPlugins?.()?.rehypePlugins ?? [],\n ) as PluggableList;\n\n const processor = unified()\n .use(remarkParse)\n .use(remarkGfm)\n .use(remarkMath)\n .use(extraRemark)\n // 独自タグは raw HTML として markdown に紛れ込んでくる。\n // allowDangerousHtml + rehype-raw で hast に展開すれば、rehype-react が\n // タグ名で components マップを引いて React 要素を組み立てる。\n .use(remarkRehype, { allowDangerousHtml: true })\n .use(rehypeRaw)\n .use(extraRehype)\n .use(rehypeReact, {\n Fragment,\n jsx: jsx as unknown as never,\n jsxs: jsxs as unknown as never,\n components: components as unknown as Record<\n string,\n ComponentType<Record<string, unknown>>\n >,\n });\n\n const file = processor.processSync(normalized);\n const tree = file.result as ReactNode;\n return props.className ? <div className={props.className}>{tree}</div> : tree;\n}\n\n// ──────────────────────────────────────────────────────────────────────────────\n// 既定の Notion 独自タグ用コンポーネント\n// ──────────────────────────────────────────────────────────────────────────────\n\ntype AnyProps = Record<string, unknown> & { children?: ReactNode };\n\nfunction NotionCallout(props: AnyProps): ReactNode {\n const { icon, color, children, ...rest } = props;\n const cls = [\"nhc-callout\"];\n if (typeof color === \"string\") cls.push(`nhc-color-${color}`);\n return createElement(\n \"div\",\n { ...rest, className: cls.join(\" \") },\n typeof icon === \"string\"\n ? createElement(\"span\", { className: \"nhc-callout-icon\" }, icon)\n : null,\n createElement(\"div\", { className: \"nhc-callout-body\" }, children),\n );\n}\n\nfunction NotionMentionPage(props: AnyProps): ReactNode {\n const { url, children } = props;\n return createElement(\n \"a\",\n {\n href: typeof url === \"string\" ? url : undefined,\n className: \"nhc-mention nhc-mention-page\",\n \"data-mention\": \"page\",\n },\n children,\n );\n}\n\nfunction NotionMentionDate(props: AnyProps): ReactNode {\n const { start, end } = props;\n const startStr = typeof start === \"string\" ? start : \"\";\n const endStr = typeof end === \"string\" ? end : undefined;\n const label = endStr ? `${startStr} – ${endStr}` : startStr;\n return createElement(\n \"time\",\n {\n className: \"nhc-mention nhc-mention-date\",\n dateTime: startStr || undefined,\n },\n label,\n );\n}\n\nfunction NotionPageLink(props: AnyProps): ReactNode {\n const { url, children } = props;\n return createElement(\n \"a\",\n {\n href: typeof url === \"string\" ? url : undefined,\n className: \"nhc-link nhc-link-page\",\n \"data-link-type\": \"page\",\n },\n children,\n );\n}\n\nfunction NotionDatabaseLink(props: AnyProps): ReactNode {\n const { url, children } = props;\n return createElement(\n \"a\",\n {\n href: typeof url === \"string\" ? url : undefined,\n className: \"nhc-link nhc-link-database\",\n \"data-link-type\": \"database\",\n },\n children,\n );\n}\n\nfunction NotionColumns(props: AnyProps): ReactNode {\n return createElement(\"div\", { className: \"nhc-columns\" }, props.children);\n}\n\nfunction NotionColumn(props: AnyProps): ReactNode {\n return createElement(\"div\", { className: \"nhc-column\" }, props.children);\n}\n\nfunction NotionFile(props: AnyProps): ReactNode {\n const { src, children } = props;\n return createElement(\n \"a\",\n {\n href: typeof src === \"string\" ? src : undefined,\n className: \"nhc-file\",\n download: true,\n },\n children,\n );\n}\n\nfunction NotionTOC(): ReactNode {\n // TOC 生成自体は別途 plugin で対応 (v1 はプレースホルダのみ)。\n return createElement(\"nav\", {\n className: \"nhc-toc\",\n \"data-placeholder\": \"true\",\n });\n}\n\nfunction NotionSpan(props: AnyProps): ReactNode {\n // <span color=\"red\"> や <span underline=\"true\"> を class に正規化。\n const { color, underline, children, ...rest } = props;\n const cls: string[] = [];\n if (typeof color === \"string\") cls.push(`nhc-color-${color}`);\n if (underline === \"true\" || underline === true) cls.push(\"nhc-underline\");\n if (cls.length === 0) return createElement(\"span\", rest, children);\n return createElement(\"span\", { ...rest, className: cls.join(\" \") }, children);\n}\n\nexport const defaultNotionComponents: RendererComponents = {\n callout: NotionCallout,\n \"mention-page\": NotionMentionPage,\n \"mention-date\": NotionMentionDate,\n page: NotionPageLink,\n database: NotionDatabaseLink,\n columns: NotionColumns,\n column: NotionColumn,\n file: NotionFile,\n table_of_contents: NotionTOC,\n span: NotionSpan,\n};\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@notion-headless-cms/fetch-markdown",
3
+ "version": "0.0.1",
4
+ "description": "Notion Markdown export API でページ本文を 1 リクエストで取得する fetch 戦略。Cloudflare Workers Free プラン (50 subrequest 上限) でも巨大ページが扱える。",
5
+ "keywords": [
6
+ "notion",
7
+ "headless-cms",
8
+ "fetcher",
9
+ "markdown",
10
+ "cloudflare-workers",
11
+ "typescript"
12
+ ],
13
+ "license": "MIT",
14
+ "homepage": "https://github.com/kjfsm/notion-headless-cms#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/kjfsm/notion-headless-cms/issues"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/kjfsm/notion-headless-cms.git",
21
+ "directory": "packages/fetch-markdown"
22
+ },
23
+ "type": "module",
24
+ "sideEffects": false,
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.mts",
28
+ "import": "./dist/index.mjs"
29
+ },
30
+ "./react": {
31
+ "types": "./dist/react.d.mts",
32
+ "import": "./dist/react.mjs"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist"
37
+ ],
38
+ "engines": {
39
+ "node": ">=24"
40
+ },
41
+ "scripts": {
42
+ "build": "tsdown src/index.ts src/react.tsx --format esm --dts --sourcemap --out-dir dist",
43
+ "typecheck": "tsc --noEmit",
44
+ "test": "vitest run",
45
+ "test:coverage": "vitest run --coverage --coverage.reporter=lcov --coverage.reporter=text",
46
+ "publint": "publint --strict",
47
+ "attw": "attw --pack . --profile esm-only"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public",
51
+ "provenance": true
52
+ },
53
+ "dependencies": {
54
+ "@notion-headless-cms/markdown-html": "workspace:*",
55
+ "@notion-headless-cms/notion-orm": "workspace:*",
56
+ "rehype-raw": "^7.0.0",
57
+ "rehype-react": "^8.0.0",
58
+ "rehype-stringify": "catalog:",
59
+ "remark-gfm": "catalog:",
60
+ "remark-math": "^6.0.0",
61
+ "remark-parse": "catalog:",
62
+ "remark-rehype": "catalog:",
63
+ "unified": "catalog:",
64
+ "unist-util-visit": "^5.1.0"
65
+ },
66
+ "peerDependencies": {
67
+ "@notionhq/client": "^5.20.0",
68
+ "react": "^19.0.0"
69
+ },
70
+ "peerDependenciesMeta": {
71
+ "react": {
72
+ "optional": true
73
+ }
74
+ },
75
+ "devDependencies": {
76
+ "@notionhq/client": "catalog:",
77
+ "@types/hast": "^3.0.4",
78
+ "@types/react": "catalog:",
79
+ "react": "catalog:"
80
+ }
81
+ }