Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow for quiz exports to handle images #357

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// The method here is too locate the placeholder in the documents, delete it, and insert the image.
// The method here is to 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,
},
},
},
],
},
});
}
}
}
Loading