Obsdian mobile: swap between two finger horizontal swipe actions and one finger horizontal actions

Save this as a .js file and point CodeScript ToolKit to it as your “Startup script path.”

It’ll make it so if you swipe down in the same way you would to activate the mobile Quick Action with two fingers instead of one, you’ll swap the one finger and two finger horizontal swipe actions.

That means, swiping right with one finger in the note content will no longer open the left sidebar. It will instead navigate back one note in that tab’s history. Swiping left with one finger will no longer open the right sidebar, it’ll navigate forward in history.

Normally, a two finger swipe left navigates forward and a two finger swipe right navigates backwards. With it swapped, two finger swipe left will open the right sidebar and two fingers right will open the left. This is kinda finicky, but it technically does work. The sidebar doesn’t like being dragged open with two fingers very much.

You can always swap the behavior back by double finger swiping from the top in the same way you did to swap them in the first place.

If you want it to start swapped on startup, change the line
let swapIt = false;
to
let swapIt = true;

Related feature requests:

JS

exports.invoke = async (app) => {
	const thingsToHookInto = [app?.workspace];
	let swapIt = false;

	function installAllSwipeHooks(thing) {
		const proto = Object.getPrototypeOf(thing);
		if (!proto || typeof proto.trigger !== "function" || proto.__swipes_hooked) return;

		const origTrigger = proto.trigger;
		proto.trigger = function (eventName, data) {
			try {
				if (eventName === "swipe" && data?.direction === "y" && data.points === 2) {
					const isDown = data.startY && data.y && data.y > data.startY;
					if (isDown && thing.rootSplit?.containerEl?.contains(data.targetEl)) {
						handleTwoSwipeDown(data);
						return;
					}
				} else if (swapIt && app.workspace.leftSplit.collapsed && app.workspace.rightSplit.collapsed && eventName === "swipe" && data && data.direction === "x" && (data.points === 1 || data.points === 2)) {
					const swapped = data.points === 1 ? 2 : (data.points === 2 ? 1 : data.points);
					data.points = swapped;
				}
			} catch (err) {
				console.error(err);
			}
			return origTrigger.apply(this, arguments);
		};

		proto.__swipes_hooked = true;
	}

	function handleTwoSwipeDown(e) {
		const el = document.body.createDiv({
			cls: "pull-action pull-down-action",
			text: swapIt ? "Switch to default swipe behavior" : "Swap double and single finger horizonatal swipe actions",
		});

		const h = el.offsetHeight;
		let progress = 0;
		let activated = false;

		e.registerCallback({
			move: (_, y) => {
				progress = Math.clamp((y - e.startY) / h, 0, 1);
				const fullyPulled = progress === 1;

				el.toggleClass("mod-activated", fullyPulled);
				el.style.transform = `translateY(${(progress - 1) * h}px)`;

				if (fullyPulled && !activated) {
					navigator.vibrate?.(100);
					activated = true;
				}

				if (!fullyPulled && activated) {
					activated = false;
				}
			},
			cancel: () => el.detach(),
			finish: () => {
				el.detach();
				if (progress === 1) {
					swapIt = !swapIt;
					swapIt ? new Notice("Double and single finger horizontal swipe actions have been swapped") : new Notice("Default horizontal swipe behavor has been restored");
				}
			},
		});
	}

	if (app.isMobile && !window.__swipe_hooks_installed) {
		for (const thing of thingsToHookInto) installAllSwipeHooks(thing);
		window.__swipe_hooks_installed = true;
	}
}

Version that automatically swaps based on if you’re in reading mode and does away with the manual switching gesture. Reading mode makes single finger swipes navigate and editing mode makes single finger swipes open the sidebars.

exports.invoke = async (app) => {
	const thingsToHookInto = [app?.workspace, app?.workspace?.rootSplit, app?.workspace?.activeLeaf];

	function installHorizontalSwipeHooks(thing) {
		const proto = Object.getPrototypeOf(thing);
		if (!proto || typeof proto.trigger !== "function" || proto.__horizontal_swipes_hooked) return;

		const origTrigger = proto.trigger;
		proto.trigger = function (eventName, data) {
			try {
				if (app.workspace.activeLeaf?.view?.currentMode?.type == "preview" && app.workspace.leftSplit.collapsed && app.workspace.rightSplit.collapsed && eventName === "swipe" && data && data.direction === "x" && (data.points === 1 || data.points === 2)) {
					const swapped = data.points === 1 ? 2 : (data.points === 2 ? 1 : data.points);
					data.points = swapped;
				}
			} catch (err) {
				console.error(err);
			}
			return origTrigger.apply(this, arguments);
		};

		proto.__horizontal_swipes_hooked = true;
	}

	if (app.isMobile && !window.__horizontal_swipe_hooks_installed) {
		for (const thing of thingsToHookInto) installHorizontalSwipeHooks(thing);
		window.__horizontal_swipe_hooks_installed = true;
	}
}
1 Like

In the automatic switching version, if you want it to not auto switch to navigation if you’re in reading mode and swipe in a direction where there isn’t any navigation history (say, you’re in reading mode in a note that has no history to go back to, so swiping from left to right wouldn’t do anything), change the line

data.points = swapped;

to

if ((data.x > data.startX && !!app.workspace.activeLeaf.history.backHistory.length) || (data.x < data.startX && !!app.workspace.activeLeaf.history.forwardHistory.length)) data.points = swapped;

This’ll make it so it’ll open the sidebar if in reading mode only if there’s no history to go to.


You could also replace

} else if (app.workspace.activeLeaf?.view?.currentMode?.type == "preview" && app.workspace.leftSplit.collapsed && app.workspace.rightSplit.collapsed && eventName === "swipe" && data && data.direction === "x" && (data.points === 1 || data.points === 2)) {

with

} else if (app.workspace.activeLeaf?.view?.currentMode?.type == "preview" && app.workspace.leftSplit.collapsed && app.workspace.rightSplit.collapsed && eventName === "swipe" && data && data.direction === "x" && (data.points === 1 || data.points === 2) && ((data.x > data.startX && !!app.workspace.activeLeaf.history.backHistory.length) || (data.x < data.startX && !!app.workspace.activeLeaf.history.forwardHistory.length))) {

but, man, that’s an unweildy if statement, lol.

1 Like