Skip to content

Commit

Permalink
feat: allow for quiz exports to handle images
Browse files Browse the repository at this point in the history
  • Loading branch information
tomwisecodes committed Nov 11, 2024
1 parent b4f24d8 commit 44f8c5d
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 21 deletions.
42 changes: 42 additions & 0 deletions packages/exports/src/gSuite/docs/findPlaceholderIndex.ts
Original file line number Diff line number Diff line change
@@ -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<number | null> {
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;
}
34 changes: 13 additions & 21 deletions packages/exports/src/gSuite/docs/populateDoc.ts
Original file line number Diff line number Diff line change
@@ -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<string, string | string[] | null | undefined>,
Expand All @@ -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",
Expand Down
65 changes: 65 additions & 0 deletions packages/exports/src/gSuite/docs/processImageReplacements.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { docs_v1 } from "@googleapis/docs";

import { findPlaceholderIndex } from "./findPlaceholderIndex";

export async function processImageReplacements<
Data extends Record<string, string | string[] | null | undefined>,
>({
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,
},
},
},
],
},
});
}
}
}
}
50 changes: 50 additions & 0 deletions packages/exports/src/gSuite/docs/processTextReplacements.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import type { docs_v1 } from "@googleapis/docs";

import type { ValueToString } from "../../utils";

export async function processTextReplacements<
Data extends Record<string, string | string[] | null | undefined>,
>({
googleDocs,
documentId,
data,
missingData,
warnIfMissing,
valueToString,
}: {
googleDocs: docs_v1.Docs;
documentId: string;
data: Data;
missingData: string[];
warnIfMissing: (keyof Data)[];
valueToString: ValueToString<Data>;
}) {
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,
},
},
},
],
},
});
}
}
}

0 comments on commit 44f8c5d

Please sign in to comment.