chore(react/launch_bar): port open_note_button_widget

This commit is contained in:
Elian Doran 2025-12-04 14:18:04 +02:00
parent 1af6200655
commit 48cbb80e79
No known key found for this signature in database
6 changed files with 87 additions and 63 deletions

View File

@ -150,7 +150,7 @@ export function isMac() {
export const hasTouchBar = (isMac() && isElectron()); export const hasTouchBar = (isMac() && isElectron());
function isCtrlKey(evt: KeyboardEvent | MouseEvent | JQuery.ClickEvent | JQuery.ContextMenuEvent | JQuery.TriggeredEvent | React.PointerEvent<HTMLCanvasElement> | JQueryEventObject) { export function isCtrlKey(evt: KeyboardEvent | MouseEvent | JQuery.ClickEvent | JQuery.ContextMenuEvent | JQuery.TriggeredEvent | React.PointerEvent<HTMLCanvasElement> | JQueryEventObject) {
return (!isMac() && evt.ctrlKey) || (isMac() && evt.metaKey); return (!isMac() && evt.ctrlKey) || (isMac() && evt.metaKey);
} }

View File

@ -1,5 +1,4 @@
import FlexContainer from "./containers/flex_container.js"; import FlexContainer from "./containers/flex_container.js";
import OpenNoteButtonWidget from "./buttons/open_note_button_widget.js";
import BookmarkFolderWidget from "./buttons/bookmark_folder.js"; import BookmarkFolderWidget from "./buttons/bookmark_folder.js";
import froca from "../services/froca.js"; import froca from "../services/froca.js";
import utils from "../services/utils.js"; import utils from "../services/utils.js";
@ -23,10 +22,6 @@ export default class BookmarkButtons extends FlexContainer<Component> {
} }
async refresh(): Promise<void> { async refresh(): Promise<void> {
this.$widget.empty();
this.children = [];
this.noteIds = [];
const bookmarkParentNote = await froca.getNote("_lbBookmarks"); const bookmarkParentNote = await froca.getNote("_lbBookmarks");
if (!bookmarkParentNote) { if (!bookmarkParentNote) {
@ -37,8 +32,7 @@ export default class BookmarkButtons extends FlexContainer<Component> {
this.noteIds.push(note.noteId); this.noteIds.push(note.noteId);
let buttonWidget: OpenNoteButtonWidget | BookmarkFolderWidget = note.isLabelTruthy("bookmarkFolder") let buttonWidget: OpenNoteButtonWidget | BookmarkFolderWidget = note.isLabelTruthy("bookmarkFolder")
? new BookmarkFolderWidget(note) ? new BookmarkFolderWidget(note);
: new OpenNoteButtonWidget(note).class("launcher-button");
if (this.settings.titlePlacement) { if (this.settings.titlePlacement) {
if (!("settings" in buttonWidget)) { if (!("settings" in buttonWidget)) {

View File

@ -1,49 +0,0 @@
import OnClickButtonWidget from "./onclick_button.js";
import linkContextMenuService from "../../menus/link_context_menu.js";
import utils from "../../services/utils.js";
import appContext from "../../components/app_context.js";
import type FNote from "../../entities/fnote.js";
export default class OpenNoteButtonWidget extends OnClickButtonWidget {
private noteToOpen: FNote;
constructor(noteToOpen: FNote) {
super();
this.noteToOpen = noteToOpen;
this.title(() => utils.escapeHtml(this.noteToOpen.title))
.icon(() => this.noteToOpen.getIcon())
.onClick((widget, evt) => this.launch(evt))
.onAuxClick((widget, evt) => this.launch(evt))
.onContextMenu((evt) => {
if (evt) {
linkContextMenuService.openContextMenu(this.noteToOpen.noteId, evt);
}
});
}
async launch(evt: JQuery.ClickEvent | JQuery.TriggeredEvent | JQuery.ContextMenuEvent) {
if (evt.which === 3) {
return;
}
const hoistedNoteId = this.getHoistedNoteId();
const ctrlKey = utils.isCtrlKey(evt);
if ((evt.which === 1 && ctrlKey) || evt.which === 2) {
const activate = evt.shiftKey ? true : false;
await appContext.tabManager.openInNewTab(this.noteToOpen.noteId, hoistedNoteId, activate);
} else {
await appContext.tabManager.openInSameTab(this.noteToOpen.noteId);
}
}
getHoistedNoteId() {
return this.noteToOpen.getRelationValue("hoistedNote") || appContext.tabManager.getActiveContext()?.hoistedNoteId;
}
initialRenderCompleteEvent() {
// we trigger refresh above
}
}

View File

@ -1,5 +1,71 @@
import { useMemo } from "preact/hooks";
import type { LaunchBarWidgetProps } from "./launch_bar_widget"; import type { LaunchBarWidgetProps } from "./launch_bar_widget";
import { CSSProperties } from "preact";
import type FNote from "../../entities/fnote";
import { useChildNotes, useNoteLabel, useNoteProperty } from "../react/hooks";
import Dropdown from "../react/Dropdown";
import ActionButton from "../react/ActionButton";
import appContext from "../../components/app_context";
import { escapeHtml, isCtrlKey } from "../../services/utils";
import link_context_menu from "../../menus/link_context_menu";
export default function BookmarkButtons({ }: LaunchBarWidgetProps) { const PARENT_NOTE_ID = "_lbBookmarks";
return <p>Bookmarks goes here.</p>;
export default function BookmarkButtons({ isHorizontalLayout }: LaunchBarWidgetProps) {
const style = useMemo<CSSProperties>(() => ({
display: "flex",
flexDirection: isHorizontalLayout ? "row" : "column",
contain: "none"
}), [ isHorizontalLayout ]);
const childNotes = useChildNotes(PARENT_NOTE_ID);
return (
<div style={style}>
{childNotes?.map(childNote => <SingleBookmark note={childNote} />)}
</div>
)
}
function SingleBookmark({ note }: { note: FNote }) {
return <OpenNoteButtonWidget note={note} />
}
function OpenNoteButtonWidget({ note }: { note: FNote }) {
const [ iconClass ] = useNoteLabel(note, "iconClass");
const title = useNoteProperty(note, "title");
async function launch(evt: MouseEvent) {
if (evt.which === 3) {
return;
}
const hoistedNoteId = getHoistedNoteId(note);
const ctrlKey = isCtrlKey(evt);
if ((evt.which === 1 && ctrlKey) || evt.which === 2) {
const activate = evt.shiftKey ? true : false;
await appContext.tabManager.openInNewTab(note.noteId, hoistedNoteId, activate);
} else {
await appContext.tabManager.openInSameTab(note.noteId);
}
}
return title && iconClass && (
<ActionButton
icon={iconClass}
text={escapeHtml(title)}
className="button-widget launcher-button"
noIconActionClass
titlePosition="right"
onClick={launch}
onAuxClick={launch}
onContextMenu={evt => {
evt.preventDefault();
link_context_menu.openContextMenu(note.noteId, evt);
}}
/>
)
}
function getHoistedNoteId(noteToOpen: FNote) {
return noteToOpen.getRelationValue("hoistedNote") || appContext.tabManager.getActiveContext()?.hoistedNoteId;
} }

View File

@ -2,13 +2,13 @@ import { useEffect, useRef, useState } from "preact/hooks";
import { CommandNames } from "../../components/app_context"; import { CommandNames } from "../../components/app_context";
import { useStaticTooltip } from "./hooks"; import { useStaticTooltip } from "./hooks";
import keyboard_actions from "../../services/keyboard_actions"; import keyboard_actions from "../../services/keyboard_actions";
import { HTMLAttributes } from "preact";
export interface ActionButtonProps { export interface ActionButtonProps extends Pick<HTMLAttributes<HTMLButtonElement>, "onClick" | "onAuxClick" | "onContextMenu"> {
text: string; text: string;
titlePosition?: "top" | "right" | "bottom" | "left"; titlePosition?: "top" | "right" | "bottom" | "left";
icon: string; icon: string;
className?: string; className?: string;
onClick?: (e: MouseEvent) => void;
triggerCommand?: CommandNames; triggerCommand?: CommandNames;
noIconActionClass?: boolean; noIconActionClass?: boolean;
frame?: boolean; frame?: boolean;
@ -16,7 +16,7 @@ export interface ActionButtonProps {
disabled?: boolean; disabled?: boolean;
} }
export default function ActionButton({ text, icon, className, onClick, triggerCommand, titlePosition, noIconActionClass, frame, active, disabled }: ActionButtonProps) { export default function ActionButton({ text, icon, className, triggerCommand, titlePosition, noIconActionClass, frame, active, disabled, ...restProps }: ActionButtonProps) {
const buttonRef = useRef<HTMLButtonElement>(null); const buttonRef = useRef<HTMLButtonElement>(null);
const [ keyboardShortcut, setKeyboardShortcut ] = useState<string[]>(); const [ keyboardShortcut, setKeyboardShortcut ] = useState<string[]>();
@ -35,8 +35,8 @@ export default function ActionButton({ text, icon, className, onClick, triggerCo
return <button return <button
ref={buttonRef} ref={buttonRef}
class={`${className ?? ""} ${!noIconActionClass ? "icon-action" : "btn"} ${icon} ${frame ? "btn btn-primary" : ""} ${disabled ? "disabled" : ""} ${active ? "active" : ""}`} class={`${className ?? ""} ${!noIconActionClass ? "icon-action" : "btn"} ${icon} ${frame ? "btn btn-primary" : ""} ${disabled ? "disabled" : ""} ${active ? "active" : ""}`}
onClick={onClick}
data-trigger-command={triggerCommand} data-trigger-command={triggerCommand}
disabled={disabled} disabled={disabled}
{...restProps}
/>; />;
} }

View File

@ -23,6 +23,7 @@ import toast, { ToastOptions } from "../../services/toast";
import utils, { escapeRegExp, reloadFrontendApp } from "../../services/utils"; import utils, { escapeRegExp, reloadFrontendApp } from "../../services/utils";
import server from "../../services/server"; import server from "../../services/server";
import { removeIndividualBinding } from "../../services/shortcuts"; import { removeIndividualBinding } from "../../services/shortcuts";
import froca from "../../services/froca";
export function useTriliumEvent<T extends EventNames>(eventName: T, handler: (data: EventData<T>) => void) { export function useTriliumEvent<T extends EventNames>(eventName: T, handler: (data: EventData<T>) => void) {
const parentComponent = useContext(ParentComponent); const parentComponent = useContext(ParentComponent);
@ -836,3 +837,15 @@ async function isNoteReadOnly(note: FNote, noteContext: NoteContext) {
return true; return true;
} }
export function useChildNotes(parentNoteId: string) {
const [ childNotes, setChildNotes ] = useState<FNote[]>([]);
async function refreshChildNotes() {
const parentNote = await froca.getNote(parentNoteId);
const childNotes = await parentNote?.getChildNotes();
setChildNotes(childNotes ?? []);
}
useEffect(() => { refreshChildNotes() }, [ parentNoteId ]);
return childNotes;
}