mirror of
https://github.com/zadam/trilium.git
synced 2025-12-15 20:59:54 +01:00
feat(layout): use collapsible for promoted attributes
This commit is contained in:
@@ -155,7 +155,7 @@ export default class DesktopLayout {
|
||||
.child(<ReadOnlyNoteInfoBar />)
|
||||
.child(<SharedInfo />)
|
||||
)
|
||||
.child(<PromotedAttributes />)
|
||||
.optChild(!isNewLayout, <PromotedAttributes />)
|
||||
.child(<SqlTableSchemas />)
|
||||
.child(<NoteDetail />)
|
||||
.child(<NoteList media="screen" />)
|
||||
|
||||
@@ -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<void>;
|
||||
export default function PromotedAttributes() {
|
||||
const { note, componentId } = useNoteContext();
|
||||
const [ cells, setCells ] = usePromotedAttributeData(note, componentId);
|
||||
return <PromotedAttributesContent note={note} componentId={componentId} cells={cells} setCells={setCells} />;
|
||||
}
|
||||
|
||||
export function PromotedAttributesContent({ note, componentId, cells, setCells }: {
|
||||
note: FNote | null | undefined;
|
||||
componentId: string;
|
||||
cells: Cell[] | undefined;
|
||||
setCells: Dispatch<StateUpdater<Cell[] | undefined>>;
|
||||
}) {
|
||||
const [ cellToFocus, setCellToFocus ] = useState<Cell>();
|
||||
|
||||
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<StateUpdater<Cell[] | undefined>> ] {
|
||||
export function usePromotedAttributeData(note: FNote | null | undefined, componentId: string): [ Cell[] | undefined, Dispatch<StateUpdater<Cell[] | undefined>> ] {
|
||||
const [ viewType ] = useNoteLabel(note, "viewType");
|
||||
const [ cells, setCells ] = useState<Cell[]>();
|
||||
|
||||
@@ -156,7 +167,7 @@ function PromotedAttributeCell(props: CellProps) {
|
||||
{correspondingInput}
|
||||
<MultiplicityCell {...props} />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const LABEL_MAPPINGS: Record<LabelType, HTMLInputTypeAttribute> = {
|
||||
@@ -219,29 +230,29 @@ function LabelInput({ inputId, ...props }: CellProps & { inputId: string }) {
|
||||
<label className="tn-checkbox">{inputNode}</label>
|
||||
</div>
|
||||
<label for={inputId}>{definition.promotedAlias ?? valueName}</label>
|
||||
</>
|
||||
} else {
|
||||
return (
|
||||
<div className="input-group">
|
||||
{inputNode}
|
||||
{ definition.labelType === "color" && <ColorPicker {...props} onChange={onChangeListener} inputId={inputId} />}
|
||||
{ definition.labelType === "url" && (
|
||||
<InputButton
|
||||
className="open-external-link-button"
|
||||
icon="bx bx-window-open"
|
||||
title={t("promoted_attributes.open_external_link")}
|
||||
onClick={(e) => {
|
||||
const inputEl = document.getElementById(inputId) as HTMLInputElement | null;
|
||||
const url = inputEl?.value;
|
||||
if (url) {
|
||||
window.open(url, "_blank");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
</>;
|
||||
}
|
||||
return (
|
||||
<div className="input-group">
|
||||
{inputNode}
|
||||
{ definition.labelType === "color" && <ColorPicker {...props} onChange={onChangeListener} inputId={inputId} />}
|
||||
{ definition.labelType === "url" && (
|
||||
<InputButton
|
||||
className="open-external-link-button"
|
||||
icon="bx bx-window-open"
|
||||
title={t("promoted_attributes.open_external_link")}
|
||||
onClick={(e) => {
|
||||
const inputEl = document.getElementById(inputId) as HTMLInputElement | null;
|
||||
const url = inputEl?.value;
|
||||
if (url) {
|
||||
window.open(url, "_blank");
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
@@ -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 && <PromotedAttributes note={note} componentId={componentId} />,
|
||||
note && noteType === "search" && <SearchProperties note={note} ntxId={ntxId} />,
|
||||
note && !isHiddenNote && noteType === "book" && <CollectionProperties note={note} />
|
||||
].filter(Boolean);
|
||||
@@ -36,3 +38,16 @@ function SearchProperties({ note, ntxId }: { note: FNote, ntxId: string | null |
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
|
||||
function PromotedAttributes({ note, componentId }: { note: FNote | null | undefined, componentId: string }) {
|
||||
const [ cells, setCells ] = usePromotedAttributeData(note, componentId);
|
||||
if (!cells?.length) return false;
|
||||
|
||||
return (note && (
|
||||
<Collapsible
|
||||
title={t("promoted_attributes.promoted_attributes")}
|
||||
>
|
||||
<PromotedAttributesContent note={note} componentId={componentId} cells={cells} setCells={setCells} />
|
||||
</Collapsible>
|
||||
));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user