From bd06c530e4a9473afcf151c049b17d0888c949c8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 19 Feb 2026 18:14:21 +0200 Subject: [PATCH 1/8] feat(mobile/global_menu): add quick entry to recent changes --- apps/client/src/widgets/buttons/global_menu.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/client/src/widgets/buttons/global_menu.tsx b/apps/client/src/widgets/buttons/global_menu.tsx index 9db0393692..25f48c5db2 100644 --- a/apps/client/src/widgets/buttons/global_menu.tsx +++ b/apps/client/src/widgets/buttons/global_menu.tsx @@ -47,6 +47,7 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout: > {isMobile() && <> + } From fb20d4998aee0c475ea6864bd43e0463fd9e728b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 19 Feb 2026 18:19:41 +0200 Subject: [PATCH 2/8] fix(mobile/tree): partly hidden due to mobile viewport (closes #8740) --- apps/client/src/stylesheets/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 2a6d66ed6c..1eb310e604 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -1587,7 +1587,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu { position: absolute; top: 0; inset-inline-start: 0; - bottom: 0; + height: 100dvh; width: 85vw; padding-top: env(safe-area-inset-top); transition: transform 250ms ease-in-out; From 0110b3c4a21278f00b3962c488c34e610a80a906 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 19 Feb 2026 18:22:47 +0200 Subject: [PATCH 3/8] fix(mobile/tree): too easy to accidentally close via drag gesture --- .../widgets/mobile_widgets/sidebar_container.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/sidebar_container.ts b/apps/client/src/widgets/mobile_widgets/sidebar_container.ts index ef719d36a9..f79d8a72c2 100644 --- a/apps/client/src/widgets/mobile_widgets/sidebar_container.ts +++ b/apps/client/src/widgets/mobile_widgets/sidebar_container.ts @@ -13,7 +13,7 @@ const DRAG_OPEN_THRESHOLD = 10; /** The number of pixels the user has to drag across the screen to the right when the sidebar is closed to trigger the drag open animation. */ const DRAG_CLOSED_START_THRESHOLD = 10; /** The number of pixels the user has to drag across the screen to the left when the sidebar is opened to trigger the drag close animation. */ -const DRAG_OPENED_START_THRESHOLD = 80; +const DRAG_OPENED_START_THRESHOLD = 100; export default class SidebarContainer extends FlexContainer { private screenName: Screen; @@ -54,7 +54,7 @@ export default class SidebarContainer extends FlexContainer { this.startX = x; // Prevent dragging if too far from the edge of the screen and the menu is closed. - let dragRefX = glob.isRtl ? this.screenWidth - x : x; + const dragRefX = glob.isRtl ? this.screenWidth - x : x; if (dragRefX > 30 && this.currentTranslate === -100) { return; } @@ -89,7 +89,7 @@ export default class SidebarContainer extends FlexContainer { } } else if (this.dragState === DRAG_STATE_DRAGGING) { const width = this.sidebarEl.offsetWidth; - let translatePercentage = Math.min(0, Math.max(this.currentTranslate + (deltaX / width) * 100, -100)); + const translatePercentage = Math.min(0, Math.max(this.currentTranslate + (deltaX / width) * 100, -100)); const backdropOpacity = Math.max(0, 1 + translatePercentage / 100); this.translatePercentage = translatePercentage; if (glob.isRtl) { @@ -160,12 +160,10 @@ export default class SidebarContainer extends FlexContainer { this.sidebarEl.classList.toggle("show", isOpen); if (isOpen) { this.sidebarEl.style.transform = "translateX(0)"; + } else if (glob.isRtl) { + this.sidebarEl.style.transform = "translateX(100%)"; } else { - if (glob.isRtl) { - this.sidebarEl.style.transform = "translateX(100%)" - } else { - this.sidebarEl.style.transform = "translateX(-100%)"; - } + this.sidebarEl.style.transform = "translateX(-100%)"; } this.sidebarEl.style.transition = this.originalSidebarTransition; From 4ebc4ece34296a964021f5c2ec19090033444546 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 19 Feb 2026 18:31:38 +0200 Subject: [PATCH 4/8] fix(mobile/tree): "toggle sidebar" tooltip shown after opening sidebar --- .../mobile_widgets/toggle_sidebar_button.tsx | 15 ++++++++++----- apps/client/src/widgets/react/ActionButton.tsx | 4 ++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/toggle_sidebar_button.tsx b/apps/client/src/widgets/mobile_widgets/toggle_sidebar_button.tsx index 8e689954b0..b87e8cb90e 100644 --- a/apps/client/src/widgets/mobile_widgets/toggle_sidebar_button.tsx +++ b/apps/client/src/widgets/mobile_widgets/toggle_sidebar_button.tsx @@ -1,5 +1,5 @@ -import ActionButton from "../react/ActionButton"; import { t } from "../../services/i18n"; +import ActionButton from "../react/ActionButton"; import { useNoteContext } from "../react/hooks"; export default function ToggleSidebarButton() { @@ -10,10 +10,15 @@ export default function ToggleSidebarButton() { { noteContext?.isMainContext() && parentComponent?.triggerCommand("setActiveScreen", { - screen: "tree" - })} + onClick={(e) => { + // Remove focus to prevent tooltip showing on top of the sidebar. + (e.currentTarget as HTMLButtonElement).blur(); + + parentComponent?.triggerCommand("setActiveScreen", { + screen: "tree" + }); + }} />} - ) + ); } diff --git a/apps/client/src/widgets/react/ActionButton.tsx b/apps/client/src/widgets/react/ActionButton.tsx index feb5972ef4..764e155c44 100644 --- a/apps/client/src/widgets/react/ActionButton.tsx +++ b/apps/client/src/widgets/react/ActionButton.tsx @@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from "preact/hooks"; import { CommandNames } from "../../components/app_context"; import keyboard_actions from "../../services/keyboard_actions"; +import { isMobile } from "../../services/utils"; import { useStaticTooltip } from "./hooks"; export interface ActionButtonProps extends Pick, "onClick" | "onAuxClick" | "onContextMenu" | "style"> { @@ -17,6 +18,8 @@ export interface ActionButtonProps extends Pick(null); const [ keyboardShortcut, setKeyboardShortcut ] = useState(); @@ -25,6 +28,7 @@ export default function ActionButton({ text, icon, className, triggerCommand, ti title: keyboardShortcut?.length ? `${text} (${keyboardShortcut?.join(",")})` : text, placement: titlePosition ?? "bottom", fallbackPlacements: [ titlePosition ?? "bottom" ], + trigger: cachedIsMobile ? "focus" : "hover focus", animation: false }); From 9fe7bdf79bb786b27f3140138a356c8bd03c0c2f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 19 Feb 2026 18:35:32 +0200 Subject: [PATCH 5/8] fix(mobile/global_menu): tooltips not visible due to position --- .../widgets/launch_bar/launch_bar_widgets.tsx | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/apps/client/src/widgets/launch_bar/launch_bar_widgets.tsx b/apps/client/src/widgets/launch_bar/launch_bar_widgets.tsx index 639b5aede7..288565cf7b 100644 --- a/apps/client/src/widgets/launch_bar/launch_bar_widgets.tsx +++ b/apps/client/src/widgets/launch_bar/launch_bar_widgets.tsx @@ -3,11 +3,14 @@ import { createContext } from "preact"; import { useContext } from "preact/hooks"; import FNote from "../../entities/fnote"; +import utils from "../../services/utils"; import ActionButton, { ActionButtonProps } from "../react/ActionButton"; import Dropdown, { DropdownProps } from "../react/Dropdown"; import { useNoteLabel, useNoteProperty } from "../react/hooks"; import Icon from "../react/Icon"; +const cachedIsMobile = utils.isMobile(); + export const LaunchBarContext = createContext<{ isHorizontalLayout: boolean; }>({ @@ -26,7 +29,7 @@ export function LaunchBarActionButton({ className, ...props }: Omit ); @@ -34,6 +37,7 @@ export function LaunchBarActionButton({ className, ...props }: Omit & { icon: string }) { const { isHorizontalLayout } = useContext(LaunchBarContext); + const titlePosition = getTitlePosition(isHorizontalLayout); return ( } - titlePosition={isHorizontalLayout ? "bottom" : "right"} + titlePosition={titlePosition} titleOptions={{ animation: false }} dropdownOptions={{ ...dropdownOptions, popperConfig: { - placement: isHorizontalLayout ? "bottom" : "right" + placement: titlePosition } }} mobileBackdrop @@ -67,3 +71,10 @@ export function useLauncherIconAndTitle(note: FNote) { title: title ?? "" }; } + +function getTitlePosition(isHorizontalLayout: boolean) { + if (cachedIsMobile) { + return "top"; + } + return isHorizontalLayout ? "bottom" : "right"; +} From b97ab913bf168993f935314b9b57c065941b442c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 19 Feb 2026 18:40:15 +0200 Subject: [PATCH 6/8] fix(mobile/jump_to_note): full text search button clipped --- apps/client/src/stylesheets/style.css | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 1eb310e604..8950f3fd3f 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -1651,13 +1651,27 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu { word-break: break-all; } - body.mobile .jump-to-note-dialog .modal-content { - overflow-y: auto; - } + body.mobile .jump-to-note-dialog { + .modal-header { + padding-bottom: 0.75rem !important; + } - body.mobile .jump-to-note-dialog .modal-dialog .aa-dropdown-menu { - max-height: unset; - overflow: auto; + .modal-content { + padding-bottom: 0 !important; + } + + .modal-body { + overflow-y: auto; + } + + .aa-dropdown-menu { + max-height: unset; + overflow: auto; + } + + .aa-suggestion { + padding-inline: 0; + } } body.mobile .modal-dialog .dropdown-menu { From 4d5d9b4b704de3bdf3b837d9314b8c2e2c664d53 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 19 Feb 2026 18:42:28 +0200 Subject: [PATCH 7/8] fix(mobile): some buttons display a + from keyboard shortcut --- apps/client/src/widgets/react/Button.tsx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/client/src/widgets/react/Button.tsx b/apps/client/src/widgets/react/Button.tsx index 1aab2153e6..6269000563 100644 --- a/apps/client/src/widgets/react/Button.tsx +++ b/apps/client/src/widgets/react/Button.tsx @@ -1,13 +1,14 @@ -import type { ComponentChildren, RefObject } from "preact"; -import type { CSSProperties } from "preact/compat"; +import type { ComponentChildren, CSSProperties, RefObject } from "preact"; import { memo } from "preact/compat"; import { useMemo } from "preact/hooks"; import { CommandNames } from "../../components/app_context"; -import { isDesktop } from "../../services/utils"; +import { isDesktop, isMobile } from "../../services/utils"; import ActionButton from "./ActionButton"; import Icon from "./Icon"; +const cachedIsMobile = isMobile(); + export interface ButtonProps { name?: string; /** Reference to the button element. Mostly useful for requesting focus. */ @@ -30,7 +31,7 @@ const Button = memo(({ name, buttonRef, className, text, onClick, keyboardShortc // Memoize classes array to prevent recreation const classes = useMemo(() => { const classList: string[] = ["btn"]; - + switch(kind) { case "primary": classList.push("btn-primary"); @@ -42,7 +43,7 @@ const Button = memo(({ name, buttonRef, className, text, onClick, keyboardShortc classList.push("btn-secondary"); break; } - + if (className) { classList.push(className); } @@ -56,7 +57,7 @@ const Button = memo(({ name, buttonRef, className, text, onClick, keyboardShortc // Memoize keyboard shortcut rendering const shortcutElements = useMemo(() => { - if (!keyboardShortcut) return null; + if (!keyboardShortcut || cachedIsMobile) return null; const splitShortcut = keyboardShortcut.split("+"); return splitShortcut.map((key, index) => ( <> From 56ce25b8e877ceee69a5135d4c6963b79f4743a1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 19 Feb 2026 19:13:31 +0200 Subject: [PATCH 8/8] fix(mobile): missing note icon for full-height notes --- apps/client/src/layouts/mobile_layout.tsx | 2 -- apps/client/src/widgets/note_icon.tsx | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/client/src/layouts/mobile_layout.tsx b/apps/client/src/layouts/mobile_layout.tsx index e194bb7271..3fc5422b0a 100644 --- a/apps/client/src/layouts/mobile_layout.tsx +++ b/apps/client/src/layouts/mobile_layout.tsx @@ -23,8 +23,6 @@ import NoteTreeWidget from "../widgets/note_tree.js"; import NoteWrapperWidget from "../widgets/note_wrapper.js"; import NoteDetail from "../widgets/NoteDetail.jsx"; import QuickSearchWidget from "../widgets/quick_search.js"; -import { useNoteContext } from "../widgets/react/hooks.jsx"; -import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx"; import ScrollPadding from "../widgets/scroll_padding"; import SearchResult from "../widgets/search_result.jsx"; import MobileEditorToolbar from "../widgets/type_widgets/text/mobile_editor_toolbar.jsx"; diff --git a/apps/client/src/widgets/note_icon.tsx b/apps/client/src/widgets/note_icon.tsx index 9df9ad48f4..31ca7e65b4 100644 --- a/apps/client/src/widgets/note_icon.tsx +++ b/apps/client/src/widgets/note_icon.tsx @@ -69,7 +69,7 @@ function MobileNoteIconSwitcher({ note, icon }: { const [ modalShown, setModalShown ] = useState(false); const { windowWidth } = useWindowSize(); - return (note && + return (
- setModalShown(false)} columnCount={Math.max(1, Math.floor(windowWidth / ICON_SIZE))} /> + {note && setModalShown(false)} columnCount={Math.max(1, Math.floor(windowWidth / ICON_SIZE))} />} ), document.body)}