Mobile improvements v2 (#8615)

This commit is contained in:
Elian Doran
2026-02-05 23:44:31 +02:00
committed by GitHub
47 changed files with 1034 additions and 445 deletions

View File

@@ -46,10 +46,6 @@ if (utils.isElectron()) {
electronContextMenu.setupContextMenu();
}
if (utils.isPWA()) {
initPWATopbarColor();
}
function initOnElectron() {
const electron: typeof Electron = utils.dynamicRequire("electron");
electron.ipcRenderer.on("globalShortcut", async (event, actionName) => appContext.triggerCommand(actionName));
@@ -134,20 +130,3 @@ function initDarkOrLightMode(style: CSSStyleDeclaration) {
const { nativeTheme } = utils.dynamicRequire("@electron/remote") as typeof ElectronRemote;
nativeTheme.themeSource = themeSource;
}
function initPWATopbarColor() {
const tracker = $("#background-color-tracker");
if (tracker.length) {
const applyThemeColor = () => {
let meta = $("meta[name='theme-color']");
if (!meta.length) {
meta = $(`<meta name="theme-color">`).appendTo($("head"));
}
meta.attr("content", tracker.css("color"));
};
tracker.on("transitionend", applyThemeColor);
applyThemeColor();
}
}

View File

@@ -1,3 +1,7 @@
#background-color-tracker {
color: var(--main-background-color) !important;
}
span.keyboard-shortcut,
kbd {
display: none;

View File

@@ -77,7 +77,6 @@ export default class MobileLayout {
.child(<NoteTitleActions />)
.child(<NoteDetail />)
.child(<NoteList media="screen" />)
.child(<StandaloneRibbonAdapter component={SearchDefinitionTab} />)
.child(<SearchResult />)
.child(<FilePropertiesWrapper />)
)

View File

@@ -1,12 +1,12 @@
import treeService from "../services/tree.js";
import froca from "../services/froca.js";
import contextMenu, { type MenuCommandItem, type MenuItem } from "./context_menu.js";
import dialogService from "../services/dialog.js";
import server from "../services/server.js";
import { t } from "../services/i18n.js";
import type { ContextMenuCommandData,FilteredCommandNames } from "../components/app_context.js";
import type { SelectMenuItemEventListener } from "../components/events.js";
import dialogService from "../services/dialog.js";
import froca from "../services/froca.js";
import { t } from "../services/i18n.js";
import server from "../services/server.js";
import treeService from "../services/tree.js";
import type NoteTreeWidget from "../widgets/note_tree.js";
import type { FilteredCommandNames, ContextMenuCommandData } from "../components/app_context.js";
import contextMenu, { type MenuCommandItem, type MenuItem } from "./context_menu.js";
type LauncherCommandNames = FilteredCommandNames<ContextMenuCommandData>;
@@ -32,8 +32,8 @@ export default class LauncherContextMenu implements SelectMenuItemEventListener<
const note = this.node.data.noteId ? await froca.getNote(this.node.data.noteId) : null;
const parentNoteId = this.node.getParent().data.noteId;
const isVisibleRoot = note?.noteId === "_lbVisibleLaunchers";
const isAvailableRoot = note?.noteId === "_lbAvailableLaunchers";
const isVisibleRoot = note?.noteId === "_lbVisibleLaunchers" || note?.noteId === "_lbMobileVisibleLaunchers";
const isAvailableRoot = note?.noteId === "_lbAvailableLaunchers" || note?.noteId === "_lbMobileAvailableLaunchers";
const isVisibleItem = parentNoteId === "_lbVisibleLaunchers" || parentNoteId === "_lbMobileVisibleLaunchers";
const isAvailableItem = parentNoteId === "_lbAvailableLaunchers" || parentNoteId === "_lbMobileAvailableLaunchers";
const isItem = isVisibleItem || isAvailableItem;

View File

@@ -410,6 +410,7 @@ body.desktop .tabulator-popup-container,
.dropdown-menu.static {
box-shadow: unset;
backdrop-filter: unset !important;
}
.dropend .dropdown-toggle::after {
@@ -1553,8 +1554,14 @@ body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu {
max-height: calc(var(--tn-modal-max-height) - var(--dropdown-bottom));
}
body.mobile #launcher-container .dropdown > .dropdown-menu.show {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
body.mobile .dropdown-menu.mobile-bottom-menu.show {
--dropdown-bottom: 0px;
padding-bottom: calc(max(var(--menu-padding-size), env(safe-area-inset-bottom))) !important;
}
#mobile-sidebar-container {

View File

@@ -57,12 +57,12 @@
height: 18px;
}
/*
/*
* SEARCH PAGE
*/
/* Button bar */
.search-definition-widget .search-setting-table tbody:last-child div {
.search-definition-widget .search-setting-table .search-actions-container {
justify-content: flex-end;
gap: 8px;
}
@@ -143,7 +143,7 @@
/*
* OPTIONS PAGES
*/
:root {
--options-card-min-width: 500px;
--options-card-max-width: 900px;
@@ -335,4 +335,4 @@ nav.options-section-tabs + .options-section {
.etapi-options-section div {
height: auto !important;
}
}

View File

@@ -662,7 +662,8 @@
"show-cheatsheet": "Show Cheatsheet",
"toggle-zen-mode": "Zen Mode",
"new-version-available": "New Update Available",
"download-update": "Get Version {{latestVersion}}"
"download-update": "Get Version {{latestVersion}}",
"search_notes": "Search notes"
},
"zen_mode": {
"button_exit": "Exit Zen Mode"
@@ -761,7 +762,8 @@
"note_revisions": "Note revisions",
"error_cannot_get_branch_id": "Cannot get branchId for notePath '{{notePath}}'",
"error_unrecognized_command": "Unrecognized command {{command}}",
"backlinks": "Backlinks"
"backlinks": "Backlinks",
"content_language_switcher": "Content language: {{language}}"
},
"note_icon": {
"change_note_icon": "Change note icon",
@@ -906,6 +908,7 @@
"debug": "debug",
"debug_description": "Debug will print extra debugging information into the console to aid in debugging complex queries",
"action": "action",
"option": "option",
"search_button": "Search",
"search_execute": "Search & Execute actions",
"save_to_note": "Save to note",
@@ -2277,5 +2280,8 @@
"title_one": "{{count}} tab",
"title_other": "{{count}} tabs",
"more_options": "More options"
},
"bookmark_buttons": {
"bookmarks": "Bookmarks"
}
}

View File

@@ -12,6 +12,7 @@
.fixed-note-tree-container {
height: 60%;
border-bottom: 1px solid var(--main-border-color);
overflow: auto;
.tree-wrapper {
padding: 0;

View File

@@ -38,7 +38,7 @@ export default function NoteDetail() {
const [ noteTypesToRender, setNoteTypesToRender ] = useState<{ [ key in ExtendedNoteType ]?: (props: TypeWidgetProps) => VNode }>({});
const [ activeNoteType, setActiveNoteType ] = useState<ExtendedNoteType>();
const widgetRequestId = useRef(0);
const hasFixedTree = noteContext?.hoistedNoteId === "_lbMobileRoot" && isMobile();
const hasFixedTree = note && noteContext?.hoistedNoteId === "_lbMobileRoot" && isMobile() && note.noteId.startsWith("_lbMobile");
const props: TypeWidgetProps = {
note: note!,
@@ -216,7 +216,7 @@ export default function NoteDetail() {
"fixed-tree": hasFixedTree
})}
>
{hasFixedTree && <FixedTree />}
{hasFixedTree && <FixedTree noteContext={noteContext} />}
{Object.entries(noteTypesToRender).map(([ itemType, Element ]) => {
return <NoteDetailWrapper
@@ -232,8 +232,7 @@ export default function NoteDetail() {
);
}
function FixedTree() {
const { noteContext } = useNoteContext();
function FixedTree({ noteContext }: { noteContext: NoteContext }) {
const [ treeEl ] = useLegacyWidget(() => new NoteTreeWidget(), { noteContext });
return <div class="fixed-note-tree-container">{treeEl}</div>;
}

View File

@@ -45,6 +45,10 @@ export default function GlobalMenu({ isHorizontalLayout }: { isHorizontalLayout:
noDropdownListStyle
mobileBackdrop
>
{isMobile() && <>
<MenuItem command="searchNotes" icon="bx bx-search" text={t("global_menu.search_notes")} />
<FormDropdownDivider />
</>}
<MenuItem command="openNewWindow" icon="bx bx-window-open" text={t("global_menu.open_new_window")} />
<MenuItem command="showShareSubtree" icon="bx bx-share-alt" text={t("global_menu.show_shared_notes_subtree")} />
@@ -105,7 +109,7 @@ function BrowserOnlyOptions() {
function DevelopmentOptions({ dropStart }: { dropStart: boolean }) {
return <>
<FormListHeader text="Development Options"></FormListHeader>
<FormListHeader text="Development Options" />
<FormDropdownSubmenu icon="bx bx-test-tube" title="Experimental features" dropStart={dropStart}>
{experimentalFeatures.map((feature) => (
<ExperimentalFeatureToggle key={feature.id} experimentalFeature={feature as ExperimentalFeature} />

View File

@@ -10,12 +10,6 @@
--card-padding: 0.6em;
}
body.mobile .board-view {
scroll-snap-type: x mandatory;
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
}
.board-view-container {
height: 100%;
display: flex;
@@ -26,6 +20,12 @@ body.mobile .board-view {
overflow-x: auto;
}
body.mobile .board-view-container {
scroll-snap-type: x mandatory;
-webkit-overflow-scrolling: touch;
scroll-behavior: smooth;
}
.board-view-container .board-column {
width: 250px;
flex-shrink: 0;

View File

@@ -18,14 +18,12 @@ import FlexContainer from "./flex_container.js";
* - `#root-container.vertical-layout`, if the current layout is horizontal.
*/
export default class RootContainer extends FlexContainer<BasicWidget> {
private originalViewportHeight: number;
constructor(isHorizontalLayout: boolean) {
super(isHorizontalLayout ? "column" : "row");
this.id("root-widget");
this.css("height", "100dvh");
this.originalViewportHeight = getViewportHeight();
}
render(): JQuery<HTMLElement> {
@@ -40,6 +38,7 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
this.#setThemeCapabilities();
this.#setLocaleAndDirection(options.get("locale"));
this.#setExperimentalFeatures();
this.#initPWATopbarColor();
return super.render();
}
@@ -65,8 +64,12 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
}
#onMobileResize() {
const currentViewportHeight = getViewportHeight();
const isKeyboardOpened = (currentViewportHeight < this.originalViewportHeight);
const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
const windowHeight = window.innerHeight;
// If viewport is significantly smaller, keyboard is likely open
const isKeyboardOpened = windowHeight - viewportHeight > 150;
this.$widget.toggleClass("virtual-keyboard-opened", isKeyboardOpened);
}
@@ -113,8 +116,23 @@ export default class RootContainer extends FlexContainer<BasicWidget> {
document.body.lang = locale;
document.body.dir = correspondingLocale?.rtl ? "rtl" : "ltr";
}
#initPWATopbarColor() {
if (!utils.isPWA()) return;
const tracker = $("#background-color-tracker");
if (tracker.length) {
const applyThemeColor = () => {
let meta = $("meta[name='theme-color']");
if (!meta.length) {
meta = $(`<meta name="theme-color">`).appendTo($("head"));
}
meta.attr("content", tracker.css("color"));
};
tracker.on("transitionend", applyThemeColor);
applyThemeColor();
}
}
}
function getViewportHeight() {
return window.visualViewport?.height ?? window.innerHeight;
}

View File

@@ -1,11 +1,16 @@
import { useContext, useMemo } from "preact/hooks";
import { LaunchBarContext, LaunchBarDropdownButton, useLauncherIconAndTitle } from "./launch_bar_widgets";
import { CSSProperties } from "preact";
import type FNote from "../../entities/fnote";
import { useChildNotes, useNoteLabelBoolean } from "../react/hooks";
import "./BookmarkButtons.css";
import { CSSProperties } from "preact";
import { useContext, useMemo } from "preact/hooks";
import type FNote from "../../entities/fnote";
import { t } from "../../services/i18n";
import { FormDropdownSubmenu, FormListItem } from "../react/FormList";
import { useChildNotes, useNote, useNoteIcon, useNoteLabelBoolean } from "../react/hooks";
import NoteLink from "../react/NoteLink";
import { CustomNoteLauncher } from "./GenericButtons";
import ResponsiveContainer from "../react/ResponsiveContainer";
import { CustomNoteLauncher, launchCustomNoteLauncher } from "./GenericButtons";
import { LaunchBarContext, LaunchBarDropdownButton, useLauncherIconAndTitle } from "./launch_bar_widgets";
const PARENT_NOTE_ID = "_lbBookmarks";
@@ -19,17 +24,64 @@ export default function BookmarkButtons() {
const childNotes = useChildNotes(PARENT_NOTE_ID);
return (
<div style={style}>
{childNotes?.map(childNote => <SingleBookmark key={childNote.noteId} note={childNote} />)}
</div>
)
<ResponsiveContainer
desktop={
<div style={style}>
{childNotes?.map(childNote => <SingleBookmark key={childNote.noteId} note={childNote} />)}
</div>
}
mobile={
<LaunchBarDropdownButton
icon="bx bx-bookmark"
title={t("bookmark_buttons.bookmarks")}
>
{childNotes?.map(childNote => <SingleBookmark key={childNote.noteId} note={childNote} />)}
</LaunchBarDropdownButton>
}
/>
);
}
function SingleBookmark({ note }: { note: FNote }) {
const [ bookmarkFolder ] = useNoteLabelBoolean(note, "bookmarkFolder");
return bookmarkFolder
? <BookmarkFolder note={note} />
: <CustomNoteLauncher launcherNote={note} getTargetNoteId={() => note.noteId} />
return <ResponsiveContainer
desktop={
bookmarkFolder
? <BookmarkFolder note={note} />
: <CustomNoteLauncher launcherNote={note} getTargetNoteId={() => note.noteId} />
}
mobile={<MobileBookmarkItem noteId={note.noteId} bookmarkFolder={bookmarkFolder} />}
/>;
}
function MobileBookmarkItem({ noteId, bookmarkFolder }: { noteId: string, bookmarkFolder: boolean }) {
const note = useNote(noteId);
const noteIcon = useNoteIcon(note);
if (!note) return null;
return (
!bookmarkFolder
? <FormListItem icon={noteIcon} onClick={(e) => launchCustomNoteLauncher(e, { launcherNote: note, getTargetNoteId: () => note.noteId })}>{note.title}</FormListItem>
: <MobileBookmarkFolder note={note} />
);
}
function MobileBookmarkFolder({ note }: { note: FNote }) {
const childNotes = useChildNotes(note.noteId);
return (
<FormDropdownSubmenu icon="bx bx-folder" title={note.title}>
{childNotes.map(childNote => (
<FormListItem
key={childNote.noteId}
icon={childNote.getIcon()}
onClick={(e) => launchCustomNoteLauncher(e, { launcherNote: childNote, getTargetNoteId: () => childNote.noteId })}
>
{childNote.title}
</FormListItem>
))}
</FormDropdownSubmenu>
);
}
function BookmarkFolder({ note }: { note: FNote }) {
@@ -55,5 +107,5 @@ function BookmarkFolder({ note }: { note: FNote }) {
</ul>
</div>
</LaunchBarDropdownButton>
)
);
}

View File

@@ -7,32 +7,18 @@ import { isCtrlKey } from "../../services/utils";
import { useGlobalShortcut, useNoteLabel } from "../react/hooks";
import { LaunchBarActionButton, useLauncherIconAndTitle } from "./launch_bar_widgets";
export function CustomNoteLauncher({ launcherNote, getTargetNoteId, getHoistedNoteId }: {
export function CustomNoteLauncher(props: {
launcherNote: FNote;
getTargetNoteId: (launcherNote: FNote) => string | null | Promise<string | null>;
getHoistedNoteId?: (launcherNote: FNote) => string | null;
keyboardShortcut?: string;
}) {
const { launcherNote, getTargetNoteId } = props;
const { icon, title } = useLauncherIconAndTitle(launcherNote);
const launch = useCallback(async (evt: MouseEvent | KeyboardEvent) => {
if (evt.which === 3) {
return;
}
const targetNoteId = await getTargetNoteId(launcherNote);
if (!targetNoteId) return;
const hoistedNoteIdWithDefault = getHoistedNoteId?.(launcherNote) || appContext.tabManager.getActiveContext()?.hoistedNoteId;
const ctrlKey = isCtrlKey(evt);
if ((evt.which === 1 && ctrlKey) || evt.which === 2) {
const activate = !!evt.shiftKey;
await appContext.tabManager.openInNewTab(targetNoteId, hoistedNoteIdWithDefault, activate);
} else {
await appContext.tabManager.openInSameTab(targetNoteId, hoistedNoteIdWithDefault);
}
}, [ launcherNote, getTargetNoteId, getHoistedNoteId ]);
await launchCustomNoteLauncher(evt, props);
}, [ props ]);
// Keyboard shortcut.
const [ shortcut ] = useNoteLabel(launcherNote, "keyboardShortcut");
@@ -54,3 +40,24 @@ export function CustomNoteLauncher({ launcherNote, getTargetNoteId, getHoistedNo
/>
);
}
export async function launchCustomNoteLauncher(evt: MouseEvent | KeyboardEvent, { launcherNote, getTargetNoteId, getHoistedNoteId }: {
launcherNote: FNote;
getTargetNoteId: (launcherNote: FNote) => string | null | Promise<string | null>;
getHoistedNoteId?: (launcherNote: FNote) => string | null;
}) {
if (evt.which === 3) return;
const targetNoteId = await getTargetNoteId(launcherNote);
if (!targetNoteId) return;
const hoistedNoteIdWithDefault = getHoistedNoteId?.(launcherNote) || appContext.tabManager.getActiveContext()?.hoistedNoteId;
const ctrlKey = isCtrlKey(evt);
if ((evt.which === 1 && ctrlKey) || evt.which === 2) {
const activate = !!evt.shiftKey;
await appContext.tabManager.openInNewTab(targetNoteId, hoistedNoteIdWithDefault, activate);
} else {
await appContext.tabManager.openInSameTab(targetNoteId, hoistedNoteIdWithDefault);
}
}

View File

@@ -49,6 +49,7 @@ export function LaunchBarDropdownButton({ children, icon, dropdownOptions, ...pr
placement: isHorizontalLayout ? "bottom" : "right"
}
}}
mobileBackdrop
{...props}
>{children}</Dropdown>
);

View File

@@ -50,6 +50,9 @@ body.experimental-feature-new-layout {
}
}
}
body.desktop .title-actions {
> .collapsible,
> .note-type-switcher {
padding-inline-start: calc(24px - var(--title-actions-padding-start));
@@ -57,3 +60,4 @@ body.experimental-feature-new-layout {
}
}
}

View File

@@ -23,7 +23,7 @@ export default function NoteTitleActions() {
<PromotedAttributes note={note} componentId={componentId} noteContext={noteContext} />
{noteType === "search" && <SearchProperties note={note} ntxId={ntxId} />}
<EditedNotes />
{viewScope?.viewMode === "default" && <NoteTypeSwitcher />}
{(!viewScope?.viewMode || viewScope.viewMode === "default") && <NoteTypeSwitcher />}
</div>
);
}

View File

@@ -58,33 +58,6 @@
.dropdown-note-info {
padding: 1em !important;
ul {
--row-block-margin: .2em;
list-style-type: none;
padding: 0;
margin: 0;
margin-top: calc(0px - var(--row-block-margin));
margin-bottom: 12px;
display: table;
li {
display: table-row;
> strong {
display: table-cell;
padding: var(--row-block-margin) 0;
opacity: .5;
}
> span {
display: table-cell;
user-select: text;
padding-left: 2em;
}
}
}
}
.dropdown-note-paths {
@@ -144,16 +117,50 @@
}
div.similar-notes-widget div.similar-notes-wrapper {
max-height: unset;
}
button.select-button:not(:focus-visible) {
outline: none;
}
}
body.experimental-feature-new-layout .note-info-content {
ul {
--row-block-margin: .2em;
list-style-type: none;
padding: 0;
margin: 0;
margin-top: calc(0px - var(--row-block-margin));
margin-bottom: 12px;
display: table;
li {
display: table-row;
> strong {
display: table-cell;
padding: var(--row-block-margin) 0;
opacity: .5;
}
> span {
display: table-cell;
user-select: text;
padding-left: 2em;
}
}
}
}
body.experimental-feature-new-layout div.similar-notes-widget div.similar-notes-wrapper {
max-height: unset;
}
body.experimental-feature-new-layout.mobile div.similar-notes-widget div.similar-notes-wrapper {
max-height: unset;
padding: 0;
}
.bottom-panel {
margin: 0 !important;
padding: 0;

View File

@@ -212,8 +212,8 @@ export function getLocaleName(locale: Locale | null | undefined) {
//#region Note info & Similar
interface NoteInfoContext extends StatusBarContext {
similarNotesShown: boolean;
setSimilarNotesShown: (value: boolean) => void;
similarNotesShown?: boolean;
setSimilarNotesShown?: (value: boolean) => void;
}
export function NoteInfoBadge(context: NoteInfoContext) {
@@ -225,7 +225,7 @@ export function NoteInfoBadge(context: NoteInfoContext) {
// Keyboard shortcut.
useTriliumEvent("toggleRibbonTabNoteInfo", () => enabled && dropdownRef.current?.show());
useTriliumEvent("toggleRibbonTabSimilarNotes", () => setSimilarNotesShown(!similarNotesShown));
useTriliumEvent("toggleRibbonTabSimilarNotes", () => setSimilarNotesShown && setSimilarNotesShown(!similarNotesShown));
return (enabled &&
<StatusBarDropdown
@@ -242,8 +242,8 @@ export function NoteInfoBadge(context: NoteInfoContext) {
);
}
function NoteInfoContent({ note, setSimilarNotesShown, noteType, dropdownRef }: NoteInfoContext & {
dropdownRef: RefObject<BootstrapDropdown>;
export function NoteInfoContent({ note, setSimilarNotesShown, noteType, dropdownRef }: Pick<NoteInfoContext, "note" | "setSimilarNotesShown"> & {
dropdownRef?: RefObject<BootstrapDropdown>;
noteType: NoteType;
}) {
const { metadata, ...sizeProps } = useNoteMetadata(note);
@@ -251,7 +251,7 @@ function NoteInfoContent({ note, setSimilarNotesShown, noteType, dropdownRef }:
const noteTypeMapping = useMemo(() => NOTE_TYPES.find(t => t.type === noteType), [ noteType ]);
return (
<>
<div className="note-info-content">
<ul>
{originalFileName && <NoteInfoValue text={t("file_properties.original_file_name")} value={originalFileName} />}
<NoteInfoValue text={t("note_info_widget.created")} value={formatDateTime(metadata?.dateCreated)} />
@@ -262,14 +262,14 @@ function NoteInfoContent({ note, setSimilarNotesShown, noteType, dropdownRef }:
<NoteInfoValue text={t("note_info_widget.note_size")} title={t("note_info_widget.note_size_info")} value={<NoteSizeWidget {...sizeProps} />} />
</ul>
<LinkButton
{setSimilarNotesShown && <LinkButton
text={t("note_info_widget.show_similar_notes")}
onClick={() => {
dropdownRef.current?.hide();
dropdownRef?.current?.hide();
setSimilarNotesShown(true);
}}
/>
</>
/>}
</div>
);
}

View File

@@ -0,0 +1,3 @@
.code-note-switcher-modal .dropdown-menu {
background: none !important;
}

View File

@@ -1,17 +1,24 @@
import "./mobile_detail_menu.css";
import { Dropdown as BootstrapDropdown } from "bootstrap";
import { createPortal, useRef, useState } from "preact/compat";
import FNote, { NotePathRecord } from "../../entities/fnote";
import { t } from "../../services/i18n";
import note_create from "../../services/note_create";
import server from "../../services/server";
import { BacklinksList, useBacklinkCount } from "../FloatingButtonsDefinitions";
import { getLocaleName, NoteInfoContent } from "../layout/StatusBar";
import ActionButton from "../react/ActionButton";
import { FormDropdownDivider, FormListItem } from "../react/FormList";
import { useNoteContext } from "../react/hooks";
import { FormDropdownDivider, FormDropdownSubmenu, FormListItem } from "../react/FormList";
import { useNoteContext, useNoteProperty } from "../react/hooks";
import Modal from "../react/Modal";
import { NoteTypeCodeNoteList, useLanguageSwitcher, useMimeTypes } from "../ribbon/BasicPropertiesTab";
import { NoteContextMenu } from "../ribbon/NoteActions";
import NoteActionsCustom from "../ribbon/NoteActionsCustom";
import { NotePathsWidget, useSortedNotePaths } from "../ribbon/NotePathsTab";
import SimilarNotesTab from "../ribbon/SimilarNotesTab";
import { useProcessedLocales } from "../type_widgets/options/components/LocaleSelector";
export default function MobileDetailMenu() {
const dropdownRef = useRef<BootstrapDropdown | null>(null);
@@ -20,6 +27,9 @@ export default function MobileDetailMenu() {
const isMainContext = noteContext?.isMainContext();
const [ backlinksModalShown, setBacklinksModalShown ] = useState(false);
const [ notePathsModalShown, setNotePathsModalShown ] = useState(false);
const [ noteInfoModalShown, setNoteInfoModalShown ] = useState(false);
const [ similarNotesModalShown, setSimilarNotesModalShown ] = useState(false);
const [ codeNoteSwitcherModalShown, setCodeNoteSwitcherModalShown ] = useState(false);
const sortedNotePaths = useSortedNotePaths(note, hoistedNoteId);
const backlinksCount = useBacklinkCount(note, viewScope?.viewMode === "default");
@@ -36,7 +46,7 @@ export default function MobileDetailMenu() {
<NoteContextMenu
dropdownRef={dropdownRef}
note={note} noteContext={noteContext}
extraItems={<>
itemsAtStart={<>
<div className="form-list-row">
<div className="form-list-col">
<FormListItem
@@ -81,6 +91,13 @@ export default function MobileDetailMenu() {
</>}
<FormDropdownDivider />
</>}
itemsNearNoteSettings={<>
{note.type === "text" && <ContentLanguageSelector note={note} />}
{note.type === "code" && <FormListItem icon={"bx bx-code"} onClick={() => setCodeNoteSwitcherModalShown(true)}>{t("status_bar.code_note_switcher")}</FormListItem>}
<FormListItem icon="bx bx-info-circle" onClick={() => setNoteInfoModalShown(true)}>{t("note_info_widget.title")}</FormListItem>
<FormListItem icon="bx bx-bar-chart" onClick={() => setSimilarNotesModalShown(true)}>{t("similar_notes.title")}</FormListItem>
<FormDropdownDivider />
</>}
/>
) : (
<ActionButton
@@ -94,13 +111,46 @@ export default function MobileDetailMenu() {
<>
<BacklinksModal note={note} modalShown={backlinksModalShown} setModalShown={setBacklinksModalShown} />
<NotePathsModal note={note} modalShown={notePathsModalShown} notePath={noteContext?.notePath} sortedNotePaths={sortedNotePaths} setModalShown={setNotePathsModalShown} />
<NoteInfoModal note={note} modalShown={noteInfoModalShown} setModalShown={setNoteInfoModalShown} />
<SimilarNotesModal note={note} modalShown={similarNotesModalShown} setModalShown={setSimilarNotesModalShown} />
<CodeNoteSwitcherModal note={note} modalShown={codeNoteSwitcherModalShown} setModalShown={setCodeNoteSwitcherModalShown} />
</>
), document.body)}
</div>
);
}
function BacklinksModal({ note, modalShown, setModalShown }: { note: FNote | null | undefined, modalShown: boolean, setModalShown: (shown: boolean) => void }) {
function ContentLanguageSelector({ note }: { note: FNote | null | undefined }) {
const { locales, DEFAULT_LOCALE, currentNoteLanguage, setCurrentNoteLanguage } = useLanguageSwitcher(note);
const { activeLocale, processedLocales } = useProcessedLocales(locales, DEFAULT_LOCALE, currentNoteLanguage ?? DEFAULT_LOCALE.id);
return (
<FormDropdownSubmenu
icon="bx bx-globe"
title={t("mobile_detail_menu.content_language_switcher", { language: getLocaleName(activeLocale ?? DEFAULT_LOCALE) })}
>
{processedLocales.map((locale, index) =>
(typeof locale === "object") ? (
<FormListItem
key={locale.id}
rtl={locale.rtl}
checked={locale.id === currentNoteLanguage}
onClick={() => setCurrentNoteLanguage(locale.id)}
>{locale.name}</FormListItem>
) : (
<FormDropdownDivider key={`divider-${index}`} />
)
)}
</FormDropdownSubmenu>
);
}
interface WithModal {
modalShown: boolean;
setModalShown: (shown: boolean) => void;
}
function BacklinksModal({ note, modalShown, setModalShown }: { note: FNote | null | undefined } & WithModal) {
return (
<Modal
className="backlinks-modal tn-backlinks-widget"
@@ -116,7 +166,7 @@ function BacklinksModal({ note, modalShown, setModalShown }: { note: FNote | nul
);
}
function NotePathsModal({ note, modalShown, notePath, sortedNotePaths, setModalShown }: { note: FNote | null | undefined, modalShown: boolean, sortedNotePaths: NotePathRecord[] | undefined, notePath: string | null | undefined, setModalShown: (shown: boolean) => void }) {
function NotePathsModal({ note, modalShown, notePath, sortedNotePaths, setModalShown }: { note: FNote | null | undefined, sortedNotePaths: NotePathRecord[] | undefined, notePath: string | null | undefined } & WithModal) {
return (
<Modal
className="note-paths-modal"
@@ -134,3 +184,57 @@ function NotePathsModal({ note, modalShown, notePath, sortedNotePaths, setModalS
</Modal>
);
}
function NoteInfoModal({ note, modalShown, setModalShown }: { note: FNote | null | undefined } & WithModal) {
return (
<Modal
className="note-info-modal"
size="md"
title={t("note_info_widget.title")}
show={modalShown}
onHidden={() => setModalShown(false)}
>
{note && <NoteInfoContent note={note} noteType={note.type} />}
</Modal>
);
}
function SimilarNotesModal({ note, modalShown, setModalShown }: { note: FNote | null | undefined } & WithModal) {
return (
<Modal
className="similar-notes-modal"
size="md"
title={t("similar_notes.title")}
show={modalShown}
onHidden={() => setModalShown(false)}
>
<SimilarNotesTab note={note} />
</Modal>
);
}
function CodeNoteSwitcherModal({ note, modalShown, setModalShown }: { note: FNote | null | undefined } & WithModal) {
const currentNoteMime = useNoteProperty(note, "mime");
const mimeTypes = useMimeTypes();
return (
<Modal
className="code-note-switcher-modal"
size="md"
title={t("status_bar.code_note_switcher")}
show={modalShown}
onHidden={() => setModalShown(false)}
>
<div className="dropdown-menu static show">
{note && <NoteTypeCodeNoteList
currentMimeType={currentNoteMime}
mimeTypes={mimeTypes}
changeNoteType={(type, mime) => {
server.put(`notes/${note.noteId}/type`, { type, mime });
setModalShown(false);
}}
/>}
</div>
</Modal>
);
}

View File

@@ -0,0 +1,12 @@
import { ComponentChildren } from "preact";
import { isMobile } from "../../services/utils";
interface ResponsiveContainerProps {
mobile?: ComponentChildren;
desktop?: ComponentChildren;
}
export default function ResponsiveContainer({ mobile, desktop }: ResponsiveContainerProps) {
return (isMobile() ? mobile : desktop);
}

View File

@@ -1,5 +1,4 @@
import { MimeType, NoteType, ToggleInParentResponse } from "@triliumnext/commons";
import { ComponentChildren } from "preact";
import { createPortal } from "preact/compat";
import { Dispatch, StateUpdater, useCallback, useEffect, useMemo, useState } from "preact/hooks";
@@ -117,19 +116,18 @@ export function NoteTypeDropdownContent({ currentNoteType, currentNoteMime, note
onClick={() => changeNoteType(type, mime)}
>{title}</FormListItem>
);
} else {
return (
<>
<FormDropdownDivider />
<FormListItem
checked={checked}
disabled
>
<strong>{title}</strong>
</FormListItem>
</>
);
}
return (
<>
<FormDropdownDivider />
<FormListItem
checked={checked}
disabled
>
<strong>{title}</strong>
</FormListItem>
</>
);
})}
{!noCodeNotes && <NoteTypeCodeNoteList mimeTypes={mimeTypes} changeNoteType={changeNoteType} setModalShown={setModalShown} />}
@@ -141,7 +139,7 @@ export function NoteTypeCodeNoteList({ currentMimeType, mimeTypes, changeNoteTyp
currentMimeType?: string;
mimeTypes: MimeType[];
changeNoteType(type: NoteType, mime: string): void;
setModalShown(shown: boolean): void;
setModalShown?(shown: boolean): void;
}) {
return (
<>
@@ -155,8 +153,10 @@ export function NoteTypeCodeNoteList({ currentMimeType, mimeTypes, changeNoteTyp
</FormListItem>
))}
<FormDropdownDivider />
<FormListItem icon="bx bx-cog" onClick={() => setModalShown(true)}>{t("basic_properties.configure_code_notes")}</FormListItem>
{setModalShown && <>
<FormDropdownDivider />
<FormListItem icon="bx bx-cog" onClick={() => setModalShown(true)}>{t("basic_properties.configure_code_notes")}</FormListItem>
</>}
</>
);
}
@@ -195,7 +195,7 @@ function ProtectedNoteSwitch({ note }: { note?: FNote | null }) {
onChange={(shouldProtect) => note && protected_session.protectNote(note.noteId, shouldProtect, false)}
/>
</div>
)
);
}
function EditabilitySelect({ note }: { note?: FNote | null }) {
@@ -417,9 +417,9 @@ function findTypeTitle(type?: NoteType, mime?: string | null) {
const found = mimeTypes.find((mt) => mt.mime === mime);
return found ? found.title : mime;
} else {
const noteType = NOTE_TYPES.find((nt) => nt.type === type);
return noteType ? noteType.title : type;
}
const noteType = NOTE_TYPES.find((nt) => nt.type === type);
return noteType ? noteType.title : type;
}

View File

@@ -63,10 +63,11 @@ function RevisionsButton({ note }: { note: FNote }) {
type ItemToFocus = "basic-properties";
export function NoteContextMenu({ note, noteContext, extraItems, dropdownRef: externalDropdownRef }: {
export function NoteContextMenu({ note, noteContext, itemsAtStart, itemsNearNoteSettings, dropdownRef: externalDropdownRef }: {
note: FNote,
noteContext?: NoteContext,
extraItems?: ComponentChildren;
itemsAtStart?: ComponentChildren;
itemsNearNoteSettings?: ComponentChildren;
dropdownRef?: RefObject<BootstrapDropdown>;
}) {
const dropdownRef = useSyncedRef<BootstrapDropdown>(externalDropdownRef, null);
@@ -112,7 +113,7 @@ export function NoteContextMenu({ note, noteContext, extraItems, dropdownRef: ex
onHidden={() => itemToFocusRef.current = null }
mobileBackdrop
>
{extraItems}
{itemsAtStart}
{isReadOnly && <>
<CommandItem icon="bx bx-pencil" text={t("read-only-info.edit-note")}
@@ -131,6 +132,8 @@ export function NoteContextMenu({ note, noteContext, extraItems, dropdownRef: ex
<FormDropdownDivider />
</>}
{itemsNearNoteSettings}
<CommandItem icon="bx bx-import" text={t("note_actions.import_files")}
disabled={isInOptionsOrHelp || note.type === "search"}
command={() => parentComponent?.triggerCommand("showImportDialog", { noteId: note.noteId })} />

View File

@@ -2,6 +2,7 @@ import "./SearchDefinitionTab.css";
import { SaveSearchNoteResponse } from "@triliumnext/commons";
import { useContext, useEffect, useState } from "preact/hooks";
import { Fragment } from "preact/jsx-runtime";
import appContext from "../../components/app_context";
import FNote from "../../entities/fnote";
@@ -15,12 +16,13 @@ import tree from "../../services/tree";
import { getErrorMessage } from "../../services/utils";
import ws from "../../services/ws";
import RenameNoteBulkAction from "../bulk_actions/note/rename_note";
import Button from "../react/Button";
import Button, { SplitButton } from "../react/Button";
import Dropdown from "../react/Dropdown";
import { FormListHeader, FormListItem } from "../react/FormList";
import { useTriliumEvent } from "../react/hooks";
import Icon from "../react/Icon";
import { ParentComponent } from "../react/react_utils";
import ResponsiveContainer from "../react/ResponsiveContainer";
import { TabContext } from "./ribbon-interface";
import { SEARCH_OPTIONS, SearchOption } from "./SearchDefinitionOptions";
@@ -84,15 +86,32 @@ export default function SearchDefinitionTab({ note, ntxId, hidden }: Pick<TabCon
<tr>
<td className="title-column">{t("search_definition.add_search_option")}</td>
<td colSpan={2} className="add-search-option">
{searchOptions?.availableOptions.map(({ icon, label, tooltip, attributeName, attributeType, defaultValue }) => (
<Button
size="small"
icon={icon}
text={label}
title={tooltip}
onClick={() => attributes.setAttribute(note, attributeType, attributeName, defaultValue ?? "")}
/>
))}
<ResponsiveContainer
desktop={searchOptions?.availableOptions.map(({ icon, label, tooltip, attributeName, attributeType, defaultValue }) => (
<Button
key={`${attributeType}-${attributeName}`}
size="small" icon={icon} text={label} title={tooltip}
onClick={() => attributes.setAttribute(note, attributeType, attributeName, defaultValue ?? "")}
/>
))}
mobile={
<Dropdown
buttonClassName="action-add-toggle btn btn-sm"
text={<><Icon icon="bx bx-plus" />{" "}{t("search_definition.option")}</>}
dropdownContainerClassName="mobile-bottom-menu" mobileBackdrop
noSelectButtonStyle
>
{searchOptions?.availableOptions.map(({ icon, label, tooltip, attributeName, attributeType, defaultValue }) => (
<FormListItem
key={`${attributeType}-${attributeName}`}
icon={icon}
description={tooltip}
onClick={() => attributes.setAttribute(note, attributeType, attributeName, defaultValue ?? "")}
>{label}</FormListItem>
))}
</Dropdown>
}
/>
<AddBulkActionButton note={note} />
</td>
@@ -113,48 +132,7 @@ export default function SearchDefinitionTab({ note, ntxId, hidden }: Pick<TabCon
})}
</tbody>
<BulkActionsList note={note} />
<tbody className="search-actions">
<tr>
<td colSpan={3}>
<div className="search-actions-container">
<Button
icon="bx bx-search"
text={t("search_definition.search_button")}
keyboardShortcut="Enter"
onClick={refreshResults}
/>
<Button
icon="bx bxs-zap"
text={t("search_definition.search_execute")}
onClick={async () => {
await server.post(`search-and-execute-note/${note.noteId}`);
refreshResults();
toast.showMessage(t("search_definition.actions_executed"), 3000);
}}
/>
{note.isHiddenCompletely() && <Button
icon="bx bx-save"
text={t("search_definition.save_to_note")}
onClick={async () => {
const { notePath } = await server.post<SaveSearchNoteResponse>("special-notes/save-search-note", { searchNoteId: note.noteId });
if (!notePath) {
return;
}
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext()?.setNote(notePath);
// Note the {{- notePathTitle}} in json file is not typo, it's unescaping
// See https://www.i18next.com/translation-function/interpolation#unescape
toast.showMessage(t("search_definition.search_note_saved", { notePathTitle: await tree.getNotePathTitle(notePath) }));
}}
/>}
</div>
</td>
</tr>
</tbody>
<SearchButtonBar note={note} refreshResults={refreshResults} />
</table>
)}
</div>
@@ -162,6 +140,56 @@ export default function SearchDefinitionTab({ note, ntxId, hidden }: Pick<TabCon
);
}
function SearchButtonBar({ note, refreshResults }: {
note: FNote;
refreshResults(): void;
}) {
async function searchAndExecuteActions() {
await server.post(`search-and-execute-note/${note.noteId}`);
refreshResults();
toast.showMessage(t("search_definition.actions_executed"), 3000);
}
async function saveSearchNote() {
const { notePath } = await server.post<SaveSearchNoteResponse>("special-notes/save-search-note", { searchNoteId: note.noteId });
if (!notePath) return;
await ws.waitForMaxKnownEntityChangeId();
await appContext.tabManager.getActiveContext()?.setNote(notePath);
// Note the {{- notePathTitle}} in json file is not typo, it's unescaping
// See https://www.i18next.com/translation-function/interpolation#unescape
toast.showMessage(t("search_definition.search_note_saved", { notePathTitle: await tree.getNotePathTitle(notePath) }));
}
return (
<tbody className="search-actions">
<tr>
<td colSpan={3}>
<ResponsiveContainer
desktop={
<div className="search-actions-container">
<Button icon="bx bx-search" text={t("search_definition.search_button")} keyboardShortcut="Enter" onClick={refreshResults} />
<Button icon="bx bxs-zap" text={t("search_definition.search_execute")} onClick={searchAndExecuteActions} />
{note.isHiddenCompletely() && <Button icon="bx bx-save" text={t("search_definition.save_to_note")} onClick={saveSearchNote} />}
</div>
}
mobile={
<SplitButton
text={t("search_definition.search_button")} icon="bx bx-search"
onClick={refreshResults}
>
<FormListItem icon="bx bxs-zap" onClick={searchAndExecuteActions}>{t("search_definition.search_execute")}</FormListItem>
{note.isHiddenCompletely() && <FormListItem icon="bx bx-save" onClick={saveSearchNote}>{t("search_definition.save_to_note")}</FormListItem>}
</SplitButton>
}
/>
</td>
</tr>
</tbody>
);
}
function BulkActionsList({ note }: { note: FNote }) {
const [ bulkActions, setBulkActions ] = useState<RenameNoteBulkAction[]>();
@@ -194,15 +222,18 @@ function AddBulkActionButton({ note }: { note: FNote }) {
buttonClassName="action-add-toggle btn btn-sm"
text={<><Icon icon="bx bxs-zap" />{" "}{t("search_definition.action")}</>}
noSelectButtonStyle
dropdownContainerClassName="mobile-bottom-menu" mobileBackdrop
>
{ACTION_GROUPS.map(({ actions, title }) => (
<>
{ACTION_GROUPS.map(({ actions, title }, index) => (
<Fragment key={index}>
<FormListHeader text={title} />
{actions.map(({ actionName, actionTitle }) => (
<FormListItem onClick={() => bulk_action.addAction(note.noteId, actionName)}>{actionTitle}</FormListItem>
))}
</>
<div>
{actions.map(({ actionName, actionTitle }) => (
<FormListItem key={actionName} onClick={() => bulk_action.addAction(note.noteId, actionName)}>{actionTitle}</FormListItem>
))}
</div>
</Fragment>
))}
</Dropdown>
);

View File

@@ -10,6 +10,12 @@
display: flex;
justify-content: space-between;
align-items: baseline;
@media (max-width: 991px) {
margin-block: 1em;
flex-direction: column;
gap: 0.5em;
}
}
/* #endregion */
@@ -42,6 +48,12 @@
display: flex;
align-items: center;
gap: 1em;
@media (max-width: 991px) {
gap: 0.5em;
flex-wrap: wrap;
.attachment-title { flex-grow: 1; }
}
}
.attachment-details {
@@ -127,10 +139,6 @@
/* #region Attachment actions */
.attachment-actions .dropdown-menu {
width: 20em;
}
.attachment-actions .dropdown-item .tn-icon {
position: relative;
top: 3px;

View File

@@ -239,6 +239,8 @@ function AttachmentActions({ attachment, copyAttachmentLinkToClipboard }: { atta
text={<Icon icon="bx bx-dots-vertical-rounded" />}
buttonClassName="icon-action-always-border"
iconAction
dropdownContainerClassName="mobile-bottom-menu"
mobileBackdrop
>
<FormListItem
icon="bx bx-file-find"

View File

@@ -1,12 +1,18 @@
<p>To easily access selected notes, you can bookmark them. See demo:</p>
<p>
<img src="Bookmarks_bookmarks.gif">
</p>
<p>Frequently used notes can be bookmarked, which will make them appear in
the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>&nbsp;for
easy access.</p>
<h2>Configuring the launch bar</h2>
<p>If bookmarks don't appear in the launch bar, then most likely the bookmark
section has been hidden. Go to the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>&nbsp;configuration
from the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_x3i7MxGccDuM">Global menu</a>&nbsp;and
ensure <em>Bookmarks</em> is in the <em>Visible Launchers</em> section.</p>
<h2>Bookmark folder</h2>
<p>Space in the left panel is limited, and you might want to bookmark many
items. One possible solution is to bookmark a folder, so it shows its children:</p>
<p>
<img src="Bookmarks_bookmark-folder.png">
</p>
<p>To do this, you need to add a <code spellcheck="false">#bookmarkFolder</code> label
to the note.</p>
<p>To do this, bookmark a folder and assign it the <code spellcheck="false">#bookmarkFolder</code> label.</p>
<h2>Mobile</h2>
<p>On mobile, bookmarks are only displayed starting with v0.102.0. Because
of the more constrained screen size, the bookmarks are grouped under a
single icon instead of displaying them as separate icons.</p>
<p>When pressed, a menu will appear listing all the bookmarks. Bookmark folders
are also supported and will appear as sub-menus.</p>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 KiB

View File

@@ -1,8 +1,11 @@
<h2>Position of the Launch bar</h2>
<p>Depending on the layout selected, the launcher bar will either be on the
left side of the screen with buttons displayed vertically or at the top
of the screen. See&nbsp;<a href="#root/_help_x0JgW8UqGXvq">Vertical and horizontal layout</a>&nbsp;for
<p>On desktop, depending on the layout selected, the launcher bar will either
be on the left side of the screen with buttons displayed vertically or
at the top of the screen. See&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_x0JgW8UqGXvq">Vertical and horizontal layout</a>&nbsp;for
more information.</p>
<p>On mobile, the launch bar will always be at the bottom.</p>
<p>If there are too many items in the launch bar to fit the screen, it will
become scrollable.</p>
<h2>Terminology</h2>
<ul>
<li><strong>Launcher</strong>: a button that can be (or is) displayed on the
@@ -12,16 +15,16 @@
<li><strong>Visible Launcher</strong>: a launcher that is currently displayed
on the launch bar.</li>
</ul>
<h2>Configuring the Launch bar</h2>
<h2>Configuring the desktop Launch bar</h2>
<p>There are two ways to configure the launch bar:</p>
<ul>
<li>Right click in the empty space between launchers on the launch bar and
select <em>Configure Launchbar.</em>
</li>
<li>Click on the&nbsp;<a href="#root/_help_x3i7MxGccDuM">Global menu</a>&nbsp;and
<li>Click on the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_x3i7MxGccDuM">Global menu</a>&nbsp;and
select <em>Configure Launchbar</em>.</li>
</ul>
<p>This will open a new tab with the&nbsp;<a href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>&nbsp;listing
<p>This will open a new tab with the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a>&nbsp;listing
the launchers.</p>
<p>
<img src="Launch Bar_image.png">
@@ -29,6 +32,19 @@
<p>Expanding <em>Available Launchers</em> section will show the list of launchers
that are not displayed on the launch bar. The <em>Visible Launchers</em> will
show the ones that are currently displayed.</p>
<h2>Configuring the mobile launch bar</h2>
<p>The launch bar on mobile uses a different configuration from the desktop
one. The reasoning is that not all desktop icons are available on mobile,
and fewer icons fit on a mobile screen.</p>
<p>To configure the launch bar on mobile, go to&nbsp;<a class="reference-link"
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_x3i7MxGccDuM">Global menu</a>&nbsp;and
select <em>Configure Launchbar</em>.</p>
<p>The configure the mobile launch bar while on the desktop (especially useful
to configure more complicated launchers such as scripts or custom widgets),
go to&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_x3i7MxGccDuM">Global menu</a>&nbsp;
Advanced → Show Hidden Subtree and look for the <em>Mobile Launch Bar</em> section.
While in the hidden subtree, it's also possible to drag launchers between
the <em>Mobile Launch Bar</em> and (Desktop) <em>Launch Bar</em> sections.</p>
<h3>Adding/removing and reordering launchers</h3>
<p>To display a new launcher in the launch bar, first look for it in the <em>Available Launchers</em> section.
Then right click it and select <em>Move to visible launchers</em>. It is
@@ -36,13 +52,13 @@
<p>Similarly, to remove it from the launch bar, simply look for it in <em>Visible Launchers</em> then
right click it and select <em>Move to available launchers</em> or use drag-and-drop.</p>
<p>Drag-and-drop the items in the&nbsp;tree&nbsp;in order to change their
order. See&nbsp;<a href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>&nbsp;for more
interaction options, including using keyboard shortcuts.</p>
order. See&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a>&nbsp;for
more interaction options, including using keyboard shortcuts.</p>
<h2>Customizing the launcher</h2>
<ul>
<li>The icon of a launcher can be changed just like a normal note. See&nbsp;
<a
href="#root/_help_p9kXRFAkwN4o">Note Icons</a>&nbsp;for more information.</li>
class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/BFs8mudNFgCS/_help_p9kXRFAkwN4o">Note Icons</a>&nbsp;for more information.</li>
<li>The title of the launcher can also be changed.</li>
</ul>
<h3>Resetting</h3>
@@ -54,42 +70,39 @@
<p>Right click either the <em>Available launchers</em> or <em>Visible launchers</em> sections
and select one of the options:</p>
<ol>
<li>
<p><strong>Note Launcher</strong>
<br>A note launcher will simply navigate to a specified note.</p>
<li><strong>Note Launcher</strong>
<br>A note launcher will simply navigate to a specified note.
<ol>
<li>Set the <code spellcheck="false">target</code> promoted attribute to the
note to navigate to.</li>
<li>Optionally, set <code spellcheck="false">hoistedNote</code> to hoist a particular
note. See&nbsp;<a href="#root/_help_OR8WJ7Iz9K4U">Note Hoisting</a>&nbsp;for
note. See&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/wArbEsdSae6g/_help_OR8WJ7Iz9K4U">Note Hoisting</a>&nbsp;for
more information.</li>
<li>Optionally, set a <code spellcheck="false">keyboardShortcut</code> to trigger
the launcher.</li>
</ol>
</li>
<li>
<p><strong>Script Launcher</strong>
<br>An advanced launcher which will run a script upon pressing. See&nbsp;
<a
href="#root/_help_CdNpE2pqjmI6">Scripts</a>&nbsp;for more information.</p>
<ol>
<li>Set <code spellcheck="false">script</code> to point to the desired script
to run.</li>
<li>Optionally, set a <code spellcheck="false">keyboardShortcut</code> to trigger
the launcher.</li>
</ol>
<li><strong>Script Launcher</strong>
<br>An advanced launcher which will run a script upon pressing. See&nbsp;
<a
class="reference-link" href="#root/pOsGYCXsbNQG/_help_CdNpE2pqjmI6">Scripting</a>&nbsp;for more information.
<ol>
<li>Set <code spellcheck="false">script</code> to point to the desired script
to run.</li>
<li>Optionally, set a <code spellcheck="false">keyboardShortcut</code> to trigger
the launcher.</li>
</ol>
</li>
<li>
<li class="ck-list-marker-bold">
<p><strong>Custom Widget</strong>
</p>
<p>Allows defining a custom widget to be rendered inside the launcher. See&nbsp;
<a
href="#root/_help_SynTBQiBsdYJ">Widget Basics</a>&nbsp;for more information.</p>
</li>
<li>
<p><strong>Spacers</strong>
<br>Launchers that create some distance between other launchers for better
visual distinction.</p>
class="reference-link" href="#root/pOsGYCXsbNQG/CdNpE2pqjmI6/yIhgI5H7A2Sm/MgibgPcfeuGz/_help_SynTBQiBsdYJ">Widget Basics</a>&nbsp;for more information.</p>
</li>
<li><strong>Spacers</strong>
<br>Launchers that create some distance between other launchers for better
visual distinction.</li>
</ol>
<p>Launchers are configured via predefined&nbsp;<a href="#root/_help_OFXdgB2nNk1F">Promoted Attributes</a>.</p>
<p>Launchers are configured via predefined&nbsp;<a class="reference-link"
href="#root/pOsGYCXsbNQG/tC7s2alapj8V/zEY4DaJG4YT5/_help_OFXdgB2nNk1F">Promoted Attributes</a>.</p>

View File

@@ -4,8 +4,7 @@ class="image image-style-align-center">
<img style="aspect-ratio:1398/1015;" src="Split View_2_Split View_im.png"
width="1398" height="1015">
</figure>
<h2><strong>Interactions</strong></h2>
<h2><strong>Interactions</strong></h2>
<ul>
<li>Press the
<img src="Split View_Split View_imag.png">button to the right of a note's title to open a new split to the right
@@ -51,11 +50,12 @@ class="image image-style-align-center">
<ul>
<li>On smartphones, the split views are laid out vertically (one on the top
and one on the bottom), instead of horizontally as on the desktop.</li>
<li>There can be only one split open per tab.</li>
<li>It's not possible to resize the two split panes.</li>
<li>When the keyboard is opened, the active note will be “maximized”, thus
allowing for more space even when a split is open. When the keyboard is
closed, the splits become equal in size again.</li>
<li
>There can be only one split open per tab.</li>
<li>It's not possible to resize the two split panes.</li>
<li>When the keyboard is opened, the active note will be “maximized”, thus
allowing for more space even when a split is open. When the keyboard is
closed, the splits become equal in size again.</li>
</ul>
<p>Interaction:</p>
<ul>

View File

@@ -8,7 +8,8 @@
<ul>
<li>For the vertical layout, the tabs will be placed at the top but to the
right of the&nbsp;<a class="reference-link" href="#root/_help_oPVyFC7WL2Lp">Note Tree</a>.</li>
<li>For the horizontal layout, the tabs will be placed at the top in full-width,
<li
>For the horizontal layout, the tabs will be placed at the top in full-width,
above the&nbsp;<a href="#root/_help_oPVyFC7WL2Lp">note tree</a>, allowing for
more tabs to be comfortably displayed.</li>
</ul>
@@ -22,7 +23,8 @@
href="#root/_help_luNhaphA37EO">Split View</a>. Each tab can have one or more
notes, displayed horizontally.</li>
<li>Tabs can be reordered by drag-and-dropping it into a new position.</li>
<li>An existing tab can be displayed in a new window by dragging the tab upwards
<li
>An existing tab can be displayed in a new window by dragging the tab upwards
or downwards. It is not possible to combine tabs back into another window.</li>
</ul>
<h2>Keyboard interaction</h2>
@@ -36,7 +38,29 @@
<li><kbd>Ctrl</kbd>+<kbd>Tab</kbd> and <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Tab</kbd> to
go to the next or previous tab.</li>
<li><kbd>Ctrl</kbd>+<kbd>1</kbd>, <kbd>Ctrl</kbd>+<kbd>2</kbd>, up to <kbd>Ctrl</kbd>+<kbd>9</kbd> to
activate the first, second and up til ninth tab.</li>
activate the first, second and up to ninth tab.</li>
<li>There is also a shortcut to go to the last tab, but it is not assigned
a key by default.</li>
</ul>
</ul>
<h2>Mobile</h2>
<figure class="image image-style-align-right image_resized" style="width:34.12%;">
<img style="aspect-ratio:1242/2688;" src="Tabs_IMG_1767.PNG"
width="1242" height="2688">
</figure>
<p>Tabs are also supported on the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/Otzi9La2YAUX/_help_RDslemsQ6gCp">Mobile Frontend</a>.</p>
<p>Since v0.102.0, the tabs are displayed by pressing the dedicated tab switcher
button in the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>.
In this view the tabs are laid out on a grid with a preview of the note
content.</p>
<p>The context menu button at the top-right of the popup allows creating
a new tab, reopening the last closed tab and closing all the tabs.</p>
<p><a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_luNhaphA37EO">Split View</a>s
are also indicated in the tab switcher, with two titles displayed in a
tab.</p>
<aside class="admonition note">
<p>Versions prior to v0.102.0 also supported tabs, but they were displayed
directly above the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>.
The decision to use a more mobile-like tab switcher was taken because the
original tab bar could not support many tabs at once and the new design
better aligns with how mobile applications handle tabs.</p>
</aside>

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

View File

@@ -1,35 +1,168 @@
<p>Trilium (<a href="#root/_help_WOcw2SLH6tbX">server edition</a>) has a mobile
web frontend which is optimized for touch based devices - smartphones and
tablets. It is activated automatically during login process based on browser
detection.</p>
<p>Mobile frontend is limited in features compared to full desktop frontend.
<figure class="image image_resized image-style-align-right" style="width:33.52%;">
<img style="aspect-ratio:1242/2688;" src="Mobile Frontend_IMG_1765.PNG"
width="1242" height="2688">
</figure>
<p>Trilium has a mobile web frontend which is optimized for touch based devices
- smartphones and tablets. It is activated automatically during login process
based on browser detection.</p>
<p>Mobile frontend is limited in features compared to the full desktop version.
See below for more details on this.</p>
<p>Note that this is not an Android/iOS app, this is just mobile friendly
web page served on the <a href="#root/_help_WOcw2SLH6tbX">server edition</a>.</p>
<h2>Layout basics</h2>
<p>Unlike the desktop version, the mobile version has a slightly different
UI meant to better fit the constrained screens of a mobile phone.</p>
<p>Here is a non-exhaustive list of differences between the desktop version
and the mobile one:</p>
<ul>
<li>
<p>The&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_oPVyFC7WL2Lp">Note Tree</a>&nbsp;is
displayed as a sidebar. To display the sidebar, press the button in the
top-left of the screen.</p>
<ul>
<li>
<p>There is also a swipe gesture that can be done from the left of the screen,
but the browser's navigation gesture interferes with it most of the time
(depending on the platform).</p>
</li>
<li>
<p>Press and hold a note to display the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/oPVyFC7WL2Lp/_help_YtSN43OrfzaA">Note tree contextual menu</a>.</p>
</li>
</ul>
</li>
<li>The&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/wArbEsdSae6g/_help_Ms1nauBra7gq">Quick search</a>&nbsp;bar
is also displayed at the top of the note tree.</li>
<li>The full&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/wArbEsdSae6g/_help_eIg8jdvaoNNd">Search</a>&nbsp;function
can be triggered either from either the&nbsp;<a class="reference-link"
href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_x3i7MxGccDuM">Global menu</a>&nbsp;or
from the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>,
if configured.</li>
<li>
<p>The&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>&nbsp;is
displayed at the bottom of the screen.</p>
<ul>
<li>
<p>The launch bar uses a different configuration for icons than the desktop
version. See the dedicated page for more information on how to configure
it.</p>
</li>
</ul>
</li>
<li>
<p>Most of the note-related actions are grouped in the horizontal dots icon
on the top-right of the note.</p>
</li>
<li>
<p>The&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_3seOhtN8uLIY">Tabs</a>&nbsp;are
grouped under a tab switcher in the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>,
where the tabs are displayed in a full-screen grid with preview for easy
switching, as well as additional options such as reopening closed tabs.</p>
</li>
<li>
<p>Since v0.100.0,&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_luNhaphA37EO">Split View</a>&nbsp;can
also be used in mobile view, but with a maximum of two panes at once. The
splits are displayed vertically instead of horizontally.</p>
</li>
<li>
<p>Starting with v0.102.0, the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_IjZS7iK5EXtb">New Layout</a>&nbsp;is
enforced on mobile. This brings features such as the note badges, note
type switcher or collection properties which would otherwise not be available.</p>
</li>
</ul>
<h2>Installing as a PWA</h2>
<p>The mobile view can be set up as a PWA. While this does not offer any
offline capabilities, it will display the application in full-screen and
makes it easy to access via your mobile phone's home screen.</p>
<h3>On iOS with Safari</h3>
<ol>
<li>Open your default web browser and access your Trilium instance.</li>
<li
>Login.</li>
<li>
<p>Press the […] button in the bottom-right of the screen and select Share.</p>
</li>
<li>
<p>Scroll down to reveal the full list of items and choose “Add to Home Screen”.</p>
</li>
<li>
<p>Press “Add” and the web app will be available.</p>
</li>
</ol>
<h3>On Android with Google Chrome</h3>
<aside class="admonition important">
<p>Google Chrome requires the server to be served over HTTPS in order to
display in full-screen. If using HTTP, the app will appear like a normal
web page (similar to a bookmark).</p>
</aside>
<ol>
<li>Open your default web browser and access your Trilium instance.</li>
<li
>Login.</li>
<li>
<p>Press the three vertical dots icon in the top-right of the screen and
select <em>Add to Home screen.</em>
</p>
</li>
<li>
<p>Select the <em>Install</em> option.</p>
</li>
<li>
<p>Select an appropriate name.</p>
</li>
<li>
<p>The web app will appear as an application, not on the home screen.</p>
</li>
</ol>
<h3>On Android with Brave</h3>
<aside class="admonition important">
<p>Brave requires the server to be served over HTTPS in order to display
in full-screen. If using HTTP, the app will appear like a normal web page
(similar to a bookmark).</p>
</aside>
<ol>
<li>Open your default web browser and access your Trilium instance.</li>
<li
>Login.</li>
<li>
<p>Press the three vertical dots icon in the bottom-right of the screen and
select <em>Add to Home screen</em>.</p>
</li>
<li>
<p>Press the <em>Install</em> option.</p>
</li>
<li>
<p>The web app will appear as an application, not on the home screen.</p>
</li>
</ol>
<h3>On Samsung Browser</h3>
<ol>
<li>Open your default web browser and access your Trilium instance.</li>
<li
>Login.</li>
<li>
<p>Press the hamburger menu in the bottom-right of the screen.</p>
</li>
<li>
<p>Select <em>Add to</em>, followed by <em>Home screen</em>.</p>
</li>
<li>
<p>Press <em>Add</em> and the web app will appear on the home page.</p>
</li>
</ol>
<h2>Testing via the desktop application</h2>
<p>If you are running Trilium without a dedicated <a href="#root/_help_WOcw2SLH6tbX">server installation</a>,
you can still test the mobile application using the desktop application.
For more information, see&nbsp;<a class="reference-link" href="#root/_help_nRqcgfTb97uV">Using the desktop application as a server</a>.
To access it go to <code spellcheck="false">http://&lt;ip&gt;:37840/login?mobile</code> .</p>
<h2>Limitations</h2>
<p>Mobile frontend provides only some of the features of the full desktop
frontend:</p>
<ul>
<li>it is possible to browse the whole note tree, read and edit all types
of notes, but you can create only text notes</li>
<li>reading and editing <a href="#root/_help_bwg0e8ewQMak">protected notes</a> is
possible, but creating them is not supported</li>
<li>editing options is not supported</li>
<li>cloning notes is not supported</li>
<li>uploading file attachments is not supported</li>
</ul>
<h2>Forcing mobile/desktop frontend</h2>
<p>Trilium decides automatically whether to use mobile or desktop frontend.
<p>Trilium decides automatically whether to use mobile or desktop front-end.
If this is not appropriate, you can use <code spellcheck="false">?mobile</code> or
<code
spellcheck="false">?desktop</code>query param on <strong>login</strong> page (Note: you might
need to log out).</p>
<p>Alternatively, simply select <em>Switch to Mobile/Desktop Version</em> in
the&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_x3i7MxGccDuM">Global menu</a>.</p>
<h2>Scripting</h2>
<p>You can alter the behavior with <a href="#root/_help_CdNpE2pqjmI6">scripts</a> just
like for normal frontend. For script notes to be executed, they need to
have labeled <code spellcheck="false">#run=mobileStartup</code>.</p>
<p>You can alter the behavior with&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/_help_CdNpE2pqjmI6">Scripting</a>,
just like for normal frontend. For script notes to be executed, they need
to have labeled <code spellcheck="false">#run=mobileStartup</code>.</p>
<p>Custom&nbsp;<a class="reference-link" href="#root/pOsGYCXsbNQG/gh7bpGYxajRS/Vc8PjrjAGuOp/_help_xYmIYSP6wE3F">Launch Bar</a>&nbsp;widgets
are also supported.</p>

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

View File

@@ -40,18 +40,24 @@ export default function buildLaunchBarConfig() {
type: "launcher",
command: "showRecentChanges",
icon: "bx bx-history"
}
},
searchNotes: {
title: t("hidden-subtree.search-notes-title"),
type: "launcher",
command: "searchNotes",
icon: "bx bx-search",
},
bookmarks: {
title: t("hidden-subtree.bookmarks-title"),
type: "launcher",
builtinWidget: "bookmarks",
icon: "bx bx-bookmark"
},
};
const desktopAvailableLaunchers: HiddenSubtreeItem[] = [
{
id: "_lbBackInHistory",
...sharedLaunchers.backInHistory
},
{
id: "_lbForwardInHistory",
...sharedLaunchers.forwardInHistory
},
{ id: "_lbBackInHistory", ...sharedLaunchers.backInHistory },
{ id: "_lbForwardInHistory", ...sharedLaunchers.forwardInHistory },
{
id: "_commandPalette",
title: t("hidden-subtree.command-palette"),
@@ -82,11 +88,7 @@ export default function buildLaunchBarConfig() {
},
{
id: "_lbSearch",
title: t("hidden-subtree.search-notes-title"),
type: "launcher",
command: "searchNotes",
icon: "bx bx-search",
attributes: [{ type: "label", name: "desktopOnly" }]
...sharedLaunchers.searchNotes
},
{
id: "_lbJumpTo",
@@ -128,13 +130,7 @@ export default function buildLaunchBarConfig() {
baseSize: "50",
growthFactor: "0"
},
{
id: "_lbBookmarks",
title: t("hidden-subtree.bookmarks-title"),
type: "launcher",
builtinWidget: "bookmarks",
icon: "bx bx-bookmark"
},
{ id: "_lbBookmarks", ...sharedLaunchers.bookmarks },
{
id: "_lbToday",
...sharedLaunchers.openToday
@@ -179,22 +175,15 @@ export default function buildLaunchBarConfig() {
const mobileAvailableLaunchers: HiddenSubtreeItem[] = [
{ id: "_lbMobileNewNote", ...sharedLaunchers.newNote },
{ id: "_lbMobileSearchNotes", ...sharedLaunchers.searchNotes },
{ id: "_lbMobileToday", ...sharedLaunchers.openToday },
{
id: "_lbMobileRecentChanges",
...sharedLaunchers.recentChanges
}
{ id: "_lbMobileRecentChanges", ...sharedLaunchers.recentChanges },
{ id: "_lbMobileBookmarks", ...sharedLaunchers.bookmarks }
];
const mobileVisibleLaunchers: HiddenSubtreeItem[] = [
{
id: "_lbMobileBackInHistory",
...sharedLaunchers.backInHistory
},
{
id: "_lbMobileForwardInHistory",
...sharedLaunchers.forwardInHistory
},
{ id: "_lbMobileBackInHistory", ...sharedLaunchers.backInHistory },
{ id: "_lbMobileForwardInHistory", ...sharedLaunchers.forwardInHistory },
{
id: "_lbMobileJumpTo",
title: t("hidden-subtree.jump-to-note-title"),
@@ -210,7 +199,8 @@ export default function buildLaunchBarConfig() {
id: "_lbMobileTabSwitcher",
title: t("hidden-subtree.tab-switcher-title"),
type: "launcher",
builtinWidget: "mobileTabSwitcher"
builtinWidget: "mobileTabSwitcher",
icon: "bx bx-rectangle"
}
];

View File

@@ -1,5 +1,5 @@
# Documentation
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/y2NjgAW23yyj/Documentation_image.png" width="205" height="162">
There are multiple types of documentation for Trilium:<img class="image-style-align-right" src="api/images/qIpo9UiTZNdm/Documentation_image.png" width="205" height="162">
* The _User Guide_ represents the user-facing documentation. This documentation can be browsed by users directly from within Trilium, by pressing <kbd>F1</kbd>.
* The _Developer's Guide_ represents a set of Markdown documents that present the internals of Trilium, for developers.

View File

@@ -1491,20 +1491,6 @@
"isInheritable": false,
"position": 20
},
{
"type": "relation",
"name": "internalLink",
"value": "bwg0e8ewQMak",
"isInheritable": false,
"position": 30
},
{
"type": "relation",
"name": "internalLink",
"value": "CdNpE2pqjmI6",
"isInheritable": false,
"position": 40
},
{
"type": "label",
"name": "shareAlias",
@@ -1518,11 +1504,90 @@
"value": "bx bx-mobile-alt",
"isInheritable": false,
"position": 60
},
{
"type": "relation",
"name": "internalLink",
"value": "x3i7MxGccDuM",
"isInheritable": false,
"position": 70
},
{
"type": "relation",
"name": "internalLink",
"value": "IjZS7iK5EXtb",
"isInheritable": false,
"position": 80
},
{
"type": "relation",
"name": "internalLink",
"value": "oPVyFC7WL2Lp",
"isInheritable": false,
"position": 90
},
{
"type": "relation",
"name": "internalLink",
"value": "YtSN43OrfzaA",
"isInheritable": false,
"position": 110
},
{
"type": "relation",
"name": "internalLink",
"value": "xYmIYSP6wE3F",
"isInheritable": false,
"position": 120
},
{
"type": "relation",
"name": "internalLink",
"value": "Ms1nauBra7gq",
"isInheritable": false,
"position": 130
},
{
"type": "relation",
"name": "internalLink",
"value": "eIg8jdvaoNNd",
"isInheritable": false,
"position": 140
},
{
"type": "relation",
"name": "internalLink",
"value": "3seOhtN8uLIY",
"isInheritable": false,
"position": 150
},
{
"type": "relation",
"name": "internalLink",
"value": "CdNpE2pqjmI6",
"isInheritable": false,
"position": 160
},
{
"type": "relation",
"name": "internalLink",
"value": "luNhaphA37EO",
"isInheritable": false,
"position": 170
}
],
"format": "markdown",
"dataFileName": "Mobile Frontend.md",
"attachments": []
"attachments": [
{
"attachmentId": "JRliU5bOLszn",
"title": "IMG_1765.PNG",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "Mobile Frontend_IMG_1765.PNG"
}
]
},
{
"isClone": false,
@@ -2793,6 +2858,20 @@
"value": "tabs",
"isInheritable": false,
"position": 40
},
{
"type": "relation",
"name": "internalLink",
"value": "RDslemsQ6gCp",
"isInheritable": false,
"position": 50
},
{
"type": "relation",
"name": "internalLink",
"value": "xYmIYSP6wE3F",
"isInheritable": false,
"position": 60
}
],
"format": "markdown",
@@ -2821,6 +2900,14 @@
"mime": "image/png",
"position": 10,
"dataFileName": "2_Tabs_image.png"
},
{
"attachmentId": "rFdKVhoVtsLA",
"title": "IMG_1767.PNG",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "Tabs_IMG_1767.PNG"
}
]
},
@@ -2840,20 +2927,6 @@
"type": "text",
"mime": "text/html",
"attributes": [
{
"type": "relation",
"name": "internalLink",
"value": "x0JgW8UqGXvq",
"isInheritable": false,
"position": 10
},
{
"type": "relation",
"name": "internalLink",
"value": "x3i7MxGccDuM",
"isInheritable": false,
"position": 20
},
{
"type": "relation",
"name": "internalLink",
@@ -2861,41 +2934,6 @@
"isInheritable": false,
"position": 30
},
{
"type": "relation",
"name": "internalLink",
"value": "p9kXRFAkwN4o",
"isInheritable": false,
"position": 40
},
{
"type": "relation",
"name": "internalLink",
"value": "OR8WJ7Iz9K4U",
"isInheritable": false,
"position": 50
},
{
"type": "relation",
"name": "internalLink",
"value": "CdNpE2pqjmI6",
"isInheritable": false,
"position": 60
},
{
"type": "relation",
"name": "internalLink",
"value": "SynTBQiBsdYJ",
"isInheritable": false,
"position": 70
},
{
"type": "relation",
"name": "internalLink",
"value": "OFXdgB2nNk1F",
"isInheritable": false,
"position": 80
},
{
"type": "label",
"name": "iconClass",
@@ -2909,6 +2947,55 @@
"value": "launch-bar",
"isInheritable": false,
"position": 90
},
{
"type": "relation",
"name": "internalLink",
"value": "OFXdgB2nNk1F",
"isInheritable": false,
"position": 100
},
{
"type": "relation",
"name": "internalLink",
"value": "SynTBQiBsdYJ",
"isInheritable": false,
"position": 110
},
{
"type": "relation",
"name": "internalLink",
"value": "CdNpE2pqjmI6",
"isInheritable": false,
"position": 120
},
{
"type": "relation",
"name": "internalLink",
"value": "OR8WJ7Iz9K4U",
"isInheritable": false,
"position": 130
},
{
"type": "relation",
"name": "internalLink",
"value": "p9kXRFAkwN4o",
"isInheritable": false,
"position": 140
},
{
"type": "relation",
"name": "internalLink",
"value": "x3i7MxGccDuM",
"isInheritable": false,
"position": 150
},
{
"type": "relation",
"name": "internalLink",
"value": "x0JgW8UqGXvq",
"isInheritable": false,
"position": 160
}
],
"format": "markdown",
@@ -5256,28 +5343,25 @@
"value": "bx bx-bookmarks",
"isInheritable": false,
"position": 30
},
{
"type": "relation",
"name": "internalLink",
"value": "xYmIYSP6wE3F",
"isInheritable": false,
"position": 40
},
{
"type": "relation",
"name": "internalLink",
"value": "x3i7MxGccDuM",
"isInheritable": false,
"position": 50
}
],
"format": "markdown",
"dataFileName": "Bookmarks.md",
"attachments": [
{
"attachmentId": "99dD0P74W8QJ",
"title": "bookmark-folder.png",
"role": "image",
"mime": "image/png",
"position": 10,
"dataFileName": "Bookmarks_bookmark-folder.png"
},
{
"attachmentId": "9dX71aLlSl9R",
"title": "bookmarks.gif",
"role": "image",
"mime": "image/gif",
"position": 10,
"dataFileName": "Bookmarks_bookmarks.gif"
}
]
"attachments": []
},
{
"isClone": false,

View File

@@ -1,12 +1,18 @@
# Bookmarks
To easily access selected notes, you can bookmark them. See demo:
Frequently used notes can be bookmarked, which will make them appear in the <a class="reference-link" href="../UI%20Elements/Launch%20Bar.md">Launch Bar</a> for easy access.
![](Bookmarks_bookmarks.gif)
## Configuring the launch bar
If bookmarks don't appear in the launch bar, then most likely the bookmark section has been hidden. Go to the <a class="reference-link" href="../UI%20Elements/Launch%20Bar.md">Launch Bar</a> configuration from the <a class="reference-link" href="../UI%20Elements/Global%20menu.md">Global menu</a> and ensure _Bookmarks_ is in the _Visible Launchers_ section.
## Bookmark folder
Space in the left panel is limited, and you might want to bookmark many items. One possible solution is to bookmark a folder, so it shows its children:
![](Bookmarks_bookmark-folder.png)
To do this, bookmark a folder and assign it the `#bookmarkFolder` label.
To do this, you need to add a `#bookmarkFolder` label to the note.
## Mobile
On mobile, bookmarks are only displayed starting with v0.102.0. Because of the more constrained screen size, the bookmarks are grouped under a single icon instead of displaying them as separate icons.
When pressed, a menu will appear listing all the bookmarks. Bookmark folders are also supported and will appear as sub-menus.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 KiB

View File

@@ -1,7 +1,11 @@
# Launch Bar
## Position of the Launch bar
Depending on the layout selected, the launcher bar will either be on the left side of the screen with buttons displayed vertically or at the top of the screen. See [Vertical and horizontal layout](Vertical%20and%20horizontal%20layout.md) for more information.
On desktop, depending on the layout selected, the launcher bar will either be on the left side of the screen with buttons displayed vertically or at the top of the screen. See <a class="reference-link" href="Vertical%20and%20horizontal%20layout.md">Vertical and horizontal layout</a> for more information.
On mobile, the launch bar will always be at the bottom.
If there are too many items in the launch bar to fit the screen, it will become scrollable.
## Terminology
@@ -9,30 +13,38 @@ Depending on the layout selected, the launcher bar will either be on the left si
* **Available Launcher**: a launcher that is not displayed on the launch bar, but can be added.
* **Visible Launcher**: a launcher that is currently displayed on the launch bar.
## Configuring the Launch bar
## Configuring the desktop Launch bar
There are two ways to configure the launch bar:
* Right click in the empty space between launchers on the launch bar and select _Configure Launchbar._
* Click on the [Global menu](Global%20menu.md) and select _Configure Launchbar_.
* Click on the <a class="reference-link" href="Global%20menu.md">Global menu</a> and select _Configure Launchbar_.
This will open a new tab with the [Note Tree](Note%20Tree.md) listing the launchers.
This will open a new tab with the <a class="reference-link" href="Note%20Tree.md">Note Tree</a> listing the launchers.
![](Launch%20Bar_image.png)
Expanding _Available Launchers_ section will show the list of launchers that are not displayed on the launch bar. The _Visible Launchers_ will show the ones that are currently displayed.
## Configuring the mobile launch bar
The launch bar on mobile uses a different configuration from the desktop one. The reasoning is that not all desktop icons are available on mobile, and fewer icons fit on a mobile screen.
To configure the launch bar on mobile, go to <a class="reference-link" href="Global%20menu.md">Global menu</a> and select _Configure Launchbar_.
The configure the mobile launch bar while on the desktop (especially useful to configure more complicated launchers such as scripts or custom widgets), go to <a class="reference-link" href="Global%20menu.md">Global menu</a> → Advanced → Show Hidden Subtree and look for the _Mobile Launch Bar_ section. While in the hidden subtree, it's also possible to drag launchers between the _Mobile Launch Bar_ and (Desktop) _Launch Bar_ sections.
### Adding/removing and reordering launchers
To display a new launcher in the launch bar, first look for it in the _Available Launchers_ section. Then right click it and select _Move to visible launchers_. It is also possible to drag and drop the item manually.
Similarly, to remove it from the launch bar, simply look for it in _Visible Launchers_ then right click it and select _Move to available launchers_ or use drag-and-drop.
Drag-and-drop the items in the tree in order to change their order. See [Note Tree](Note%20Tree.md) for more interaction options, including using keyboard shortcuts.
Drag-and-drop the items in the tree in order to change their order. See <a class="reference-link" href="Note%20Tree.md">Note Tree</a> for more interaction options, including using keyboard shortcuts.
## Customizing the launcher
* The icon of a launcher can be changed just like a normal note. See [Note Icons](../Notes/Note%20Icons.md) for more information.
* The icon of a launcher can be changed just like a normal note. See <a class="reference-link" href="../Notes/Note%20Icons.md">Note Icons</a> for more information.
* The title of the launcher can also be changed.
### Resetting
@@ -45,19 +57,17 @@ Right click either the _Available launchers_ or _Visible launchers_ sections and
1. **Note Launcher**
A note launcher will simply navigate to a specified note.
1. Set the `target` promoted attribute to the note to navigate to.
2. Optionally, set `hoistedNote` to hoist a particular note. See [Note Hoisting](../Navigation/Note%20Hoisting.md) for more information.
2. Optionally, set `hoistedNote` to hoist a particular note. See <a class="reference-link" href="../Navigation/Note%20Hoisting.md">Note Hoisting</a> for more information.
3. Optionally, set a `keyboardShortcut` to trigger the launcher.
2. **Script Launcher**
An advanced launcher which will run a script upon pressing. See [Scripts](../../Scripting.md) for more information.
An advanced launcher which will run a script upon pressing. See <a class="reference-link" href="../../Scripting.md">Scripting</a> for more information.
1. Set `script` to point to the desired script to run.
2. Optionally, set a `keyboardShortcut` to trigger the launcher.
3. **Custom Widget**
Allows defining a custom widget to be rendered inside the launcher. See [Widget Basics](../../Scripting/Frontend%20Basics/Custom%20Widgets/Widget%20Basics.md) for more information.
Allows defining a custom widget to be rendered inside the launcher. See <a class="reference-link" href="../../Scripting/Frontend%20Basics/Custom%20Widgets/Widget%20Basics.md">Widget Basics</a> for more information.
4. **Spacers**
Launchers that create some distance between other launchers for better visual distinction.
Launchers are configured via predefined [Promoted Attributes](../../Advanced%20Usage/Attributes/Promoted%20Attributes.md).
Launchers are configured via predefined <a class="reference-link" href="../../Advanced%20Usage/Attributes/Promoted%20Attributes.md">Promoted Attributes</a>.

View File

@@ -26,5 +26,20 @@ Since tabs are a commonly used feature, there are multiple keyboard shortcuts th
* <kbd>Ctrl</kbd>+<kbd>W</kbd> to close the current tab.
* <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>T</kbd> to reopen the last closed tab.
* <kbd>Ctrl</kbd>+<kbd>Tab</kbd> and <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>Tab</kbd> to go to the next or previous tab.
* <kbd>Ctrl</kbd>+<kbd>1</kbd>, <kbd>Ctrl</kbd>+<kbd>2</kbd>, up to <kbd>Ctrl</kbd>+<kbd>9</kbd> to activate the first, second and up til ninth tab.
* There is also a shortcut to go to the last tab, but it is not assigned a key by default.
* <kbd>Ctrl</kbd>+<kbd>1</kbd>, <kbd>Ctrl</kbd>+<kbd>2</kbd>, up to <kbd>Ctrl</kbd>+<kbd>9</kbd> to activate the first, second and up to ninth tab.
* There is also a shortcut to go to the last tab, but it is not assigned a key by default.
## Mobile
<figure class="image image-style-align-right image_resized" style="width:34.12%;"><img style="aspect-ratio:1242/2688;" src="Tabs_IMG_1767.PNG" width="1242" height="2688"></figure>
Tabs are also supported on the <a class="reference-link" href="../../Installation%20%26%20Setup/Mobile%20Frontend.md">Mobile Frontend</a>.
Since v0.102.0, the tabs are displayed by pressing the dedicated tab switcher button in the <a class="reference-link" href="Launch%20Bar.md">Launch Bar</a>. In this view the tabs are laid out on a grid with a preview of the note content.
The context menu button at the top-right of the popup allows creating a new tab, reopening the last closed tab and closing all the tabs.
<a class="reference-link" href="Split%20View.md">Split View</a>s are also indicated in the tab switcher, with two titles displayed in a tab.
> [!NOTE]
> Versions prior to v0.102.0 also supported tabs, but they were displayed directly above the <a class="reference-link" href="Launch%20Bar.md">Launch Bar</a>. The decision to use a more mobile-like tab switcher was taken because the original tab bar could not support many tabs at once and the new design better aligns with how mobile applications handle tabs.

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

View File

@@ -1,28 +1,85 @@
# Mobile Frontend
Trilium ([server edition](Server%20Installation.md)) has a mobile web frontend which is optimized for touch based devices - smartphones and tablets. It is activated automatically during login process based on browser detection.
<figure class="image image_resized image-style-align-right" style="width:33.52%;"><img style="aspect-ratio:1242/2688;" src="Mobile Frontend_IMG_1765.PNG" width="1242" height="2688"></figure>
Mobile frontend is limited in features compared to full desktop frontend. See below for more details on this.
Trilium has a mobile web frontend which is optimized for touch based devices - smartphones and tablets. It is activated automatically during login process based on browser detection.
Note that this is not an Android/iOS app, this is just mobile friendly web page served on the [server edition](Server%20Installation.md).
Mobile frontend is limited in features compared to the full desktop version. See below for more details on this.
## Layout basics
Unlike the desktop version, the mobile version has a slightly different UI meant to better fit the constrained screens of a mobile phone.
Here is a non-exhaustive list of differences between the desktop version and the mobile one:
* The <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Note%20Tree.md">Note Tree</a> is displayed as a sidebar. To display the sidebar, press the button in the top-left of the screen.
* There is also a swipe gesture that can be done from the left of the screen, but the browser's navigation gesture interferes with it most of the time (depending on the platform).
* Press and hold a note to display the <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Note%20Tree/Note%20tree%20contextual%20menu.md">Note tree contextual menu</a>.
* The <a class="reference-link" href="../Basic%20Concepts%20and%20Features/Navigation/Quick%20search.md">Quick search</a> bar is also displayed at the top of the note tree.
* The full <a class="reference-link" href="../Basic%20Concepts%20and%20Features/Navigation/Search.md">Search</a> function can be triggered either from either the <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Global%20menu.md">Global menu</a> or from the <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Launch%20Bar.md">Launch Bar</a>, if configured.
* The <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Launch%20Bar.md">Launch Bar</a> is displayed at the bottom of the screen.
* The launch bar uses a different configuration for icons than the desktop version. See the dedicated page for more information on how to configure it.
* Most of the note-related actions are grouped in the horizontal dots icon on the top-right of the note.
* The <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Tabs.md">Tabs</a> are grouped under a tab switcher in the <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Launch%20Bar.md">Launch Bar</a>, where the tabs are displayed in a full-screen grid with preview for easy switching, as well as additional options such as reopening closed tabs.
* Since v0.100.0, <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Split%20View.md">Split View</a> can also be used in mobile view, but with a maximum of two panes at once. The splits are displayed vertically instead of horizontally.
* Starting with v0.102.0, the <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/New%20Layout.md">New Layout</a> is enforced on mobile. This brings features such as the note badges, note type switcher or collection properties which would otherwise not be available.
## Installing as a PWA
The mobile view can be set up as a PWA. While this does not offer any offline capabilities, it will display the application in full-screen and makes it easy to access via your mobile phone's home screen.
### On iOS with Safari
1. Open your default web browser and access your Trilium instance.
2. Login.
3. Press the \[…\] button in the bottom-right of the screen and select Share.
4. Scroll down to reveal the full list of items and choose “Add to Home Screen”.
5. Press “Add” and the web app will be available.
### On Android with Google Chrome
> [!IMPORTANT]
> Google Chrome requires the server to be served over HTTPS in order to display in full-screen. If using HTTP, the app will appear like a normal web page (similar to a bookmark).
1. Open your default web browser and access your Trilium instance.
2. Login.
3. Press the three vertical dots icon in the top-right of the screen and select _Add to Home screen._
4. Select the _Install_ option.
5. Select an appropriate name.
6. The web app will appear as an application, not on the home screen.
### On Android with Brave
> [!IMPORTANT]
> Brave requires the server to be served over HTTPS in order to display in full-screen. If using HTTP, the app will appear like a normal web page (similar to a bookmark).
1. Open your default web browser and access your Trilium instance.
2. Login.
3. Press the three vertical dots icon in the bottom-right of the screen and select _Add to Home screen_.
4. Press the _Install_ option.
5. The web app will appear as an application, not on the home screen.
### On Samsung Browser
1. Open your default web browser and access your Trilium instance.
2. Login.
3. Press the hamburger menu in the bottom-right of the screen.
4. Select _Add to_, followed by _Home screen_.
5. Press _Add_ and the web app will appear on the home page.
## Testing via the desktop application
If you are running Trilium without a dedicated [server installation](Server%20Installation.md), you can still test the mobile application using the desktop application. For more information, see <a class="reference-link" href="Desktop%20Installation/Using%20the%20desktop%20application%20.md">Using the desktop application as a server</a>. To access it go to `http://<ip>:37840/login?mobile` .
## Limitations
Mobile frontend provides only some of the features of the full desktop frontend:
* it is possible to browse the whole note tree, read and edit all types of notes, but you can create only text notes
* reading and editing [protected notes](../Basic%20Concepts%20and%20Features/Notes/Protected%20Notes.md) is possible, but creating them is not supported
* editing options is not supported
* cloning notes is not supported
* uploading file attachments is not supported
## Forcing mobile/desktop frontend
Trilium decides automatically whether to use mobile or desktop frontend. If this is not appropriate, you can use `?mobile` or `?desktop` query param on **login** page (Note: you might need to log out).
Trilium decides automatically whether to use mobile or desktop front-end. If this is not appropriate, you can use `?mobile` or `?desktop` query param on **login** page (Note: you might need to log out).
Alternatively, simply select _Switch to Mobile/Desktop Version_ in the <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Global%20menu.md">Global menu</a>.
## Scripting
You can alter the behavior with [scripts](../Scripting.md) just like for normal frontend. For script notes to be executed, they need to have labeled `#run=mobileStartup`.
You can alter the behavior with <a class="reference-link" href="../Scripting.md">Scripting</a>, just like for normal frontend. For script notes to be executed, they need to have labeled `#run=mobileStartup`.
Custom <a class="reference-link" href="../Basic%20Concepts%20and%20Features/UI%20Elements/Launch%20Bar.md">Launch Bar</a> widgets are also supported.

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB