Found a Solution!
I added a code block that parses the markdown text into google doc requests
// Function to convert Markdown to Google Docs API requests
function convertMarkdownToGoogleDocsRequests(markdown) {
const requests = [];
let index = 1; // Start inserting at position 1
// Split by line breaks to handle formatting separately
const lines = markdown.split('\n');
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
const rawText = lines[lineNum].trim();
// Handle empty lines
if (!rawText) {
// Insert just a newline for empty lines
requests.push({
insertText: {
location: { index },
text: "\n"
}
});
index += 1;
continue;
}
let text = rawText;
const lineStartIndex = index;
// Track how many characters we remove from the beginning
let prefixRemoved = 0;
// Remove the markdown symbols for heading and list indicators
if (text.startsWith("# ")) {
text = text.substring(2);
prefixRemoved = 2;
} else if (text.startsWith("## ")) {
text = text.substring(3);
prefixRemoved = 3;
} else if (text.startsWith("### ")) {
text = text.substring(4);
prefixRemoved = 4;
} else if (text.startsWith("#### ")) {
text = text.substring(5);
prefixRemoved = 5;
} else if (text.startsWith("- ")) {
text = text.substring(2).trimStart(); // Trim any extra spaces
prefixRemoved = rawText.length - text.length;
} else if (text.startsWith("* ")) {
text = text.substring(2).trimStart(); // Trim any extra spaces
prefixRemoved = rawText.length - text.length;
} else if (/^\d+\.\s/.test(text)) {
const match = text.match(/^\d+\.\s+/); // Match number, dot, and all spaces
text = text.substring(match[0].length);
prefixRemoved = match[0].length;
}
// Parse and process text with formatting
const parsedContent = parseMarkdownLine(text);
// Insert the clean text
requests.push({
insertText: {
location: { index: lineStartIndex },
text: parsedContent.cleanText + "\n"
}
});
// Calculate the actual end position of the text (before newline)
const textEndIndex = lineStartIndex + parsedContent.cleanText.length;
// Apply paragraph styles ONLY if there's actual content
if (parsedContent.cleanText.length > 0) {
// Apply heading styles
if (rawText.startsWith("# ")) {
requests.push({
updateParagraphStyle: {
range: { startIndex: lineStartIndex, endIndex: textEndIndex },
paragraphStyle: { namedStyleType: "HEADING_1" },
fields: "namedStyleType"
}
});
} else if (rawText.startsWith("## ")) {
requests.push({
updateParagraphStyle: {
range: { startIndex: lineStartIndex, endIndex: textEndIndex },
paragraphStyle: { namedStyleType: "HEADING_2" },
fields: "namedStyleType"
}
});
} else if (rawText.startsWith("### ")) {
requests.push({
updateParagraphStyle: {
range: { startIndex: lineStartIndex, endIndex: textEndIndex },
paragraphStyle: { namedStyleType: "HEADING_3" },
fields: "namedStyleType"
}
});
} else if (rawText.startsWith("#### ")) {
requests.push({
updateParagraphStyle: {
range: { startIndex: lineStartIndex, endIndex: textEndIndex },
paragraphStyle: { namedStyleType: "HEADING_4" },
fields: "namedStyleType"
}
});
} else if (rawText.startsWith("- ") || rawText.startsWith("* ")) {
// Format as bulleted list
requests.push({
createParagraphBullets: {
range: { startIndex: lineStartIndex, endIndex: textEndIndex },
bulletPreset: "BULLET_DISC_CIRCLE_SQUARE"
}
});
// Add proper indentation
requests.push({
updateParagraphStyle: {
range: { startIndex: lineStartIndex, endIndex: textEndIndex },
paragraphStyle: {
indentStart: { magnitude: 36, unit: "PT" }
},
fields: "indentStart"
}
});
} else if (/^\d+\.\s/.test(rawText)) {
// Format as numbered list
requests.push({
createParagraphBullets: {
range: { startIndex: lineStartIndex, endIndex: textEndIndex },
bulletPreset: "NUMBERED_DECIMAL_ALPHA_ROMAN"
}
});
// Add proper indentation
requests.push({
updateParagraphStyle: {
range: { startIndex: lineStartIndex, endIndex: textEndIndex },
paragraphStyle: {
indentStart: { magnitude: 36, unit: "PT" }
},
fields: "indentStart"
}
});
}
}
// Apply text formatting
for (const format of parsedContent.formatting) {
const startPos = lineStartIndex + format.start;
const endPos = lineStartIndex + format.end;
// Ensure we're not going beyond the text bounds
if (endPos > textEndIndex) {
console.warn(`Skipping formatting that would exceed text bounds at ${startPos}-${endPos}, text ends at ${textEndIndex}`);
continue;
}
const textStyle = {};
const fields = [];
if (format.bold) {
textStyle.bold = true;
fields.push("bold");
}
if (format.italic) {
textStyle.italic = true;
fields.push("italic");
}
if (format.link) {
textStyle.link = { url: format.link };
fields.push("link");
}
if (fields.length > 0) {
requests.push({
updateTextStyle: {
range: {
startIndex: startPos,
endIndex: endPos
},
textStyle: textStyle,
fields: fields.join(",")
}
});
}
}
// Update index for next line (clean text length + newline)
index = textEndIndex + 1;
}
return requests;
}
// Helper function to parse markdown formatting in a line
function parseMarkdownLine(text) {
const formatting = [];
let cleanText = '';
let currentPos = 0;
let cleanPos = 0;
// Process character by character to handle nested formatting
while (currentPos < text.length) {
// Check for bold
if (text.substring(currentPos, currentPos + 2) === '**') {
const boldEnd = text.indexOf('**', currentPos + 2);
if (boldEnd !== -1) {
const boldContent = text.substring(currentPos + 2, boldEnd);
// Check if bold contains a link
const linkMatch = /^\[([^\]]+)\]\(([^)]+)\)$/.exec(boldContent);
if (linkMatch) {
// Bold link combo
const linkText = linkMatch[1];
const linkUrl = linkMatch[2];
formatting.push({
start: cleanPos,
end: cleanPos + linkText.length,
bold: true,
link: linkUrl
});
cleanText += linkText;
cleanPos += linkText.length;
currentPos = boldEnd + 2;
} else {
// Regular bold
const innerParsed = parseMarkdownLine(boldContent);
// Add bold formatting to all inner content
for (const innerFormat of innerParsed.formatting) {
formatting.push({
start: cleanPos + innerFormat.start,
end: cleanPos + innerFormat.end,
bold: true,
italic: innerFormat.italic,
link: innerFormat.link
});
}
// If no inner formatting, add bold to entire content
if (innerParsed.formatting.length === 0) {
formatting.push({
start: cleanPos,
end: cleanPos + innerParsed.cleanText.length,
bold: true
});
}
cleanText += innerParsed.cleanText;
cleanPos += innerParsed.cleanText.length;
currentPos = boldEnd + 2;
}
continue;
}
}
// Check for italic
if (text[currentPos] === '*' &&
(currentPos === 0 || text[currentPos - 1] !== '*') &&
(currentPos + 1 < text.length && text[currentPos + 1] !== '*')) {
const italicEnd = text.indexOf('*', currentPos + 1);
if (italicEnd !== -1 &&
(italicEnd + 1 >= text.length || text[italicEnd + 1] !== '*')) {
const italicContent = text.substring(currentPos + 1, italicEnd);
const innerParsed = parseMarkdownLine(italicContent);
// Add italic formatting to all inner content
for (const innerFormat of innerParsed.formatting) {
formatting.push({
start: cleanPos + innerFormat.start,
end: cleanPos + innerFormat.end,
bold: innerFormat.bold,
italic: true,
link: innerFormat.link
});
}
// If no inner formatting, add italic to entire content
if (innerParsed.formatting.length === 0) {
formatting.push({
start: cleanPos,
end: cleanPos + innerParsed.cleanText.length,
italic: true
});
}
cleanText += innerParsed.cleanText;
cleanPos += innerParsed.cleanText.length;
currentPos = italicEnd + 1;
continue;
}
}
// Check for link
if (text[currentPos] === '[') {
const linkTextEnd = text.indexOf(']', currentPos + 1);
if (linkTextEnd !== -1 && text[linkTextEnd + 1] === '(') {
const linkUrlEnd = text.indexOf(')', linkTextEnd + 2);
if (linkUrlEnd !== -1) {
const linkText = text.substring(currentPos + 1, linkTextEnd);
const linkUrl = text.substring(linkTextEnd + 2, linkUrlEnd);
formatting.push({
start: cleanPos,
end: cleanPos + linkText.length,
link: linkUrl
});
cleanText += linkText;
cleanPos += linkText.length;
currentPos = linkUrlEnd + 1;
continue;
}
}
}
// Regular character
cleanText += text[currentPos];
cleanPos += 1;
currentPos += 1;
}
return {
cleanText: cleanText,
formatting: formatting
};
}