Compare commits

...

1 Commits

Author SHA1 Message Date
Ammar Ahmed
7ccbdc120b mobile: fix keep in view in editor 2023-10-07 13:54:49 +05:00
2 changed files with 68 additions and 18 deletions

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Editor, Extension, posToDOMRect } from "@tiptap/core";
import { inlineDebounce } from "../../utils/debounce";
type KeepInViewOptions = {
scrollIntoViewOnWindowResize: boolean;
@@ -45,28 +46,35 @@ export const KeepInView = Extension.create<KeepInViewOptions>({
window.removeEventListener("resize", onWindowResize);
onWindowResize = undefined;
},
addKeyboardShortcuts() {
return {
Enter: ({ editor }) => {
setTimeout(() => {
keepLastLineInView(editor);
});
return false;
}
};
onSelectionUpdate() {
inlineDebounce(
"keep_in_view",
() => {
keepLastLineInView(this.editor);
},
100
);
}
});
export function keepLastLineInView(
editor: Editor,
THRESHOLD = 100,
THRESHOLD = 80,
SCROLL_THRESHOLD = 100
) {
if (!editor.state.selection.empty) return;
const node = editor.state.selection.$from;
const { top } = posToDOMRect(editor.view, node.pos, node.pos + 1);
const isBelowThreshold = window.innerHeight - top < THRESHOLD;
if (isBelowThreshold) {
const isAboveThreshold = top < THRESHOLD;
const DIFF_BOTTOM = THRESHOLD - (window.innerHeight - top);
let DIFF_TOP = THRESHOLD - top;
if (DIFF_TOP > 0) DIFF_TOP = DIFF_TOP * -1;
if (isBelowThreshold || isAboveThreshold) {
let { node: domNode } = editor.view.domAtPos(node.pos);
if (domNode.nodeType === Node.TEXT_NODE && domNode.parentNode)
domNode = domNode.parentNode;
@@ -74,7 +82,10 @@ export function keepLastLineInView(
if (domNode instanceof HTMLElement) {
const container = findScrollContainer(domNode);
if (container) {
container.scrollBy({ top: SCROLL_THRESHOLD, behavior: "smooth" });
container.scrollBy({
top: isAboveThreshold ? DIFF_TOP + 10 : DIFF_BOTTOM,
behavior: "smooth"
});
} else domNode.scrollIntoView({ behavior: "smooth", block: "center" });
}
}

View File

@@ -16,17 +16,56 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
export function debounce<F extends (...args: never[]) => void>(
interface DebouncedFunction<
Args extends any[],
F extends (...args: Args) => any
> {
(this: ThisParameterType<F>, ...args: Args & Parameters<F>): void;
}
interface DebouncedFunctionWithId<
Args extends any[],
F extends (...args: Args) => any
> {
(
this: ThisParameterType<F>,
id: string | number,
...args: Args & Parameters<F>
): void;
}
export function debounce<Args extends any[], F extends (...args: Args) => void>(
func: F,
waitFor: number
) {
): DebouncedFunction<Args, F> {
let timeout: number | null;
const debounced = (...args: Parameters<F>) => {
return (...args: Args) => {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => func(...args), waitFor) as unknown as number;
};
return debounced;
}
export function debounceWithId<
Args extends any[],
F extends (...args: Args) => void
>(func: F, waitFor: number): DebouncedFunctionWithId<Args, F> {
let timeout: number | null;
let debounceId: string | number | null = null;
return (id: string | number, ...args: Parameters<F>) => {
if (timeout && id === debounceId) clearTimeout(timeout);
debounceId = id;
timeout = setTimeout(() => {
func(...args);
}, waitFor) as unknown as number;
};
}
const DEBOUNCE_TIMEOUTS: Record<string, NodeJS.Timeout> = {};
export function inlineDebounce(id: string, func: () => void, waitFor: number) {
clearTimeout(DEBOUNCE_TIMEOUTS[id]);
DEBOUNCE_TIMEOUTS[id] = setTimeout(func, waitFor);
}