diff --git a/packages/exports/src/gSuite/docs/findPlaceholderIndex.ts b/packages/exports/src/gSuite/docs/findPlaceholderIndex.ts new file mode 100644 index 000000000..c31495607 --- /dev/null +++ b/packages/exports/src/gSuite/docs/findPlaceholderIndex.ts @@ -0,0 +1,42 @@ +import type { docs_v1 } from "@googleapis/docs"; + +/** + * Helper to find the index of a placeholder in the document. + */ +export async function findPlaceholderIndex( + googleDocs: docs_v1.Docs, + documentId: string, + placeholder: string, +): Promise { + const doc = await googleDocs.documents.get({ documentId }); + const body = doc.data.body?.content; + + if (!body) return null; + + for (const element of body) { + if (element.table) { + for (const row of element.table.tableRows || []) { + for (const cell of row.tableCells || []) { + for (const cellElement of cell.content || []) { + if (cellElement.paragraph?.elements) { + for (const textElement of cellElement.paragraph.elements) { + if (textElement.textRun?.content?.includes(placeholder)) { + return textElement.startIndex ?? null; + } + } + } + } + } + } + } + + if (element.paragraph?.elements) { + for (const textElement of element.paragraph.elements) { + if (textElement.textRun?.content?.includes(placeholder)) { + return textElement.startIndex ?? null; + } + } + } + } + return null; +} diff --git a/packages/exports/src/gSuite/docs/populateDoc.ts b/packages/exports/src/gSuite/docs/populateDoc.ts index 2af3b893d..849ecc7f1 100644 --- a/packages/exports/src/gSuite/docs/populateDoc.ts +++ b/packages/exports/src/gSuite/docs/populateDoc.ts @@ -1,11 +1,13 @@ import type { docs_v1 } from "@googleapis/docs"; import type { Result } from "../../types"; -import type { ValueToString} from "../../utils"; +import type { ValueToString } from "../../utils"; import { defaultValueToString } from "../../utils"; +import { processImageReplacements } from "./processImageReplacements"; +import { processTextReplacements } from "./processTextReplacements"; /** - * @description Populates the template document with the given data. + * Populates the template document with the given data, handling image replacements for all placeholders. */ export async function populateDoc< Data extends Record, @@ -26,34 +28,24 @@ export async function populateDoc< const requests: docs_v1.Schema$Request[] = []; const missingData: string[] = []; - Object.entries(data).forEach(([key, value]) => { - const valueStr = valueToString(key, value); - if (!valueStr.trim() && warnIfMissing.includes(key)) { - missingData.push(key); - } - requests.push({ - replaceAllText: { - replaceText: valueStr, - containsText: { - text: `{{${key}}}`, - matchCase: false, - }, - }, - }); - }); + await processImageReplacements({ googleDocs, documentId, data }); - await googleDocs.documents.batchUpdate({ + await processTextReplacements({ + googleDocs, documentId, - requestBody: { - requests, - }, + data, + missingData, + warnIfMissing, + valueToString, }); + return { data: { missingData, }, }; } catch (error) { + console.error("Failed to populate document:", error); return { error, message: "Failed to populate doc template", diff --git a/packages/exports/src/gSuite/docs/processImageReplacements.ts b/packages/exports/src/gSuite/docs/processImageReplacements.ts new file mode 100644 index 000000000..8f6edb3f4 --- /dev/null +++ b/packages/exports/src/gSuite/docs/processImageReplacements.ts @@ -0,0 +1,65 @@ +import type { docs_v1 } from "@googleapis/docs"; + +import { findPlaceholderIndex } from "./findPlaceholderIndex"; + +export async function processImageReplacements< + Data extends Record, +>({ + googleDocs, + documentId, + data, +}: { + googleDocs: docs_v1.Docs; + documentId: string; + data: Data; +}) { + // The method here is too locate the placeholder in the documents, delete it, and insert the image. + for (const [key, value] of Object.entries(data)) { + if (typeof value === "string" && value.includes("![image](")) { + const imageUrl = value.match(/!\[.*?\]\((.*?)\)/)?.[1]; + if (!imageUrl) continue; + + const placeholder = `{{${key}}}`; + const index = await findPlaceholderIndex( + googleDocs, + documentId, + placeholder, + ); + + if (index !== null) { + await googleDocs.documents.batchUpdate({ + documentId, + requestBody: { + requests: [ + { + deleteContentRange: { + range: { + startIndex: index, + endIndex: index + placeholder.length, + }, + }, + }, + ], + }, + }); + + await googleDocs.documents.batchUpdate({ + documentId, + requestBody: { + requests: [ + { + insertInlineImage: { + uri: imageUrl, + location: { + segmentId: null, + index, + }, + }, + }, + ], + }, + }); + } + } + } +} diff --git a/packages/exports/src/gSuite/docs/processTextReplacements.ts b/packages/exports/src/gSuite/docs/processTextReplacements.ts new file mode 100644 index 000000000..4e1cc48f2 --- /dev/null +++ b/packages/exports/src/gSuite/docs/processTextReplacements.ts @@ -0,0 +1,50 @@ +import type { docs_v1 } from "@googleapis/docs"; + +import type { ValueToString } from "../../utils"; + +export async function processTextReplacements< + Data extends Record, +>({ + googleDocs, + documentId, + data, + missingData, + warnIfMissing, + valueToString, +}: { + googleDocs: docs_v1.Docs; + documentId: string; + data: Data; + missingData: string[]; + warnIfMissing: (keyof Data)[]; + valueToString: ValueToString; +}) { + for (const [key, value] of Object.entries(data)) { + const valueStr = valueToString(key, value); + + // check if the value is empty and mark as missing if needed + if (!valueStr.trim() && warnIfMissing.includes(key)) { + missingData.push(key); + } + + // text replacement logic + if (typeof value !== "string" || !value.includes("![image](")) { + await googleDocs.documents.batchUpdate({ + documentId, + requestBody: { + requests: [ + { + replaceAllText: { + replaceText: valueStr, + containsText: { + text: `{{${key}}}`, + matchCase: false, + }, + }, + }, + ], + }, + }); + } + } +}