@@ -58,6 +58,17 @@ if(!databaseId) {
5858}
5959const git = simpleGit();
6060
61+ // Required database properties
62+ const REQUIRED_PROPERTIES = {
63+ title: { type: 'title', name: 'title' },
64+ description: { type: 'rich_text', name: 'description' },
65+ published: { type: 'checkbox', name: 'published' },
66+ date: { type: 'date', name: 'date' },
67+ tags: { type: 'rich_text', name: 'tags' },
68+ editor: { type: 'rich_text', name: 'editor' },
69+ dateCreated: { type: 'date', name: 'dateCreated' }
70+ };
71+
6172async function syncMarkdownFilesToNotion() {
6273 console.log("Starting syncMarkdownFilesToNotion");
6374 const markdownFiles = await getMarkdownFiles("./");
@@ -235,38 +246,114 @@ async function getNotionPageByTitle(title: string): Promise<any | null> {
235246 }
236247}
237248
238- // Update Notion page
239- async function updateNotionPage(pageId: string, metadata: MarkdownMetadata, content: string) {
240- try {
241- const updateData = {
242- page_id: pageId,
243- properties: {
244- description: { rich_text: [{ text: { content: metadata.description } }] },
245- published: { checkbox: metadata.published },
246- date: { date: { start: metadata.date } },
247- tags: { rich_text: [{ text: { content: metadata.tags } }] },
248- editor: { rich_text: [{ text: { content: metadata.editor } }] },
249- dateCreated: { date: { start: metadata.dateCreated } },
250- }
251- };
252- await notion.pages.update(updateData);
253-
254- // Update content in a separate call
255- await notion.blocks.children.append({
256- block_id: pageId,
257- children: [
258- {
249+ // Convert markdown content to Notion blocks
250+ function markdownToBlocks(markdown: string): any[] {
251+ const blocks: any[] = [];
252+ const lines = markdown.split('\n');
253+ let currentBlock: any = null;
254+
255+ for (let line of lines) {
256+ // Handle headers
257+ const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
258+ if (headerMatch) {
259+ const level = headerMatch[1].length;
260+ const text = headerMatch[2];
261+ blocks.push({
262+ object: "block",
263+ type: `heading_${level}`,
264+ [`heading_${level}`]: {
265+ rich_text: [{ type: "text", text: { content: text } }]
266+ }
267+ });
268+ continue;
269+ }
270+
271+ // Handle code blocks
272+ if (line.startsWith('```')) {
273+ if (!currentBlock) {
274+ currentBlock = {
259275 object: "block",
260- type: "paragraph",
261- paragraph: {
262- rich_text: [{ type: "text", text: { content } }]
276+ type: "code",
277+ code: {
278+ language: line.slice(3) || "plain text",
279+ rich_text: [{ type: "text", text: { content: "" } }]
263280 }
281+ };
282+ } else {
283+ blocks.push(currentBlock);
284+ currentBlock = null;
285+ }
286+ continue;
287+ }
288+
289+ // Add content to code block
290+ if (currentBlock?.type === "code") {
291+ currentBlock.code.rich_text[0].text.content += line + "\n";
292+ continue;
293+ }
294+
295+ // Handle bullet points
296+ if (line.match(/^[\-\*]\s/)) {
297+ blocks.push({
298+ object: "block",
299+ type: "bulleted_list_item",
300+ bulleted_list_item: {
301+ rich_text: [{
302+ type: "text",
303+ text: { content: line.slice(2) }
304+ }]
264305 }
265- ]
266- });
267- } catch (error) {
268- console.error("Error in updateNotionPage:", error);
306+ });
307+ continue;
308+ }
309+
310+ // Handle numbered lists
311+ const numberedListMatch = line.match(/^\d+\.\s+(.+)$/);
312+ if (numberedListMatch) {
313+ blocks.push({
314+ object: "block",
315+ type: "numbered_list_item",
316+ numbered_list_item: {
317+ rich_text: [{
318+ type: "text",
319+ text: { content: numberedListMatch[1] }
320+ }]
321+ }
322+ });
323+ continue;
324+ }
325+
326+ // Handle blockquotes
327+ if (line.startsWith('>')) {
328+ blocks.push({
329+ object: "block",
330+ type: "quote",
331+ quote: {
332+ rich_text: [{
333+ type: "text",
334+ text: { content: line.slice(1).trim() }
335+ }]
336+ }
337+ });
338+ continue;
339+ }
340+
341+ // Handle regular paragraphs (including blank lines)
342+ if (line.trim() || blocks.length === 0 || blocks[blocks.length - 1].type !== "paragraph") {
343+ blocks.push({
344+ object: "block",
345+ type: "paragraph",
346+ paragraph: {
347+ rich_text: [{
348+ type: "text",
349+ text: { content: line }
350+ }]
351+ }
352+ });
353+ }
269354 }
355+
356+ return blocks;
270357}
271358
272359// Create Notion page
@@ -285,21 +372,45 @@ async function createNotionPage(metadata: MarkdownMetadata, content: string) {
285372 editor: { rich_text: [{ text: { content: metadata.editor } }] },
286373 dateCreated: { date: { start: metadata.dateCreated } },
287374 },
288- children: [
289- {
290- object: "block",
291- type: "paragraph",
292- paragraph: {
293- rich_text: [{ type: "text", text: { content } }]
294- }
295- }
296- ]
375+ children: markdownToBlocks(content)
297376 });
298377 } catch (error) {
299378 console.error("Error in createNotionPage:", error);
300379 }
301380}
302381
382+ // Update Notion page
383+ async function updateNotionPage(pageId: string, metadata: MarkdownMetadata, content: string) {
384+ try {
385+ const updateData = {
386+ page_id: pageId,
387+ properties: {
388+ description: { rich_text: [{ text: { content: metadata.description } }] },
389+ published: { checkbox: metadata.published },
390+ date: { date: { start: metadata.date } },
391+ tags: { rich_text: [{ text: { content: metadata.tags } }] },
392+ editor: { rich_text: [{ text: { content: metadata.editor } }] },
393+ dateCreated: { date: { start: metadata.dateCreated } },
394+ }
395+ };
396+ await notion.pages.update(updateData);
397+
398+ // First delete existing content
399+ const existingBlocks = await notion.blocks.children.list({ block_id: pageId });
400+ for (const block of existingBlocks.results) {
401+ await notion.blocks.delete({ block_id: block.id });
402+ }
403+
404+ // Then add new content as blocks
405+ await notion.blocks.children.append({
406+ block_id: pageId,
407+ children: markdownToBlocks(content)
408+ });
409+ } catch (error) {
410+ console.error("Error in updateNotionPage:", error);
411+ }
412+ }
413+
303414// Update markdown file from Notion page
304415async function updateMarkdownFile(filePath: string, notionPage: any) {
305416 // Get properties
@@ -338,19 +449,56 @@ async function updateMarkdownFile(filePath: string, notionPage: any) {
338449 await writeFile(filePath, newFileContent);
339450}
340451
452+ // Ensure database has required properties
453+ async function ensureDatabaseProperties() {
454+ try {
455+ const database = await notion.databases.retrieve({ database_id: databaseId });
456+ const existingProps = database.properties;
457+ const updates: any = { properties: {} };
458+ let needsUpdate = false;
459+
460+ // Check each required property
461+ for (const [key, config] of Object.entries(REQUIRED_PROPERTIES)) {
462+ if (!existingProps[key]) {
463+ needsUpdate = true;
464+ updates.properties[key] = {
465+ name: config.name,
466+ [config.type]: {}
467+ };
468+ }
469+ }
470+
471+ // Update database if needed
472+ if (needsUpdate) {
473+ console.log('Adding missing properties to database...');
474+ await notion.databases.update({
475+ database_id: databaseId,
476+ ...updates
477+ });
478+ console.log('Database properties updated successfully');
479+ }
480+ } catch (error) {
481+ console.error('Error ensuring database properties:', error);
482+ throw error;
483+ }
484+ }
485+
486+ // Only run sync if this is the main module
487+ if (require.main === module) {
488+ ensureDatabaseProperties()
489+ .then(() => syncMarkdownFilesToNotion())
490+ .catch((error) => {
491+ console.error("Global error:", error);
492+ });
493+ }
494+
341495// Export functions for testing
342496module.exports = {
343497 getMarkdownFiles,
344498 extractMetadataAndContent,
345499 getNotionPageByTitle,
346500 updateNotionPage,
347501 createNotionPage,
348- updateMarkdownFile
349- };
350-
351- // Only run sync if this is the main module
352- if (require.main === module) {
353- syncMarkdownFilesToNotion().catch((error) => {
354- console.error("Global error:", error);
355- });
356- }
502+ updateMarkdownFile,
503+ ensureDatabaseProperties
504+ };
0 commit comments