1 | |
2 |
|
3 | |
4 | * Insert Table |
5 | * Insert a table into a Google Doc and fill it from `rows`, an array of rows of cell values, e.g. `[["Name","Role"],["Ada","Engineer"]]` (use empty strings for an empty table). The column count is the longest row. Leave `index` empty to append at the end; `bold_header` bolds the first row. Set `tab_id` (from List Tabs) to target a tab other than the first. Returns the table's start index. |
6 | */ |
7 | export async function main( |
8 | auth: RT.Gdocs, |
9 | document_id: string, |
10 | rows: string[][], |
11 | index: number | undefined, |
12 | bold_header: boolean = false, |
13 | tab_id: string | undefined |
14 | ) { |
15 | const tabId = tab_id || undefined |
16 | const columns = Math.max(...rows.map((r) => r.length)) |
17 | if (!rows.length || !columns) throw new Error("rows must not be empty") |
18 |
|
19 | await batchUpdate(auth, document_id, [ |
20 | { |
21 | insertTable: { |
22 | rows: rows.length, |
23 | columns, |
24 | ...(index == null |
25 | ? { endOfSegmentLocation: { tabId } } |
26 | : { location: { index, tabId } }), |
27 | }, |
28 | }, |
29 | ]) |
30 |
|
31 | |
32 | const tables = (await getBody(auth, document_id, tabId)).filter( |
33 | (el: any) => el.table |
34 | ) |
35 | const table = |
36 | index == null |
37 | ? tables.at(-1) |
38 | : tables.find((t: any) => t.startIndex >= index) |
39 |
|
40 | |
41 | const requests: any[] = [] |
42 | table.table.tableRows.forEach((row: any, r: number) => |
43 | row.tableCells.forEach((cell: any, c: number) => { |
44 | const text = String(rows[r]?.[c] ?? "") |
45 | if (!text) return |
46 | const start = cell.content[0].startIndex |
47 | requests.unshift({ insertText: { location: { index: start, tabId }, text } }) |
48 | if (bold_header && r === 0) |
49 | requests.splice(1, 0, { |
50 | updateTextStyle: { |
51 | range: { startIndex: start, endIndex: start + text.length, tabId }, |
52 | textStyle: { bold: true }, |
53 | fields: "bold", |
54 | }, |
55 | }) |
56 | }) |
57 | ) |
58 | if (requests.length) await batchUpdate(auth, document_id, requests) |
59 |
|
60 | return { |
61 | document_id, |
62 | table_start_index: table.startIndex, |
63 | rows: rows.length, |
64 | columns, |
65 | } |
66 | } |
67 |
|
68 | async function batchUpdate(auth: RT.Gdocs, document_id: string, requests: any[]) { |
69 | const response = await fetch( |
70 | `https://docs.googleapis.com/v1/documents/${document_id}:batchUpdate`, |
71 | { |
72 | method: "POST", |
73 | headers: { |
74 | Authorization: `Bearer ${auth.token}`, |
75 | "Content-Type": "application/json", |
76 | Accept: "application/json", |
77 | }, |
78 | body: JSON.stringify({ requests }), |
79 | } |
80 | ) |
81 | if (!response.ok) { |
82 | throw new Error(`${response.status} ${await response.text()}`) |
83 | } |
84 | return await response.json() |
85 | } |
86 |
|
87 | async function getBody(auth: RT.Gdocs, document_id: string, tab_id?: string) { |
88 | const url = new URL(`https://docs.googleapis.com/v1/documents/${document_id}`) |
89 | if (tab_id) url.searchParams.append("includeTabsContent", "true") |
90 | const response = await fetch(url, { |
91 | headers: { |
92 | Authorization: `Bearer ${auth.token}`, |
93 | Accept: "application/json", |
94 | }, |
95 | }) |
96 | if (!response.ok) { |
97 | throw new Error(`${response.status} ${await response.text()}`) |
98 | } |
99 | const doc = await response.json() |
100 | if (!tab_id) return doc.body.content |
101 | const find = (tabs: any[]): any => |
102 | tabs.map((t) => (t.tabProperties.tabId === tab_id ? t : find(t.childTabs ?? []))).find(Boolean) |
103 | return find(doc.tabs).documentTab.body.content |
104 | } |
105 |
|