curl --location '<https://api.notion.com/v1/blocks/2bc14096c91f80a0a1aed5800eee69ce/children>' \\
--header 'Authorization: Bearer <NOTION_TOKEN>' \\
--header 'Notion-Version: 2022-06-28'

Use this curl to fetch notion page content Note: Few blocks content is not visible for that you need to hit another curl with the id you get in above call.

curl --location --request GET \\
  "<https://graph.microsoft.com/beta/sites/${SITE_ID}/pages/${PAGE_ID}/microsoft.graph.sitePage/webparts>" \\
  --header "Authorization: Bearer ${ACCESS_TOKEN}" \\
  --header "Content-Type: application/json"

This is the api to get sharepoint page content

Pseudocode For mapping

function notionBlocksToSharePointWebParts(notionBlocks) {
  const webParts = [];
  let htmlBuffer = "";

  const flushTextWebPart = () => {
    if (!htmlBuffer.trim()) return;
    webParts.push({
      "@odata.type": "#microsoft.graph.textWebPart",
      "innerHtml": htmlBuffer
    });
    htmlBuffer = "";
  };

  for (const block of notionBlocks) {
    switch (block.type) {
      // TEXT GROUP – stay in htmlBuffer
      case "paragraph":
      case "heading_1":
      case "heading_2":
      case "heading_3":
      case "bulleted_list_item":
      case "numbered_list_item":
      case "quote":
      case "to_do":
      case "toggle":
      case "callout":
      case "divider":           // if using <hr> approach
      case "table":
        htmlBuffer += renderBlockToHtml(block);  // your function to turn Notion block -> HTML
        break;

      // SPECIAL BLOCKS – flush text, then emit dedicated webpart
      case "image":
        flushTextWebPart();
        webParts.push(buildImageWebPart(block));       // upload + build JSON like your example
        break;

      case "video":
        flushTextWebPart();
        webParts.push(buildVideoWebPart(block));       // File and Media or YouTube depending on type
        break;

      case "audio":
        flushTextWebPart();
        webParts.push(buildAudioWebPart(block));       // File and Media or Embed
        break;

      case "file":
        flushTextWebPart();
        webParts.push(buildFileWebPart(block));
        break;

      case "code":
        flushTextWebPart();
        webParts.push(buildCodeSnippetWebPart(block));
        break;

      case "bookmark":
        flushTextWebPart();
        webParts.push(addBookmarkToQuickLinks(block)); // or Button
        break;

      case "child_page":
        flushTextWebPart();
        webParts.push(addChildPageQuickLink(block));   // assumes you've created the child page already
        break;

      case "child_database":
        flushTextWebPart();
        webParts.push(addDatabaseListWebPart(block));  // assumes you've created the list
        break;

      default:
        // unsupported: log and maybe add a comment in HTML
        htmlBuffer += `<!-- unsupported block: ${block.type} -->`;
        break;
    }
  }

  flushTextWebPart();
  return webParts;
}

Helper Function

/**
 * Turn a single Notion block into HTML suitable for a SharePoint text web part.
 * Assumes block structure from Notion API v2022-06-28.
 */
function renderBlockToHtml(block) {
  if (!block || !block.type) return "";

  switch (block.type) {
    case "paragraph":
      return renderParagraph(block.paragraph);

    case "heading_1":
      return renderHeading(block.heading_1, 1);

    case "heading_2":
      return renderHeading(block.heading_2, 2);

    case "heading_3":
      return renderHeading(block.heading_3, 3);

    case "bulleted_list_item":
      return renderBulletedListItem(block.bulleted_list_item);

    case "numbered_list_item":
      return renderNumberedListItem(block.numbered_list_item);

    case "quote":
      return renderQuote(block.quote);

    case "to_do":
      return renderTodo(block.to_do);

    case "toggle":
      return renderToggle(block.toggle);

    case "callout":
      return renderCallout(block.callout);

    case "divider":
      return `<hr class="notion-divider" />\\n`;

    case "table":
      // Assumes you've already loaded table_row children into block.children
      return renderTable(block);

    default:
      // For any text-ish unknown types, just dump a comment
      return `<!-- unsupported block type in renderBlockToHtml: ${escapeHtml(
        block.type
      )} -->\\n`;
  }
}

/* ---------- TEXT HELPERS ---------- */

