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