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

TOC 左侧固定

  1. // ==UserScript==
  2. // @name Notion Sticky TOC (2022 Available)
  3. // @name:zh-CN Notion 固定左侧 TOC (2022 亲测可用)
  4. // @namespace https://github.com/soraliu
  5. // @version 0.6.0
  6. // @description Set Notion TOC Sticky.
  7. // @description:zh-cn TOC 左侧固定
  8. // @author Sora Liu<soraliu.dev@gmail.com>
  9. // @match https://www.notion.so/*
  10. // @grant none
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. /* jshint esversion:6 */
  15. (function() {
  16. 'use strict';
  17. // selectors
  18. const SELECTOR_NOTION_APP = 'notion-app';
  19. const SELECTOR_NOTION_SCROLLER = '.notion-scroller';
  20. const SELECTOR_NOTION_TOC = '.notion-table_of_contents-block';
  21. const SELECTOR_MODAL_PAGE = '.notion-peek-renderer'; // the selector which used to check if the page is in any modal
  22.  
  23. // toc config
  24. const TOC_CONFIG_WIDTH = '168px';
  25. const TOC_CONFIG_LEFT = '256px';
  26.  
  27. /* Helper function to wait for the element ready */
  28. const waitFor = (...selectors) => new Promise(resolve => {
  29. const delay = 500;
  30. const f = () => {
  31. const elements = selectors.map(selector => document.querySelector(selector));
  32. if (elements.every(element => element != null)) {
  33. resolve(elements);
  34. } else {
  35. setTimeout(f, delay);
  36. }
  37. };
  38. f();
  39. });
  40.  
  41. // for performance
  42. const LISTENED_SELECTORS = new WeakMap();
  43. const addScrollListener = (selectors, fn) => {
  44. let lastKnownScrollPosition = 0;
  45. let ticking = false;
  46.  
  47. fn(lastKnownScrollPosition); // init once
  48.  
  49. selectors.forEach(selector => {
  50. if (LISTENED_SELECTORS.has(selector)) {
  51. return;
  52. }
  53. // set listened
  54. LISTENED_SELECTORS.set(selector, true);
  55.  
  56. selector.addEventListener('scroll', function(e) {
  57. lastKnownScrollPosition = window.scrollY;
  58.  
  59. if (!ticking) {
  60. window.requestAnimationFrame(function() {
  61. fn(lastKnownScrollPosition);
  62. ticking = false;
  63. });
  64.  
  65. ticking = true;
  66. }
  67. }, false);
  68. });
  69. };
  70.  
  71. const callback = function(mutations) {
  72. waitFor(SELECTOR_NOTION_TOC).then(([el]) => {
  73. const toc = document.querySelector(SELECTOR_NOTION_TOC);
  74. const modal = document.querySelector(SELECTOR_MODAL_PAGE);
  75. if (!modal && toc) {
  76. toc.style.position = 'fixed';
  77. toc.style.top = '50%';
  78. toc.style.transform= 'translateY(-50%)';
  79. toc.style.zIndex = 999
  80. toc.style.maxHeight = 'calc(100vh - 168px)'
  81. toc.style.overflowY = 'auto'
  82.  
  83. const sidebarWidth = window.innerWidth - toc.closest(SELECTOR_NOTION_SCROLLER).clientWidth
  84. toc.style.left = `${sidebarWidth + 16}px`;
  85. toc.style.width = `${(toc.closest(SELECTOR_NOTION_SCROLLER).clientWidth - 900) / 2}px`
  86. }
  87. });
  88. };
  89.  
  90. const observer = new MutationObserver(callback);
  91. observer.observe(document.getElementById(SELECTOR_NOTION_APP), { childList: true, subtree: true } );
  92. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址