一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
73 行
2.1 KiB
TypeScript
73 行
2.1 KiB
TypeScript
import { useEffect, useCallback, useRef } from 'react';
|
|
import shim from '@joplin/lib/shim';
|
|
|
|
interface HookDependencies {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TinyMCE editor instance; the typed Editor surface is narrower than what we actually use (getDoc/getWin)
|
|
editor: any;
|
|
onScroll: (event: { percent: number })=> void;
|
|
}
|
|
|
|
export default function useScroll(dependencies: HookDependencies) {
|
|
const { editor, onScroll } = dependencies;
|
|
const scrollTimeoutId_ = useRef(null);
|
|
|
|
const maxScrollTop = useCallback(() => {
|
|
if (!editor) return 0;
|
|
|
|
const doc = editor.getDoc();
|
|
const win = editor.getWin();
|
|
if (!doc || !win) return 0;
|
|
const firstChild = doc.firstElementChild;
|
|
if (!firstChild) return 0;
|
|
|
|
const winHeight = win.innerHeight;
|
|
const contentHeight = firstChild.scrollHeight;
|
|
return contentHeight < winHeight ? 0 : contentHeight - winHeight;
|
|
}, [editor]);
|
|
|
|
const scrollTop = useCallback(() => {
|
|
if (!editor) return 0;
|
|
const win = editor.getWin();
|
|
if (!win) return 0;
|
|
return win.scrollY;
|
|
}, [editor]);
|
|
|
|
const scrollPercent = useCallback(() => {
|
|
const m = maxScrollTop();
|
|
const t = scrollTop();
|
|
return m <= 0 ? 0 : t / m;
|
|
}, [maxScrollTop, scrollTop]);
|
|
|
|
const scrollToPercent = useCallback((percent: number) => {
|
|
if (!editor) return;
|
|
editor.getWin().scrollTo(0, maxScrollTop() * percent);
|
|
}, [editor, maxScrollTop]);
|
|
|
|
const scheduleOnScroll = useCallback((event: { percent: number }) => {
|
|
if (scrollTimeoutId_.current) {
|
|
shim.clearTimeout(scrollTimeoutId_.current);
|
|
scrollTimeoutId_.current = null;
|
|
}
|
|
|
|
scrollTimeoutId_.current = shim.setTimeout(() => {
|
|
scrollTimeoutId_.current = null;
|
|
onScroll(event);
|
|
}, 10);
|
|
}, [onScroll]);
|
|
|
|
const onEditorScroll = useCallback(() => {
|
|
scheduleOnScroll({ percent: scrollPercent() });
|
|
}, [scheduleOnScroll, scrollPercent]);
|
|
|
|
useEffect(() => {
|
|
if (!editor) return () => {};
|
|
|
|
editor.getDoc().addEventListener('scroll', onEditorScroll);
|
|
return () => {
|
|
editor.getDoc().removeEventListener('scroll', onEditorScroll);
|
|
};
|
|
}, [editor, onEditorScroll]);
|
|
|
|
return { scrollToPercent };
|
|
}
|