jupyter notebook auto scroll

Add auto scroll to bottom feature for jupyter notebook pages.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         jupyter notebook auto scroll
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  Add auto scroll to bottom feature for jupyter notebook pages.
// @author       Scruel Tao
// @homepage     https://github.com/scruel/tampermonkey-scripts/blob/main/jupyter-notebook-auto-scroll.js
// @match        http*://*/notebook*/*
// @grant        none
// ==/UserScript==

(function () {
    "use strict";

    var scrollDelay = 100;
    var defalutEnabled = true;

    var scrollEleIndex = 1;
    // To store the last scroll height attr of element.
    // If disable, set to -1.
    var scrollEleIndexMap = new Map();

    var lastScrollMap = new Map();
    var lastScrollTimekey = null;

    document.querySelectorAll(".output_scroll").forEach((e) => {
        e.scrollTop = e.scrollHeight;
    });

    function callScrollToBottom() {
        setTimeout(scrollToBottom, scrollDelay);
    }

    function scrollMonitor(event) {
        // Sometimes, the user needs to check the output. For output elements that have already scrolled
        // to the bottom, user typically will try to scroll up continuously on the output element, however
        // this operation will be failed if the auto-scroll feature is enabled, and they would have to
        // manually disable this feature before performing the upward scrolling. This function is mean to
        // make it more convenient: if the user attempts multiple upward scrolls, it will automatically
        // disable the auto-scroll feature to prevent the "meaningless scolling operations".
        var currentIndex = event.target.getAttribute("scroll_checkbox_index");
        if (!currentIndex) {
            return;
        }
        currentIndex = parseInt(currentIndex);
        const scrollHeight = scrollEleIndexMap.get(currentIndex);
        if (scrollHeight != -1) {
            const now = new Date();
            const timekey = `${now.getHours()}_${now.getMinutes()}`;
            // Only consider events within one minutes
            if (lastScrollTimekey != timekey) {
                lastScrollTimekey = timekey;
                lastScrollMap.clear();
            }
            const key = `${currentIndex}_${timekey}`;
            if (!lastScrollMap.has(key)) {
                lastScrollMap.set(key, 0);
            }

            if (scrollHeight - (event.target.scrollTop + event.target.clientHeight) > 0) {
                lastScrollMap.set(key, lastScrollMap.get(key) + 1);
            }
            if (lastScrollMap.get(key) > 7) {
                // Disable auto scroll via click checkbox
                document.querySelector(`input[scroll_checkbox_index="${currentIndex}"]`).click();
                lastScrollMap.clear();
            }
        }
    }

    function createCheckboxDiv(ele, currentIndex) {
        var div = document.createElement("div");
        var checkbox = document.createElement("input");
        checkbox.type = "checkbox";
        if (defalutEnabled) {
            checkbox.checked = "checked";
            scrollEleIndexMap.set(currentIndex, ele.scrollHeight);
        }
        checkbox.setAttribute("scroll_checkbox_index", currentIndex);
        checkbox.onclick = function () {
            if (checkbox.checked) {
                scrollEleIndexMap.set(currentIndex, ele.scrollHeight);
            } else {
                scrollEleIndexMap.set(currentIndex, -1);
            }
        };
        div.append("Auto Scroll: ");
        div.append(checkbox);
        return div;
    }

    function scrollToBottom() {
        const outputEles = document.querySelectorAll(".output_scroll");
        outputEles.forEach((ele) => {
            var currentIndex = ele.getAttribute("scroll_checkbox_index");
            if (!currentIndex) {
                currentIndex = scrollEleIndex++;
                ele.setAttribute("scroll_checkbox_index", currentIndex);
                const checkboxDiv = createCheckboxDiv(ele, currentIndex);
                ele.parentElement.before(checkboxDiv);
                ele.addEventListener("scrollend", scrollMonitor);
            } else {
                currentIndex = parseInt(currentIndex);
            }
            var autoScroll = scrollEleIndexMap.get(currentIndex) != -1;
            if (autoScroll) {
                ele.scrollTop = ele.scrollHeight;
            }
        });
        callScrollToBottom();
    }

    scrollToBottom();
})();