Further mobile improvements (#8769)

This commit is contained in:
Elian Doran 2026-02-19 19:42:24 +02:00 committed by GitHub
commit bc89bc8270
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 65 additions and 33 deletions

View File

@ -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";

View File

@ -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;
@ -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 {

View File

@ -47,6 +47,7 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout:
>
{isMobile() && <>
<MenuItem command="searchNotes" icon="bx bx-search" text={t("global_menu.search_notes")} />
<MenuItem command="showRecentChanges" icon="bx bx-history" text={t("recent_changes.title")} />
<FormDropdownDivider />
</>}

View File

@ -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<ActionButton
<ActionButton
className={clsx("button-widget launcher-button", className)}
noIconActionClass
titlePosition={isHorizontalLayout ? "bottom" : "right"}
titlePosition={getTitlePosition(isHorizontalLayout)}
{...props}
/>
);
@ -34,6 +37,7 @@ export function LaunchBarActionButton({ className, ...props }: Omit<ActionButton
export function LaunchBarDropdownButton({ children, icon, dropdownOptions, ...props }: Pick<DropdownProps, "title" | "children" | "onShown" | "dropdownOptions" | "dropdownRef"> & { icon: string }) {
const { isHorizontalLayout } = useContext(LaunchBarContext);
const titlePosition = getTitlePosition(isHorizontalLayout);
return (
<Dropdown
@ -41,12 +45,12 @@ export function LaunchBarDropdownButton({ children, icon, dropdownOptions, ...pr
buttonClassName="right-dropdown-button launcher-button"
hideToggleArrow
text={<Icon icon={icon} />}
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";
}

View File

@ -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<BasicWidget> {
private screenName: Screen;
@ -54,7 +54,7 @@ export default class SidebarContainer extends FlexContainer<BasicWidget> {
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<BasicWidget> {
}
} 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<BasicWidget> {
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;

View File

@ -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() && <ActionButton
icon="bx bx-sidebar"
text={t("note_tree.toggle-sidebar")}
onClick={() => 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"
});
}}
/>}
</div>
)
);
}

View File

@ -69,7 +69,7 @@ function MobileNoteIconSwitcher({ note, icon }: {
const [ modalShown, setModalShown ] = useState(false);
const { windowWidth } = useWindowSize();
return (note &&
return (
<div className="note-icon-widget">
<ActionButton
className="note-icon"
@ -86,7 +86,7 @@ function MobileNoteIconSwitcher({ note, icon }: {
className="icon-switcher note-icon-widget"
scrollable
>
<NoteIconList note={note} onHide={() => setModalShown(false)} columnCount={Math.max(1, Math.floor(windowWidth / ICON_SIZE))} />
{note && <NoteIconList note={note} onHide={() => setModalShown(false)} columnCount={Math.max(1, Math.floor(windowWidth / ICON_SIZE))} />}
</Modal>
), document.body)}
</div>

View File

@ -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<HTMLAttributes<HTMLButtonElement>, "onClick" | "onAuxClick" | "onContextMenu" | "style"> {
@ -17,6 +18,8 @@ export interface ActionButtonProps extends Pick<HTMLAttributes<HTMLButtonElement
disabled?: boolean;
}
const cachedIsMobile = isMobile();
export default function ActionButton({ text, icon, className, triggerCommand, titlePosition, noIconActionClass, frame, active, disabled, ...restProps }: ActionButtonProps) {
const buttonRef = useRef<HTMLButtonElement>(null);
const [ keyboardShortcut, setKeyboardShortcut ] = useState<string[]>();
@ -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
});

View File

@ -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) => (
<>