From 5e981da4df25bf8dd9729e2d5a8b78f9b2a0fe77 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 20:48:55 +0200 Subject: [PATCH 01/16] feat(mobile): use the desktop version of note actions --- .../mobile_widgets/mobile_detail_menu.tsx | 84 ++----------------- .../client/src/widgets/ribbon/NoteActions.tsx | 2 +- 2 files changed, 7 insertions(+), 79 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index 1566d0889..9fc3f9d3b 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -1,84 +1,12 @@ -import { useContext } from "preact/hooks"; - -import appContext, { CommandMappings } from "../../components/app_context"; -import contextMenu, { MenuItem } from "../../menus/context_menu"; -import branches from "../../services/branches"; -import { t } from "../../services/i18n"; -import { getHelpUrlForNote } from "../../services/in_app_help"; -import note_create from "../../services/note_create"; -import tree from "../../services/tree"; -import { openInAppHelpFromUrl } from "../../services/utils"; -import BasicWidget from "../basic_widget"; -import ActionButton from "../react/ActionButton"; -import { ParentComponent } from "../react/react_utils"; +import { useNoteContext } from "../react/hooks"; +import { NoteContextMenu } from "../ribbon/NoteActions"; export default function MobileDetailMenu() { - const parentComponent = useContext(ParentComponent); + const { note, noteContext } = useNoteContext(); return ( - { - const ntxId = (parentComponent as BasicWidget | null)?.getClosestNtxId(); - if (!ntxId) return; - - const noteContext = appContext.tabManager.getNoteContextById(ntxId); - const subContexts = noteContext.getMainContext().getSubContexts(); - const isMainContext = noteContext?.isMainContext(); - const note = noteContext.note; - const helpUrl = getHelpUrlForNote(note); - - const items: (MenuItem)[] = [ - { title: t("mobile_detail_menu.insert_child_note"), command: "insertChildNote", uiIcon: "bx bx-plus", enabled: note?.type !== "search" }, - { title: t("mobile_detail_menu.delete_this_note"), command: "delete", uiIcon: "bx bx-trash", enabled: note?.noteId !== "root" }, - { kind: "separator" }, - { title: t("mobile_detail_menu.note_revisions"), command: "showRevisions", uiIcon: "bx bx-history" }, - { kind: "separator" }, - helpUrl && { - title: t("help-button.title"), - uiIcon: "bx bx-help-circle", - handler: () => openInAppHelpFromUrl(helpUrl) - }, - { kind: "separator" }, - subContexts.length < 2 && { title: t("create_pane_button.create_new_split"), command: "openNewNoteSplit", uiIcon: "bx bx-dock-right" }, - !isMainContext && { title: t("close_pane_button.close_this_pane"), command: "closeThisNoteSplit", uiIcon: "bx bx-x" } - ].filter(i => !!i) as MenuItem[]; - - const lastItem = items.at(-1); - if (lastItem && "kind" in lastItem && lastItem.kind === "separator") { - items.pop(); - } - - contextMenu.show({ - x: e.pageX, - y: e.pageY, - items, - selectMenuItemHandler: async ({ command }) => { - if (command === "insertChildNote") { - note_create.createNote(appContext.tabManager.getActiveContextNotePath() ?? undefined); - } else if (command === "delete") { - const notePath = appContext.tabManager.getActiveContextNotePath(); - if (!notePath) { - throw new Error("Cannot get note path to delete."); - } - - const branchId = await tree.getBranchIdFromUrl(notePath); - - if (!branchId) { - throw new Error(t("mobile_detail_menu.error_cannot_get_branch_id", { notePath })); - } - - if (await branches.deleteNotes([branchId]) && parentComponent) { - parentComponent.triggerCommand("setActiveScreen", { screen: "tree" }); - } - } else if (command && parentComponent) { - parentComponent.triggerCommand(command, { ntxId }); - } - }, - forcePositionOnMobile: true - }); - }} - /> +
+ {note && } +
); } diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 6db1d384e..266ca38f9 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -63,7 +63,7 @@ function RevisionsButton({ note }: { note: FNote }) { type ItemToFocus = "basic-properties"; -function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) { +export function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) { const dropdownRef = useRef(null); const parentComponent = useContext(ParentComponent); const noteType = useNoteProperty(note, "type") ?? ""; From 90e3f7508a712e78f6231681dea4a2ab168cccae Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 20:50:00 +0200 Subject: [PATCH 02/16] chore(mobile): enforce new layout --- apps/client/src/services/experimental_features.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/services/experimental_features.ts b/apps/client/src/services/experimental_features.ts index 62d4ebb05..b6211b4d1 100644 --- a/apps/client/src/services/experimental_features.ts +++ b/apps/client/src/services/experimental_features.ts @@ -1,5 +1,6 @@ import { t } from "./i18n"; import options from "./options"; +import { isMobile } from "./utils"; export interface ExperimentalFeature { id: string; @@ -21,7 +22,7 @@ let enabledFeatures: Set | null = null; export function isExperimentalFeatureEnabled(featureId: ExperimentalFeatureId): boolean { if (featureId === "new-layout") { - return options.is("newLayout"); + return (isMobile() || options.is("newLayout")); } return getEnabledFeatures().has(featureId); From f91add3cd4fdca503e40dd90119234da38ff05e0 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 20:53:33 +0200 Subject: [PATCH 03/16] chore(mobile/note_actions): position like global menu --- apps/client/src/stylesheets/style.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 69632dd29..3a2483007 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -1530,7 +1530,8 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu { @media (max-width: 991px) { body.mobile #launcher-pane .dropdown.global-menu > .dropdown-menu.show, - body.mobile #launcher-container .dropdown > .dropdown-menu.show { + body.mobile #launcher-container .dropdown > .dropdown-menu.show, + body.mobile .dropdown.note-actions .dropdown-menu.show { --dropdown-bottom: calc(var(--mobile-bottom-offset) + var(--launcher-pane-size)); position: fixed !important; bottom: var(--dropdown-bottom) !important; From d3c733c57f034c5f32f80076979d51a0cdd32e3b Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 20:57:01 +0200 Subject: [PATCH 04/16] feat(mobile/note_actions): add backdrop --- apps/client/src/widgets/buttons/global_menu.tsx | 4 +--- apps/client/src/widgets/react/Dropdown.tsx | 14 +++++++++++--- apps/client/src/widgets/ribbon/NoteActions.tsx | 1 + 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/apps/client/src/widgets/buttons/global_menu.tsx b/apps/client/src/widgets/buttons/global_menu.tsx index 255cf89c9..95a3b8b94 100644 --- a/apps/client/src/widgets/buttons/global_menu.tsx +++ b/apps/client/src/widgets/buttons/global_menu.tsx @@ -29,7 +29,6 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout: const isVerticalLayout = !isHorizontalLayout; const parentComponent = useContext(ParentComponent); const { isUpdateAvailable, latestVersion } = useTriliumUpdateStatus(); - const isMobileLocal = isMobile(); const logoRef = useRef(null); useStaticTooltip(logoRef); @@ -44,8 +43,7 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout: } } noDropdownListStyle - onShown={isMobileLocal ? () => document.getElementById("context-menu-cover")?.classList.add("show", "global-menu-cover") : undefined} - onHidden={isMobileLocal ? () => document.getElementById("context-menu-cover")?.classList.remove("show", "global-menu-cover") : undefined} + mobileBackdrop > diff --git a/apps/client/src/widgets/react/Dropdown.tsx b/apps/client/src/widgets/react/Dropdown.tsx index 5af2f6228..407b14a63 100644 --- a/apps/client/src/widgets/react/Dropdown.tsx +++ b/apps/client/src/widgets/react/Dropdown.tsx @@ -3,6 +3,7 @@ import { ComponentChildren, HTMLAttributes } from "preact"; import { CSSProperties, HTMLProps } from "preact/compat"; import { MutableRef, useCallback, useEffect, useRef, useState } from "preact/hooks"; +import { isMobile } from "../../services/utils"; import { useTooltip, useUniqueName } from "./hooks"; type DataAttributes = { @@ -32,9 +33,10 @@ export interface DropdownProps extends Pick, "id" | "c dropdownRef?: MutableRef; titlePosition?: "top" | "right" | "bottom" | "left"; titleOptions?: Partial; + mobileBackdrop?: boolean; } -export default function Dropdown({ id, className, buttonClassName, isStatic, children, title, text, dropdownContainerStyle, dropdownContainerClassName, dropdownContainerRef: externalContainerRef, hideToggleArrow, iconAction, disabled, noSelectButtonStyle, noDropdownListStyle, forceShown, onShown: externalOnShown, onHidden: externalOnHidden, dropdownOptions, buttonProps, dropdownRef, titlePosition, titleOptions }: DropdownProps) { +export default function Dropdown({ id, className, buttonClassName, isStatic, children, title, text, dropdownContainerStyle, dropdownContainerClassName, dropdownContainerRef: externalContainerRef, hideToggleArrow, iconAction, disabled, noSelectButtonStyle, noDropdownListStyle, forceShown, onShown: externalOnShown, onHidden: externalOnHidden, dropdownOptions, buttonProps, dropdownRef, titlePosition, titleOptions, mobileBackdrop }: DropdownProps) { const containerRef = useRef(null); const triggerRef = useRef(null); const dropdownContainerRef = useRef(null); @@ -74,12 +76,18 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi setShown(true); externalOnShown?.(); hideTooltip(); - }, [ hideTooltip ]); + if (mobileBackdrop && isMobile()) { + document.getElementById("context-menu-cover")?.classList.add("show", "global-menu-cover"); + } + }, [ hideTooltip, mobileBackdrop ]); const onHidden = useCallback(() => { setShown(false); externalOnHidden?.(); - }, []); + if (mobileBackdrop && isMobile()) { + document.getElementById("context-menu-cover")?.classList.remove("show", "global-menu-cover"); + } + }, [ mobileBackdrop ]); useEffect(() => { if (!containerRef.current) return; diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 266ca38f9..607f55a0a 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -104,6 +104,7 @@ export function NoteContextMenu({ note, noteContext }: { note: FNote, noteContex noDropdownListStyle iconAction onHidden={() => itemToFocusRef.current = null } + mobileBackdrop > {isReadOnly && <> From 2a4280b5bf12280febea83b41c75d8891bc94dc8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 20:59:50 +0200 Subject: [PATCH 05/16] feat(mobile/note_actions): remove bottom rounded corners --- apps/client/src/widgets/ribbon/NoteActions.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 607f55a0a..6aa05234d 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -99,6 +99,7 @@ export function NoteContextMenu({ note, noteContext }: { note: FNote, noteContex dropdownRef={dropdownRef} buttonClassName={ isNewLayout ? "bx bx-dots-horizontal-rounded" : "bx bx-dots-vertical-rounded" } className="note-actions" + dropdownContainerClassName="mobile-bottom-menu" hideToggleArrow noSelectButtonStyle noDropdownListStyle From e88c0f732635bc34c159b8467bf9a15e854cf576 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 21:02:46 +0200 Subject: [PATCH 06/16] fix(mobile/note_actions): toggles on two rows --- 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 3a2483007..1bed8cbef 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -454,7 +454,7 @@ body.desktop .tabulator-popup-container, visibility: hidden; } -body.desktop .dropdown-menu:not(#context-menu-container) .dropdown-item, +.dropdown-menu:not(#context-menu-container) .dropdown-item, body.desktop .dropdown-menu .dropdown-toggle, body #context-menu-container .dropdown-item > span, body.mobile .dropdown .dropdown-submenu > span { From c06a90913af5bc424ed2e05ade3acf7e2ffd32ad Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 21:12:50 +0200 Subject: [PATCH 07/16] fix(mobile/note_actions): submenus not working --- apps/client/src/stylesheets/style.css | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/apps/client/src/stylesheets/style.css b/apps/client/src/stylesheets/style.css index 1bed8cbef..93163c06d 100644 --- a/apps/client/src/stylesheets/style.css +++ b/apps/client/src/stylesheets/style.css @@ -462,6 +462,15 @@ body.mobile .dropdown .dropdown-submenu > span { align-items: center; } + +body.mobile .dropdown .dropdown-submenu { + flex-wrap: wrap; + + & > span { + flex-grow: 1; + } +} + .dropdown-item span.keyboard-shortcut, .dropdown-item *:not(.keyboard-shortcut) > kbd { flex-grow: 1; @@ -1531,7 +1540,7 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu { @media (max-width: 991px) { body.mobile #launcher-pane .dropdown.global-menu > .dropdown-menu.show, body.mobile #launcher-container .dropdown > .dropdown-menu.show, - body.mobile .dropdown.note-actions .dropdown-menu.show { + body.mobile .dropdown.note-actions > .dropdown-menu.show { --dropdown-bottom: calc(var(--mobile-bottom-offset) + var(--launcher-pane-size)); position: fixed !important; bottom: var(--dropdown-bottom) !important; From e2363d860c58c9fa3cf6e94a6bc7aac547271aae Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 21:25:40 +0200 Subject: [PATCH 08/16] fix(mobile/note_actions): font too big --- apps/client/src/layouts/mobile_layout.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/apps/client/src/layouts/mobile_layout.tsx b/apps/client/src/layouts/mobile_layout.tsx index dbe88ade8..37fcf2f68 100644 --- a/apps/client/src/layouts/mobile_layout.tsx +++ b/apps/client/src/layouts/mobile_layout.tsx @@ -27,7 +27,6 @@ import FilePropertiesTab from "../widgets/ribbon/FilePropertiesTab.jsx"; import SearchDefinitionTab from "../widgets/ribbon/SearchDefinitionTab.jsx"; import SearchResult from "../widgets/search_result.jsx"; import SharedInfoWidget from "../widgets/shared_info.js"; -import TabRowWidget from "../widgets/tab_row.js"; import MobileEditorToolbar from "../widgets/type_widgets/text/mobile_editor_toolbar.jsx"; import { applyModals } from "./layout_commons.js"; @@ -148,7 +147,6 @@ export default class MobileLayout { .child( new FlexContainer("row") .contentSized() - .css("font-size", "larger") .css("align-items", "center") .child() .child() From 76492475e3160143dfa9946e14a473ffdca86095 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 21:27:24 +0200 Subject: [PATCH 09/16] fix(mobile/note_actions): find not working --- apps/client/src/layouts/mobile_layout.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/client/src/layouts/mobile_layout.tsx b/apps/client/src/layouts/mobile_layout.tsx index 37fcf2f68..7fb925533 100644 --- a/apps/client/src/layouts/mobile_layout.tsx +++ b/apps/client/src/layouts/mobile_layout.tsx @@ -7,6 +7,7 @@ import FlexContainer from "../widgets/containers/flex_container.js"; import RootContainer from "../widgets/containers/root_container.js"; import ScrollingContainer from "../widgets/containers/scrolling_container.js"; import SplitNoteContainer from "../widgets/containers/split_note_container.js"; +import FindWidget from "../widgets/find.js"; import FloatingButtons from "../widgets/FloatingButtons.jsx"; import { MOBILE_FLOATING_BUTTONS } from "../widgets/FloatingButtonsDefinitions.jsx"; import LauncherContainer from "../widgets/launch_bar/LauncherContainer.jsx"; @@ -169,6 +170,7 @@ export default class MobileLayout { .child() ) .child() + .child(new FindWidget()) ) ) ) From 92991cc03c4c50b1145acea2f6041b7cd71c93ce Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 21:37:20 +0200 Subject: [PATCH 10/16] fix(promoted_attributes): displayed in non-default view modes --- apps/client/src/widgets/PromotedAttributes.tsx | 11 ++++++----- apps/client/src/widgets/layout/NoteTitleActions.tsx | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/client/src/widgets/PromotedAttributes.tsx b/apps/client/src/widgets/PromotedAttributes.tsx index a9618d3a6..cbe573725 100644 --- a/apps/client/src/widgets/PromotedAttributes.tsx +++ b/apps/client/src/widgets/PromotedAttributes.tsx @@ -5,6 +5,7 @@ import clsx from "clsx"; import { ComponentChild, HTMLInputTypeAttribute, InputHTMLAttributes, MouseEventHandler, TargetedEvent, TargetedInputEvent } from "preact"; import { Dispatch, StateUpdater, useEffect, useRef, useState } from "preact/hooks"; +import NoteContext from "../components/note_context"; import FAttribute from "../entities/fattribute"; import FNote from "../entities/fnote"; import { Attribute } from "../services/attribute_parser"; @@ -40,8 +41,8 @@ type OnChangeEventData = TargetedEvent | InputEvent | J type OnChangeListener = (e: OnChangeEventData) => Promise; export default function PromotedAttributes() { - const { note, componentId } = useNoteContext(); - const [ cells, setCells ] = usePromotedAttributeData(note, componentId); + const { note, componentId, noteContext } = useNoteContext(); + const [ cells, setCells ] = usePromotedAttributeData(note, componentId, noteContext); return ; } @@ -74,12 +75,12 @@ export function PromotedAttributesContent({ note, componentId, cells, setCells } * * The cells are returned as a state since they can also be altered internally if needed, for example to add a new empty cell. */ -export function usePromotedAttributeData(note: FNote | null | undefined, componentId: string): [ Cell[] | undefined, Dispatch> ] { +export function usePromotedAttributeData(note: FNote | null | undefined, componentId: string, noteContext: NoteContext | undefined): [ Cell[] | undefined, Dispatch> ] { const [ viewType ] = useNoteLabel(note, "viewType"); const [ cells, setCells ] = useState(); function refresh() { - if (!note || viewType === "table") { + if (!note || viewType === "table" || noteContext?.viewScope?.viewMode !== "default") { setCells([]); return; } @@ -124,7 +125,7 @@ export function usePromotedAttributeData(note: FNote | null | undefined, compone setCells(cells); } - useEffect(refresh, [ note, viewType ]); + useEffect(refresh, [ note, viewType, noteContext ]); useTriliumEvent("entitiesReloaded", ({ loadResults }) => { if (loadResults.getAttributeRows(componentId).find((attr) => attributes.isAffecting(attr, note))) { refresh(); diff --git a/apps/client/src/widgets/layout/NoteTitleActions.tsx b/apps/client/src/widgets/layout/NoteTitleActions.tsx index 6886acc7e..96a2e9296 100644 --- a/apps/client/src/widgets/layout/NoteTitleActions.tsx +++ b/apps/client/src/widgets/layout/NoteTitleActions.tsx @@ -48,7 +48,7 @@ function PromotedAttributes({ note, componentId, noteContext }: { componentId: string, noteContext: NoteContext | undefined }) { - const [ cells, setCells ] = usePromotedAttributeData(note, componentId); + const [ cells, setCells ] = usePromotedAttributeData(note, componentId, noteContext); const [ expanded, setExpanded ] = useState(false); useEffect(() => { From 35ac5fc51426f2ff10d4cbb9c9fe0baedb09fa55 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 21:42:48 +0200 Subject: [PATCH 11/16] fix(mobile/note_actions): reintroduce insert child note --- .../widgets/mobile_widgets/mobile_detail_menu.tsx | 14 ++++++++++++-- apps/client/src/widgets/ribbon/NoteActions.tsx | 9 +++++---- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index 9fc3f9d3b..c096e1d64 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -1,12 +1,22 @@ +import { t } from "../../services/i18n"; +import { FormDropdownDivider } from "../react/FormList"; import { useNoteContext } from "../react/hooks"; -import { NoteContextMenu } from "../ribbon/NoteActions"; +import { CommandItem, NoteContextMenu } from "../ribbon/NoteActions"; export default function MobileDetailMenu() { const { note, noteContext } = useNoteContext(); return (
- {note && } + {note && ( + + + + } + /> + )}
); } diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 6aa05234d..39c3ebe35 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -1,6 +1,6 @@ import { ConvertToAttachmentResponse } from "@triliumnext/commons"; import { Dropdown as BootstrapDropdown } from "bootstrap"; -import { RefObject } from "preact"; +import { ComponentChildren, RefObject } from "preact"; import { useContext, useEffect, useRef } from "preact/hooks"; import appContext, { CommandNames } from "../../components/app_context"; @@ -14,7 +14,7 @@ import { t } from "../../services/i18n"; import protected_session from "../../services/protected_session"; import server from "../../services/server"; import toast from "../../services/toast"; -import { isElectron as getIsElectron, isMac as getIsMac } from "../../services/utils"; +import { isElectron as getIsElectron, isMac as getIsMac, isMobile } from "../../services/utils"; import ws from "../../services/ws"; import ClosePaneButton from "../buttons/close_pane_button"; import CreatePaneButton from "../buttons/create_pane_button"; @@ -63,7 +63,7 @@ function RevisionsButton({ note }: { note: FNote }) { type ItemToFocus = "basic-properties"; -export function NoteContextMenu({ note, noteContext }: { note: FNote, noteContext?: NoteContext }) { +export function NoteContextMenu({ note, noteContext, extraItems }: { note: FNote, noteContext?: NoteContext, extraItems?: ComponentChildren; }) { const dropdownRef = useRef(null); const parentComponent = useContext(ParentComponent); const noteType = useNoteProperty(note, "type") ?? ""; @@ -107,6 +107,7 @@ export function NoteContextMenu({ note, noteContext }: { note: FNote, noteContex onHidden={() => itemToFocusRef.current = null } mobileBackdrop > + {extraItems} {isReadOnly && <> void), disabled?: boolean, destructive?: boolean }) { +export function CommandItem({ icon, text, title, command, disabled }: { icon: string, text: string, title?: string, command: CommandNames | (() => void), disabled?: boolean, destructive?: boolean }) { return Date: Sun, 1 Feb 2026 21:45:43 +0200 Subject: [PATCH 12/16] fix(mobile/note_actions): reintroduce help button --- .../src/widgets/mobile_widgets/mobile_detail_menu.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index c096e1d64..5bb1f434e 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -1,10 +1,13 @@ import { t } from "../../services/i18n"; -import { FormDropdownDivider } from "../react/FormList"; +import { getHelpUrlForNote } from "../../services/in_app_help"; +import { openInAppHelpFromUrl } from "../../services/utils"; +import { FormDropdownDivider, FormListItem } from "../react/FormList"; import { useNoteContext } from "../react/hooks"; import { CommandItem, NoteContextMenu } from "../ribbon/NoteActions"; export default function MobileDetailMenu() { const { note, noteContext } = useNoteContext(); + const helpUrl = getHelpUrlForNote(note); return (
@@ -14,6 +17,11 @@ export default function MobileDetailMenu() { extraItems={<> + {helpUrl && openInAppHelpFromUrl(helpUrl)} + >{t("help-button.title")}} + } /> )} From ce9ca1917db01bb91e2703d5b80d007bdd8367a3 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 21:59:19 +0200 Subject: [PATCH 13/16] fix(mobile/note_actions): reintroduce split buttons --- .../mobile_widgets/mobile_detail_menu.tsx | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index 5bb1f434e..3458dcb0c 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -6,8 +6,10 @@ import { useNoteContext } from "../react/hooks"; import { CommandItem, NoteContextMenu } from "../ribbon/NoteActions"; export default function MobileDetailMenu() { - const { note, noteContext } = useNoteContext(); + const { note, noteContext, parentComponent, ntxId } = useNoteContext(); const helpUrl = getHelpUrlForNote(note); + const subContexts = noteContext?.getMainContext().getSubContexts() ?? []; + const isMainContext = noteContext?.isMainContext(); return (
@@ -16,11 +18,27 @@ export default function MobileDetailMenu() { note={note} noteContext={noteContext} extraItems={<> - - {helpUrl && openInAppHelpFromUrl(helpUrl)} - >{t("help-button.title")}} + {helpUrl && <> + + openInAppHelpFromUrl(helpUrl)} + >{t("help-button.title")} + } + {subContexts.length < 2 && <> + + parentComponent.triggerCommand("openNewNoteSplit", { ntxId })} + icon="bx bx-dock-right" + >{t("create_pane_button.create_new_split")} + } + {!isMainContext && <> + + parentComponent.triggerCommand("closeThisNoteSplit", { ntxId })} + >{t("close_pane_button.close_this_pane")} + } } /> From fd6f9108247dc08c0063746b5a065dcfd5c00884 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 22:02:54 +0200 Subject: [PATCH 14/16] fix(mobile/note_actions): backdrop remains when closing split --- .../src/widgets/mobile_widgets/mobile_detail_menu.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index 3458dcb0c..3db1738fe 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -36,7 +36,12 @@ export default function MobileDetailMenu() { parentComponent.triggerCommand("closeThisNoteSplit", { ntxId })} + onClick={() => { + // Wait first for the context menu to be dismissed, otherwise the backdrop stays on. + requestAnimationFrame(() => { + parentComponent.triggerCommand("closeThisNoteSplit", { ntxId }); + }); + }} >{t("close_pane_button.close_this_pane")} } From 841fab77a805b033b3d0feb1845500283b3d244a Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 22:22:25 +0200 Subject: [PATCH 15/16] chore(client): address requested changes --- apps/client/src/services/experimental_features.ts | 2 +- apps/client/src/widgets/ribbon/NoteActions.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/client/src/services/experimental_features.ts b/apps/client/src/services/experimental_features.ts index b6211b4d1..8cfbe126e 100644 --- a/apps/client/src/services/experimental_features.ts +++ b/apps/client/src/services/experimental_features.ts @@ -30,7 +30,7 @@ export function isExperimentalFeatureEnabled(featureId: ExperimentalFeatureId): export function getEnabledExperimentalFeatureIds() { const values = [ ...getEnabledFeatures().values() ]; - if (options.is("newLayout")) { + if (isMobile() || options.is("newLayout")) { values.push("new-layout"); } return values; diff --git a/apps/client/src/widgets/ribbon/NoteActions.tsx b/apps/client/src/widgets/ribbon/NoteActions.tsx index 39c3ebe35..51788085d 100644 --- a/apps/client/src/widgets/ribbon/NoteActions.tsx +++ b/apps/client/src/widgets/ribbon/NoteActions.tsx @@ -14,7 +14,7 @@ import { t } from "../../services/i18n"; import protected_session from "../../services/protected_session"; import server from "../../services/server"; import toast from "../../services/toast"; -import { isElectron as getIsElectron, isMac as getIsMac, isMobile } from "../../services/utils"; +import { isElectron as getIsElectron, isMac as getIsMac } from "../../services/utils"; import ws from "../../services/ws"; import ClosePaneButton from "../buttons/close_pane_button"; import CreatePaneButton from "../buttons/create_pane_button"; From c36ce3ea143df67a17b4275846652777503d8d91 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sun, 1 Feb 2026 22:29:43 +0200 Subject: [PATCH 16/16] fix(mobile/note_actions): insert child note not working --- .../src/widgets/mobile_widgets/mobile_detail_menu.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx index 3db1738fe..7db00d0bc 100644 --- a/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx +++ b/apps/client/src/widgets/mobile_widgets/mobile_detail_menu.tsx @@ -1,9 +1,10 @@ import { t } from "../../services/i18n"; import { getHelpUrlForNote } from "../../services/in_app_help"; +import note_create from "../../services/note_create"; import { openInAppHelpFromUrl } from "../../services/utils"; import { FormDropdownDivider, FormListItem } from "../react/FormList"; import { useNoteContext } from "../react/hooks"; -import { CommandItem, NoteContextMenu } from "../ribbon/NoteActions"; +import { NoteContextMenu } from "../ribbon/NoteActions"; export default function MobileDetailMenu() { const { note, noteContext, parentComponent, ntxId } = useNoteContext(); @@ -17,7 +18,10 @@ export default function MobileDetailMenu() { - + noteContext?.notePath && note_create.createNote(noteContext.notePath)} + icon="bx bx-plus" + >{t("mobile_detail_menu.insert_child_note")} {helpUrl && <>