software/hoardy-web/./extension/page/help.js

Passively capture, archive, and hoard your web browsing history, including the contents of the pages you visit, for later offline viewing, replay, mirroring, data scraping, and/or indexing. Your own personal private Wayback Machine that can also archive HTTP POST requests and responses, as well as most other HTTP-level data.

Files

Raw Source

Contents

/*
 * Copyright (c) 2023-2025 Jan Malakhovski <oxij@oxij.org>
 *
 * This file is a part of `hoardy-web` project.
 *
 * 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
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * The "Help" page.
 */

"use strict";

document.addEventListener("DOMContentLoaded", async () => {
    let body = document.getElementById("body");
    let iframe = document.getElementById("iframe");
    let minWidth = 1355; // see ./help.template
    let columns = true;

    // allow to un-highlight currently highlighted node
    document.body.addEventListener("click", (event) => {
        highlightNode(null);
        broadcastToPopup("highlightNode", null);
    });

    setupHistoryPopState();
    classifyDocumentLinks(document, [
        ["/page/help.html", "internal"],
        ["/page/popup.html", "popup"],
        ["/", "local"],
    ], (link, info) => {
        let klass = info.klass;
        if (klass !== undefined)
            link.classList.add(klass);

        switch (info.klass) {
        case "internal":
            link.href = "javascript:void(0)";
            link.onclick = (event) => {
                event.cancelBubble = true;
                historyFromTo({ id: info.id }, { id: info.target });
                focusNode(info.target);
            };
            link.onmouseover = (event) => {
                if (columns)
                    broadcastToPopup("highlightNode", null);
            };
            break;
        case "popup":
            link.href = "javascript:void(0)";
            link.onclick = (event) => {
                event.cancelBubble = true;
                if (!columns)
                    historyFromTo({ id: info.id }, info.href);
                broadcastToPopup("focusNode", info.target);
            };
            link.onmouseover = (event) => {
                if (columns)
                    broadcastToPopup("focusNode", info.target);
            };
            break;
        case "local":
        default:
            link.onclick = (event) => {
                historyFromTo({ id: info.id });
            };
            link.onmouseover = (event) => {
                if (columns)
                    broadcastToPopup("highlightNode", null);
            };
        }
    });

    // Resize elements to window. This switches between `columns` and
    // `linear` layouts depending on width. This part is not done via
    // `CSS` because we use the `columns` value above too.
    function resize() {
        let w = window.innerWidth;
        let h = window.innerHeight;

        console.log("current viewport:", w, h);
        columns = w >= minWidth;

        setConditionalClass(document.body, "columns", columns);
        setConditionalClass(document.body, "linear", !columns);

        // Prevent independent scroll in `columns` layout.
        let h1 = columns ? `${h - 5}px` : null;
        body.style["min-height"]
            = body.style["max-height"]
            = body.style["height"]
            = h1;

        let ib = iframe.contentDocument.body;
        if (ib === null)
            // not yet loaded
            return;

        // Prevent in-iframe scroll in `linear` layout.
        let h2 = columns ? h1 : `${ib.scrollHeight + 20}px`;
        iframe.style["min-height"]
            = iframe.style["max-height"]
            = iframe.style["height"]
            = h2;
    }

    // expand shortcut macros
    let shortcuts = await getShortcuts();
    macroShortcuts(body, shortcuts, (inner, shortcut, sname) => {
        let sk = manifest.commands[sname];
        let def;
        if (sk.suggested_key && sk.suggested_key.default)
            def = sk.suggested_key.default;
        if (def) {
            if (shortcut) {
                return (shortcut === def)
                    ? `currently bound to \`${shortcut}\` (= default)`
                    : `currently bound to \`${shortcut}\` (default: \`${def}\`)`
            } else
                return `unbound at the moment (default: \`${def}\`)`;
        } else if (shortcut)
            return `currently bound to \`${shortcut}\` (default: unbound)`
        else
            return `unbound at the moment (= default)`;
    });

    async function processUpdate(update) {
        let [what, data] = update;
        switch (what) {
        case "updateConfig":
            setRootClasses(data);
            break;
        case "popupResized":
            resize();
            break;
        default:
            await webextRPCHandleMessageDefault(update);
        }
    }

    // add default handlers
    await subscribeToExtensionSimple("help", catchAll(processUpdate));

    {
        let config = await browser.runtime.sendMessage(["getConfig"]);
        setRootClasses(config);
    }

    window.onresize = catchAll(resize);
    catchAll(resize)();

    // give it a chance to re-compute the layout
    await sleep(1);

    // show UI
    document.body.style["visibility"] = null;

    // highlight current target
    focusHashNode();
});