From 91ee5c76d39cb1aebe34581857e62dbd3846e872 Mon Sep 17 00:00:00 2001 From: Wojtek Kosior Date: Tue, 16 Aug 2022 11:54:16 +0200 Subject: update Google Sheets fix to work with more sheet pages --- .../google_sheets_download.js | 351 ++++++++++++++------- src/docs-google-com-fix-sheets-display/index.json | 4 +- 2 files changed, 239 insertions(+), 116 deletions(-) (limited to 'src') diff --git a/src/docs-google-com-fix-sheets-display/google_sheets_download.js b/src/docs-google-com-fix-sheets-display/google_sheets_download.js index c4861c2..e4cd905 100644 --- a/src/docs-google-com-fix-sheets-display/google_sheets_download.js +++ b/src/docs-google-com-fix-sheets-display/google_sheets_download.js @@ -1,9 +1,9 @@ /** * SPDX-License-Identifier: LicenseRef-GPL-3.0-or-later-WITH-js-exceptions * - * Make spreadsheets on drive.google.com browsable without nonfree js + * Make spreadsheets on docs.google.com browsable without nonfree js. * - * Copyright (C) 2021 Wojtek Kosior + * Copyright (C) 2021,2022 Wojtek Kosior * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -43,168 +43,291 @@ /* Use with https://docs.google.com/spreadsheets/d/** */ -/* Make the view scrollable. */ +"use strict"; -document.body.setAttribute("style", - "width: 100vw; height: 100vh; overflow: scroll;" + - (document.body.getAttribute("style") || "")); +const current_params = new URLSearchParams(window.location.search); +const work_url = new URL(window.location.href); +let reload_needed = false; -let container = document.querySelectorAll(".waffle")[0]; -let main_gid = null; - -while (container) { - container = container.parentElement; - console.log(container); - if (container === document.body || !container) - break; - - const match = /([0-9]+)-grid-container/.exec(container.id); - if (match) - main_gid = match[1]; - - container.setAttribute("style", - "width: fit-content; width: -moz-fit-content;"); +/* + * URLs ending with "/pubhtml/sheet?" allow displaying a single sheet + * from a document. If we're on one of those URLs, reload to a corresponding URL + * ending with "/pub". + */ +if (work_url.pathname.endsWith("/pubhtml/sheet")) { + reload_needed = true; + work_url.pathname = work_url.pathname.replace(/pubhtml\/sheet$/, "pub"); } +/* + * The "widget=true" parameter in the URL may cause preview to be + * unavailable. If this is the case, reload without it. + */ +const widget_setting = current_params.get("widget"); +if (widget_setting !== null && widget_setting.toLowerCase() == "true") { + if (work_url.pathname.endsWith("/pub") || + work_url.pathname.endsWith("/pubhtml")) + reload_needed = true; +} -/* Remove editor toolbars and bottom bar - these don't work anyway. */ +/* + * When reloading, get rid or parameters other than "gid" - we don't use them + * anyway. + */ +if (reload_needed) { + const gid = current_params.get("gid"); + if (gid === null) + work_url.search = ""; + else + work_url.search = new URLSearchParams({gid}); + + window.location.href = work_url; +} -const docs_chrome = document.getElementById("docs-chrome"); -if (docs_chrome) - docs_chrome.remove(); -const grid_bottom_bar = document.getElementById("grid-bottom-bar"); -if (grid_bottom_bar) - grid_bottom_bar.remove(); +function initial_gid() { + let gid = null; + const param_strings_to_try = [ + window.location.hash.substring(1), + window.location.search.substring(1) + ]; -/* Remove no Javascript warning. */ + for (const params in param_strings_to_try) + gid = gid || new URLSearchParams(params).get("gid"); -for (const no_js_warning of document.querySelectorAll("noscript")) - no_js_warning.remove(); + return gid; +} +/* For URLs where path ends with "/edit". */ +function PageHandler_Edit() { +} -/* Get opengraph data. */ +PageHandler_Edit.prototype.get_sheets_count = function() { + /* Extract sheet data from a script that sets the `bootstrapData' variable. */ + let data = null; + for (const script of document.scripts) { + const match = /bootstrapData = ({([^;]|[^}];)+})/ + .exec(script.textContent); + if (!match) + continue; + data = JSON.parse(match[1]); + } -const og = {}; + let count = 0; -for (const node of document.head.childNodes) { - if (node.tagName === "STYLE") { - document.head.removeChild(node); - continue; + for (const entry of data.changes.topsnapshot) { + if (Array.isArray(entry) && entry[0] === 21350203) + count++; } - if (node.tagName !== "META") - continue; + return count; +} - const match = /^og:(.+)/.exec(node.getAttribute("property")); - if (!match) - continue; +PageHandler_Edit.prototype.make_preview = function() { + /* Make sure the view is scrollable. */ + document.body.style.width = "100vw"; + document.body.style.height = "100vh"; + document.body.style.overflow = "scroll"; - og[match[1]] = node.getAttribute("content"); -} + let container = document.querySelectorAll(".waffle")[0]; + let main_gid = null; + while (container) { + container = container.parentElement; + console.log(container); + if (container === document.body || !container) + break; -/* Construct download link. */ + const match = /([0-9]+)-grid-container/.exec(container.id); + if (match) + main_gid = match[1]; -let download_link = null; + container.removeAttribute("style"); + container.style.width = "-moz-fit-content"; + container.style.width = "fit-content"; + } -const match = new RegExp("/spreadsheets/d/([^/]+)").exec(document.URL); -if (match) - download_link = `https://docs.google.com/spreadsheets/d/${match[1]}/export`; + /* + * Hide editor bottom bar - it doesn't work anyway. Also hide no Javascript + * warning (if any). + */ + for (const selector of [ + "#grid-bottom-bar", + ".jfk-butterBar-warning", + "noscript" + ]) { + for (const element of document.querySelectorAll(selector)) + element.style.display = "none"; + } + /* Hide non-working tool bars and widgets above the sheet grid. */ + const editor_container = document.getElementById("docs-editor-container"); + for (const element of editor_container.parentElement.children) { + if (element !== editor_container) + element.style.display = "none"; + } -/* Add title bar with sheet name and download button. */ + /* Add information about additional sheets that are not visible. */ + try { + const subsheets_count = this.get_sheets_count(); + if (subsheets_count > 1) { + const notice = document.createElement("aside"); -const title_bar = document.createElement("div"); -const title_heading = document.createElement("h1"); -const title_text = document.createElement("span"); -const main_download_button = document.createElement("a"); + notice.style.display = "block"; + notice.style.backgroundColor = "white"; + notice.style.margin = "5px"; + notice.style.fontStyle = "italic"; -main_download_button.textContent = "download"; -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;"); + notice.innerText = `This document contains ${subsheets_count - 1} additional subsheet(s). Download to view.`; -if (og.title) { - title_text.textContent = og.title; - title_heading.appendChild(title_text); + editor_container.prepend(notice); + } + } catch(e) { + console.error(e); + } } -title_text.setAttribute("style", "margin-right: 10px;"); - -if (download_link) { - main_download_button.setAttribute("href", download_link); - title_heading.appendChild(main_download_button); +PageHandler_Edit.prototype.get_doc_title = function() { + const title_span = document.getElementById("docs-title-input-label-inner"); + return title_span ? title_span.innerText : ""; } -title_bar.setAttribute("style", "padding: 0 20px; color: #555;"); +PageHandler_Edit.prototype.get_download_link = function() { + const link = new URL(window.location.href); + link.pathname = link.pathname.replace(/edit$/, "export"); + link.search = new URLSearchParams({format: "ods"}); + + return "" + link; +} -title_bar.appendChild(title_heading); +/* For URLs where path ends with "/pub", "/htmlview" and "/pubhtml". */ +function PageHandler_PubHtmlviewPubhtml() { +} -document.body.insertBefore(title_bar, document.body.firstElementChild); +PageHandler_PubHtmlviewPubhtml.prototype.make_preview = function() { + /* Remove the "Updated automatically every 5 minutes" text. */ + for (const elem of [...document.querySelectorAll("#footer>.dash+a~*")]) + elem.remove(); + /* + * If there is more than one sheet, make sheets switchable through button + * clicks. + */ + const views = [...document.querySelectorAll("#sheets-viewport>[id]")]; -/* Extract sheet data from a script that sets the `bootstrapData' variable. */ + function get_button(sheet_view) { + return document.getElementById(`sheet-button-${sheet_view.id}`); + } -let data = null; -for (const script of document.scripts) { - const match = /bootstrapData = ({([^;]|[^}];)+})/.exec(script.textContent); - if (!match) - continue; - data = JSON.parse(match[1]); -} + function show_view(sheet_view) { + for (const processed_view of views) { + const button = get_button(processed_view); -/* - * Add download buttons for individual sheets belonging to this spreadsheet. - * Data schema has been observed by looking at various spreadsheets. - */ + processed_view.style.display = processed_view === sheet_view ? + "initial": "none"; -function add_sheet_download(data) -{ - if (!Array.isArray(data)) - return; + if (button !== null) + button.style.textDecoration = processed_view === sheet_view ? + "underline" : ""; + } + } - const gid = data[2]; - if (!["string", "number"].includes(typeof gid)) - return; + for (const sheet_view of views) { + const button = get_button(sheet_view); + if (button !== null) + button.addEventListener("click", () => show_view(sheet_view)); + } - const sheet_download_link = `${download_link}?gid=${gid}`; - const sheet_download_button = document.createElement("a"); + /* Make one of the sheets visible from the beginning. */ + if (views.length > 0) { + let initial_view = views[0]; - 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;"); - sheet_download_button.setAttribute("href", sheet_download_link); + const gid = "" + initial_gid(); - let sheet_name = null; - if (Array.isArray(data[3]) && - data[3][0] && typeof data[3][0] === "object" - && Array.isArray(data[3][0][1]) && - Array.isArray(data[3][0][1][0]) && - typeof data[3][0][1][0][2] === "string") { + for (const sheet_view of views) { + if (sheet_view.id === gid) + initial_view = sheet_view; + } - const sheet_name = data[3][0][1][0][2]; - sheet_download_button.textContent = sheet_name; - if (gid == main_gid) - title_text.textContent = `${title_text.textContent} - ${sheet_name}`; - } else { - sheet_download_button.textContent = ``; + show_view(initial_view); } - title_bar.appendChild(sheet_download_button); + /* Hide doc title (we'll be replacing it with our own element). */ + const doc_title_elem = document.getElementById("doc-title"); + if (doc_title_elem !== null) + doc_title_elem.style.display = "none"; } -if (download_link) { - for (const entry of data.changes.topsnapshot) { - if (!Array.isArray(entry) || entry[0] !== 21350203 || - typeof entry[1] !== "string") - continue; +PageHandler_PubHtmlviewPubhtml.prototype.get_doc_title = function() { + const title_span = document.querySelector("#doc-title .name"); + return title_span ? title_span.innerText : ""; +} + +PageHandler_PubHtmlviewPubhtml.prototype.get_download_link = function() { + if (!window.location.pathname.endsWith("/pub")) + return null; + + const link = new URL(window.location.href); + link.search = new URLSearchParams({output: "ods"}); + + return "" + link; +} + +function main() { + let page_handler = null; - let entry_data = null; + if (window.location.pathname.endsWith("/edit")) + page_handler = new PageHandler_Edit(); + else if (window.location.pathname.endsWith("/pub") || + window.location.pathname.endsWith("/htmlview") || + window.location.pathname.endsWith("/pubhtml")) + page_handler = new PageHandler_PubHtmlviewPubhtml(); + if (page_handler === null) { + console.error("Unknown type of docs page."); + } else { try { - entry_data = JSON.parse(entry[1]); - } catch (e) { - console.log(e); - continue; + page_handler.make_preview(); + } catch(e) { + console.error(e); + } + + /* Add our own title&download bar */ + const title = page_handler.get_doc_title(); + const download_link = page_handler.get_download_link(); + + const doc_heading = document.createElement("h3"); + const title_text = document.createElement("span"); + const download_button = document.createElement("a"); + + title_text.style.margin = "0 20px"; + title_text.textContent = title; + doc_heading.append(title_text); + + download_button.style.borderRadius = "5px"; + download_button.style.padding = "10px"; + download_button.style.color = "#333"; + download_button.style.backgroundColor = "lightgreen"; + download_button.style.textDecoration = "none"; + download_button.style.boxShadow = "-4px 8px 8px #888"; + download_button.style.display = "inline-block"; + download_button.textContent = "download"; + + if (download_link === null) { + download_button.style.backgroundColor = "lightgray"; + download_button.style.userSelect = "none"; + download_button.style.color = "#606060"; + download_button.textContent = "download unavailable"; + } else { + download_button.href = download_link; } - add_sheet_download(entry_data); + doc_heading.append(download_button); + + doc_heading.style.padding = "0 20px; color: #555"; + document.body.prepend(doc_heading); } } + +if (!reload_needed) + main(); diff --git a/src/docs-google-com-fix-sheets-display/index.json b/src/docs-google-com-fix-sheets-display/index.json index 92fee67..f7fc848 100644 --- a/src/docs-google-com-fix-sheets-display/index.json +++ b/src/docs-google-com-fix-sheets-display/index.json @@ -20,8 +20,8 @@ "identifier": "docs-google-com-fix-sheets-display", "long_name": "Google Sheets display&download fix", "uuid": "ad496895-3302-4045-8f2d-ecac8f151d5b", - "version": [2022, 2, 21], - "revision": 2, + "version": [2022, 8, 16], + "revision": 1, "description": "Make spreadsheets on docs.google.com viewable and downloadable without relying on site-served JavaScript.", "dependencies": [], "scripts": [{"file": "google_sheets_download.js"}], -- cgit v1.2.3