From d99b8f5864efd3fd93ff56a28a414d84f72ab440 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 22 Nov 2025 22:10:51 +0200 Subject: [PATCH] chore(promoted_attributes): build list of cells --- .../client/src/widgets/PromotedAttributes.css | 87 +++++++++++ .../client/src/widgets/PromotedAttributes.tsx | 50 +++++- .../client/src/widgets/promoted_attributes.ts | 142 +----------------- 3 files changed, 137 insertions(+), 142 deletions(-) create mode 100644 apps/client/src/widgets/PromotedAttributes.css diff --git a/apps/client/src/widgets/PromotedAttributes.css b/apps/client/src/widgets/PromotedAttributes.css new file mode 100644 index 000000000..ea95fd455 --- /dev/null +++ b/apps/client/src/widgets/PromotedAttributes.css @@ -0,0 +1,87 @@ +body.mobile .promoted-attributes-widget { + /* https://github.com/zadam/trilium/issues/4468 */ + flex-shrink: 0.4; + overflow: auto; +} + +.promoted-attributes-container { + margin: 0 1.5em; + overflow: auto; + max-height: 400px; + flex-wrap: wrap; + display: table; +} +.promoted-attribute-cell { + display: flex; + align-items: center; + margin: 10px; + display: table-row; +} +.promoted-attribute-cell > label { + user-select: none; + font-weight: bold; + vertical-align: middle; +} +.promoted-attribute-cell > * { + display: table-cell; + padding: 1px 0; +} + +.promoted-attribute-cell div.input-group { + margin-inline-start: 10px; + display: flex; + min-height: 40px; +} +.promoted-attribute-cell strong { + word-break:keep-all; + white-space: nowrap; +} + +.promoted-attribute-cell input[type="checkbox"] { + width: 22px !important; + flex-grow: 0; + width: unset; +} + +/* Restore default apperance */ +.promoted-attribute-cell input[type="number"], +.promoted-attribute-cell input[type="checkbox"] { + appearance: auto; +} + +.promoted-attribute-cell input[type="color"] { + width: 24px; + height: 24px; + margin-top: 2px; + appearance: none; + padding: 0; + border: 0; + outline: none; + border-radius: 25% !important; +} + +.promoted-attribute-cell input[type="color"]::-webkit-color-swatch-wrapper { + padding: 0; +} + +.promoted-attribute-cell input[type="color"]::-webkit-color-swatch { + border: none; + border-radius: 25%; +} + +.promoted-attribute-label-color input[type="hidden"][value=""] + input[type="color"] { + position: relative; + opacity: 0.5; +} + +.promoted-attribute-label-color input[type="hidden"][value=""] + input[type="color"]:after { + content: ""; + position: absolute; + top: 10px; + inset-inline-start: 0px; + inset-inline-end: 0; + height: 2px; + background: rgba(0, 0, 0, 0.5); + transform: rotate(45deg); + pointer-events: none; +} \ No newline at end of file diff --git a/apps/client/src/widgets/PromotedAttributes.tsx b/apps/client/src/widgets/PromotedAttributes.tsx index 1a597b281..58ed87336 100644 --- a/apps/client/src/widgets/PromotedAttributes.tsx +++ b/apps/client/src/widgets/PromotedAttributes.tsx @@ -1,3 +1,51 @@ +import { useEffect, useState } from "preact/hooks"; +import "./PromotedAttributes.css"; +import { useNoteContext } from "./react/hooks"; +import { Attribute } from "../services/attribute_parser"; + export default function PromotedAttributes() { - return

Promoted attributes go here.

+ const { note } = useNoteContext(); + const [ cells, setCells ] = useState(); + + useEffect(() => { + if (!note) return; + const promotedDefAttrs = note.getPromotedDefinitionAttributes(); + const ownedAttributes = note.getOwnedAttributes(); + // attrs are not resorted if position changes after the initial load + // promoted attrs are sorted primarily by order of definitions, but with multi-valued promoted attrs + // the order of attributes is important as well + ownedAttributes.sort((a, b) => a.position - b.position); + + const cells: Attribute[] = []; + for (const definitionAttr of promotedDefAttrs) { + const valueType = definitionAttr.name.startsWith("label:") ? "label" : "relation"; + const valueName = definitionAttr.name.substr(valueType.length + 1); + + let valueAttrs = ownedAttributes.filter((el) => el.name === valueName && el.type === valueType) as Attribute[]; + + if (valueAttrs.length === 0) { + valueAttrs.push({ + attributeId: "", + type: valueType, + name: valueName, + value: "" + }); + } + + if (definitionAttr.getDefinition().multiplicity === "single") { + valueAttrs = valueAttrs.slice(0, 1); + } + + cells.push(...valueAttrs); + } + setCells(cells); + }, [ note ]); + + return ( +
+
+ +
+
+ ); } diff --git a/apps/client/src/widgets/promoted_attributes.ts b/apps/client/src/widgets/promoted_attributes.ts index 5853e1d87..7620bfa4b 100644 --- a/apps/client/src/widgets/promoted_attributes.ts +++ b/apps/client/src/widgets/promoted_attributes.ts @@ -12,102 +12,6 @@ import type { Attribute } from "../services/attribute_parser.js"; import type FAttribute from "../entities/fattribute.js"; import type { EventData } from "../components/app_context.js"; -const TPL = /*html*/` -`; - // TODO: Deduplicate interface AttributeResult { attributeId: string; @@ -126,36 +30,12 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget { } doRender() { - this.$widget = $(TPL); this.contentSized(); - this.$container = this.$widget.find(".promoted-attributes-container"); - } - - getTitle(note: FNote) { - const promotedDefAttrs = note.getPromotedDefinitionAttributes(); - - if (promotedDefAttrs.length === 0) { - return { show: false }; - } - - return { - show: true, - activate: options.is("promotedAttributesOpenInRibbon"), - title: t("promoted_attributes.promoted_attributes"), - icon: "bx bx-table" - }; } async refreshWithNote(note: FNote) { this.$container.empty(); - const promotedDefAttrs = note.getPromotedDefinitionAttributes(); - const ownedAttributes = note.getOwnedAttributes(); - // attrs are not resorted if position changes after the initial load - // promoted attrs are sorted primarily by order of definitions, but with multi-valued promoted attrs - // the order of attributes is important as well - ownedAttributes.sort((a, b) => a.position - b.position); - if (promotedDefAttrs.length === 0 || note.getLabelValue("viewType") === "table") { this.toggleInt(false); return; @@ -163,33 +43,13 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget { const $cells: JQuery[] = []; - for (const definitionAttr of promotedDefAttrs) { - const valueType = definitionAttr.name.startsWith("label:") ? "label" : "relation"; - const valueName = definitionAttr.name.substr(valueType.length + 1); - - let valueAttrs = ownedAttributes.filter((el) => el.name === valueName && el.type === valueType) as Attribute[]; - - if (valueAttrs.length === 0) { - valueAttrs.push({ - attributeId: "", - type: valueType, - name: valueName, - value: "" - }); - } - - if (definitionAttr.getDefinition().multiplicity === "single") { - valueAttrs = valueAttrs.slice(0, 1); - } - - for (const valueAttr of valueAttrs) { + for (const valueAttr of valueAttrs) { const $cell = await this.createPromotedAttributeCell(definitionAttr, valueAttr, valueName); if ($cell) { $cells.push($cell); } } - } // we replace the whole content in one step, so there can't be any race conditions // (previously we saw promoted attributes doubling)