gentoo-dots/.config/qutebrowser/greasemonkey/scrollHelper.js

84 lines
3.6 KiB
JavaScript

// ==UserScript==
// @name Adds scrolling JS that can be used within QB to do smarter scrolling
// @qute-js-world jseval
// @run-at document-start
// ==/UserScript==
unsafeWindow.scrollHelper = (() => {
const scrollableElemOverflowTypes = [
'auto',
'scroll',
]
const getFocusedWindow = (nextElem) => {
if (nextElem === null) return null
if (nextElem === undefined) return getFocusedWindow(window.document.activeElement ?? null)
return getFocusedWindow(nextElem.contentDocument?.activeElement ?? null) ?? nextElem.ownerDocument?.defaultView ?? null
}
const getScrollMaxY = ({ document: { documentElement } }) => documentElement.scrollHeight - documentElement.clientHeight
const getWindowVisibleArea = ({ document: { documentElement } }) => documentElement.clientHeight * documentElement.clientWidth
const findVertScrollableWindow = () => {
const focusedWindow = getFocusedWindow() ?? window
if (getScrollMaxY(focusedWindow) > 0) return focusedWindow
if (getScrollMaxY(window) > 0) return window
return Array
.from(window.frames)
.sort((x, y) => getWindowVisibleArea(y) - getWindowVisibleArea(x))
.find((frame) => getScrollMaxY(frame) > 0) ?? window
}
const getScrollTopMax = (elem) => elem.scrollHeight - elem.clientHeight
const isElementVertScrollable = (element) => element.clientHeight !==0 &&
scrollableElemOverflowTypes.includes(getComputedStyle(element).overflowY)
const findVertScrollableAncestor = (delta, nextElem) => {
if (!(nextElem?.parentNode instanceof Element)) return nextElem
if (isElementVertScrollable(nextElem)) {
if (delta < 0 && nextElem.scrollTop > 0) return nextElem
if (delta > 0 && nextElem.scrollTop < getScrollTopMax(nextElem)) return nextElem
if (delta === 0 && getScrollTopMax(nextElem) > 0) return nextElem
}
return findVertScrollableAncestor(delta, nextElem.parentNode)
}
const getSelectionElem = () => {
const selection = getFocusedWindow().getSelection()
return selection.rangeCount !== 0
? selection.getRangeAt(0).startContainer
: null
}
const getParentIfNotElement = (maybeElement) => maybeElement instanceof Element ? maybeElement : maybeElement?.parentNode
const findVertScrollable = (delta = 0) => {
const selectionScrollableElem = findVertScrollableAncestor(delta, getParentIfNotElement(getSelectionElem()))
if (selectionScrollableElem instanceof Element) return selectionScrollableElem
const scrollableDoc = findVertScrollableWindow().document
const scrollableElem = scrollableDoc.body || scrollableDoc.getElementsByTagName('body')[0] || scrollableDoc.documentElement
return findVertScrollableAncestor(delta, getParentIfNotElement(scrollableElem))
}
return {
scrollTo: (position) => findVertScrollable().scrollTo({top: position}),
scrollToPercent: (percentPosition) => {
const scrollElement = findVertScrollable()
const paneHeight = scrollElement.scrollHeight
scrollElement.scrollTo({top: percentPosition / 100 * paneHeight})
},
scrollBy: (delta) => findVertScrollable(delta).scrollBy({top: delta, behavior: 'smooth'}),
scrollPage: (pages) => {
const fakeDelta = pages < 0 ? -10 : 10
const scrollElement = findVertScrollable(fakeDelta)
const pageHeight = scrollElement.clientHeight
scrollElement.scrollBy({top: pageHeight * pages})
},
}
})()