@notionx/create-notionx-app 1.0.0 → 2.0.0
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/cli-notionx.js +25 -1
- package/dist/cli-notionx.js.map +1 -1
- package/dist/locale-add/persist.js +113 -0
- package/dist/locale-add/persist.js.map +1 -0
- package/dist/locale-add/plan.js +202 -21
- package/dist/locale-add/plan.js.map +1 -1
- package/dist/metadata.js.map +1 -1
- package/dist/notion-translation-sources/apply.js +11 -26
- package/dist/notion-translation-sources/apply.js.map +1 -1
- package/dist/notion-translation-sources/plan.js +25 -0
- package/dist/notion-translation-sources/plan.js.map +1 -1
- package/dist/provision/__tests__/translation-properties.test.js +86 -0
- package/dist/provision/__tests__/translation-properties.test.js.map +1 -0
- package/dist/provision/credentials.js +67 -0
- package/dist/provision/credentials.js.map +1 -0
- package/dist/provision/index.js +188 -11
- package/dist/provision/index.js.map +1 -1
- package/dist/provision/notion.js +422 -269
- package/dist/provision/notion.js.map +1 -1
- package/dist/provision/notion.test.js +143 -116
- package/dist/provision/notion.test.js.map +1 -1
- package/dist/provision/wire.js +16 -0
- package/dist/provision/wire.js.map +1 -1
- package/dist/registry/install.test.js +2 -0
- package/dist/registry/install.test.js.map +1 -1
- package/dist/registry/project-meta.js +4 -2
- package/dist/registry/project-meta.js.map +1 -1
- package/dist/registry/registry-types.js.map +1 -1
- package/dist/registry/render-content-source-files.js +1 -0
- package/dist/registry/render-content-source-files.js.map +1 -1
- package/dist/registry/render-multi-source.js +72 -28
- package/dist/registry/render-multi-source.js.map +1 -1
- package/dist/registry/update.test.js +2 -0
- package/dist/registry/update.test.js.map +1 -1
- package/dist/render.js +2 -0
- package/dist/render.js.map +1 -1
- package/dist/render.test.js +18 -12
- package/dist/render.test.js.map +1 -1
- package/dist/templates/.dev.vars.example.tmpl +4 -0
- package/dist/templates/__tests__/middleware-integration.test.ts +58 -0
- package/dist/templates/app/{{contentSourceListPath}}/[slug]/page.tsx.tmpl +8 -4
- package/dist/templates/app/{{contentSourceListPath}}/page.tsx.tmpl +8 -4
- package/dist/templates/components/page-blocks/feature-grid-block.tsx.tmpl +4 -56
- package/dist/templates/components/page-blocks/hero-block.tsx.tmpl +6 -67
- package/dist/templates/components/page-blocks/latest-posts-block.tsx.tmpl +11 -19
- package/dist/templates/components/page-blocks/story-block.tsx.tmpl +4 -62
- package/dist/templates/components/page-blocks.tsx.tmpl +5 -5
- package/dist/templates/components/site/site-header.tsx.tmpl +5 -3
- package/dist/templates/env.d.ts.tmpl +8 -0
- package/dist/templates/lib/blocks/translations.ts.tmpl +22 -1
- package/dist/templates/lib/blog/translations.ts.tmpl +18 -2
- package/dist/templates/lib/content/models.ts.tmpl +6 -0
- package/dist/templates/lib/pages/source.ts.tmpl +136 -334
- package/dist/templates/lib/pages/translations.ts.tmpl +23 -1
- package/dist/templates/lib/site/request-env.ts.tmpl +16 -0
- package/dist/templates/lib/site/settings.ts.tmpl +96 -179
- package/dist/templates/lib/site/translations.ts.tmpl +34 -11
- package/dist/templates/middleware.ts.tmpl +56 -0
- package/dist/templates/worker/index.ts.tmpl +14 -5
- package/dist/templates/wrangler.jsonc.tmpl +5 -1
- package/package.json +1 -1
package/dist/provision/notion.js
CHANGED
|
@@ -563,133 +563,239 @@ function sampleBlocks(input) {
|
|
|
563
563
|
if (input.locale?.toLowerCase().startsWith("zh")) {
|
|
564
564
|
return [
|
|
565
565
|
{
|
|
566
|
-
|
|
566
|
+
name: "首页 Hero",
|
|
567
567
|
slug: "home-hero",
|
|
568
568
|
type: "hero",
|
|
569
|
-
description: "首页顶部主视觉区块,适合放标题、副标题与主行动按钮。",
|
|
570
|
-
pageKeys: ["home"],
|
|
571
569
|
order: 10,
|
|
572
570
|
coverSeed: "home-hero-zh",
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
571
|
+
children: [
|
|
572
|
+
{
|
|
573
|
+
type: "heading_1",
|
|
574
|
+
heading_1: {
|
|
575
|
+
rich_text: [{ text: { content: "从一个可以持续编辑的首页开始" } }],
|
|
576
|
+
},
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
type: "paragraph",
|
|
580
|
+
paragraph: {
|
|
581
|
+
rich_text: [
|
|
582
|
+
{
|
|
583
|
+
text: {
|
|
584
|
+
content: "把首页的一句话价值、介绍文案和主行动按钮交给 Notion,站点布局继续由代码稳定控制。",
|
|
585
|
+
},
|
|
586
|
+
},
|
|
587
|
+
],
|
|
588
|
+
},
|
|
589
|
+
},
|
|
590
|
+
{
|
|
591
|
+
type: "callout",
|
|
592
|
+
callout: {
|
|
593
|
+
rich_text: [
|
|
594
|
+
{ text: { content: "CTA: 查看内容列表 → /blog" } },
|
|
595
|
+
],
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
],
|
|
582
599
|
},
|
|
583
600
|
{
|
|
584
|
-
|
|
601
|
+
name: "首页功能展示",
|
|
585
602
|
slug: "home-feature-grid",
|
|
586
603
|
type: "feature-grid",
|
|
587
|
-
description: "用于首页中段的功能/能力展示区块。",
|
|
588
|
-
pageKeys: ["home"],
|
|
589
604
|
order: 20,
|
|
590
605
|
coverSeed: "home-feature-grid-zh",
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
606
|
+
children: [
|
|
607
|
+
{
|
|
608
|
+
type: "heading_2",
|
|
609
|
+
heading_2: {
|
|
610
|
+
rich_text: [
|
|
611
|
+
{ text: { content: "把内容、运行时和发布流程串成一个清晰系统" } },
|
|
612
|
+
],
|
|
613
|
+
},
|
|
614
|
+
},
|
|
595
615
|
{
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
616
|
+
type: "paragraph",
|
|
617
|
+
paragraph: {
|
|
618
|
+
rich_text: [
|
|
619
|
+
{
|
|
620
|
+
text: {
|
|
621
|
+
content: "这个区块默认用三列卡片展示项目能力,适合介绍内容工作流、部署基础设施和持续发布能力。",
|
|
622
|
+
},
|
|
623
|
+
},
|
|
624
|
+
],
|
|
625
|
+
},
|
|
600
626
|
},
|
|
601
627
|
{
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
628
|
+
type: "bulleted_list_item",
|
|
629
|
+
bulleted_list_item: {
|
|
630
|
+
rich_text: [
|
|
631
|
+
{ text: { content: "内容编辑:让编辑直接在 Notion 中维护页面与内容。" } },
|
|
632
|
+
],
|
|
633
|
+
},
|
|
605
634
|
},
|
|
606
635
|
{
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
636
|
+
type: "bulleted_list_item",
|
|
637
|
+
bulleted_list_item: {
|
|
638
|
+
rich_text: [
|
|
639
|
+
{ text: { content: "云端运行:基于 Cloudflare Workers、D1 和 KV 提供轻量稳定的运行时。" } },
|
|
640
|
+
],
|
|
641
|
+
},
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
type: "bulleted_list_item",
|
|
645
|
+
bulleted_list_item: {
|
|
646
|
+
rich_text: [
|
|
647
|
+
{ text: { content: `持续更新:${input.contentSourceTitle} 列表可以持续发布新内容。` } },
|
|
648
|
+
],
|
|
649
|
+
},
|
|
611
650
|
},
|
|
612
651
|
],
|
|
613
652
|
},
|
|
614
653
|
{
|
|
615
|
-
|
|
654
|
+
name: "首页最新文章",
|
|
616
655
|
slug: "home-latest-posts",
|
|
617
656
|
type: "latest-posts",
|
|
618
|
-
description: "在首页展示最近发布内容的文章卡片区块。",
|
|
619
|
-
pageKeys: ["home"],
|
|
620
657
|
order: 30,
|
|
621
658
|
coverSeed: "home-latest-posts-zh",
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
659
|
+
children: [
|
|
660
|
+
{
|
|
661
|
+
type: "heading_2",
|
|
662
|
+
heading_2: {
|
|
663
|
+
rich_text: [{ text: { content: "看看最近更新了什么" } }],
|
|
664
|
+
},
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
type: "paragraph",
|
|
668
|
+
paragraph: {
|
|
669
|
+
rich_text: [
|
|
670
|
+
{
|
|
671
|
+
text: {
|
|
672
|
+
content: "默认展示最新发布的 6 篇内容,既能丰富首页,也能直接验证博客内容链路是否生效。",
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
],
|
|
676
|
+
},
|
|
677
|
+
},
|
|
678
|
+
],
|
|
627
679
|
},
|
|
628
680
|
];
|
|
629
681
|
}
|
|
630
682
|
return [
|
|
631
683
|
{
|
|
632
|
-
|
|
684
|
+
name: "Homepage Hero",
|
|
633
685
|
slug: "home-hero",
|
|
634
686
|
type: "hero",
|
|
635
|
-
description: "Homepage hero module for headline, supporting copy, and primary CTA.",
|
|
636
|
-
pageKeys: ["home"],
|
|
637
687
|
order: 10,
|
|
638
688
|
coverSeed: "home-hero",
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
689
|
+
children: [
|
|
690
|
+
{
|
|
691
|
+
type: "heading_1",
|
|
692
|
+
heading_1: {
|
|
693
|
+
rich_text: [
|
|
694
|
+
{ text: { content: "Start with a homepage you can keep editing" } },
|
|
695
|
+
],
|
|
696
|
+
},
|
|
697
|
+
},
|
|
698
|
+
{
|
|
699
|
+
type: "paragraph",
|
|
700
|
+
paragraph: {
|
|
701
|
+
rich_text: [
|
|
702
|
+
{
|
|
703
|
+
text: {
|
|
704
|
+
content: "Keep the layout stable in code while the hero copy, positioning, and primary call to action evolve in Notion.",
|
|
705
|
+
},
|
|
706
|
+
},
|
|
707
|
+
],
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
type: "callout",
|
|
712
|
+
callout: {
|
|
713
|
+
rich_text: [{ text: { content: "CTA: Explore the blog → /blog" } }],
|
|
714
|
+
},
|
|
715
|
+
},
|
|
716
|
+
],
|
|
648
717
|
},
|
|
649
718
|
{
|
|
650
|
-
|
|
719
|
+
name: "Homepage Feature Grid",
|
|
651
720
|
slug: "home-feature-grid",
|
|
652
721
|
type: "feature-grid",
|
|
653
|
-
description: "Mid-page feature section for capabilities, benefits, or service pillars.",
|
|
654
|
-
pageKeys: ["home"],
|
|
655
722
|
order: 20,
|
|
656
723
|
coverSeed: "home-feature-grid",
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
724
|
+
children: [
|
|
725
|
+
{
|
|
726
|
+
type: "heading_2",
|
|
727
|
+
heading_2: {
|
|
728
|
+
rich_text: [
|
|
729
|
+
{ text: { content: "Show the system working together" } },
|
|
730
|
+
],
|
|
731
|
+
},
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
type: "paragraph",
|
|
735
|
+
paragraph: {
|
|
736
|
+
rich_text: [
|
|
737
|
+
{
|
|
738
|
+
text: {
|
|
739
|
+
content: "Use this grid to explain how editing, infrastructure, and publishing fit together without overwhelming the homepage.",
|
|
740
|
+
},
|
|
741
|
+
},
|
|
742
|
+
],
|
|
743
|
+
},
|
|
744
|
+
},
|
|
661
745
|
{
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
746
|
+
type: "bulleted_list_item",
|
|
747
|
+
bulleted_list_item: {
|
|
748
|
+
rich_text: [
|
|
749
|
+
{ text: { content: "Editorial workflows: Use Notion as the editor for pages, posts, and reusable sections." } },
|
|
750
|
+
],
|
|
751
|
+
},
|
|
666
752
|
},
|
|
667
753
|
{
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
754
|
+
type: "bulleted_list_item",
|
|
755
|
+
bulleted_list_item: {
|
|
756
|
+
rich_text: [
|
|
757
|
+
{ text: { content: "Cloudflare runtime: Ship on Workers with storage and caching primitives ready to grow." } },
|
|
758
|
+
],
|
|
759
|
+
},
|
|
671
760
|
},
|
|
672
761
|
{
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
762
|
+
type: "bulleted_list_item",
|
|
763
|
+
bulleted_list_item: {
|
|
764
|
+
rich_text: [
|
|
765
|
+
{ text: { content: `${input.contentSourceTitle} updates: Publish new entries and surface them through the generated routes automatically.` } },
|
|
766
|
+
],
|
|
767
|
+
},
|
|
677
768
|
},
|
|
678
769
|
],
|
|
679
770
|
},
|
|
680
771
|
{
|
|
681
|
-
|
|
772
|
+
name: "Homepage Latest Posts",
|
|
682
773
|
slug: "home-latest-posts",
|
|
683
774
|
type: "latest-posts",
|
|
684
|
-
description: "Homepage latest-posts block for previewing the newest published content.",
|
|
685
|
-
pageKeys: ["home"],
|
|
686
775
|
order: 30,
|
|
687
776
|
coverSeed: "home-latest-posts",
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
777
|
+
children: [
|
|
778
|
+
{
|
|
779
|
+
type: "heading_2",
|
|
780
|
+
heading_2: {
|
|
781
|
+
rich_text: [
|
|
782
|
+
{ text: { content: "Read the latest from the blog" } },
|
|
783
|
+
],
|
|
784
|
+
},
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
type: "paragraph",
|
|
788
|
+
paragraph: {
|
|
789
|
+
rich_text: [
|
|
790
|
+
{
|
|
791
|
+
text: {
|
|
792
|
+
content: "Use this section to prove the content model is working with a grid of recent published posts right on the homepage.",
|
|
793
|
+
},
|
|
794
|
+
},
|
|
795
|
+
],
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
],
|
|
693
799
|
},
|
|
694
800
|
];
|
|
695
801
|
}
|
|
@@ -1058,7 +1164,7 @@ function findMatchingField(properties, fields, key, fallback) {
|
|
|
1058
1164
|
return fallback;
|
|
1059
1165
|
return configured;
|
|
1060
1166
|
}
|
|
1061
|
-
async function createDatabaseWithProperties(input) {
|
|
1167
|
+
export async function createDatabaseWithProperties(input) {
|
|
1062
1168
|
const titleProp = Object.entries(input.properties).find(([, value]) => notionPropertyDefinitionType(value) === "title");
|
|
1063
1169
|
const dbTitlePropName = titleProp ? titleProp[0] : "Name";
|
|
1064
1170
|
const body = {
|
|
@@ -1210,7 +1316,7 @@ function buildSamplePage(input) {
|
|
|
1210
1316
|
],
|
|
1211
1317
|
};
|
|
1212
1318
|
}
|
|
1213
|
-
function buildPageProperties() {
|
|
1319
|
+
function buildPageProperties(blocksDatabaseId) {
|
|
1214
1320
|
return {
|
|
1215
1321
|
Name: { title: {} },
|
|
1216
1322
|
Key: { rich_text: {} },
|
|
@@ -1230,7 +1336,12 @@ function buildPageProperties() {
|
|
|
1230
1336
|
"Footer Group": { select: {} },
|
|
1231
1337
|
"Footer Order": { number: {} },
|
|
1232
1338
|
"Content Source": { rich_text: {} },
|
|
1233
|
-
Blocks
|
|
1339
|
+
// Blocks is a Notion relation to the Blocks database. The
|
|
1340
|
+
// relation array order (set by drag-and-drop in Notion) is
|
|
1341
|
+
// the native sort order for page blocks.
|
|
1342
|
+
Blocks: blocksDatabaseId
|
|
1343
|
+
? { relation: { single_property: { database_id: blocksDatabaseId } } }
|
|
1344
|
+
: { relation: { database_property: {} } },
|
|
1234
1345
|
Cover: { files: {} },
|
|
1235
1346
|
};
|
|
1236
1347
|
}
|
|
@@ -1238,46 +1349,22 @@ function buildBlocksProperties() {
|
|
|
1238
1349
|
return {
|
|
1239
1350
|
Name: { title: {} },
|
|
1240
1351
|
Slug: { rich_text: {} },
|
|
1241
|
-
Status: { select: {} },
|
|
1242
1352
|
Type: { select: {} },
|
|
1243
|
-
Description: { rich_text: {} },
|
|
1244
|
-
"Page Keys": { rich_text: {} },
|
|
1245
1353
|
Order: { number: {} },
|
|
1246
1354
|
Cover: { files: {} },
|
|
1247
|
-
|
|
1248
|
-
Headline: { rich_text: {} },
|
|
1249
|
-
Subheadline: { rich_text: {} },
|
|
1250
|
-
"Primary CTA Label": { rich_text: {} },
|
|
1251
|
-
"Primary CTA Href": { url: {} },
|
|
1252
|
-
"Secondary CTA Label": { rich_text: {} },
|
|
1253
|
-
"Secondary CTA Href": { url: {} },
|
|
1254
|
-
Alignment: { select: {} },
|
|
1255
|
-
Theme: { select: {} },
|
|
1256
|
-
Columns: { number: {} },
|
|
1257
|
-
Count: { number: {} },
|
|
1258
|
-
Items: { rich_text: {} },
|
|
1259
|
-
Body: { rich_text: {} },
|
|
1260
|
-
Quote: { rich_text: {} },
|
|
1261
|
-
"Quote Attribution": { rich_text: {} },
|
|
1262
|
-
"Media Url": { url: {} },
|
|
1263
|
-
Layout: { select: {} },
|
|
1355
|
+
Published: { checkbox: {} },
|
|
1264
1356
|
};
|
|
1265
1357
|
}
|
|
1266
1358
|
function richText(content) {
|
|
1267
1359
|
return content ? [{ text: { content } }] : [];
|
|
1268
1360
|
}
|
|
1269
|
-
function selectPropertyValue(name) {
|
|
1270
|
-
return name ? { select: { name } } : { select: null };
|
|
1271
|
-
}
|
|
1272
|
-
function urlPropertyValue(url) {
|
|
1273
|
-
return { url: url ?? null };
|
|
1274
|
-
}
|
|
1275
|
-
function numberPropertyValue(value) {
|
|
1276
|
-
return { number: value ?? null };
|
|
1277
|
-
}
|
|
1278
1361
|
function buildSitePagePayload(input) {
|
|
1279
|
-
const { databaseId, page, projectName } = input;
|
|
1362
|
+
const { databaseId, page, projectName, blockPageIdsBySlug } = input;
|
|
1280
1363
|
const coverUrl = `https://picsum.photos/seed/${slugify(projectName)}-${page.coverSeed}/1200/600`;
|
|
1364
|
+
const blockRelationIds = (page.blocks ?? [])
|
|
1365
|
+
.map((ref) => blockPageIdsBySlug?.[ref.slug])
|
|
1366
|
+
.filter((id) => Boolean(id))
|
|
1367
|
+
.map((id) => ({ id }));
|
|
1281
1368
|
return {
|
|
1282
1369
|
parent: { type: "database_id", database_id: databaseId },
|
|
1283
1370
|
cover: {
|
|
@@ -1303,9 +1390,9 @@ function buildSitePagePayload(input) {
|
|
|
1303
1390
|
"Footer Group": { select: { name: page.footerGroup } },
|
|
1304
1391
|
"Footer Order": { number: page.footerOrder },
|
|
1305
1392
|
"Content Source": { rich_text: richText(page.contentSource ?? "") },
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1393
|
+
...(blockRelationIds.length > 0
|
|
1394
|
+
? { Blocks: { relation: blockRelationIds } }
|
|
1395
|
+
: {}),
|
|
1309
1396
|
Cover: {
|
|
1310
1397
|
files: [
|
|
1311
1398
|
{
|
|
@@ -1351,65 +1438,11 @@ function buildSiteBlockPayload(input) {
|
|
|
1351
1438
|
external: { url: coverUrl },
|
|
1352
1439
|
},
|
|
1353
1440
|
properties: {
|
|
1354
|
-
Name: { title: richText(block.
|
|
1441
|
+
Name: { title: richText(block.name) },
|
|
1355
1442
|
Slug: { rich_text: richText(block.slug) },
|
|
1356
|
-
Status: { select: { name: "Published" } },
|
|
1357
1443
|
Type: { select: { name: block.type } },
|
|
1358
|
-
Description: { rich_text: richText(block.description) },
|
|
1359
|
-
"Page Keys": { rich_text: richText(JSON.stringify(block.pageKeys)) },
|
|
1360
1444
|
Order: { number: block.order },
|
|
1361
|
-
|
|
1362
|
-
rich_text: richText(block.type === "hero" ? block.eyebrow : ""),
|
|
1363
|
-
},
|
|
1364
|
-
Headline: {
|
|
1365
|
-
rich_text: richText(block.type === "hero" ||
|
|
1366
|
-
block.type === "feature-grid" ||
|
|
1367
|
-
block.type === "story" ||
|
|
1368
|
-
block.type === "latest-posts"
|
|
1369
|
-
? block.headline
|
|
1370
|
-
: ""),
|
|
1371
|
-
},
|
|
1372
|
-
Subheadline: {
|
|
1373
|
-
rich_text: richText(block.type === "hero" ? block.subheadline : ""),
|
|
1374
|
-
},
|
|
1375
|
-
"Primary CTA Label": {
|
|
1376
|
-
rich_text: richText(block.type === "hero"
|
|
1377
|
-
? block.primaryCtaLabel
|
|
1378
|
-
: block.type === "latest-posts"
|
|
1379
|
-
? block.primaryCtaLabel
|
|
1380
|
-
: ""),
|
|
1381
|
-
},
|
|
1382
|
-
"Primary CTA Href": urlPropertyValue(block.type === "hero"
|
|
1383
|
-
? block.primaryCtaHref
|
|
1384
|
-
: block.type === "latest-posts"
|
|
1385
|
-
? block.primaryCtaHref
|
|
1386
|
-
: undefined),
|
|
1387
|
-
"Secondary CTA Label": {
|
|
1388
|
-
rich_text: richText(block.type === "hero" ? block.secondaryCtaLabel ?? "" : ""),
|
|
1389
|
-
},
|
|
1390
|
-
"Secondary CTA Href": urlPropertyValue(block.type === "hero" ? block.secondaryCtaHref : undefined),
|
|
1391
|
-
Alignment: selectPropertyValue(block.type === "hero" ? block.alignment : undefined),
|
|
1392
|
-
Theme: selectPropertyValue(block.type === "hero" ? block.theme : undefined),
|
|
1393
|
-
Columns: numberPropertyValue(block.type === "feature-grid" ? block.columns : undefined),
|
|
1394
|
-
Count: numberPropertyValue(block.type === "latest-posts" ? block.count : undefined),
|
|
1395
|
-
Items: {
|
|
1396
|
-
rich_text: richText(block.type === "feature-grid" ? JSON.stringify(block.items) : ""),
|
|
1397
|
-
},
|
|
1398
|
-
Body: {
|
|
1399
|
-
rich_text: richText(block.type === "feature-grid" ||
|
|
1400
|
-
block.type === "story" ||
|
|
1401
|
-
block.type === "latest-posts"
|
|
1402
|
-
? block.body
|
|
1403
|
-
: ""),
|
|
1404
|
-
},
|
|
1405
|
-
Quote: {
|
|
1406
|
-
rich_text: richText(block.type === "story" ? block.quote ?? "" : ""),
|
|
1407
|
-
},
|
|
1408
|
-
"Quote Attribution": {
|
|
1409
|
-
rich_text: richText(block.type === "story" ? block.quoteAttribution ?? "" : ""),
|
|
1410
|
-
},
|
|
1411
|
-
"Media Url": urlPropertyValue(block.type === "story" ? block.mediaUrl : undefined),
|
|
1412
|
-
Layout: selectPropertyValue(block.type === "story" ? block.layout : undefined),
|
|
1445
|
+
Published: { checkbox: true },
|
|
1413
1446
|
Cover: {
|
|
1414
1447
|
files: [
|
|
1415
1448
|
{
|
|
@@ -1420,7 +1453,7 @@ function buildSiteBlockPayload(input) {
|
|
|
1420
1453
|
],
|
|
1421
1454
|
},
|
|
1422
1455
|
},
|
|
1423
|
-
children:
|
|
1456
|
+
children: block.children,
|
|
1424
1457
|
};
|
|
1425
1458
|
}
|
|
1426
1459
|
/** Probe `ntn` — returns true if it's installed. */
|
|
@@ -1537,7 +1570,7 @@ export async function ensureNotionDatabase(input) {
|
|
|
1537
1570
|
export async function ensurePagesDatabase(input) {
|
|
1538
1571
|
const title = `${input.projectName} Pages`;
|
|
1539
1572
|
const stableKey = "pages:default";
|
|
1540
|
-
const properties = buildPageProperties();
|
|
1573
|
+
const properties = buildPageProperties(input.blocksDatabaseId);
|
|
1541
1574
|
const existingByStableKey = await findExistingDatabaseByStableKey({
|
|
1542
1575
|
apiToken: input.apiToken,
|
|
1543
1576
|
parentPageId: input.parentPageId,
|
|
@@ -1590,6 +1623,7 @@ export async function ensurePagesDatabase(input) {
|
|
|
1590
1623
|
databaseId,
|
|
1591
1624
|
projectName: input.projectName,
|
|
1592
1625
|
page,
|
|
1626
|
+
blockPageIdsBySlug: input.blockPageIdsBySlug,
|
|
1593
1627
|
});
|
|
1594
1628
|
const result = await runNtn(["api", "v1/pages", "-d", JSON.stringify(body)], {
|
|
1595
1629
|
env: { NOTION_API_TOKEN: input.apiToken },
|
|
@@ -1661,6 +1695,7 @@ export async function ensureBlocksDatabase(input) {
|
|
|
1661
1695
|
stableKey,
|
|
1662
1696
|
});
|
|
1663
1697
|
let seeded = 0;
|
|
1698
|
+
const seededPageIdsBySlug = {};
|
|
1664
1699
|
for (const block of sampleBlocks(input)) {
|
|
1665
1700
|
const body = buildSiteBlockPayload({
|
|
1666
1701
|
databaseId,
|
|
@@ -1672,6 +1707,17 @@ export async function ensureBlocksDatabase(input) {
|
|
|
1672
1707
|
});
|
|
1673
1708
|
if (result.code === 0) {
|
|
1674
1709
|
seeded++;
|
|
1710
|
+
// Capture the block page ID so the Pages database can link
|
|
1711
|
+
// to it via the Blocks relation during seeding.
|
|
1712
|
+
try {
|
|
1713
|
+
const created = JSON.parse(result.stdout);
|
|
1714
|
+
if (created.id) {
|
|
1715
|
+
seededPageIdsBySlug[block.slug] = created.id;
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
catch {
|
|
1719
|
+
// Non-fatal: relation seeding is best-effort.
|
|
1720
|
+
}
|
|
1675
1721
|
}
|
|
1676
1722
|
else {
|
|
1677
1723
|
const detail = (result.stderr || result.stdout).trim().slice(0, 500);
|
|
@@ -1684,6 +1730,7 @@ export async function ensureBlocksDatabase(input) {
|
|
|
1684
1730
|
url,
|
|
1685
1731
|
created: true,
|
|
1686
1732
|
seeded,
|
|
1733
|
+
seededPageIdsBySlug,
|
|
1687
1734
|
};
|
|
1688
1735
|
}
|
|
1689
1736
|
async function seedPlaceholderPages(apiToken, databaseId, dataSourceId, title, locale, fields, schema, count) {
|
|
@@ -1749,74 +1796,58 @@ export const _internal = {
|
|
|
1749
1796
|
mergeDescriptionWithScaffoldMarker,
|
|
1750
1797
|
missingPropertiesForPatch,
|
|
1751
1798
|
buildSiteSettingsProperties,
|
|
1752
|
-
|
|
1799
|
+
buildSiteSettingsSeedPages,
|
|
1753
1800
|
};
|
|
1754
1801
|
// ---------------------------------------------------------------------------
|
|
1755
|
-
// Site settings (
|
|
1802
|
+
// Site settings (multi-row key-value)
|
|
1756
1803
|
//
|
|
1757
1804
|
// The generated project reads site-level config (name, tagline, description,
|
|
1758
|
-
//
|
|
1805
|
+
// SEO, navigation, theme, footer) from a dedicated Notion data source. The
|
|
1759
1806
|
// scaffolder creates that data source here, with a fixed schema the runtime
|
|
1760
|
-
// loader knows how to read, and seeds
|
|
1761
|
-
// project name
|
|
1762
|
-
// Notion after scaffolding; changes show up within 5 minutes
|
|
1763
|
-
// or immediately via the admin revalidate endpoint.
|
|
1807
|
+
// loader knows how to read, and seeds multiple rows — one per setting item —
|
|
1808
|
+
// pre-populated with the project name and sensible defaults. Operators can
|
|
1809
|
+
// edit rows in Notion after scaffolding; changes show up within 5 minutes
|
|
1810
|
+
// (KV cache TTL) or immediately via the admin revalidate endpoint.
|
|
1764
1811
|
// ---------------------------------------------------------------------------
|
|
1765
1812
|
/** Field names the runtime loader reads in `lib/site/settings.ts`. */
|
|
1766
1813
|
export const SITE_SETTINGS_FIELDS = [
|
|
1767
|
-
"
|
|
1768
|
-
"
|
|
1769
|
-
"
|
|
1770
|
-
"
|
|
1771
|
-
"
|
|
1814
|
+
"Name", // title
|
|
1815
|
+
"Section", // select
|
|
1816
|
+
"Key", // rich_text
|
|
1817
|
+
"Value", // rich_text
|
|
1818
|
+
"Type", // select
|
|
1819
|
+
"Published", // checkbox
|
|
1772
1820
|
];
|
|
1773
1821
|
/**
|
|
1774
1822
|
* Build the Notion `properties` object for the site-settings data source.
|
|
1775
1823
|
*
|
|
1776
|
-
*
|
|
1777
|
-
* `
|
|
1778
|
-
*
|
|
1779
|
-
*
|
|
1780
|
-
* - `Description` → rich_text
|
|
1781
|
-
* - `Default Locale` → select
|
|
1782
|
-
* - `Social Image` → url
|
|
1824
|
+
* Multi-row key-value design: each row is one setting item grouped by
|
|
1825
|
+
* `Section`. This is more intuitive for operators to edit in Notion —
|
|
1826
|
+
* they see a clean table grouped by section instead of one wide row
|
|
1827
|
+
* with 17 columns.
|
|
1783
1828
|
*
|
|
1784
|
-
*
|
|
1785
|
-
*
|
|
1829
|
+
* Fields:
|
|
1830
|
+
* - `Name` (title) — human-readable label, e.g. "Site Name"
|
|
1831
|
+
* - `Section` (select) — grouping: branding/seo/theme/nav/footer
|
|
1832
|
+
* - `Key` (rich_text) — machine key: name/tagline/description/...
|
|
1833
|
+
* - `Value` (rich_text) — the setting value (text, JSON, etc.)
|
|
1834
|
+
* - `Type` (select) — text/url/json/color/select
|
|
1835
|
+
* - `Published` (checkbox) — whether this setting is active
|
|
1786
1836
|
*/
|
|
1787
1837
|
export function buildSiteSettingsProperties() {
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
// 12 new (0.5.4) — SEO, navigation, theme, footer
|
|
1796
|
-
"Meta Title": { rich_text: {} },
|
|
1797
|
-
"Meta Description": { rich_text: {} },
|
|
1798
|
-
"OG Image": { url: {} },
|
|
1799
|
-
Nav: { rich_text: {} },
|
|
1800
|
-
"Nav CTA": { rich_text: {} },
|
|
1801
|
-
"Primary Color": { select: {} },
|
|
1802
|
-
"Accent Color": { select: {} },
|
|
1803
|
-
"Font Family": { select: {} },
|
|
1804
|
-
"Footer Columns": { rich_text: {} },
|
|
1805
|
-
"Footer Copyright": { rich_text: {} },
|
|
1806
|
-
"Footer Social Links": { rich_text: {} },
|
|
1807
|
-
"Footer Tagline": { rich_text: {} },
|
|
1838
|
+
return {
|
|
1839
|
+
Name: { title: {} },
|
|
1840
|
+
Section: { select: {} },
|
|
1841
|
+
Key: { rich_text: {} },
|
|
1842
|
+
Value: { rich_text: {} },
|
|
1843
|
+
Type: { select: {} },
|
|
1844
|
+
Published: { checkbox: {} },
|
|
1808
1845
|
};
|
|
1809
|
-
return props;
|
|
1810
1846
|
}
|
|
1811
1847
|
/**
|
|
1812
|
-
*
|
|
1813
|
-
*
|
|
1814
|
-
*
|
|
1815
|
-
* the home page renders something useful before the operator customizes
|
|
1816
|
-
* it in Notion. The runtime loader falls back to
|
|
1817
|
-
* `fallbackSiteConfig` if the row is missing, so an unedited seed
|
|
1818
|
-
* page is fine — but a populated one means the very first request
|
|
1819
|
-
* after scaffolding already shows the right site name everywhere.
|
|
1848
|
+
* Seed rows for the site-settings data source — one page per setting
|
|
1849
|
+
* item. Each row carries a Section/Key/Value/Type tuple so the
|
|
1850
|
+
* runtime loader can aggregate them into a single `SiteConfig`.
|
|
1820
1851
|
*
|
|
1821
1852
|
* `parent` uses `data_source_id` (Notion's 2025-09-03 schema).
|
|
1822
1853
|
* Passing the legacy `database_id` here silently fails with
|
|
@@ -1824,7 +1855,25 @@ export function buildSiteSettingsProperties() {
|
|
|
1824
1855
|
* is exactly the bug we hit when the Notion API started requiring
|
|
1825
1856
|
* data sources for page parents.
|
|
1826
1857
|
*/
|
|
1827
|
-
|
|
1858
|
+
const SETTINGS_SEED_ROWS = [
|
|
1859
|
+
{ name: "Site Name", section: "branding", key: "name", value: "My NotionX Site", type: "text" },
|
|
1860
|
+
{ name: "Tagline", section: "branding", key: "tagline", value: "Built with NotionX", type: "text" },
|
|
1861
|
+
{ name: "Description", section: "branding", key: "description", value: "", type: "text" },
|
|
1862
|
+
{ name: "Meta Title", section: "seo", key: "metaTitle", value: "", type: "text" },
|
|
1863
|
+
{ name: "Meta Description", section: "seo", key: "metaDescription", value: "", type: "text" },
|
|
1864
|
+
{ name: "Social Image", section: "seo", key: "socialImage", value: "", type: "url" },
|
|
1865
|
+
{ name: "OG Image", section: "seo", key: "ogImage", value: "", type: "url" },
|
|
1866
|
+
{ name: "Primary Color", section: "theme", key: "primaryColor", value: "blue", type: "select" },
|
|
1867
|
+
{ name: "Accent Color", section: "theme", key: "accentColor", value: "green", type: "select" },
|
|
1868
|
+
{ name: "Font Family", section: "theme", key: "fontFamily", value: "Inter", type: "select" },
|
|
1869
|
+
{ name: "Nav Items", section: "nav", key: "items", value: "[]", type: "json" },
|
|
1870
|
+
{ name: "Nav CTA", section: "nav", key: "cta", value: "{}", type: "json" },
|
|
1871
|
+
{ name: "Footer Columns", section: "footer", key: "columns", value: "[]", type: "json" },
|
|
1872
|
+
{ name: "Footer Copyright", section: "footer", key: "copyright", value: "", type: "text" },
|
|
1873
|
+
{ name: "Footer Social Links", section: "footer", key: "socialLinks", value: "[]", type: "json" },
|
|
1874
|
+
{ name: "Footer Tagline", section: "footer", key: "footerTagline", value: "", type: "text" },
|
|
1875
|
+
];
|
|
1876
|
+
export function buildSiteSettingsSeedPages(input) {
|
|
1828
1877
|
const defaultNav = JSON.stringify([
|
|
1829
1878
|
{ label: "Home", href: "/" },
|
|
1830
1879
|
{ label: "About", href: "/about" },
|
|
@@ -1850,57 +1899,53 @@ export function buildSiteSettingsSeedPage(input) {
|
|
|
1850
1899
|
const footerCopyright = `© ${new Date().getFullYear()} ${input.projectName}`;
|
|
1851
1900
|
const socialImageUrl = `https://picsum.photos/seed/${slugify(input.projectName)}-social/1200/630`;
|
|
1852
1901
|
const tagline = `${input.projectName} on Notion and Cloudflare`;
|
|
1853
|
-
|
|
1902
|
+
// Override seed defaults with project-specific values where
|
|
1903
|
+
// available so the very first request after scaffolding already
|
|
1904
|
+
// shows the right site name everywhere.
|
|
1905
|
+
const valueOverrides = {
|
|
1906
|
+
name: input.projectName,
|
|
1907
|
+
tagline,
|
|
1908
|
+
description: input.description,
|
|
1909
|
+
metaTitle: input.projectName,
|
|
1910
|
+
metaDescription: input.description,
|
|
1911
|
+
socialImage: socialImageUrl,
|
|
1912
|
+
ogImage: socialImageUrl,
|
|
1913
|
+
items: defaultNav,
|
|
1914
|
+
cta: "{}",
|
|
1915
|
+
columns: defaultFooterColumns,
|
|
1916
|
+
copyright: footerCopyright,
|
|
1917
|
+
socialLinks: "[]",
|
|
1918
|
+
footerTagline: tagline,
|
|
1919
|
+
};
|
|
1920
|
+
return SETTINGS_SEED_ROWS.map((row) => ({
|
|
1854
1921
|
parent: { type: "data_source_id", data_source_id: input.dataSourceId },
|
|
1855
1922
|
properties: {
|
|
1856
|
-
|
|
1857
|
-
title: [{ text: { content:
|
|
1858
|
-
},
|
|
1859
|
-
Tagline: {
|
|
1860
|
-
rich_text: [{ text: { content: tagline } }],
|
|
1861
|
-
},
|
|
1862
|
-
Description: {
|
|
1863
|
-
rich_text: [{ text: { content: input.description } }],
|
|
1864
|
-
},
|
|
1865
|
-
"Default Locale": {
|
|
1866
|
-
select: { name: input.defaultLocale },
|
|
1867
|
-
},
|
|
1868
|
-
"Meta Title": {
|
|
1869
|
-
rich_text: [{ text: { content: input.projectName } }],
|
|
1923
|
+
Name: {
|
|
1924
|
+
title: [{ text: { content: row.name } }],
|
|
1870
1925
|
},
|
|
1871
|
-
|
|
1872
|
-
|
|
1926
|
+
Section: {
|
|
1927
|
+
select: { name: row.section },
|
|
1873
1928
|
},
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
Nav: {
|
|
1877
|
-
rich_text: [{ text: { content: defaultNav } }],
|
|
1929
|
+
Key: {
|
|
1930
|
+
rich_text: [{ text: { content: row.key } }],
|
|
1878
1931
|
},
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
"Footer Columns": {
|
|
1884
|
-
rich_text: [{ text: { content: defaultFooterColumns } }],
|
|
1885
|
-
},
|
|
1886
|
-
"Footer Copyright": {
|
|
1887
|
-
rich_text: [{ text: { content: footerCopyright } }],
|
|
1888
|
-
},
|
|
1889
|
-
"Footer Social Links": {
|
|
1890
|
-
rich_text: [{ text: { content: "[]" } }],
|
|
1932
|
+
Value: {
|
|
1933
|
+
rich_text: [
|
|
1934
|
+
{ text: { content: valueOverrides[row.key] ?? row.value } },
|
|
1935
|
+
],
|
|
1891
1936
|
},
|
|
1892
|
-
|
|
1893
|
-
|
|
1937
|
+
Type: {
|
|
1938
|
+
select: { name: row.type },
|
|
1894
1939
|
},
|
|
1940
|
+
Published: { checkbox: true },
|
|
1895
1941
|
},
|
|
1896
|
-
};
|
|
1942
|
+
}));
|
|
1897
1943
|
}
|
|
1898
1944
|
/**
|
|
1899
1945
|
* Create the site-settings data source under the given parent page and
|
|
1900
|
-
* insert the seed
|
|
1901
|
-
* `ensureNotionDatabase`,
|
|
1902
|
-
*
|
|
1903
|
-
* has opened Notion.
|
|
1946
|
+
* insert the seed rows — one page per setting item. Same Notion API
|
|
1947
|
+
* dance as `ensureNotionDatabase`, but with multi-row seeding so the
|
|
1948
|
+
* home page works before the operator has opened Notion.
|
|
1904
1949
|
*/
|
|
1905
1950
|
export async function ensureSiteSettingsDatabase(input) {
|
|
1906
1951
|
const stableKey = "site-settings";
|
|
@@ -1920,7 +1965,7 @@ export async function ensureSiteSettingsDatabase(input) {
|
|
|
1920
1965
|
title,
|
|
1921
1966
|
}));
|
|
1922
1967
|
if (existing) {
|
|
1923
|
-
// Make sure the schema has every property the
|
|
1968
|
+
// Make sure the schema has every property the current schema
|
|
1924
1969
|
// expects. Notion adds new properties with no destructive
|
|
1925
1970
|
// effect on existing rows.
|
|
1926
1971
|
await ensureDataSourceProperties({
|
|
@@ -1958,24 +2003,132 @@ export async function ensureSiteSettingsDatabase(input) {
|
|
|
1958
2003
|
existingDescription: "",
|
|
1959
2004
|
stableKey,
|
|
1960
2005
|
});
|
|
1961
|
-
// 4) Seed
|
|
1962
|
-
const
|
|
2006
|
+
// 4) Seed one page per setting item.
|
|
2007
|
+
const seeds = buildSiteSettingsSeedPages({
|
|
1963
2008
|
projectName: input.projectName,
|
|
1964
2009
|
description: input.description,
|
|
1965
2010
|
defaultLocale: input.defaultLocale,
|
|
1966
2011
|
dataSourceId,
|
|
1967
2012
|
});
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
const
|
|
1971
|
-
|
|
2013
|
+
let seeded = 0;
|
|
2014
|
+
for (const seed of seeds) {
|
|
2015
|
+
const seedResult = await runNtn(["api", "v1/pages", "-d", JSON.stringify(seed)], { env: { NOTION_API_TOKEN: input.apiToken } });
|
|
2016
|
+
if (seedResult.code !== 0) {
|
|
2017
|
+
const detail = (seedResult.stderr || seedResult.stdout).trim().slice(0, 500);
|
|
2018
|
+
console.warn(`[notion site-settings seed] failed (code ${seedResult.code}): ${detail}`);
|
|
2019
|
+
}
|
|
2020
|
+
else {
|
|
2021
|
+
seeded++;
|
|
2022
|
+
}
|
|
1972
2023
|
}
|
|
1973
2024
|
return {
|
|
1974
2025
|
dataSourceId,
|
|
1975
2026
|
databaseId,
|
|
1976
2027
|
url,
|
|
1977
2028
|
reused: false,
|
|
1978
|
-
seeded
|
|
2029
|
+
seeded,
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
/**
|
|
2033
|
+
* Build the Notion `properties` object for the blog-translations
|
|
2034
|
+
* data source. Mirrors `blogTranslationFields` in
|
|
2035
|
+
* `@notionx/core/locale-contract/built-in`.
|
|
2036
|
+
*
|
|
2037
|
+
* `Source` is a relation to the base blog data source so each
|
|
2038
|
+
* translation row points at its base post.
|
|
2039
|
+
*/
|
|
2040
|
+
export function buildBlogTranslationProperties(baseDatabaseId) {
|
|
2041
|
+
return {
|
|
2042
|
+
Title: { title: {} },
|
|
2043
|
+
Source: baseDatabaseId
|
|
2044
|
+
? { relation: { single_property: { database_id: baseDatabaseId } } }
|
|
2045
|
+
: { relation: { database_property: {} } },
|
|
2046
|
+
Locale: { select: {} },
|
|
2047
|
+
Slug: { rich_text: {} },
|
|
2048
|
+
Description: { rich_text: {} },
|
|
2049
|
+
"SEO Title": { rich_text: {} },
|
|
2050
|
+
"SEO Description": { rich_text: {} },
|
|
2051
|
+
// Body content lives in the translation page's children blocks,
|
|
2052
|
+
// not a rich_text field — removes the 2000-char limit.
|
|
2053
|
+
Published: { checkbox: {} },
|
|
1979
2054
|
};
|
|
1980
2055
|
}
|
|
2056
|
+
export function buildPageTranslationProperties(baseDatabaseId) {
|
|
2057
|
+
return {
|
|
2058
|
+
Title: { title: {} },
|
|
2059
|
+
Source: baseDatabaseId
|
|
2060
|
+
? { relation: { single_property: { database_id: baseDatabaseId } } }
|
|
2061
|
+
: { relation: { database_property: {} } },
|
|
2062
|
+
Locale: { select: {} },
|
|
2063
|
+
Slug: { rich_text: {} },
|
|
2064
|
+
Description: { rich_text: {} },
|
|
2065
|
+
"SEO Title": { rich_text: {} },
|
|
2066
|
+
"SEO Description": { rich_text: {} },
|
|
2067
|
+
"Nav Label": { rich_text: {} },
|
|
2068
|
+
"Footer Label": { rich_text: {} },
|
|
2069
|
+
// Body content lives in the translation page's children blocks,
|
|
2070
|
+
// not a rich_text field — removes the 2000-char limit.
|
|
2071
|
+
Published: { checkbox: {} },
|
|
2072
|
+
};
|
|
2073
|
+
}
|
|
2074
|
+
export function buildBlockTranslationProperties(baseDatabaseId) {
|
|
2075
|
+
return {
|
|
2076
|
+
Title: { title: {} },
|
|
2077
|
+
Source: baseDatabaseId
|
|
2078
|
+
? { relation: { single_property: { database_id: baseDatabaseId } } }
|
|
2079
|
+
: { relation: { database_property: {} } },
|
|
2080
|
+
Locale: { select: {} },
|
|
2081
|
+
// Body content lives in the translation page's children blocks,
|
|
2082
|
+
// not a rich_text field — removes the 2000-char limit.
|
|
2083
|
+
Published: { checkbox: {} },
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
export function buildSiteSettingsTranslationProperties(baseDatabaseId) {
|
|
2087
|
+
return {
|
|
2088
|
+
Title: { title: {} },
|
|
2089
|
+
Source: baseDatabaseId
|
|
2090
|
+
? { relation: { single_property: { database_id: baseDatabaseId } } }
|
|
2091
|
+
: { relation: { database_property: {} } },
|
|
2092
|
+
Locale: { select: {} },
|
|
2093
|
+
Value: { rich_text: {} },
|
|
2094
|
+
Published: { checkbox: {} },
|
|
2095
|
+
};
|
|
2096
|
+
}
|
|
2097
|
+
export async function ensureTranslationDatabase(input) {
|
|
2098
|
+
const title = titleForTranslationModel(input.modelId);
|
|
2099
|
+
const properties = propertiesForTranslationModel(input.modelId, input.baseDatabaseId);
|
|
2100
|
+
const result = await createDatabaseWithProperties({
|
|
2101
|
+
apiToken: input.apiToken,
|
|
2102
|
+
parentPageId: input.parentPageId,
|
|
2103
|
+
title,
|
|
2104
|
+
properties,
|
|
2105
|
+
});
|
|
2106
|
+
return {
|
|
2107
|
+
databaseId: result.databaseId,
|
|
2108
|
+
dataSourceId: result.dataSourceId,
|
|
2109
|
+
url: result.url,
|
|
2110
|
+
reused: false,
|
|
2111
|
+
};
|
|
2112
|
+
}
|
|
2113
|
+
function titleForTranslationModel(modelId) {
|
|
2114
|
+
const map = {
|
|
2115
|
+
"blog-translations": "Blog Translations",
|
|
2116
|
+
"page-translations": "Page Translations",
|
|
2117
|
+
"block-translations": "Block Translations",
|
|
2118
|
+
"site-settings-translations": "Site Settings Translations",
|
|
2119
|
+
};
|
|
2120
|
+
return map[modelId];
|
|
2121
|
+
}
|
|
2122
|
+
function propertiesForTranslationModel(modelId, baseDatabaseId) {
|
|
2123
|
+
switch (modelId) {
|
|
2124
|
+
case "blog-translations":
|
|
2125
|
+
return buildBlogTranslationProperties(baseDatabaseId);
|
|
2126
|
+
case "page-translations":
|
|
2127
|
+
return buildPageTranslationProperties(baseDatabaseId);
|
|
2128
|
+
case "block-translations":
|
|
2129
|
+
return buildBlockTranslationProperties(baseDatabaseId);
|
|
2130
|
+
case "site-settings-translations":
|
|
2131
|
+
return buildSiteSettingsTranslationProperties(baseDatabaseId);
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
1981
2134
|
//# sourceMappingURL=notion.js.map
|