1
|
/**
|
2
|
* SPDX-License-Identifier: LicenseRef-GPL-3.0-or-later-WITH-js-exceptions
|
3
|
*
|
4
|
* Make spreadsheets on drive.google.com browsable without nonfree js
|
5
|
*
|
6
|
* Copyright (C) 2021 Wojtek Kosior <koszko@koszko.org>
|
7
|
*
|
8
|
* This program is free software: you can redistribute it and/or modify
|
9
|
* it under the terms of the GNU General Public License as published by
|
10
|
* the Free Software Foundation, either version 3 of the License, or
|
11
|
* (at your option) any later version.
|
12
|
*
|
13
|
* This program is distributed in the hope that it will be useful,
|
14
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
15
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
16
|
* GNU General Public License for more details.
|
17
|
*
|
18
|
* As additional permission under GNU GPL version 3 section 7, you
|
19
|
* may distribute forms of that code without the copy of the GNU
|
20
|
* GPL normally required by section 4, provided you include this
|
21
|
* license notice and, in case of non-source distribution, a URL
|
22
|
* through which recipients can access the Corresponding Source.
|
23
|
* If you modify file(s) with this exception, you may extend this
|
24
|
* exception to your version of the file(s), but you are not
|
25
|
* obligated to do so. If you do not wish to do so, delete this
|
26
|
* exception statement from your version.
|
27
|
*
|
28
|
* As a special exception to the GPL, any HTML file which merely
|
29
|
* makes function calls to this code, and for that purpose
|
30
|
* includes it by reference shall be deemed a separate work for
|
31
|
* copyright law purposes. If you modify this code, you may extend
|
32
|
* this exception to your version of the code, but you are not
|
33
|
* obligated to do so. If you do not wish to do so, delete this
|
34
|
* exception statement from your version.
|
35
|
*
|
36
|
* You should have received a copy of the GNU General Public License
|
37
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
38
|
*
|
39
|
* I, Wojtek Kosior, thereby promise not to sue for violation of this file's
|
40
|
* license. Although I request that you do not make use of this code in a
|
41
|
* proprietary program, I am not going to enforce this in court.
|
42
|
*/
|
43
|
|
44
|
/* Use with https://docs.google.com/spreadsheets/d/** */
|
45
|
|
46
|
/* Make the view scrollable. */
|
47
|
|
48
|
document.body.setAttribute("style",
|
49
|
"width: 100vw; height: 100vh; overflow: scroll;" +
|
50
|
(document.body.getAttribute("style") || ""));
|
51
|
|
52
|
let container = document.querySelectorAll(".waffle")[0];
|
53
|
let main_gid = null;
|
54
|
|
55
|
while (container) {
|
56
|
container = container.parentElement;
|
57
|
console.log(container);
|
58
|
if (container === document.body || !container)
|
59
|
break;
|
60
|
|
61
|
const match = /([0-9]+)-grid-container/.exec(container.id);
|
62
|
if (match)
|
63
|
main_gid = match[1];
|
64
|
|
65
|
container.setAttribute("style",
|
66
|
"width: fit-content; width: -moz-fit-content;");
|
67
|
}
|
68
|
|
69
|
|
70
|
/* Remove editor toolbars and bottom bar - these don't work anyway. */
|
71
|
|
72
|
const docs_chrome = document.getElementById("docs-chrome");
|
73
|
if (docs_chrome)
|
74
|
docs_chrome.remove();
|
75
|
const grid_bottom_bar = document.getElementById("grid-bottom-bar");
|
76
|
if (grid_bottom_bar)
|
77
|
grid_bottom_bar.remove();
|
78
|
|
79
|
|
80
|
/* Remove no Javascript warning. */
|
81
|
|
82
|
for (const no_js_warning of document.querySelectorAll("noscript"))
|
83
|
no_js_warning.remove();
|
84
|
|
85
|
|
86
|
/* Get opengraph data. */
|
87
|
|
88
|
const og = {};
|
89
|
|
90
|
for (const node of document.head.childNodes) {
|
91
|
if (node.tagName === "STYLE") {
|
92
|
document.head.removeChild(node);
|
93
|
continue;
|
94
|
}
|
95
|
|
96
|
if (node.tagName !== "META")
|
97
|
continue;
|
98
|
|
99
|
const match = /^og:(.+)/.exec(node.getAttribute("property"));
|
100
|
if (!match)
|
101
|
continue;
|
102
|
|
103
|
og[match[1]] = node.getAttribute("content");
|
104
|
}
|
105
|
|
106
|
|
107
|
/* Construct download link. */
|
108
|
|
109
|
let download_link = null;
|
110
|
|
111
|
const match = new RegExp("/spreadsheets/d/([^/]+)").exec(document.URL);
|
112
|
if (match)
|
113
|
download_link = `https://docs.google.com/spreadsheets/d/${match[1]}/export`;
|
114
|
|
115
|
|
116
|
/* Add title bar with sheet name and download button. */
|
117
|
|
118
|
const title_bar = document.createElement("div");
|
119
|
const title_heading = document.createElement("h1");
|
120
|
const title_text = document.createElement("span");
|
121
|
const main_download_button = document.createElement("a");
|
122
|
|
123
|
main_download_button.textContent = "download";
|
124
|
main_download_button.setAttribute("style", "border-radius: 10px; padding: 20px; color: #333; background-color: lightgreen; text-decoration: none; box-shadow: -4px 8px 8px #888; display: inline-block;");
|
125
|
|
126
|
if (og.title) {
|
127
|
title_text.textContent = og.title;
|
128
|
title_heading.appendChild(title_text);
|
129
|
}
|
130
|
|
131
|
title_text.setAttribute("style", "margin-right: 10px;");
|
132
|
|
133
|
if (download_link) {
|
134
|
main_download_button.setAttribute("href", download_link);
|
135
|
title_heading.appendChild(main_download_button);
|
136
|
}
|
137
|
|
138
|
title_bar.setAttribute("style", "padding: 0 20px; color: #555;");
|
139
|
|
140
|
title_bar.appendChild(title_heading);
|
141
|
|
142
|
document.body.insertBefore(title_bar, document.body.firstElementChild);
|
143
|
|
144
|
|
145
|
/* Extract sheet data from a script that sets the `bootstrapData' variable. */
|
146
|
|
147
|
let data = null;
|
148
|
for (const script of document.scripts) {
|
149
|
const match = /bootstrapData = ({([^;]|[^}];)+})/.exec(script.textContent);
|
150
|
if (!match)
|
151
|
continue;
|
152
|
data = JSON.parse(match[1]);
|
153
|
}
|
154
|
|
155
|
/*
|
156
|
* Add download buttons for individual sheets belonging to this spreadsheet.
|
157
|
* Data schema has been observed by looking at various spreadsheets.
|
158
|
*/
|
159
|
|
160
|
function add_sheet_download(data)
|
161
|
{
|
162
|
if (!Array.isArray(data))
|
163
|
return;
|
164
|
|
165
|
const gid = data[2];
|
166
|
if (!["string", "number"].includes(typeof gid))
|
167
|
return;
|
168
|
|
169
|
const sheet_download_link = `${download_link}?gid=${gid}`;
|
170
|
const sheet_download_button = document.createElement("a");
|
171
|
|
172
|
sheet_download_button.setAttribute("style", "border-radius: 5px; padding: 10px; color: #333; background-color: lightgreen; text-decoration: none; box-shadow: -4px 8px 8px #888; display: inline-block; margin: 0 5px 5px 0;");
|
173
|
sheet_download_button.setAttribute("href", sheet_download_link);
|
174
|
|
175
|
let sheet_name = null;
|
176
|
if (Array.isArray(data[3]) &&
|
177
|
data[3][0] && typeof data[3][0] === "object"
|
178
|
&& Array.isArray(data[3][0][1]) &&
|
179
|
Array.isArray(data[3][0][1][0]) &&
|
180
|
typeof data[3][0][1][0][2] === "string") {
|
181
|
|
182
|
const sheet_name = data[3][0][1][0][2];
|
183
|
sheet_download_button.textContent = sheet_name;
|
184
|
if (gid == main_gid)
|
185
|
title_text.textContent = `${title_text.textContent} - ${sheet_name}`;
|
186
|
} else {
|
187
|
sheet_download_button.textContent = `<sheet gid=${gid}>`;
|
188
|
}
|
189
|
|
190
|
title_bar.appendChild(sheet_download_button);
|
191
|
}
|
192
|
|
193
|
if (download_link) {
|
194
|
for (const entry of data.changes.topsnapshot) {
|
195
|
if (!Array.isArray(entry) || entry[0] !== 21350203 ||
|
196
|
typeof entry[1] !== "string")
|
197
|
continue;
|
198
|
|
199
|
let entry_data = null;
|
200
|
|
201
|
try {
|
202
|
entry_data = JSON.parse(entry[1]);
|
203
|
} catch (e) {
|
204
|
console.log(e);
|
205
|
continue;
|
206
|
}
|
207
|
|
208
|
add_sheet_download(entry_data);
|
209
|
}
|
210
|
}
|