jupyter notebook auto scroll

Add auto scroll to bottom feature for jupyter notebook pages.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==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();
})();