Greasy Fork is available in English.

Notion 固定左侧 TOC (2022 亲测可用)

TOC 左侧固定

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Notion Sticky TOC (2022 Available)
// @name:zh-CN   Notion 固定左侧 TOC (2022 亲测可用)
// @namespace    https://github.com/soraliu
// @version      0.6.0
// @description  Set Notion TOC Sticky.
// @description:zh-cn TOC 左侧固定
// @author       Sora Liu<[email protected]>
// @match        https://www.notion.so/*
// @grant        none
// @license MIT
// ==/UserScript==

/* jshint esversion:6 */
(function() {
    'use strict';
    // selectors
    const SELECTOR_NOTION_APP = 'notion-app';
    const SELECTOR_NOTION_SCROLLER = '.notion-scroller';
    const SELECTOR_NOTION_TOC = '.notion-table_of_contents-block';
    const SELECTOR_MODAL_PAGE = '.notion-peek-renderer'; // the selector which used to check if the page is in any modal

    // toc config
    const TOC_CONFIG_WIDTH = '168px';
    const TOC_CONFIG_LEFT = '256px';

    /* Helper function to wait for the element ready */
    const waitFor = (...selectors) => new Promise(resolve => {
        const delay = 500;
        const f = () => {
            const elements = selectors.map(selector => document.querySelector(selector));
            if (elements.every(element => element != null)) {
                resolve(elements);
            } else {
                setTimeout(f, delay);
            }
        };
        f();
    });

    // for performance
    const LISTENED_SELECTORS = new WeakMap();
    const addScrollListener = (selectors, fn) => {
        let lastKnownScrollPosition = 0;
        let ticking = false;

        fn(lastKnownScrollPosition); // init once

        selectors.forEach(selector => {
            if (LISTENED_SELECTORS.has(selector)) {
                return;
            }
            // set listened
            LISTENED_SELECTORS.set(selector, true);

            selector.addEventListener('scroll', function(e) {
                lastKnownScrollPosition = window.scrollY;

                if (!ticking) {
                    window.requestAnimationFrame(function() {
                        fn(lastKnownScrollPosition);
                        ticking = false;
                    });

                    ticking = true;
                }
            }, false);
        });
    };

    const callback = function(mutations) {
        waitFor(SELECTOR_NOTION_TOC).then(([el]) => {
            const toc = document.querySelector(SELECTOR_NOTION_TOC);
            const modal = document.querySelector(SELECTOR_MODAL_PAGE);
            if (!modal && toc) {
                toc.style.position = 'fixed';
                toc.style.top = '50%';
                toc.style.transform= 'translateY(-50%)';
                toc.style.zIndex = 999
                toc.style.maxHeight = 'calc(100vh - 168px)'
                toc.style.overflowY = 'auto'

                const sidebarWidth = window.innerWidth - toc.closest(SELECTOR_NOTION_SCROLLER).clientWidth
                toc.style.left = `${sidebarWidth + 16}px`;
                toc.style.width = `${(toc.closest(SELECTOR_NOTION_SCROLLER).clientWidth - 900) / 2}px`
            }
        });
    };

    const observer = new MutationObserver(callback);
    observer.observe(document.getElementById(SELECTOR_NOTION_APP), { childList: true, subtree: true } );
})();