0

Insert Table

by
Published today

Insert a table into a Google Doc and fill it with rows of values.

Script gdocs Verified

The script

Submitted by hugo989 Typescript (fetch-only)
Verified 5 hours ago
1
//native
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
  // The insert reply has no location: the new table is the last one, or the first one starting after `index`.
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
  // Fill cells from last to first so earlier cells' indexes stay valid.
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