function renderParagraph(paragraph) {
  const text = renderRichText(paragraph?.rich_text || []);
  // preserve “blank lines” for spacing
  const inner = text.trim() === "" ? "&nbsp;" : text;
  return `<p class="noSpacingAbove spacingBelow" data-text-type="withSpacing">${inner}</p>\\n`;
}

function renderHeading(heading, level) {
  const text = renderRichText(heading?.rich_text || []);
  const tag = `h${Math.min(Math.max(level, 1), 6)}`;
  return `<${tag} class="headingSpacingAbove headingSpacingBelow lineHeight1_4">${text}</${tag}>\\n`;
}

function renderBulletedListItem(item) {
  const text = renderRichText(item?.rich_text || []);
  // NOTE: This wraps each item in its own <ul>. If you want grouped lists,
  // you’d handle grouping in notionBlocksToSharePointWebParts instead.
  return `<ul class="customListStyle" style="list-style-type:disc;"><li>${text}</li></ul>\\n`;
}

function renderNumberedListItem(item) {
  const text = renderRichText(item?.rich_text || []);
  return `<ol class="customListStyle"><li>${text}</li></ol>\\n`;
}

function renderQuote(quote) {
  const text = renderRichText(quote?.rich_text || []);
  return `<blockquote class="notion-quote">${text}</blockquote>\\n`;
}

function renderTodo(todo) {
  const text = renderRichText(todo?.rich_text || []);
  const checked = todo?.checked;
  const checkbox = checked ? "☑" : "☐";
  return `<p class="notion-todo noSpacingAbove spacingBelow">${checkbox} ${text}</p>\\n`;
}

function renderToggle(toggle) {
  const text = renderRichText(toggle?.rich_text || []);
  // Flattened – if you want to render children, do it in your recursive walker
  return `<p class="notion-toggle noSpacingAbove spacingBelow"><strong>${text}</strong></p>\\n`;
}

function renderCallout(callout) {
  const text = renderRichText(callout?.rich_text || []);
  const emoji =
    callout?.icon?.type === "emoji" ? escapeHtml(callout.icon.emoji) : "💡";
  const colorClass =
    callout?.color && callout.color !== "default"
      ? ` notion-color-${callout.color}`
      : "";
  return `<div class="notion-callout${colorClass}"><span class="notion-callout-icon">${emoji}</span><span class="notion-callout-text">${text}</span></div>\\n`;
}

/* ---------- TABLE ---------- */

function renderTable(tableBlock) {
  const hasHeader = !!tableBlock.table?.has_column_header;
  const rows = Array.isArray(tableBlock.children)
    ? tableBlock.children
    : []; // assumes you attach table_row children beforehand

  let thead = "";
  let tbody = "";

  rows.forEach((row, index) => {
    if (row.type !== "table_row") return;
    const cells = row.table_row?.cells || [];

    const cellTag = hasHeader && index === 0 ? "th" : "td";
    const rowHtml =
      "<tr>" +
      cells
        .map((cellRichTextArray) => {
          const cellText = renderRichText(cellRichTextArray || []);
          return `<${cellTag}>${cellText || "&nbsp;"}</${cellTag}>`;
        })
        .join("") +
      "</tr>";

    if (hasHeader && index === 0) {
      thead += rowHtml;
    } else {
      tbody += rowHtml;
    }
  });

  if (!thead && !tbody) {
    return "<!-- empty table -->\\n";
  }

  return `
<figure class="table canvasRteResponsiveTable tableLeftAlign" title="Table">
  <table class="customCells rteTableBackgroundTransparent rteTableUnboldTableHeaderCell">
    ${thead ? `<thead>${thead}</thead>` : ""}
    <tbody>${tbody}</tbody>
  </table>
</figure>\\n`;
}

/* ---------- RICH TEXT ---------- */

function renderRichText(richTextArray) {
  if (!Array.isArray(richTextArray)) return "";

  return richTextArray
    .map((rt) => {
      const plain =
        (rt.plain_text ||
          (rt.text && rt.text.content) ||
          "").replace(/\\n/g, "<br />");

      let html = escapeHtml(plain);

      const ann = rt.annotations || {};

      if (ann.code) html = `<code>${html}</code>`;
      if (ann.bold) html = `<strong>${html}</strong>`;
      if (ann.italic) html = `<em>${html}</em>`;
      if (ann.underline) html = `<u>${html}</u>`;
      if (ann.strikethrough) html = `<s>${html}</s>`;

      const href =
        rt.href || (rt.text && rt.text.link && rt.text.link.url) || null;
      if (href) {
        html = `<a href="${escapeHtml(href)}">${html}</a>`;
      }

      const color = ann.color;
      if (color && color !== "default") {
        // Map Notion colors to CSS classes, you can style them in your CSS:
        // .notion-color-red { color: #e03e3e; } etc.
        html = `<span class="notion-color-${color}">${html}</span>`;
      }

      return html;
    })
    .join("");
}

