From 158f5ac310024604a03c5a684bf50037d1d6d5c8 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Mon, 15 Dec 2025 11:57:18 +0200 Subject: [PATCH] feat(layout): use collapsible for promoted attributes --- apps/client/src/layouts/desktop_layout.tsx | 2 +- .../client/src/widgets/PromotedAttributes.tsx | 93 +++++++++++-------- .../src/widgets/layout/NoteTitleActions.tsx | 17 +++- 3 files changed, 69 insertions(+), 43 deletions(-) diff --git a/apps/client/src/layouts/desktop_layout.tsx b/apps/client/src/layouts/desktop_layout.tsx index f4ac06e87..4f6f7e5af 100644 --- a/apps/client/src/layouts/desktop_layout.tsx +++ b/apps/client/src/layouts/desktop_layout.tsx @@ -155,7 +155,7 @@ export default class DesktopLayout { .child() .child() ) - .child() + .optChild(!isNewLayout, ) .child() .child() .child() diff --git a/apps/client/src/widgets/PromotedAttributes.tsx b/apps/client/src/widgets/PromotedAttributes.tsx index e25420f68..cb6dc2671 100644 --- a/apps/client/src/widgets/PromotedAttributes.tsx +++ b/apps/client/src/widgets/PromotedAttributes.tsx @@ -1,19 +1,21 @@ -import { Dispatch, StateUpdater, useEffect, useRef, useState } from "preact/hooks"; import "./PromotedAttributes.css"; -import { useNoteContext, useNoteLabel, useTriliumEvent, useUniqueName } from "./react/hooks"; -import { Attribute } from "../services/attribute_parser"; -import FAttribute from "../entities/fattribute"; + +import { UpdateAttributeResponse } from "@triliumnext/commons"; import clsx from "clsx"; +import { ComponentChild, HTMLInputTypeAttribute, InputHTMLAttributes, MouseEventHandler, TargetedEvent, TargetedInputEvent } from "preact"; +import { Dispatch, StateUpdater, useEffect, useRef, useState } from "preact/hooks"; + +import FAttribute from "../entities/fattribute"; +import FNote from "../entities/fnote"; +import { Attribute } from "../services/attribute_parser"; +import attributes from "../services/attributes"; +import debounce from "../services/debounce"; import { t } from "../services/i18n"; import { DefinitionObject, extractAttributeDefinitionTypeAndName, LabelType } from "../services/promoted_attribute_definition_parser"; import server from "../services/server"; -import FNote from "../entities/fnote"; -import { ComponentChild, HTMLInputTypeAttribute, InputHTMLAttributes, MouseEventHandler, TargetedEvent, TargetedInputEvent } from "preact"; -import NoteAutocomplete from "./react/NoteAutocomplete"; import ws from "../services/ws"; -import { UpdateAttributeResponse } from "@triliumnext/commons"; -import attributes from "../services/attributes"; -import debounce from "../services/debounce"; +import { useNoteContext, useNoteLabel, useTriliumEvent, useUniqueName } from "./react/hooks"; +import NoteAutocomplete from "./react/NoteAutocomplete"; interface Cell { uniqueId: string; @@ -39,6 +41,15 @@ type OnChangeListener = (e: OnChangeEventData) => Promise; export default function PromotedAttributes() { const { note, componentId } = useNoteContext(); const [ cells, setCells ] = usePromotedAttributeData(note, componentId); + return ; +} + +export function PromotedAttributesContent({ note, componentId, cells, setCells }: { + note: FNote | null | undefined; + componentId: string; + cells: Cell[] | undefined; + setCells: Dispatch>; +}) { const [ cellToFocus, setCellToFocus ] = useState(); return ( @@ -62,7 +73,7 @@ export default function PromotedAttributes() { * * The cells are returned as a state since they can also be altered internally if needed, for example to add a new empty cell. */ -function usePromotedAttributeData(note: FNote | null | undefined, componentId: string): [ Cell[] | undefined, Dispatch> ] { +export function usePromotedAttributeData(note: FNote | null | undefined, componentId: string): [ Cell[] | undefined, Dispatch> ] { const [ viewType ] = useNoteLabel(note, "viewType"); const [ cells, setCells ] = useState(); @@ -156,7 +167,7 @@ function PromotedAttributeCell(props: CellProps) { {correspondingInput} - ) + ); } const LABEL_MAPPINGS: Record = { @@ -219,29 +230,29 @@ function LabelInput({ inputId, ...props }: CellProps & { inputId: string }) { - - } else { - return ( -
- {inputNode} - { definition.labelType === "color" && } - { definition.labelType === "url" && ( - { - const inputEl = document.getElementById(inputId) as HTMLInputElement | null; - const url = inputEl?.value; - if (url) { - window.open(url, "_blank"); - } - }} - /> - )} -
- ); + ; } + return ( +
+ {inputNode} + { definition.labelType === "color" && } + { definition.labelType === "url" && ( + { + const inputEl = document.getElementById(inputId) as HTMLInputElement | null; + const url = inputEl?.value; + if (url) { + window.open(url, "_blank"); + } + }} + /> + )} +
+ ); + } @@ -282,7 +293,7 @@ function ColorPicker({ cell, onChange, inputId }: CellProps & { }} /> - ) + ); } function RelationInput({ inputId, ...props }: CellProps & { inputId: string }) { @@ -295,7 +306,7 @@ function RelationInput({ inputId, ...props }: CellProps & { inputId: string }) { await updateAttribute(note, cell, componentId, value, setCells); }} /> - ) + ); } function MultiplicityCell({ cell, cells, setCells, setCellToFocus, note, componentId }: CellProps) { @@ -346,13 +357,13 @@ function MultiplicityCell({ cell, cells, setCells, setCellToFocus, note, compone name: cell.valueName, value: "" } - }) + }); } setCells(cells.toSpliced(index, 1, ...newOnesToInsert)); }} /> - ) + ); } function PromotedActionButton({ icon, title, onClick }: { @@ -366,7 +377,7 @@ function PromotedActionButton({ icon, title, onClick }: { title={title} onClick={onClick} /> - ) + ); } function InputButton({ icon, className, title, onClick }: { @@ -381,7 +392,7 @@ function InputButton({ icon, className, title, onClick }: { title={title} onClick={onClick} /> - ) + ); } function setupTextLabelAutocomplete(el: HTMLInputElement, valueAttr: Attribute, onChangeListener: OnChangeListener) { @@ -406,7 +417,7 @@ function setupTextLabelAutocomplete(el: HTMLInputElement, valueAttr: Attribute, [ { displayKey: "value", - source: function (term, cb) { + source (term, cb) { term = term.toLowerCase(); const filtered = attributeValues.filter((attr) => attr.value.toLowerCase().includes(term)); diff --git a/apps/client/src/widgets/layout/NoteTitleActions.tsx b/apps/client/src/widgets/layout/NoteTitleActions.tsx index 31323ccbf..9678084f6 100644 --- a/apps/client/src/widgets/layout/NoteTitleActions.tsx +++ b/apps/client/src/widgets/layout/NoteTitleActions.tsx @@ -5,16 +5,18 @@ import clsx from "clsx"; import FNote from "../../entities/fnote"; import { t } from "../../services/i18n"; import CollectionProperties from "../note_bars/CollectionProperties"; +import { PromotedAttributesContent, usePromotedAttributeData } from "../PromotedAttributes"; import Collapsible from "../react/Collapsible"; import { useNoteContext, useNoteProperty } from "../react/hooks"; import SearchDefinitionTab from "../ribbon/SearchDefinitionTab"; export default function NoteTitleActions() { - const { note, ntxId } = useNoteContext(); + const { note, ntxId, componentId } = useNoteContext(); const isHiddenNote = note && note.noteId !== "_search" && note.noteId.startsWith("_"); const noteType = useNoteProperty(note, "type"); const items = [ + note && , note && noteType === "search" && , note && !isHiddenNote && noteType === "book" && ].filter(Boolean); @@ -36,3 +38,16 @@ function SearchProperties({ note, ntxId }: { note: FNote, ntxId: string | null | ); } + +function PromotedAttributes({ note, componentId }: { note: FNote | null | undefined, componentId: string }) { + const [ cells, setCells ] = usePromotedAttributeData(note, componentId); + if (!cells?.length) return false; + + return (note && ( + + + + )); +}