|
// ==UserScript== |
|
// @name Notion Heading Navigator |
|
// @namespace x.com/Jehong_Ahn |
|
// @version 0.1 |
|
// @description Keyboard shortcuts for navigating between headings in Notion |
|
// @author Jehong_Ahn |
|
// @homepageURL https://x.com/Jehong_Ahn |
|
// @updateURL https://gist.githubusercontent.com/Jehong-Ahn/2ffa68d580a0dac99e15dcda96885ad0/raw/notion-heading-navigator.js |
|
// @downloadURL https://gist.githubusercontent.com/Jehong-Ahn/2ffa68d580a0dac99e15dcda96885ad0/raw/notion-heading-navigator.js |
|
// @match https://www.notion.so/* |
|
// @grant none |
|
// ==/UserScript== |
|
|
|
(function() { |
|
'use strict'; |
|
|
|
// Selector for Notion heading blocks |
|
const HEADING_SELECTOR = '.notion-sub_header-block'; |
|
|
|
// Keyboard shortcut configuration |
|
const KEY_CONFIG = { |
|
next: { |
|
key: ']', |
|
alt: true |
|
}, |
|
prev: { |
|
key: '[', |
|
alt: true |
|
} |
|
}; |
|
|
|
// Get all headings with their position information |
|
function getVisibleHeadings() { |
|
const headings = Array.from(document.querySelectorAll(HEADING_SELECTOR)); |
|
const viewportHeight = window.innerHeight; |
|
|
|
return headings.map(heading => { |
|
const rect = heading.getBoundingClientRect(); |
|
return { |
|
element: heading, |
|
top: rect.top, |
|
// true if the heading is within the viewport |
|
isVisible: rect.top >= 0 && rect.top <= viewportHeight |
|
}; |
|
}); |
|
} |
|
|
|
// Navigate to the next or previous heading |
|
function moveToHeading(direction) { |
|
const headings = getVisibleHeadings(); |
|
if (!headings.length) return; |
|
|
|
// Find currently visible heading |
|
const visibleHeading = headings.find(h => h.isVisible); |
|
|
|
let targetHeading; |
|
if (direction === 'next') { |
|
// If there's a visible heading, find the next one |
|
// If not, find the first heading below the current scroll position |
|
targetHeading = visibleHeading |
|
? headings.find(h => h.top > visibleHeading.top + 10) |
|
: headings.find(h => h.top > 0); |
|
} else { |
|
// For previous direction, reverse the array and find the last heading above |
|
const reversedHeadings = [...headings].reverse(); |
|
targetHeading = visibleHeading |
|
? reversedHeadings.find(h => h.top < visibleHeading.top - 10) |
|
: reversedHeadings.find(h => h.top < 0); |
|
} |
|
|
|
// Scroll to target heading if found |
|
if (targetHeading) { |
|
targetHeading.element.scrollIntoView({ |
|
behavior: 'smooth', |
|
block: 'start' // Align heading to the top of the viewport |
|
}); |
|
} |
|
} |
|
|
|
// Event listener for keyboard shortcuts |
|
document.addEventListener('keydown', (e) => { |
|
if (e.key === KEY_CONFIG.next.key && e.altKey === KEY_CONFIG.next.alt) { |
|
e.preventDefault(); |
|
moveToHeading('next'); |
|
} else if (e.key === KEY_CONFIG.prev.key && e.altKey === KEY_CONFIG.prev.alt) { |
|
e.preventDefault(); |
|
moveToHeading('prev'); |
|
} |
|
}); |
|
})(); |