/* ---------- UTILS ---------- */

function escapeHtml(str) {
  return String(str)
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

Other Helper function

/* =============================================================================
   BUILDER FUNCTIONS FOR SHAREPOINT WEBPART JSON
============================================================================= */

function buildImageWebPart(block, options = {}) {
  const img = block.image;
  const url =
    img?.file?.url ||
    img?.external?.url ||
    "";

  const fileName = url.split("/").pop().split("?")[0];

  return {
    "@odata.type": "#microsoft.graph.standardWebPart",
    "webPartType": "d1d91016-032f-456d-98a4-721247c305e8",
    "data": {
      "audiences": [],
      "dataVersion": "1.2",
      "title": "Image",
      "properties": {
        "imageSourceType": 2,
        "captionText": "",
        "altText": "",
        "overlayText": "",
        "fileName": fileName
      },
      "serverProcessedContent": {
        "imageSources": [
          {
            "key": "imageSource",
            "value": url
          }
        ],
        "links": [],
        "htmlStrings": [],
        "searchablePlainTexts": []
      }
    }
  };
}

function buildVideoWebPart(block, options = {}) {
  const video = block.video;

  // Case 1: YouTube or external URL = YouTube embed webpart
  if (video?.external?.url?.includes("youtube.com") || video?.external?.url?.includes("youtu.be")) {
    return {
      "@odata.type": "#microsoft.graph.standardWebPart",
      "webPartType": "544dd15b-cf3c-441b-96da-004d5a8cea1d",
      "data": {
        "dataVersion": "1.2",
        "title": "YouTube",
        "properties": {
          "embedCode": video.external.url,
          "thumbnailUrl": "",
          "shouldScaleWidth": true
        },
        "serverProcessedContent": {
          "htmlStrings": [],
          "imageSources": [],
          "links": []
        }
      }
    };
  }

  // Case 2: file video → File and Media
  const url = video?.file?.url || "";
  const fileName = url.split("/").pop().split("?")[0];

  return {
    "@odata.type": "#microsoft.graph.standardWebPart",
    "webPartType": "b7dd04e1-19ce-4b24-9132-b60a1c2b910d",
    "data": {
      "dataVersion": "1.4",
      "title": "Video",
      "properties": {
        "file": url,
        "fileName": fileName
      },
      "serverProcessedContent": {
        "links": [
          { key: "serverRelativeUrl", value: url }
        ],
        "searchablePlainTexts": [],
        "imageSources": []
      }
    }
  };
}

function buildAudioWebPart(block, options = {}) {
  const audio = block.audio;
  const url = audio?.external?.url || "";

  return {
    "@odata.type": "#microsoft.graph.standardWebPart",
    "webPartType": "490d7c76-1824-45b2-9de3-676421c997fa",
    "data": {
      "dataVersion": "1.2",
      "title": "Audio",
      "properties": {
        "embedCode": url,
        "shouldScaleWidth": true
      },
      "serverProcessedContent": {
        "links": [],
        "imageSources": [],
        "htmlStrings": []
      }
    }
  };
}

function buildFileWebPart(block, options = {}) {
  const file = block.file;
  const url =
    file?.external?.url ||
    file?.file?.url ||
    "";
  const fileName = url.split("/").pop().split("?")[0];

  return {
    "@odata.type": "#microsoft.graph.standardWebPart",
    "webPartType": "b7dd04e1-19ce-4b24-9132-b60a1c2b910d",
    "data": {
      "title": "File",
      "properties": { "file": url, "fileName": fileName },
      "serverProcessedContent": {
        "links": [{ key: "serverRelativeUrl", value: url }],
        "htmlStrings": [],
        "imageSources": [],
        "searchablePlainTexts": []
      }
    }
  };
}

function buildCodeSnippetWebPart(block) {
  const codeText = (block.code?.rich_text || [])
    .map((rt) => rt.plain_text)
    .join("");

  return {
    "@odata.type": "#microsoft.graph.standardWebPart",
    "webPartType": "7b317bca-c919-4982-af2f-8399173e5a1e",
    "data": {
      "title": "Code Snippet",
      "properties": {
        "language": block.code?.language || "javascript",
        "lineNumbers": true,
        "lineWrapping": true,
        "theme": "Monokai"
      },
      "serverProcessedContent": {
        "htmlStrings": [],
        "searchablePlainTexts": [
          { key: "code", value: codeText }
        ],
        "links": [],
        "imageSources": []
      }
    }
  };
}

function addBookmarkToQuickLinks(block) {
  const url = block.bookmark?.url || "";

  return {
    "@odata.type": "#microsoft.graph.standardWebPart",
    "webPartType": "c70391ea-0b10-4ee9-b2b4-006d3fcad0cd",
    "data": {
      "title": "Quick Link",
      "properties": {
        "layoutId": "CompactCard",
        "items": [
          {
            id: 1,
            name: "Bookmark",
            thumbnailType: 3,
            sourceItem: {
              url
            }
          }
        ]
      },
      "serverProcessedContent": {
        "links": [
          { key: "items[0].sourceItem.url", value: url }
        ],
        "htmlStrings": [],
        "imageSources": []
      }
    }
  };
}

function addChildPageQuickLink(block, options = {}) {
  const title = block.child_page?.title || "Child Page";
  const url = options.childPageResolver
    ? options.childPageResolver(block.id)
    : "#";

  return {
    "@odata.type": "#microsoft.graph.standardWebPart",
    "webPartType": "c70391ea-0b10-4ee9-b2b4-006d3fcad0cd",
    "data": {
      "title": "Subpage Link",
      "properties": {
        "layoutId": "CompactCard",
        "items": [
          {
            id: 1,
            name: title,
            thumbnailType: 3,
            sourceItem: {
              url
            }
          }
        ]
      },
      "serverProcessedContent": {
        "links": [
          { key: "items[0].sourceItem.url", value: url }
        ]
      }
    }
  };
}

function addDatabaseListWebPart(block, options = {}) {
  const listId =
    options.databaseListResolver?.(block.id) ||
    "00000000-0000-0000-0000-000000000000";

  return {
    "@odata.type": "#microsoft.graph.standardWebPart",
    "webPartType": "f92bf067-bc19-489e-a556-7fe95f508720",
    "data": {
      "title": block.child_database?.title || "Database",
      "properties": {
        "selectedListId": listId,
        "isDocumentLibrary": false
      },
      "serverProcessedContent": {
        "htmlStrings": [],
        "links": [],
        "imageSources": []
      }
    }
  };
}

Complete mapping

/****************************************************************************************
 *  COMPLETE NOTION → SHAREPOINT TEXT + WEBPART MAPPER
 *  -------------------------------------------------
 *  This file contains:
 *   - renderBlockToHtml + helpers
 *   - notionBlocksToSharePointWebParts
 *   - All builder functions (image, video, file, audio, code, bookmark, database...)
 *
 *  All functions are self-contained and runnable.
 ****************************************************************************************/

/* =============================================================================
   RENDER NOTION BLOCK → HTML (used inside SharePoint text web parts)
============================================================================= */

function renderBlockToHtml(block) {
  if (!block || !block.type) return "";

  switch (block.type) {
    case "paragraph":
      return renderParagraph(block.paragraph);

    case "heading_1":
      return renderHeading(block.heading_1, 1);

    case "heading_2":
      return renderHeading(block.heading_2, 2);

    case "heading_3":
      return renderHeading(block.heading_3, 3);

    case "bulleted_list_item":
      return renderBulletedListItem(block.bulleted_list_item);

    case "numbered_list_item":
      return renderNumberedListItem(block.numbered_list_item);

    case "quote":
      return renderQuote(block.quote);

    case "to_do":
      return renderTodo(block.to_do);

    case "toggle":
      return renderToggle(block.toggle);

    case "callout":
      return renderCallout(block.callout);

    case "divider":
      return `<hr class="notion-divider" />\\n`;

    case "table":
      return renderTable(block);

    default:
      return `<!-- unsupported block type in renderBlockToHtml: ${escapeHtml(
        block.type
      )} -->\\n`;
  }
}

/* ======== TEXT HELPERS ========= */

function renderParagraph(paragraph) {
  const text = renderRichText(paragraph?.rich_text || []);
  const inner = text.trim() === "" ? "&nbsp;" : text;
  return `<p class="noSpacingAbove spacingBelow" data-text-type="withSpacing">${inner}</p>\\n`;
}

function renderHeading(heading, level) {
  const text = renderRichText(heading?.rich_text || []);
  const tag = `h${Math.min(Math.max(level, 1), 6)}`;
  return `<${tag} class="headingSpacingAbove headingSpacingBelow lineHeight1_4">${text}</${tag}>\\n`;
}

function renderBulletedListItem(item) {
  const text = renderRichText(item?.rich_text || []);
  return `<ul class="customListStyle" style="list-style-type:disc;"><li>${text}</li></ul>\\n`;
}

function renderNumberedListItem(item) {
  const text = renderRichText(item?.rich_text || []);
  return `<ol class="customListStyle"><li>${text}</li></ol>\\n`;
}

function renderQuote(quote) {
  const text = renderRichText(quote?.rich_text || []);
  return `<blockquote class="notion-quote">${text}</blockquote>\\n`;
}

function renderTodo(todo) {
  const text = renderRichText(todo?.rich_text || []);
  const checked = todo?.checked;
  const checkbox = checked ? "☑" : "☐";
  return `<p class="notion-todo noSpacingAbove spacingBelow">${checkbox} ${text}</p>\\n`;
}

function renderToggle(toggle) {
  const text = renderRichText(toggle?.rich_text || []);
  return `<p class="notion-toggle noSpacingAbove spacingBelow"><strong>${text}</strong></p>\\n`;
}

function renderCallout(callout) {
  const text = renderRichText(callout?.rich_text || []);
  const emoji =
    callout?.icon?.type === "emoji" ? escapeHtml(callout.icon.emoji) : "💡";
  const colorClass =
    callout?.color && callout.color !== "default"
      ? ` notion-color-${callout.color}`
      : "";
  return `<div class="notion-callout${colorClass}"><span class="notion-callout-icon">${emoji}</span><span class="notion-callout-text">${text}</span></div>\\n`;
}

/* ======== TABLE ======== */

function renderTable(tableBlock) {
  const hasHeader = !!tableBlock.table?.has_column_header;
  const rows = Array.isArray(tableBlock.children)
    ? tableBlock.children
    : [];

  let thead = "";
  let tbody = "";

  rows.forEach((row, index) => {
    if (row.type !== "table_row") return;
    const cells = row.table_row?.cells || [];

    const cellTag = hasHeader && index === 0 ? "th" : "td";
    const rowHtml =
      "<tr>" +
      cells
        .map((cellRichTextArray) => {
          const cellText = renderRichText(cellRichTextArray || []);
          return `<${cellTag}>${cellText || "&nbsp;"}</${cellTag}>`;
        })
        .join("") +
      "</tr>";

    if (hasHeader && index === 0) {
      thead += rowHtml;
    } else {
      tbody += rowHtml;
    }
  });

  return `
<figure class="table canvasRteResponsiveTable tableLeftAlign" title="Table">
  <table class="customCells rteTableBackgroundTransparent rteTableUnboldTableHeaderCell">
    ${thead ? `<thead>${thead}</thead>` : ""}
    <tbody>${tbody}</tbody>
  </table>
</figure>\\n`;
}

/* ======== RICH TEXT ======== */

function renderRichText(richTextArray) {
  if (!Array.isArray(richTextArray)) return "";

  return richTextArray
    .map((rt) => {
      const plain =
        (rt.plain_text ||
          (rt.text && rt.text.content) ||
          "").replace(/\\n/g, "<br />");

      let html = escapeHtml(plain);

      const ann = rt.annotations || {};

      if (ann.code) html = `<code>${html}</code>`;
      if (ann.bold) html = `<strong>${html}</strong>`;
      if (ann.italic) html = `<em>${html}</em>`;
      if (ann.underline) html = `<u>${html}</u>`;
      if (ann.strikethrough) html = `<s>${html}</s>`;

      const href =
        rt.href || (rt.text && rt.text.link && rt.text.link.url) || null;
      if (href) {
        html = `<a href="${escapeHtml(href)}">${html}</a>`;
      }

      const color = ann.color;
      if (color && color !== "default") {
        html = `<span class="notion-color-${color}">${html}</span>`;
      }

      return html;
    })
    .join("");
}

/* ======== UTILS ======== */

function escapeHtml(str) {
  return String(str)
    .replace(/&/g, "&amp;")
    .replace(/</g, "&lt;")
    .replace(/>/g, "&gt;")
    .replace(/"/g, "&quot;")
    .replace(/'/g, "&#39;");
}

/* =============================================================================
   NOTION → SHAREPOINT WEBPART BUILDER
============================================================================= */

function notionBlocksToSharePointWebParts(notionBlocks, options = {}) {
  const webParts = [];
  let htmlBuffer = "";

  const flushTextWebPart = () => {
    if (!htmlBuffer.trim()) return;
    webParts.push({
      "@odata.type": "#microsoft.graph.textWebPart",
      innerHtml: htmlBuffer
    });
    htmlBuffer = "";
  };

  for (const block of notionBlocks) {
    switch (block.type) {
      // TEXT = merge into HTML buffer
      case "paragraph":
      case "heading_1":
      case "heading_2":
      case "heading_3":
      case "bulleted_list_item":
      case "numbered_list_item":
      case "quote":
      case "to_do":
      case "toggle":
      case "callout":
      case "divider":
      case "table":
        htmlBuffer += renderBlockToHtml(block);
        break;

      // SPECIAL BLOCKS = separate webparts
      case "image":
        flushTextWebPart();
        webParts.push(buildImageWebPart(block, options));
        break;

      case "video":
        flushTextWebPart();
        webParts.push(buildVideoWebPart(block, options));
        break;

      case "audio":
        flushTextWebPart();
        webParts.push(buildAudioWebPart(block, options));
        break;

      case "file":
        flushTextWebPart();
        webParts.push(buildFileWebPart(block, options));
        break;

      case "code":
        flushTextWebPart();
        webParts.push(buildCodeSnippetWebPart(block));
        break;

      case "bookmark":
        flushTextWebPart();
        webParts.push(addBookmarkToQuickLinks(block));
        break;

      case "child_page":
        flushTextWebPart();
        webParts.push(addChildPageQuickLink(block, options));
        break;

      case "child_database":
        flushTextWebPart();
        webParts.push(addDatabaseListWebPart(block, options));
        break;

      default:
        htmlBuffer += `<!-- unsupported block: ${block.type} -->\\n`;
        break;
    }
  }

  flushTextWebPart();
  return webParts;
}

/* =============================================================================
   BUILDER FUNCTIONS FOR SHAREPOINT WEBPART JSON
============================================================================= */

function buildImageWebPart(block, options = {}) {
  const img = block.image;
  const url =
    img?.file?.url ||
    img?.external?.url ||
    "";

  const fileName = url.split("/").pop().split("?")[0];

  return {
    "@odata.type": "#microsoft.graph.standardWebPart",
    "webPartType": "d1d91016-032f-456d-98a4-721247c305e8",
    "data": {
      "audiences": [],
      "dataVersion": "1.2",
      "title": "Image",
      "properties": {
        "imageSourceType": 2,
        "captionText": "",
        "altText": "",
        "overlayText": "",
        "fileName": fileName
      },
      "serverProcessedContent": {
        "imageSources": [
          {
            "key": "imageSource",
            "value": url
          }
        ],
        "links": [],
        "htmlStrings": [],
        "searchablePlainTexts": []
      }
    }
  };
}

function buildVideoWebPart(block, options = {}) {
  const video = block.video;

  // Case 1: YouTube or external URL = YouTube embed webpart
  if (video?.external?.url?.includes("youtube.com") || video?.external?.url?.includes("youtu.be")) {
    return {
      "@odata.type": "#microsoft.graph.standardWebPart",
      "webPartType": "544dd15b-cf3c-441b-96da-004d5a8cea1d",
      "data": {
        "dataVersion": "1.2",
        "title": "YouTube",
        "properties": {
          "embedCode": video.external.url,
          "thumbnailUrl": "",
          "shouldScaleWidth": true
        },
        "serverProcessedContent": {
          "htmlStrings": [],
          "imageSources": [],
          "links": []
        }
      }
    };
  }

  // Case 2: file video → File and Media
  const url = video?.file?.url || "";
  const fileName = url.split("/").pop().split("?")[0];

  return {
    "@odata.type": "#microsoft.graph.standardWebPart",
    "webPartType": "b7dd04e1-19ce-4b24-9132-b60a1c2b910d",
    "data": {
      "dataVersion": "1.4",
      "title": "Video",
      "properties": {
        "file": url,
        "fileName": fileName
      },
      "serverProcessedContent": {
        "links": [
          { key: "serverRelativeUrl", value: url }
        ],
        "searchablePlainTexts": [],
        "imageSources": []
      }
    }
  };
}

function buildAudioWebPart(block, options = {}) {
  const audio = block.audio;
  const url = audio?.external?.url || "";

  return {
    "@odata.type": "#microsoft.graph.standardWebPart",
    "webPartType": "490d7c76-1824-45b2-9de3-676421c997fa",
    "data": {
      "dataVersion": "1.2",
      "title": "Audio",
      "properties": {
        "embedCode": url,
        "shouldScaleWidth": true
      },
      "serverProcessedContent": {
        "links": [],
        "imageSources": [],
        "htmlStrings": []
      }
    }
  };
}

function buildFileWebPart(block, options = {}) {
  const file = block.file;
  const url =
    file?.external?.url ||
    file?.file?.url ||
    "";
  const fileName = url.split("/").pop().split("?")[0];

  return {
    "@odata.type": "#microsoft.graph.standardWebPart",
    "webPartType": "b7dd04e1-19ce-4b24-9132-b60a1c2b910d",
    "data": {
      "title": "File",
      "properties": { "file": url, "fileName": fileName },
      "serverProcessedContent": {
        "links": [{ key: "serverRelativeUrl", value: url }],
        "htmlStrings": [],
        "imageSources": [],
        "searchablePlainTexts": []
      }
    }
  };
}

function buildCodeSnippetWebPart(block) {
  const codeText = (block.code?.rich_text || [])
    .map((rt) => rt.plain_text)
    .join("");

  return {
    "@odata.type": "#microsoft.graph.standardWebPart",
    "webPartType": "7b317bca-c919-4982-af2f-8399173e5a1e",
    "data": {
      "title": "Code Snippet",
      "properties": {
        "language": block.code?.language || "javascript",
        "lineNumbers": true,
        "lineWrapping": true,
        "theme": "Monokai"
      },
      "serverProcessedContent": {
        "htmlStrings": [],
        "searchablePlainTexts": [
          { key: "code", value: codeText }
        ],
        "links": [],
        "imageSources": []
      }
    }
  };
}

function addBookmarkToQuickLinks(block) {
  const url = block.bookmark?.url || "";

  return {
    "@odata.type": "#microsoft.graph.standardWebPart",
    "webPartType": "c70391ea-0b10-4ee9-b2b4-006d3fcad0cd",
    "data": {
      "title": "Quick Link",
      "properties": {
        "layoutId": "CompactCard",
        "items": [
          {
            id: 1,
            name: "Bookmark",
            thumbnailType: 3,
            sourceItem: {
              url
            }
          }
        ]
      },
      "serverProcessedContent": {
        "links": [
          { key: "items[0].sourceItem.url", value: url }
        ],
        "htmlStrings": [],
        "imageSources": []
      }
    }
  };
}

function addChildPageQuickLink(block, options = {}) {
  const title = block.child_page?.title || "Child Page";
  const url = options.childPageResolver
    ? options.childPageResolver(block.id)
    : "#";

  return {
    "@odata.type": "#microsoft.graph.standardWebPart",
    "webPartType": "c70391ea-0b10-4ee9-b2b4-006d3fcad0cd",
    "data": {
      "title": "Subpage Link",
      "properties": {
        "layoutId": "CompactCard",
        "items": [
          {
            id: 1,
            name: title,
            thumbnailType: 3,
            sourceItem: {
              url
            }
          }
        ]
      },
      "serverProcessedContent": {
        "links": [
          { key: "items[0].sourceItem.url", value: url }
        ]
      }
    }
  };
}

function addDatabaseListWebPart(block, options = {}) {
  const listId =
    options.databaseListResolver?.(block.id) ||
    "00000000-0000-0000-0000-000000000000";

  return {
    "@odata.type": "#microsoft.graph.standardWebPart",
    "webPartType": "f92bf067-bc19-489e-a556-7fe95f508720",
    "data": {
      "title": block.child_database?.title || "Database",
      "properties": {
        "selectedListId": listId,
        "isDocumentLibrary": false
      },
      "serverProcessedContent": {
        "htmlStrings": [],
        "links": [],
        "imageSources": []
      }
    }
  };
}

All blocks space