Skip to content

Commit 1273850

Browse files
committed
Add notion sync files
1 parent f738cf7 commit 1273850

9 files changed

Lines changed: 133 additions & 0 deletions

File tree

.DS_Store

6 KB
Binary file not shown.

.github/workflows/notion-sync.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,12 @@ jobs:
3030
# Step 3: Install dependencies
3131
- name: Install dependencies
3232
run: npm install
33+
working-directory: ./notion
3334

3435
# Step 4: Run the Notion sync script
3536
- name: Run Notion sync
3637
run: node sync_notion.js
38+
working-directory: ./notion
3739
env:
3840
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
3941

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

notion/package.json

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "devnotes",
3+
"version": "1.0.0",
4+
"description": "---",
5+
"main": "sync_notion.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/Nymphalys/DevNotes.git"
12+
},
13+
"keywords": [],
14+
"author": "",
15+
"license": "ISC",
16+
"type": "commonjs",
17+
"bugs": {
18+
"url": "https://github.com/Nymphalys/DevNotes/issues"
19+
},
20+
"homepage": "https://github.com/Nymphalys/DevNotes#readme",
21+
"dependencies": {
22+
"@notionhq/client": "^5.9.0",
23+
"dotenv": "^17.2.4",
24+
"notion-to-md": "^3.1.9",
25+
"simple-git": "^3.30.0"
26+
}
27+
}

notion/sync_notion.js

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
require('dotenv').config({ path: '../.env' });
2+
const { Client } = require('@notionhq/client');
3+
const { NotionToMarkdown } = require('notion-to-md');
4+
const fs = require('fs');
5+
const path = require('path');
6+
7+
const notion = new Client({ auth: process.env.NOTION_TOKEN });
8+
const n2m = new NotionToMarkdown({ notionClient: notion });
9+
10+
function getPageTitle(page) {
11+
const titleProp = Object.values(page.properties).find(p => p.type === 'title');
12+
return titleProp?.title?.[0]?.plain_text || 'Untitled Page';
13+
}
14+
15+
async function processPage(pageId, parentFolder, childPages = []) {
16+
try {
17+
const page = await notion.pages.retrieve({ page_id: pageId });
18+
19+
const mdBlocks = await n2m.pageToMarkdown(pageId);
20+
let finalMarkdown = n2m.toMarkdownString(mdBlocks)?.parent || '';
21+
22+
let title = getPageTitle(page);
23+
24+
if ((title === "Untitled Page" || !title) && mdBlocks.length > 0) {
25+
const firstBlock = mdBlocks.find(b => {
26+
const text = b.plain_text || (b[b.type]?.text?.map(t => t.plain_text).join("") || "");
27+
return text && text.trim() !== "";
28+
});
29+
if (firstBlock) {
30+
title = firstBlock.plain_text || (firstBlock[firstBlock.type]?.text?.map(t => t.plain_text).join("") || "");
31+
}
32+
}
33+
34+
if (!title) title = "Untitled Page";
35+
if (!finalMarkdown || finalMarkdown.trim() === "") {
36+
finalMarkdown = "*This page has no content*";
37+
}
38+
39+
const safeTitle = title.replace(/[/\\?%*:|"<>]/g, "-");
40+
41+
const pageFolder = path.join(parentFolder, safeTitle);
42+
if (fs.existsSync(pageFolder)) {
43+
fs.rmSync(pageFolder, { recursive: true, force: true });
44+
}
45+
fs.mkdirSync(pageFolder, { recursive: true });
46+
47+
const children = await notion.blocks.children.list({ block_id: pageId });
48+
const childPageBlocks = [];
49+
50+
for (const block of children.results) {
51+
if (block.type === "child_page") {
52+
childPageBlocks.push(block);
53+
}
54+
}
55+
56+
const processedChildren = [];
57+
for (const block of childPageBlocks) {
58+
const childTitle = block.child_page?.title || "Untitled";
59+
const childSafeTitle = childTitle.replace(/[/\\?%*:|"<>]/g, "-");
60+
processedChildren.push({ id: block.id, title: childTitle, safeTitle: childSafeTitle });
61+
await processPage(block.id, pageFolder);
62+
}
63+
64+
const fileUrlPattern = /\[file\]\(https:\/\/prod-files-secure\.s3[^)]+\)/g;
65+
let matchIndex = 0;
66+
finalMarkdown = finalMarkdown.replace(fileUrlPattern, () => {
67+
if (matchIndex < processedChildren.length) {
68+
const child = processedChildren[matchIndex];
69+
matchIndex++;
70+
return `[${child.title}](./${child.safeTitle}/${child.safeTitle}.md)`;
71+
}
72+
return '[file]()';
73+
});
74+
75+
fs.writeFileSync(path.join(pageFolder, `${safeTitle}.md`), finalMarkdown, "utf8");
76+
} catch (err) {
77+
console.error(`Error processing page ${pageId}:`, err.message);
78+
}
79+
}
80+
81+
async function main() {
82+
try {
83+
const pageIds = JSON.parse(fs.readFileSync(path.join(__dirname, 'pages.json'), 'utf8'));
84+
85+
if (!pageIds || pageIds.length === 0) {
86+
console.error('No page IDs found in pages.json');
87+
process.exit(1);
88+
}
89+
90+
const outputDir = path.join(__dirname, '..', 'notes');
91+
92+
for (const pageId of pageIds) {
93+
console.log(`Processing page: ${pageId}`);
94+
await processPage(pageId, outputDir);
95+
}
96+
97+
console.log('✅ Notion sync completed successfully');
98+
} catch (err) {
99+
console.error('❌ Sync failed:', err.message);
100+
process.exit(1);
101+
}
102+
}
103+
104+
main();

0 commit comments

Comments
 (0)