Inserting texts on google doc and hyperlink specific keywords from that google doc

I am trying to hyperlink the texts on google doc but I am unable to do so.

I can see the blog being posted on google doc but instead of hyperlink I am getting the entire website link besides the keyword that was supposed to be hyper linked

Share the output returned by the last node

Information on your n8n setup

  • **n8n version:1.97.0
  • **Database (default: SQLite): everything is google sheet
  • n8n EXECUTIONS_PROCESS setting (default: own, main):
  • **Running n8n via (Docker, npm, n8n cloud, desktop app):docker
  • **Operating system:windows

I guess it’s still need to be processed via API like this

Yes! I tried something similar. But I was trying to publish with hyperlinked text. I will try to publish the texts on the google doc → retrieve the data → apply markdown and hyperlink and again try to publish the texts.

i hope it does not hyperlink all the selected texts instance on google doc

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
    };
}
1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.