notionic 0.0.0-stage → 0.8.3

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,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
@@ -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 ? `![${plainText(item.image.caption)}](${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 };
@@ -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
+ }