From 1835676d09aec5cbf513d9c1f6950c10c48a4f5c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 16 Dec 2025 18:54:15 +0200 Subject: [PATCH] feat(layout): keyboard shortcut for owned & inherited attributes --- apps/client/src/widgets/layout/StatusBar.tsx | 4 ++ .../ribbon/components/AttributeEditor.tsx | 57 ++++++++++--------- 2 files changed, 34 insertions(+), 27 deletions(-) diff --git a/apps/client/src/widgets/layout/StatusBar.tsx b/apps/client/src/widgets/layout/StatusBar.tsx index d12e92099..424fd81fa 100644 --- a/apps/client/src/widgets/layout/StatusBar.tsx +++ b/apps/client/src/widgets/layout/StatusBar.tsx @@ -349,6 +349,10 @@ function AttributesPane({ note, noteContext, attributesShown, setAttributesShown // Show on keyboard shortcuts. useTriliumEvents([ "addNewLabel", "addNewRelation" ], () => setAttributesShown(true)); + useTriliumEvents([ "toggleRibbonTabOwnedAttributes", "toggleRibbonTabInheritedAttributes" ], () => setAttributesShown(!attributesShown)); + + // Auto-focus the owned attributes. + useEffect(() => api.current?.focus(), [ attributesShown ]); // Interaction with the attribute editor. useLegacyImperativeHandlers(useMemo(() => ({ diff --git a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx index bf0aa9428..ee9129bbd 100644 --- a/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx +++ b/apps/client/src/widgets/ribbon/components/AttributeEditor.tsx @@ -1,25 +1,26 @@ -import { MutableRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "preact/hooks"; import { AttributeEditor as CKEditorAttributeEditor, MentionFeed, ModelElement, ModelNode, ModelPosition } from "@triliumnext/ckeditor5"; +import { AttributeType } from "@triliumnext/commons"; +import { MutableRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from "preact/hooks"; + +import type { CommandData, FilteredCommandNames } from "../../../components/app_context"; +import FAttribute from "../../../entities/fattribute"; +import FNote from "../../../entities/fnote"; +import contextMenu from "../../../menus/context_menu"; +import attribute_parser, { Attribute } from "../../../services/attribute_parser"; +import attribute_renderer from "../../../services/attribute_renderer"; +import attributes from "../../../services/attributes"; +import froca from "../../../services/froca"; import { t } from "../../../services/i18n"; -import server from "../../../services/server"; +import link from "../../../services/link"; import note_autocomplete, { Suggestion } from "../../../services/note_autocomplete"; +import note_create from "../../../services/note_create"; +import server from "../../../services/server"; +import { isIMEComposing } from "../../../services/shortcuts"; +import { escapeQuotes, getErrorMessage } from "../../../services/utils"; +import AttributeDetailWidget from "../../attribute_widgets/attribute_detail"; +import ActionButton from "../../react/ActionButton"; import CKEditor, { CKEditorApi } from "../../react/CKEditor"; import { useLegacyImperativeHandlers, useLegacyWidget, useTooltip, useTriliumEvent, useTriliumOption } from "../../react/hooks"; -import FAttribute from "../../../entities/fattribute"; -import attribute_renderer from "../../../services/attribute_renderer"; -import FNote from "../../../entities/fnote"; -import AttributeDetailWidget from "../../attribute_widgets/attribute_detail"; -import attribute_parser, { Attribute } from "../../../services/attribute_parser"; -import ActionButton from "../../react/ActionButton"; -import { escapeQuotes, getErrorMessage } from "../../../services/utils"; -import link from "../../../services/link"; -import { isIMEComposing } from "../../../services/shortcuts"; -import froca from "../../../services/froca"; -import contextMenu from "../../../menus/context_menu"; -import type { CommandData, FilteredCommandNames } from "../../../components/app_context"; -import { AttributeType } from "@triliumnext/commons"; -import attributes from "../../../services/attributes"; -import note_create from "../../../services/note_create"; type AttributeCommandNames = FilteredCommandNames; @@ -52,7 +53,7 @@ const mentionSetup: MentionFeed[] = [ return names.map((name) => { return { id: `#${name}`, - name: name + name }; }); }, @@ -66,7 +67,7 @@ const mentionSetup: MentionFeed[] = [ return names.map((name) => { return { id: `~${name}`, - name: name + name }; }); }, @@ -85,9 +86,10 @@ interface AttributeEditorProps { } export interface AttributeEditorImperativeHandlers { - save: () => Promise; - refresh: () => void; - renderOwnedAttributes: (ownedAttributes: FAttribute[]) => Promise; + save(): Promise; + refresh(): void; + focus(): void; + renderOwnedAttributes(ownedAttributes: FAttribute[]): Promise; } export default function AttributeEditor({ api, note, componentId, notePath, ntxId, hidden }: AttributeEditorProps) { @@ -124,7 +126,7 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI // attrs are not resorted if position changes after the initial load ownedAttributes.sort((a, b) => a.position - b.position); - let htmlAttrs = ("

" + (await attribute_renderer.renderAttributes(ownedAttributes, true)).html() + "

"); + let htmlAttrs = (`

${(await attribute_renderer.renderAttributes(ownedAttributes, true)).html()}

`); if (saved) { lastSavedContent.current = htmlAttrs; @@ -162,7 +164,7 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI wrapperRef.current.style.opacity = "0"; setTimeout(() => { if (wrapperRef.current) { - wrapperRef.current.style.opacity = "1" + wrapperRef.current.style.opacity = "1"; } }, 100); } @@ -252,7 +254,7 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI if (notePath) { result = await note_create.createNoteWithTypePrompt(notePath, { activate: false, - title: title + title }); } @@ -274,7 +276,8 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI useImperativeHandle(api, () => ({ save, refresh, - renderOwnedAttributes: (attributes) => renderOwnedAttributes(attributes as FAttribute[], false) + renderOwnedAttributes: (attributes) => renderOwnedAttributes(attributes as FAttribute[], false), + focus: () => editorRef.current?.focus() }), [ save, refresh, renderOwnedAttributes ]); return ( @@ -404,7 +407,7 @@ export default function AttributeEditor({ api, note, componentId, notePath, ntxI {attributeDetailWidgetEl} - ) + ); } function getPreprocessedData(currentValue: string) {