@notionx/core 0.1.1 → 0.1.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.
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/rate-limit.js.map +1 -1
- package/dist/auth/routes/google-callback.js.map +1 -1
- package/dist/auth/routes/google.js.map +1 -1
- package/dist/auth/routes/index.js.map +1 -1
- package/dist/auth/routes/verify-email.js.map +1 -1
- package/dist/auth/routes/viewer.js.map +1 -1
- package/dist/auth/turnstile.js.map +1 -1
- package/dist/auth/user-session.d.ts +1 -1
- package/dist/auth/user-session.js.map +1 -1
- package/dist/auth/users.js.map +1 -1
- package/dist/content/index.d.ts +3 -2
- package/dist/content/index.js +176 -60
- package/dist/content/index.js.map +1 -1
- package/dist/content/localized.d.ts +67 -0
- package/dist/content/localized.js +170 -0
- package/dist/content/localized.js.map +1 -0
- package/dist/content/revalidate.d.ts +2 -1
- package/dist/content/revalidate.js +5 -28
- package/dist/content/revalidate.js.map +1 -1
- package/dist/content/search-index.d.ts +1 -1
- package/dist/content/search-index.js.map +1 -1
- package/dist/content/search.d.ts +2 -5
- package/dist/content/search.js +3 -32
- package/dist/content/search.js.map +1 -1
- package/dist/email/index.js.map +1 -1
- package/dist/{env-C5qu-0R-.d.ts → env-hoez1e-n.d.ts} +0 -4
- package/dist/i18n/index.d.ts +18 -24
- package/dist/i18n/index.js +29 -54
- package/dist/i18n/index.js.map +1 -1
- package/dist/index.js +0 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/admin/index.js.map +1 -1
- package/dist/media/index.js +3 -2
- package/dist/media/index.js.map +1 -1
- package/dist/media/routes/index.js +0 -1
- package/dist/media/routes/index.js.map +1 -1
- package/dist/media/routes/notion-media.js +0 -1
- package/dist/media/routes/notion-media.js.map +1 -1
- package/dist/notion/config.d.ts +1 -4
- package/dist/notion/config.js +1 -23
- package/dist/notion/config.js.map +1 -1
- package/dist/notion/content-cache.d.ts +1 -1
- package/dist/notion/generic-source.js +0 -1
- package/dist/notion/generic-source.js.map +1 -1
- package/dist/notion/index.d.ts +4 -4
- package/dist/notion/index.js +8 -23
- package/dist/notion/index.js.map +1 -1
- package/dist/notion/mappers.js.map +1 -1
- package/dist/notion/media.d.ts +1 -1
- package/dist/notion/media.js +1 -1
- package/dist/notion/media.js.map +1 -1
- package/dist/notion/property-mappers.d.ts +2 -1
- package/dist/notion/property-mappers.js +7 -0
- package/dist/notion/property-mappers.js.map +1 -1
- package/dist/notion/routes/index.d.ts +1 -1
- package/dist/notion/routes/index.js +0 -1
- package/dist/notion/routes/index.js.map +1 -1
- package/dist/notion/routes/webhook.d.ts +1 -1
- package/dist/notion/routes/webhook.js +0 -1
- package/dist/notion/routes/webhook.js.map +1 -1
- package/dist/notion/types.d.ts +1 -73
- package/dist/notion/webhook.d.ts +1 -1
- package/dist/notion/webhook.js +0 -1
- package/dist/notion/webhook.js.map +1 -1
- package/dist/pages/index.d.ts +117 -0
- package/dist/pages/index.js +487 -0
- package/dist/pages/index.js.map +1 -0
- package/dist/platform/current.d.ts +1 -1
- package/dist/platform/current.js.map +1 -1
- package/dist/platform/index.d.ts +1 -1
- package/dist/platform/index.js.map +1 -1
- package/dist/platform/runtime.d.ts +1 -1
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/routes/cdn.js.map +1 -1
- package/dist/storage/routes/files.js.map +1 -1
- package/dist/storage/routes/index.js.map +1 -1
- package/dist/util/index.d.ts +1 -1
- package/dist/util/index.js +1 -2
- package/dist/util/index.js.map +1 -1
- package/dist/worker/index.js +0 -1
- package/dist/worker/index.js.map +1 -1
- package/dist/worker/routes/content-revalidate.d.ts +1 -1
- package/dist/worker/routes/health.js.map +1 -1
- package/dist/worker/routes/index.d.ts +1 -1
- package/dist/worker/routes/index.js.map +1 -1
- package/package.json +14 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { NotionBlock, NotionPageLike } from '../notion/types.js';
|
|
2
|
+
|
|
3
|
+
type SitePageLayout = "home" | "default" | "legal" | "content-list";
|
|
4
|
+
type SitePageFields = {
|
|
5
|
+
title: string;
|
|
6
|
+
key: string;
|
|
7
|
+
slug: string;
|
|
8
|
+
status: string;
|
|
9
|
+
layout: string;
|
|
10
|
+
description: string;
|
|
11
|
+
seoTitle: string;
|
|
12
|
+
seoDescription: string;
|
|
13
|
+
showHeader: string;
|
|
14
|
+
showFooter: string;
|
|
15
|
+
showInNav: string;
|
|
16
|
+
navLabel: string;
|
|
17
|
+
navOrder: string;
|
|
18
|
+
showInFooter: string;
|
|
19
|
+
footerLabel: string;
|
|
20
|
+
footerGroup: string;
|
|
21
|
+
footerOrder: string;
|
|
22
|
+
contentSource: string;
|
|
23
|
+
cover: string;
|
|
24
|
+
};
|
|
25
|
+
type SitePage = {
|
|
26
|
+
pageId: string;
|
|
27
|
+
key: string;
|
|
28
|
+
slug: string;
|
|
29
|
+
href: string;
|
|
30
|
+
title: string;
|
|
31
|
+
description: string;
|
|
32
|
+
seoTitle: string;
|
|
33
|
+
seoDescription: string;
|
|
34
|
+
layout: SitePageLayout;
|
|
35
|
+
published: boolean;
|
|
36
|
+
showHeader: boolean;
|
|
37
|
+
showFooter: boolean;
|
|
38
|
+
showInNav: boolean;
|
|
39
|
+
navLabel: string;
|
|
40
|
+
navOrder: number;
|
|
41
|
+
showInFooter: boolean;
|
|
42
|
+
footerLabel: string;
|
|
43
|
+
footerGroup: string;
|
|
44
|
+
footerOrder: number;
|
|
45
|
+
contentSource: string;
|
|
46
|
+
coverImage: string | null;
|
|
47
|
+
editUrl: string | null;
|
|
48
|
+
blocks: NotionBlock[];
|
|
49
|
+
};
|
|
50
|
+
type SitePageNavItem = {
|
|
51
|
+
label: string;
|
|
52
|
+
href: string;
|
|
53
|
+
order: number;
|
|
54
|
+
pageKey: string;
|
|
55
|
+
};
|
|
56
|
+
type SitePageFooterGroup = {
|
|
57
|
+
label: string;
|
|
58
|
+
items: SitePageNavItem[];
|
|
59
|
+
};
|
|
60
|
+
type SitePageModel = {
|
|
61
|
+
source: {
|
|
62
|
+
tokenEnv: string;
|
|
63
|
+
dataSourceEnv: string;
|
|
64
|
+
defaultDataSourceId?: string;
|
|
65
|
+
fields?: Partial<SitePageFields>;
|
|
66
|
+
query?: {
|
|
67
|
+
pageSize?: number;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
type DefinedSitePageModel = SitePageModel & {
|
|
72
|
+
source: Omit<SitePageModel["source"], "fields" | "query"> & {
|
|
73
|
+
fields: SitePageFields;
|
|
74
|
+
query: {
|
|
75
|
+
pageSize: number;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
type SitePageSourceDeps = {
|
|
80
|
+
queryDataSource: (input?: {
|
|
81
|
+
startCursor?: string;
|
|
82
|
+
}) => Promise<{
|
|
83
|
+
results?: unknown[];
|
|
84
|
+
has_more?: boolean;
|
|
85
|
+
next_cursor?: string | null;
|
|
86
|
+
}>;
|
|
87
|
+
getPageBlocks: (pageId: string) => Promise<NotionBlock[]>;
|
|
88
|
+
editBaseUrl?: string;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
declare const defaultSitePageFields: SitePageFields;
|
|
92
|
+
declare const defaultPagesDataSourceEnv = "NOTION_PAGES_DATA_SOURCE_ID";
|
|
93
|
+
declare function defineSitePageModel(model: SitePageModel): DefinedSitePageModel;
|
|
94
|
+
|
|
95
|
+
declare function slugToHref(slug: string): string;
|
|
96
|
+
declare function normalizePageSlug(slug: string): string;
|
|
97
|
+
declare function mapNotionPageToSitePage(page: NotionPageLike, fields: SitePageFields, blocks?: NotionBlock[], options?: {
|
|
98
|
+
editBaseUrl?: string;
|
|
99
|
+
}): SitePage | null;
|
|
100
|
+
declare function createSitePageSource(modelInput: SitePageModel, deps: SitePageSourceDeps): {
|
|
101
|
+
listPages(): Promise<SitePage[]>;
|
|
102
|
+
};
|
|
103
|
+
declare function createSitePagesApi(input: {
|
|
104
|
+
model: SitePageModel;
|
|
105
|
+
fallbackPages?: readonly SitePage[];
|
|
106
|
+
}): {
|
|
107
|
+
listSitePages: () => Promise<SitePage[]>;
|
|
108
|
+
getSitePageByKey(key: string): Promise<SitePage | null>;
|
|
109
|
+
getSitePageBySlug(slug: string): Promise<SitePage | null>;
|
|
110
|
+
getSitePageForContentSource(sourceId: string): Promise<SitePage | null>;
|
|
111
|
+
getSiteNavigation(): Promise<SitePageNavItem[]>;
|
|
112
|
+
getSiteFooterGroups(): Promise<SitePageFooterGroup[]>;
|
|
113
|
+
};
|
|
114
|
+
declare function deriveSiteNavigation(pages: readonly SitePage[]): SitePageNavItem[];
|
|
115
|
+
declare function deriveSiteFooterGroups(pages: readonly SitePage[]): SitePageFooterGroup[];
|
|
116
|
+
|
|
117
|
+
export { type DefinedSitePageModel, type SitePage, type SitePageFields, type SitePageFooterGroup, type SitePageLayout, type SitePageModel, type SitePageNavItem, type SitePageSourceDeps, createSitePageSource, createSitePagesApi, defaultPagesDataSourceEnv, defaultSitePageFields, defineSitePageModel, deriveSiteFooterGroups, deriveSiteNavigation, mapNotionPageToSitePage, normalizePageSlug, slugToHref };
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
// src/pages/model.ts
|
|
2
|
+
var defaultSitePageFields = {
|
|
3
|
+
title: "Name",
|
|
4
|
+
key: "Key",
|
|
5
|
+
slug: "Slug",
|
|
6
|
+
status: "Status",
|
|
7
|
+
layout: "Layout",
|
|
8
|
+
description: "Description",
|
|
9
|
+
seoTitle: "SEO Title",
|
|
10
|
+
seoDescription: "SEO Description",
|
|
11
|
+
showHeader: "Show Header",
|
|
12
|
+
showFooter: "Show Footer",
|
|
13
|
+
showInNav: "Show in Nav",
|
|
14
|
+
navLabel: "Nav Label",
|
|
15
|
+
navOrder: "Nav Order",
|
|
16
|
+
showInFooter: "Show in Footer",
|
|
17
|
+
footerLabel: "Footer Label",
|
|
18
|
+
footerGroup: "Footer Group",
|
|
19
|
+
footerOrder: "Footer Order",
|
|
20
|
+
contentSource: "Content Source",
|
|
21
|
+
cover: "Cover"
|
|
22
|
+
};
|
|
23
|
+
var defaultPagesDataSourceEnv = "NOTION_PAGES_DATA_SOURCE_ID";
|
|
24
|
+
function defineSitePageModel(model) {
|
|
25
|
+
return {
|
|
26
|
+
...model,
|
|
27
|
+
source: {
|
|
28
|
+
...model.source,
|
|
29
|
+
fields: {
|
|
30
|
+
...defaultSitePageFields,
|
|
31
|
+
...model.source.fields ?? {}
|
|
32
|
+
},
|
|
33
|
+
query: {
|
|
34
|
+
pageSize: 100,
|
|
35
|
+
...model.source.query ?? {}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/pages/source.ts
|
|
42
|
+
import { cache } from "react";
|
|
43
|
+
|
|
44
|
+
// src/notion/blocks.ts
|
|
45
|
+
function normalizeBlock(input) {
|
|
46
|
+
if (!input || typeof input !== "object") return null;
|
|
47
|
+
const block = input;
|
|
48
|
+
return block.id && block.type ? block : null;
|
|
49
|
+
}
|
|
50
|
+
async function listBlockChildren(client, blockId) {
|
|
51
|
+
const results = [];
|
|
52
|
+
let cursor;
|
|
53
|
+
do {
|
|
54
|
+
const response = await client.blocks.children.list({
|
|
55
|
+
block_id: blockId,
|
|
56
|
+
page_size: 100,
|
|
57
|
+
...cursor ? { start_cursor: cursor } : {}
|
|
58
|
+
});
|
|
59
|
+
for (const item of response.results ?? []) {
|
|
60
|
+
const block = normalizeBlock(item);
|
|
61
|
+
if (block) results.push(block);
|
|
62
|
+
}
|
|
63
|
+
cursor = response.next_cursor ?? void 0;
|
|
64
|
+
if (!response.has_more) break;
|
|
65
|
+
} while (cursor);
|
|
66
|
+
return results;
|
|
67
|
+
}
|
|
68
|
+
async function listBlockChildrenDeep(client, blockId, options) {
|
|
69
|
+
const maxDepth = options?.maxDepth ?? 6;
|
|
70
|
+
async function visit(id, depth) {
|
|
71
|
+
const children = await listBlockChildren(client, id);
|
|
72
|
+
if (depth >= maxDepth) return children;
|
|
73
|
+
return Promise.all(
|
|
74
|
+
children.map(async (block) => {
|
|
75
|
+
if (!block.has_children) return block;
|
|
76
|
+
return {
|
|
77
|
+
...block,
|
|
78
|
+
children: await visit(block.id, depth + 1)
|
|
79
|
+
};
|
|
80
|
+
})
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return visit(blockId, 0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/notion/client.ts
|
|
87
|
+
import { Client } from "@notionhq/client";
|
|
88
|
+
function createNotionClient(config) {
|
|
89
|
+
return new Client({
|
|
90
|
+
auth: config.token,
|
|
91
|
+
baseUrl: config.apiBaseUrl,
|
|
92
|
+
notionVersion: "2026-03-11"
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/notion/config.ts
|
|
97
|
+
function readProcessEnv() {
|
|
98
|
+
const env = {
|
|
99
|
+
NOTION_TOKEN: process.env.NOTION_TOKEN,
|
|
100
|
+
NOTION_DATA_SOURCE_ID: process.env.NOTION_DATA_SOURCE_ID,
|
|
101
|
+
NOTION_API_BASE_URL: process.env.NOTION_API_BASE_URL,
|
|
102
|
+
NOTION_EDIT_BASE_URL: process.env.NOTION_EDIT_BASE_URL,
|
|
103
|
+
NOTION_WEBHOOK_VERIFICATION_TOKEN: process.env.NOTION_WEBHOOK_VERIFICATION_TOKEN
|
|
104
|
+
};
|
|
105
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
106
|
+
if (key.startsWith("NOTION_") && typeof value === "string") {
|
|
107
|
+
env[key] = value;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return env;
|
|
111
|
+
}
|
|
112
|
+
async function readWorkerEnv() {
|
|
113
|
+
try {
|
|
114
|
+
const mod = await import(
|
|
115
|
+
/* webpackIgnore: true */
|
|
116
|
+
"cloudflare:workers"
|
|
117
|
+
);
|
|
118
|
+
const env = {};
|
|
119
|
+
for (const [key, value] of Object.entries(mod.env ?? {})) {
|
|
120
|
+
if (key.startsWith("NOTION_") && typeof value === "string") {
|
|
121
|
+
env[key] = value;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return env;
|
|
125
|
+
} catch {
|
|
126
|
+
return {};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function readString(source, name) {
|
|
130
|
+
const value = String(source[name] ?? "").trim();
|
|
131
|
+
return value || void 0;
|
|
132
|
+
}
|
|
133
|
+
function mergeEnv(...sources) {
|
|
134
|
+
const merged = {};
|
|
135
|
+
for (const source of sources) {
|
|
136
|
+
for (const name of Object.keys(source)) {
|
|
137
|
+
if (!name.startsWith("NOTION_")) continue;
|
|
138
|
+
const value = readString(source, name);
|
|
139
|
+
if (value) merged[name] = value;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return merged;
|
|
143
|
+
}
|
|
144
|
+
async function readEnv() {
|
|
145
|
+
const processEnv = readProcessEnv();
|
|
146
|
+
return mergeEnv(await readWorkerEnv(), processEnv);
|
|
147
|
+
}
|
|
148
|
+
function readRequired(source, name) {
|
|
149
|
+
const value = readString(source, name);
|
|
150
|
+
if (!value) {
|
|
151
|
+
throw new Error(`Missing required Notion env: ${name}`);
|
|
152
|
+
}
|
|
153
|
+
return value;
|
|
154
|
+
}
|
|
155
|
+
async function hasNotionModelConfig(model) {
|
|
156
|
+
const env = await readEnv();
|
|
157
|
+
return Boolean(
|
|
158
|
+
readString(env, "NOTION_TOKEN") && (readString(env, model.source.dataSourceEnv) || model.source.defaultDataSourceId)
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
async function getNotionConfigForModel(model) {
|
|
162
|
+
const env = await readEnv();
|
|
163
|
+
const dataSourceId = readString(env, model.source.dataSourceEnv) ?? model.source.defaultDataSourceId;
|
|
164
|
+
if (!dataSourceId) {
|
|
165
|
+
throw new Error(`Missing required Notion env: ${model.source.dataSourceEnv}`);
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
token: readRequired(env, model.source.tokenEnv),
|
|
169
|
+
dataSourceId,
|
|
170
|
+
apiBaseUrl: readString(env, "NOTION_API_BASE_URL"),
|
|
171
|
+
editBaseUrl: readString(env, "NOTION_EDIT_BASE_URL"),
|
|
172
|
+
webhookVerificationToken: readString(
|
|
173
|
+
env,
|
|
174
|
+
"NOTION_WEBHOOK_VERIFICATION_TOKEN"
|
|
175
|
+
)
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// src/notion/media.ts
|
|
180
|
+
function stripLeadingSlash(value) {
|
|
181
|
+
return value.startsWith("/") ? value.slice(1) : value;
|
|
182
|
+
}
|
|
183
|
+
function encodePathPart(value) {
|
|
184
|
+
return encodeURIComponent(stripLeadingSlash(value));
|
|
185
|
+
}
|
|
186
|
+
function appendVersion(path, version) {
|
|
187
|
+
const value = String(version ?? "").trim();
|
|
188
|
+
if (!value) return path;
|
|
189
|
+
return `${path}?${new URLSearchParams({ v: value })}`;
|
|
190
|
+
}
|
|
191
|
+
function notionPageCoverMediaPath(pageId) {
|
|
192
|
+
return `/api/notion/media/page/${encodePathPart(pageId)}/cover`;
|
|
193
|
+
}
|
|
194
|
+
function notionPagePropertyMediaPath(pageId, propertyName) {
|
|
195
|
+
return `/api/notion/media/page/${encodePathPart(pageId)}/property/${encodePathPart(propertyName)}`;
|
|
196
|
+
}
|
|
197
|
+
function normalizeNotionFileSource(input) {
|
|
198
|
+
const file = input;
|
|
199
|
+
if (!file || typeof file !== "object") return null;
|
|
200
|
+
if (file.type === "external") {
|
|
201
|
+
const url = String(file.external?.url ?? "").trim();
|
|
202
|
+
return url ? { type: "external", url } : null;
|
|
203
|
+
}
|
|
204
|
+
if (file.type === "file") {
|
|
205
|
+
const url = String(file.file?.url ?? "").trim();
|
|
206
|
+
if (!url) return null;
|
|
207
|
+
return {
|
|
208
|
+
type: "file",
|
|
209
|
+
url,
|
|
210
|
+
expiryTime: String(file.file?.expiry_time ?? "").trim() || null
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
function pickFirstFilesPropertyValue(property) {
|
|
216
|
+
const value = property;
|
|
217
|
+
if (!value || value.type !== "files" || !Array.isArray(value.files)) {
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
return value.files[0] ?? null;
|
|
221
|
+
}
|
|
222
|
+
function pickPageCoverFile(page) {
|
|
223
|
+
return page.cover ?? null;
|
|
224
|
+
}
|
|
225
|
+
function coverImageUrlForPage(page, coverPropertyName = "Cover") {
|
|
226
|
+
const propertyFile = pickFirstFilesPropertyValue(
|
|
227
|
+
page.properties?.[coverPropertyName]
|
|
228
|
+
);
|
|
229
|
+
const propertySource = normalizeNotionFileSource(propertyFile);
|
|
230
|
+
if (propertySource) {
|
|
231
|
+
return appendVersion(
|
|
232
|
+
notionPagePropertyMediaPath(page.id, coverPropertyName),
|
|
233
|
+
page.last_edited_time
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
const coverSource = normalizeNotionFileSource(pickPageCoverFile(page));
|
|
237
|
+
if (coverSource) {
|
|
238
|
+
return appendVersion(notionPageCoverMediaPath(page.id), page.last_edited_time);
|
|
239
|
+
}
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/notion/property-mappers.ts
|
|
244
|
+
function isRecord(value) {
|
|
245
|
+
return Boolean(value && typeof value === "object");
|
|
246
|
+
}
|
|
247
|
+
function getPlainText(parts) {
|
|
248
|
+
if (!Array.isArray(parts)) return "";
|
|
249
|
+
return parts.map((part) => part.plain_text ?? "").join("").trim();
|
|
250
|
+
}
|
|
251
|
+
function getProperty(properties, key) {
|
|
252
|
+
return properties[key];
|
|
253
|
+
}
|
|
254
|
+
function getRichTextProperty(properties, key) {
|
|
255
|
+
const property = getProperty(properties, key);
|
|
256
|
+
if (!property) return "";
|
|
257
|
+
if (property.type === "title") return getPlainText(property.title);
|
|
258
|
+
if (property.type === "rich_text") return getPlainText(property.rich_text);
|
|
259
|
+
if (property.type === "url") return String(property.url ?? "").trim();
|
|
260
|
+
if (property.type === "email") return String(property.email ?? "").trim();
|
|
261
|
+
if (property.type === "phone_number") {
|
|
262
|
+
return String(property.phone_number ?? "").trim();
|
|
263
|
+
}
|
|
264
|
+
return "";
|
|
265
|
+
}
|
|
266
|
+
function getSelectProperty(properties, key) {
|
|
267
|
+
const property = getProperty(properties, key);
|
|
268
|
+
if (property?.type !== "select") return "";
|
|
269
|
+
const select = property.select;
|
|
270
|
+
return String(select?.name ?? "").trim();
|
|
271
|
+
}
|
|
272
|
+
function getCheckboxProperty(properties, key) {
|
|
273
|
+
const property = getProperty(properties, key);
|
|
274
|
+
if (property?.type !== "checkbox") return false;
|
|
275
|
+
return Boolean(property.checkbox);
|
|
276
|
+
}
|
|
277
|
+
function getNumberProperty(properties, key, fallback = 0) {
|
|
278
|
+
const property = getProperty(properties, key);
|
|
279
|
+
if (property?.type !== "number") return fallback;
|
|
280
|
+
const value = Number(property.number);
|
|
281
|
+
return Number.isFinite(value) ? value : fallback;
|
|
282
|
+
}
|
|
283
|
+
function notionPageEditUrl(pageId, editBaseUrl) {
|
|
284
|
+
const compactPageId = pageId.replaceAll("-", "");
|
|
285
|
+
if (editBaseUrl?.includes("{pageId}")) {
|
|
286
|
+
return editBaseUrl.replaceAll("{pageId}", compactPageId);
|
|
287
|
+
}
|
|
288
|
+
return `https://www.notion.so/${compactPageId}`;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// src/pages/source.ts
|
|
292
|
+
function normalizePage(input) {
|
|
293
|
+
if (!input || typeof input !== "object") return null;
|
|
294
|
+
const page = input;
|
|
295
|
+
return page.id ? page : null;
|
|
296
|
+
}
|
|
297
|
+
function slugToHref(slug) {
|
|
298
|
+
const normalized = slug.trim().replace(/^\/+|\/+$/g, "");
|
|
299
|
+
return normalized ? `/${normalized}` : "/";
|
|
300
|
+
}
|
|
301
|
+
function normalizePageSlug(slug) {
|
|
302
|
+
return slug.trim().replace(/^\/+|\/+$/g, "").toLowerCase();
|
|
303
|
+
}
|
|
304
|
+
function isPublishedStatus(value) {
|
|
305
|
+
return value.trim().toLowerCase() === "published";
|
|
306
|
+
}
|
|
307
|
+
function getCheckboxPropertyWithFallback(properties, key, fallback) {
|
|
308
|
+
const property = properties[key];
|
|
309
|
+
if (property?.type !== "checkbox") return fallback;
|
|
310
|
+
return getCheckboxProperty(properties, key);
|
|
311
|
+
}
|
|
312
|
+
function normalizeLayout(value) {
|
|
313
|
+
const normalized = value.trim().toLowerCase();
|
|
314
|
+
if (normalized === "home") return "home";
|
|
315
|
+
if (normalized === "legal") return "legal";
|
|
316
|
+
if (normalized === "content-list") return "content-list";
|
|
317
|
+
return "default";
|
|
318
|
+
}
|
|
319
|
+
function mapNotionPageToSitePage(page, fields, blocks = [], options) {
|
|
320
|
+
const properties = isRecord(page.properties) ? page.properties : {};
|
|
321
|
+
const title = getRichTextProperty(properties, fields.title);
|
|
322
|
+
const key = getRichTextProperty(properties, fields.key).toLowerCase();
|
|
323
|
+
const slug = normalizePageSlug(getRichTextProperty(properties, fields.slug));
|
|
324
|
+
const status = getSelectProperty(properties, fields.status);
|
|
325
|
+
if (!title || !key || !isPublishedStatus(status)) return null;
|
|
326
|
+
const description = getRichTextProperty(properties, fields.description);
|
|
327
|
+
const seoTitle = getRichTextProperty(properties, fields.seoTitle) || title;
|
|
328
|
+
const seoDescription = getRichTextProperty(properties, fields.seoDescription) || description;
|
|
329
|
+
const navLabel = getRichTextProperty(properties, fields.navLabel) || title;
|
|
330
|
+
const footerLabel = getRichTextProperty(properties, fields.footerLabel) || navLabel;
|
|
331
|
+
return {
|
|
332
|
+
pageId: page.id,
|
|
333
|
+
key,
|
|
334
|
+
slug,
|
|
335
|
+
href: slugToHref(slug),
|
|
336
|
+
title,
|
|
337
|
+
description,
|
|
338
|
+
seoTitle,
|
|
339
|
+
seoDescription,
|
|
340
|
+
layout: normalizeLayout(getSelectProperty(properties, fields.layout)),
|
|
341
|
+
published: true,
|
|
342
|
+
showHeader: getCheckboxPropertyWithFallback(
|
|
343
|
+
properties,
|
|
344
|
+
fields.showHeader,
|
|
345
|
+
true
|
|
346
|
+
),
|
|
347
|
+
showFooter: getCheckboxPropertyWithFallback(
|
|
348
|
+
properties,
|
|
349
|
+
fields.showFooter,
|
|
350
|
+
true
|
|
351
|
+
),
|
|
352
|
+
showInNav: getCheckboxProperty(properties, fields.showInNav),
|
|
353
|
+
navLabel,
|
|
354
|
+
navOrder: getNumberProperty(properties, fields.navOrder, 100),
|
|
355
|
+
showInFooter: getCheckboxProperty(properties, fields.showInFooter),
|
|
356
|
+
footerLabel,
|
|
357
|
+
footerGroup: getSelectProperty(properties, fields.footerGroup) || "Company",
|
|
358
|
+
footerOrder: getNumberProperty(properties, fields.footerOrder, 100),
|
|
359
|
+
contentSource: getRichTextProperty(properties, fields.contentSource),
|
|
360
|
+
coverImage: coverImageUrlForPage(page, fields.cover),
|
|
361
|
+
editUrl: notionPageEditUrl(page.id, options?.editBaseUrl),
|
|
362
|
+
blocks
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function createSitePageSource(modelInput, deps) {
|
|
366
|
+
const model = defineSitePageModel(modelInput);
|
|
367
|
+
const fields = model.source.fields;
|
|
368
|
+
return {
|
|
369
|
+
async listPages() {
|
|
370
|
+
const pages = [];
|
|
371
|
+
let cursor;
|
|
372
|
+
do {
|
|
373
|
+
const response = await deps.queryDataSource({ startCursor: cursor });
|
|
374
|
+
for (const item of response.results ?? []) {
|
|
375
|
+
const page = normalizePage(item);
|
|
376
|
+
if (!page) continue;
|
|
377
|
+
const mapped = mapNotionPageToSitePage(
|
|
378
|
+
page,
|
|
379
|
+
fields,
|
|
380
|
+
await deps.getPageBlocks(page.id),
|
|
381
|
+
{ editBaseUrl: deps.editBaseUrl }
|
|
382
|
+
);
|
|
383
|
+
if (mapped) pages.push(mapped);
|
|
384
|
+
}
|
|
385
|
+
cursor = response.next_cursor ?? void 0;
|
|
386
|
+
if (!response.has_more) break;
|
|
387
|
+
} while (cursor);
|
|
388
|
+
return pages.sort((a, b) => a.navOrder - b.navOrder || a.title.localeCompare(b.title));
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
async function createDefaultSitePageSource(modelInput) {
|
|
393
|
+
const model = defineSitePageModel(modelInput);
|
|
394
|
+
if (!await hasNotionModelConfig(model)) return null;
|
|
395
|
+
const config = await getNotionConfigForModel(model);
|
|
396
|
+
const client = createNotionClient(config);
|
|
397
|
+
return createSitePageSource(model, {
|
|
398
|
+
editBaseUrl: config.editBaseUrl,
|
|
399
|
+
queryDataSource: async ({ startCursor } = {}) => client.dataSources.query({
|
|
400
|
+
data_source_id: config.dataSourceId,
|
|
401
|
+
page_size: model.source.query.pageSize,
|
|
402
|
+
sorts: [{ property: model.source.fields.navOrder, direction: "ascending" }],
|
|
403
|
+
...startCursor ? { start_cursor: startCursor } : {}
|
|
404
|
+
}),
|
|
405
|
+
getPageBlocks: (pageId) => listBlockChildrenDeep(client, pageId)
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
var defaultSourceCache = cache(createDefaultSitePageSource);
|
|
409
|
+
function createSitePagesApi(input) {
|
|
410
|
+
const fallbackPages = [...input.fallbackPages ?? []];
|
|
411
|
+
const listSitePages = cache(async () => {
|
|
412
|
+
try {
|
|
413
|
+
const source = await defaultSourceCache(input.model);
|
|
414
|
+
const pages = source ? await source.listPages() : [];
|
|
415
|
+
return pages.length ? pages : fallbackPages;
|
|
416
|
+
} catch {
|
|
417
|
+
return fallbackPages;
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
return {
|
|
421
|
+
listSitePages,
|
|
422
|
+
async getSitePageByKey(key) {
|
|
423
|
+
const pages = await listSitePages();
|
|
424
|
+
return pages.find((page) => page.key === key.toLowerCase()) ?? null;
|
|
425
|
+
},
|
|
426
|
+
async getSitePageBySlug(slug) {
|
|
427
|
+
const normalized = normalizePageSlug(slug);
|
|
428
|
+
const pages = await listSitePages();
|
|
429
|
+
return pages.find((page) => page.slug === normalized) ?? null;
|
|
430
|
+
},
|
|
431
|
+
async getSitePageForContentSource(sourceId) {
|
|
432
|
+
const pages = await listSitePages();
|
|
433
|
+
return pages.find(
|
|
434
|
+
(page) => page.layout === "content-list" && page.contentSource === sourceId
|
|
435
|
+
) ?? null;
|
|
436
|
+
},
|
|
437
|
+
async getSiteNavigation() {
|
|
438
|
+
const pages = await listSitePages();
|
|
439
|
+
return deriveSiteNavigation(pages);
|
|
440
|
+
},
|
|
441
|
+
async getSiteFooterGroups() {
|
|
442
|
+
const pages = await listSitePages();
|
|
443
|
+
return deriveSiteFooterGroups(pages);
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
function deriveSiteNavigation(pages) {
|
|
448
|
+
return pages.filter((page) => page.showInNav).map((page) => ({
|
|
449
|
+
label: page.navLabel,
|
|
450
|
+
href: page.href,
|
|
451
|
+
order: page.navOrder,
|
|
452
|
+
pageKey: page.key
|
|
453
|
+
})).sort((a, b) => a.order - b.order || a.label.localeCompare(b.label));
|
|
454
|
+
}
|
|
455
|
+
function deriveSiteFooterGroups(pages) {
|
|
456
|
+
const groups = /* @__PURE__ */ new Map();
|
|
457
|
+
for (const page of pages.filter((candidate) => candidate.showInFooter)) {
|
|
458
|
+
const label = page.footerGroup || "Company";
|
|
459
|
+
const items = groups.get(label) ?? [];
|
|
460
|
+
items.push({
|
|
461
|
+
label: page.footerLabel,
|
|
462
|
+
href: page.href,
|
|
463
|
+
order: page.footerOrder,
|
|
464
|
+
pageKey: page.key
|
|
465
|
+
});
|
|
466
|
+
groups.set(label, items);
|
|
467
|
+
}
|
|
468
|
+
return Array.from(groups.entries()).map(([label, items]) => ({
|
|
469
|
+
label,
|
|
470
|
+
items: items.sort(
|
|
471
|
+
(a, b) => a.order - b.order || a.label.localeCompare(b.label)
|
|
472
|
+
)
|
|
473
|
+
}));
|
|
474
|
+
}
|
|
475
|
+
export {
|
|
476
|
+
createSitePageSource,
|
|
477
|
+
createSitePagesApi,
|
|
478
|
+
defaultPagesDataSourceEnv,
|
|
479
|
+
defaultSitePageFields,
|
|
480
|
+
defineSitePageModel,
|
|
481
|
+
deriveSiteFooterGroups,
|
|
482
|
+
deriveSiteNavigation,
|
|
483
|
+
mapNotionPageToSitePage,
|
|
484
|
+
normalizePageSlug,
|
|
485
|
+
slugToHref
|
|
486
|
+
};
|
|
487
|
+
//# sourceMappingURL=index.js.map
|