notionic 0.0.0-stage → 0.8.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/CHANGELOG.md +129 -0
- package/LICENSE +21 -0
- package/README.md +261 -2
- package/dist/fetch.d.ts +62 -0
- package/dist/fetch.js +259 -0
- package/dist/files-DIc0m6LM.js +104 -0
- package/dist/index.d.ts +112 -0
- package/dist/index.js +1266 -0
- package/dist/markdown.d.ts +15 -0
- package/dist/markdown.js +162 -0
- package/dist/page-BmeweSIt.js +27 -0
- package/dist/styles.css +108 -0
- package/dist/types-DOxr5KpT.d.ts +1305 -0
- package/dist/utils-BL_oAR33.js +18 -0
- package/package.json +69 -4
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { m as RenderContext, u as NotionBlock } from "./types-DOxr5KpT.js";
|
|
2
|
+
//#region src/markdown.d.ts
|
|
3
|
+
export type MarkdownOptions = {
|
|
4
|
+
/** Where links to Notion pages point, as in `NotionRenderer`. Defaults to notion.so. */
|
|
5
|
+
pageHref?: (pageId: string) => string | null;
|
|
6
|
+
/** Rewrites URLs of files uploaded to Notion, as in `NotionRenderer`. */
|
|
7
|
+
transformFileUrl?: RenderContext["transformFileUrl"];
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Notion blocks as GitHub-flavored Markdown, for feeds, `llms.txt` and other
|
|
11
|
+
* plain-text uses. Links and files resolve as in `NotionRenderer`; Notion's
|
|
12
|
+
* heading 1 becomes `##`, so add the page title as `#` yourself.
|
|
13
|
+
*/
|
|
14
|
+
export declare function blocksToMarkdown(blocks: NotionBlock[], options?: MarkdownOptions): string;
|
|
15
|
+
//#endregion
|
package/dist/markdown.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { a as resolvePageLink, i as resolveLink, n as defaultPageHref, t as resolveFileUrl } from "./files-DIc0m6LM.js";
|
|
2
|
+
import { n as plainText } from "./utils-BL_oAR33.js";
|
|
3
|
+
//#region src/markdown.ts
|
|
4
|
+
function inline(text, context) {
|
|
5
|
+
return text.map((item) => {
|
|
6
|
+
if (item.type === "equation") return `$${item.equation.expression}$`;
|
|
7
|
+
let value = item.plain_text;
|
|
8
|
+
if (!value) return "";
|
|
9
|
+
const { annotations } = item;
|
|
10
|
+
if (annotations.code) value = `\`${value}\``;
|
|
11
|
+
if (annotations.bold) value = `**${value}**`;
|
|
12
|
+
if (annotations.italic) value = `*${value}*`;
|
|
13
|
+
if (annotations.strikethrough) value = `~~${value}~~`;
|
|
14
|
+
const link = resolveLink(item.href, context);
|
|
15
|
+
if (link) value = `[${value}](${link.href})`;
|
|
16
|
+
return value.replace(/\n/g, " \n");
|
|
17
|
+
}).join("");
|
|
18
|
+
}
|
|
19
|
+
function indent(markdown, prefix) {
|
|
20
|
+
return markdown.split("\n").map((line) => line ? prefix + line : line).join("\n");
|
|
21
|
+
}
|
|
22
|
+
function quote(markdown) {
|
|
23
|
+
return markdown.split("\n").map((line) => line ? `> ${line}` : ">").join("\n");
|
|
24
|
+
}
|
|
25
|
+
function fence(code) {
|
|
26
|
+
const longest = Math.max(2, ...(code.match(/`+/g) ?? []).map((run) => run.length));
|
|
27
|
+
return "`".repeat(longest + 1);
|
|
28
|
+
}
|
|
29
|
+
function linkLine(label, url, context) {
|
|
30
|
+
const link = url ? resolveLink(url, context) : null;
|
|
31
|
+
return link ? `[${label || link.href}](${link.href})` : "";
|
|
32
|
+
}
|
|
33
|
+
function headingLevel(type) {
|
|
34
|
+
return Number(type.slice(-1)) + 1;
|
|
35
|
+
}
|
|
36
|
+
function listItems(items, context) {
|
|
37
|
+
const first = items[0];
|
|
38
|
+
const start = first?.type === "numbered_list_item" ? first.numbered_list_item.list_start_index ?? 1 : 1;
|
|
39
|
+
return items.map((item, index) => {
|
|
40
|
+
let marker = "-";
|
|
41
|
+
let text = [];
|
|
42
|
+
if (item.type === "bulleted_list_item") text = item.bulleted_list_item.rich_text;
|
|
43
|
+
if (item.type === "numbered_list_item") {
|
|
44
|
+
marker = `${start + index}.`;
|
|
45
|
+
text = item.numbered_list_item.rich_text;
|
|
46
|
+
}
|
|
47
|
+
if (item.type === "to_do") {
|
|
48
|
+
marker = item.to_do.checked ? "- [x]" : "- [ ]";
|
|
49
|
+
text = item.to_do.rich_text;
|
|
50
|
+
}
|
|
51
|
+
const children = item.children?.length ? `\n${indent(render(item.children, context), " ")}` : "";
|
|
52
|
+
return `${marker} ${inline(text, context)}${children}`;
|
|
53
|
+
}).join("\n");
|
|
54
|
+
}
|
|
55
|
+
function table(block, context) {
|
|
56
|
+
const [header, ...body] = (block.children ?? []).flatMap((row) => row.type === "table_row" ? [row.table_row.cells] : []);
|
|
57
|
+
if (!header) return "";
|
|
58
|
+
const line = (cells) => `| ${cells.map((cell) => inline(cell, context).replace(/\|/g, "\\|").replace(/ {2}\n/g, " ")).join(" | ")} |`;
|
|
59
|
+
return [
|
|
60
|
+
line(header),
|
|
61
|
+
`| ${header.map(() => "---").join(" | ")} |`,
|
|
62
|
+
...body.map(line)
|
|
63
|
+
].join("\n");
|
|
64
|
+
}
|
|
65
|
+
function block(item, context) {
|
|
66
|
+
const children = item.children?.length ? render(item.children, context) : "";
|
|
67
|
+
const withChildren = (markdown) => [markdown, children].filter(Boolean).join("\n\n");
|
|
68
|
+
switch (item.type) {
|
|
69
|
+
case "paragraph": return withChildren(inline(item.paragraph.rich_text, context));
|
|
70
|
+
case "heading_1":
|
|
71
|
+
case "heading_2":
|
|
72
|
+
case "heading_3":
|
|
73
|
+
case "heading_4": {
|
|
74
|
+
const text = inline((item.type === "heading_1" ? item.heading_1 : item.type === "heading_2" ? item.heading_2 : item.type === "heading_3" ? item.heading_3 : item.heading_4).rich_text, context).trim();
|
|
75
|
+
return withChildren(text ? `${"#".repeat(headingLevel(item.type))} ${text}` : "");
|
|
76
|
+
}
|
|
77
|
+
case "quote": return quote(withChildren(inline(item.quote.rich_text, context)));
|
|
78
|
+
case "callout": return quote(withChildren((item.callout.icon?.type === "emoji" ? `${item.callout.icon.emoji} ` : "") + inline(item.callout.rich_text, context)));
|
|
79
|
+
case "toggle": return `<details>\n<summary>${plainText(item.toggle.rich_text)}</summary>\n\n${children}\n\n</details>`;
|
|
80
|
+
case "divider": return "---";
|
|
81
|
+
case "equation": return `$$\n${item.equation.expression}\n$$`;
|
|
82
|
+
case "code": {
|
|
83
|
+
const code = plainText(item.code.rich_text).replace(/\n$/, "");
|
|
84
|
+
const language = item.code.language === "plain text" ? "" : item.code.language;
|
|
85
|
+
const marks = fence(code);
|
|
86
|
+
const caption = plainText(item.code.caption);
|
|
87
|
+
return `${marks}${language}\n${code}\n${marks}${caption ? `\n\n${caption}` : ""}`;
|
|
88
|
+
}
|
|
89
|
+
case "table": return table(item, context);
|
|
90
|
+
case "column_list":
|
|
91
|
+
case "column":
|
|
92
|
+
case "synced_block": return children;
|
|
93
|
+
case "tab": return (item.children ?? []).flatMap((tab) => {
|
|
94
|
+
if (tab.type !== "paragraph") return [];
|
|
95
|
+
return [[`**${plainText(tab.paragraph.rich_text)}**`, tab.children?.length ? render(tab.children, context) : ""].filter(Boolean).join("\n\n")];
|
|
96
|
+
}).join("\n\n");
|
|
97
|
+
case "image": {
|
|
98
|
+
const url = resolveFileUrl(item.image, item.id, context);
|
|
99
|
+
return url ? `` : "";
|
|
100
|
+
}
|
|
101
|
+
case "video": return linkLine(plainText(item.video.caption) || "Video", resolveFileUrl(item.video, item.id, context), context);
|
|
102
|
+
case "audio": return linkLine(plainText(item.audio.caption) || "Audio", resolveFileUrl(item.audio, item.id, context), context);
|
|
103
|
+
case "file": return linkLine(item.file.name || plainText(item.file.caption), resolveFileUrl(item.file, item.id, context), context);
|
|
104
|
+
case "pdf": return linkLine(plainText(item.pdf.caption) || "PDF", resolveFileUrl(item.pdf, item.id, context), context);
|
|
105
|
+
case "bookmark": return linkLine(plainText(item.bookmark.caption) || item.bookmarkPreview?.title || "", item.bookmark.url, context);
|
|
106
|
+
case "embed": return linkLine(plainText(item.embed.caption) || item.bookmarkPreview?.title || "", item.embed.url, context);
|
|
107
|
+
case "link_preview": return linkLine(item.bookmarkPreview?.title ?? "", item.link_preview.url, context);
|
|
108
|
+
case "child_page": {
|
|
109
|
+
const link = resolvePageLink(item.id, context);
|
|
110
|
+
const title = item.child_page.title || "Untitled";
|
|
111
|
+
return link ? `[${title}](${link.href})` : title;
|
|
112
|
+
}
|
|
113
|
+
case "link_to_page": {
|
|
114
|
+
const page = item.linkedPage;
|
|
115
|
+
if (!page) return "";
|
|
116
|
+
const link = resolvePageLink(page.id, context);
|
|
117
|
+
const title = page.title || "Untitled";
|
|
118
|
+
return link ? `[${title}](${link.href})` : title;
|
|
119
|
+
}
|
|
120
|
+
default: return "";
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const LIST_TYPES = /* @__PURE__ */ new Set([
|
|
124
|
+
"bulleted_list_item",
|
|
125
|
+
"numbered_list_item",
|
|
126
|
+
"to_do"
|
|
127
|
+
]);
|
|
128
|
+
function render(blocks, context) {
|
|
129
|
+
const parts = [];
|
|
130
|
+
let list = [];
|
|
131
|
+
const flush = () => {
|
|
132
|
+
if (list.length) parts.push(listItems(list, context));
|
|
133
|
+
list = [];
|
|
134
|
+
};
|
|
135
|
+
for (const item of blocks) {
|
|
136
|
+
if (LIST_TYPES.has(item.type)) {
|
|
137
|
+
if (list[0] && list[0].type !== item.type) flush();
|
|
138
|
+
list.push(item);
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
flush();
|
|
142
|
+
const markdown = block(item, context);
|
|
143
|
+
if (markdown.trim()) parts.push(markdown);
|
|
144
|
+
}
|
|
145
|
+
flush();
|
|
146
|
+
return parts.join("\n\n");
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Notion blocks as GitHub-flavored Markdown, for feeds, `llms.txt` and other
|
|
150
|
+
* plain-text uses. Links and files resolve as in `NotionRenderer`; Notion's
|
|
151
|
+
* heading 1 becomes `##`, so add the page title as `#` yourself.
|
|
152
|
+
*/
|
|
153
|
+
function blocksToMarkdown(blocks, options = {}) {
|
|
154
|
+
return render(blocks, {
|
|
155
|
+
pageHref: options.pageHref ?? defaultPageHref,
|
|
156
|
+
transformFileUrl: options.transformFileUrl,
|
|
157
|
+
headings: [],
|
|
158
|
+
components: {}
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
export { blocksToMarkdown };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { n as plainText } from "./utils-BL_oAR33.js";
|
|
2
|
+
//#region src/page.ts
|
|
3
|
+
/** A page's title: the text of its `title` property. */
|
|
4
|
+
function pageTitle(page) {
|
|
5
|
+
for (const property of Object.values(page.properties)) if (property.type === "title") return plainText(property.title);
|
|
6
|
+
return "";
|
|
7
|
+
}
|
|
8
|
+
function toNotionPage(page) {
|
|
9
|
+
return {
|
|
10
|
+
id: page.id,
|
|
11
|
+
url: page.url,
|
|
12
|
+
title: pageTitle(page),
|
|
13
|
+
icon: page.icon,
|
|
14
|
+
cover: page.cover,
|
|
15
|
+
lastEditedTime: page.last_edited_time,
|
|
16
|
+
properties: page.properties
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function toLinkedPage(page) {
|
|
20
|
+
return {
|
|
21
|
+
id: page.id,
|
|
22
|
+
title: pageTitle(page),
|
|
23
|
+
icon: page.icon
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { toLinkedPage as n, toNotionPage as r, pageTitle as t };
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* notionic's styles, for Tailwind CSS v4. Import it after tailwindcss:
|
|
3
|
+
*
|
|
4
|
+
* @import "tailwindcss";
|
|
5
|
+
* @import "notionic/styles.css";
|
|
6
|
+
*
|
|
7
|
+
* Components use shadcn/ui's theme tokens (foreground, muted, border…), so
|
|
8
|
+
* they follow your theme. If your theme names them differently, map them; the
|
|
9
|
+
* README lists the ones notionic needs.
|
|
10
|
+
*
|
|
11
|
+
* Notion's nine colors are the --notionic-* variables below. Override the text
|
|
12
|
+
* colors to match your brand, e.g. `--notionic-red: var(--destructive);`. Each
|
|
13
|
+
* background color (--notionic-*-bg) is mixed from its text color and your
|
|
14
|
+
* --background, so it follows both, in light and dark mode.
|
|
15
|
+
*
|
|
16
|
+
* The dark text colors apply under the `dark` class, `data-theme="dark"` or
|
|
17
|
+
* `data-mode="dark"`, and on pages whose `color-scheme` is dark (e.g. a page
|
|
18
|
+
* that declares `color-scheme: light dark`, on a dark OS).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/* Scan notionic's components for the classes they use. */
|
|
22
|
+
@source "./";
|
|
23
|
+
|
|
24
|
+
:root {
|
|
25
|
+
--notionic-gray: oklch(44.6% 0.03 256.802);
|
|
26
|
+
--notionic-brown: oklch(47.3% 0.137 46.201);
|
|
27
|
+
--notionic-orange: oklch(55.3% 0.195 38.402);
|
|
28
|
+
--notionic-yellow: oklch(55.4% 0.135 66.442);
|
|
29
|
+
--notionic-green: oklch(52.7% 0.154 150.069);
|
|
30
|
+
--notionic-blue: oklch(48.8% 0.243 264.376);
|
|
31
|
+
--notionic-purple: oklch(49.6% 0.265 301.924);
|
|
32
|
+
--notionic-pink: oklch(52.5% 0.223 3.958);
|
|
33
|
+
--notionic-red: oklch(50.5% 0.213 27.518);
|
|
34
|
+
/* Inset outline on images, so light ones keep an edge: pure black at 10%,
|
|
35
|
+
pure white at 10% in dark mode, never a tinted neutral. */
|
|
36
|
+
--notionic-image-outline: oklch(0 0 0 / 0.1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
* Follow the page's color-scheme. Behind @supports so browsers without
|
|
41
|
+
* light-dark() keep the light colors above. Pages that never declare a dark
|
|
42
|
+
* color-scheme always get the light values.
|
|
43
|
+
*/
|
|
44
|
+
@supports (color: light-dark(white, black)) {
|
|
45
|
+
:root {
|
|
46
|
+
--notionic-gray: light-dark(oklch(44.6% 0.03 256.802), oklch(70.7% 0.022 261.325));
|
|
47
|
+
--notionic-brown: light-dark(oklch(47.3% 0.137 46.201), oklch(82.8% 0.189 84.429));
|
|
48
|
+
--notionic-orange: light-dark(oklch(55.3% 0.195 38.402), oklch(75% 0.183 55.934));
|
|
49
|
+
--notionic-yellow: light-dark(oklch(55.4% 0.135 66.442), oklch(85.2% 0.199 91.936));
|
|
50
|
+
--notionic-green: light-dark(oklch(52.7% 0.154 150.069), oklch(79.2% 0.209 151.711));
|
|
51
|
+
--notionic-blue: light-dark(oklch(48.8% 0.243 264.376), oklch(70.7% 0.165 254.624));
|
|
52
|
+
--notionic-purple: light-dark(oklch(49.6% 0.265 301.924), oklch(71.4% 0.203 305.504));
|
|
53
|
+
--notionic-pink: light-dark(oklch(52.5% 0.223 3.958), oklch(71.8% 0.202 349.761));
|
|
54
|
+
--notionic-red: light-dark(oklch(50.5% 0.213 27.518), oklch(70.4% 0.191 22.216));
|
|
55
|
+
--notionic-image-outline: light-dark(oklch(0 0 0 / 0.1), oklch(1 0 0 / 0.1));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.dark,
|
|
60
|
+
[data-theme="dark"],
|
|
61
|
+
[data-mode="dark"] {
|
|
62
|
+
--notionic-gray: oklch(70.7% 0.022 261.325);
|
|
63
|
+
--notionic-brown: oklch(82.8% 0.189 84.429);
|
|
64
|
+
--notionic-orange: oklch(75% 0.183 55.934);
|
|
65
|
+
--notionic-yellow: oklch(85.2% 0.199 91.936);
|
|
66
|
+
--notionic-green: oklch(79.2% 0.209 151.711);
|
|
67
|
+
--notionic-blue: oklch(70.7% 0.165 254.624);
|
|
68
|
+
--notionic-purple: oklch(71.4% 0.203 305.504);
|
|
69
|
+
--notionic-pink: oklch(71.8% 0.202 349.761);
|
|
70
|
+
--notionic-red: oklch(70.4% 0.191 22.216);
|
|
71
|
+
--notionic-image-outline: oklch(1 0 0 / 0.1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/*
|
|
75
|
+
* Repeated under the dark selectors so the mix is recomputed wherever the
|
|
76
|
+
* text colors change, not only on :root. Override --notionic-bg-mix to make
|
|
77
|
+
* backgrounds stronger or subtler, or any --notionic-*-bg to set one directly.
|
|
78
|
+
*/
|
|
79
|
+
:root,
|
|
80
|
+
.dark,
|
|
81
|
+
[data-theme="dark"],
|
|
82
|
+
[data-mode="dark"] {
|
|
83
|
+
--notionic-bg-mix: 14%;
|
|
84
|
+
--notionic-gray-bg: color-mix(in oklch, var(--notionic-gray) var(--notionic-bg-mix), var(--background, var(--color-background, white)));
|
|
85
|
+
--notionic-brown-bg: color-mix(in oklch, var(--notionic-brown) var(--notionic-bg-mix), var(--background, var(--color-background, white)));
|
|
86
|
+
--notionic-orange-bg: color-mix(in oklch, var(--notionic-orange) var(--notionic-bg-mix), var(--background, var(--color-background, white)));
|
|
87
|
+
--notionic-yellow-bg: color-mix(in oklch, var(--notionic-yellow) var(--notionic-bg-mix), var(--background, var(--color-background, white)));
|
|
88
|
+
--notionic-green-bg: color-mix(in oklch, var(--notionic-green) var(--notionic-bg-mix), var(--background, var(--color-background, white)));
|
|
89
|
+
--notionic-blue-bg: color-mix(in oklch, var(--notionic-blue) var(--notionic-bg-mix), var(--background, var(--color-background, white)));
|
|
90
|
+
--notionic-purple-bg: color-mix(in oklch, var(--notionic-purple) var(--notionic-bg-mix), var(--background, var(--color-background, white)));
|
|
91
|
+
--notionic-pink-bg: color-mix(in oklch, var(--notionic-pink) var(--notionic-bg-mix), var(--background, var(--color-background, white)));
|
|
92
|
+
--notionic-red-bg: color-mix(in oklch, var(--notionic-red) var(--notionic-bg-mix), var(--background, var(--color-background, white)));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.dark,
|
|
96
|
+
[data-theme="dark"],
|
|
97
|
+
[data-mode="dark"] {
|
|
98
|
+
--notionic-bg-mix: 22%;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
* Native audio and video controls only turn dark when the page's color-scheme
|
|
103
|
+
* is dark. Under the dark selectors, tell the browser so, or a bright player
|
|
104
|
+
* sits on a dark page.
|
|
105
|
+
*/
|
|
106
|
+
:is(.dark, [data-theme="dark"], [data-mode="dark"]) .notionic :is(audio, video) {
|
|
107
|
+
color-scheme: dark;
|
|
108
|
+
}
|