mirror of
https://github.com/zadam/trilium.git
synced 2025-12-17 21:59:55 +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(<ReadOnlyNoteInfoBar />)
|
||||||
.child(<SharedInfo />)
|
.child(<SharedInfo />)
|
||||||
)
|
)
|
||||||
.child(<PromotedAttributes />)
|
.optChild(!isNewLayout, <PromotedAttributes />)
|
||||||
.child(<SqlTableSchemas />)
|
.child(<SqlTableSchemas />)
|
||||||
.child(<NoteDetail />)
|
.child(<NoteDetail />)
|
||||||
.child(<NoteList media="screen" />)
|
.child(<NoteList media="screen" />)
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
import { Dispatch, StateUpdater, useEffect, useRef, useState } from "preact/hooks";
|
|
||||||
import "./PromotedAttributes.css";
|
import "./PromotedAttributes.css";
|
||||||
import { useNoteContext, useNoteLabel, useTriliumEvent, useUniqueName } from "./react/hooks";
|
|
||||||
import { Attribute } from "../services/attribute_parser";
|
import { UpdateAttributeResponse } from "@triliumnext/commons";
|
||||||
import FAttribute from "../entities/fattribute";
|
|
||||||
import clsx from "clsx";
|
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 { t } from "../services/i18n";
|
||||||
import { DefinitionObject, extractAttributeDefinitionTypeAndName, LabelType } from "../services/promoted_attribute_definition_parser";
|
import { DefinitionObject, extractAttributeDefinitionTypeAndName, LabelType } from "../services/promoted_attribute_definition_parser";
|
||||||
import server from "../services/server";
|
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 ws from "../services/ws";
|
||||||
import { UpdateAttributeResponse } from "@triliumnext/commons";
|
import { useNoteContext, useNoteLabel, useTriliumEvent, useUniqueName } from "./react/hooks";
|
||||||
import attributes from "../services/attributes";
|
import NoteAutocomplete from "./react/NoteAutocomplete";
|
||||||
import debounce from "../services/debounce";
|
|
||||||
|
|
||||||
interface Cell {
|
interface Cell {
|
||||||
uniqueId: string;
|
uniqueId: string;
|
||||||
@@ -39,6 +41,15 @@ type OnChangeListener = (e: OnChangeEventData) => Promise<void>;
|
|||||||
export default function PromotedAttributes() {
|
export default function PromotedAttributes() {
|
||||||
const { note, componentId } = useNoteContext();
|
const { note, componentId } = useNoteContext();
|
||||||
const [ cells, setCells ] = usePromotedAttributeData(note, componentId);
|
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>();
|
const [ cellToFocus, setCellToFocus ] = useState<Cell>();
|
||||||
|
|
||||||
return (
|
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.
|
* 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 [ viewType ] = useNoteLabel(note, "viewType");
|
||||||
const [ cells, setCells ] = useState<Cell[]>();
|
const [ cells, setCells ] = useState<Cell[]>();
|
||||||
|
|
||||||
@@ -156,7 +167,7 @@ function PromotedAttributeCell(props: CellProps) {
|
|||||||
{correspondingInput}
|
{correspondingInput}
|
||||||
<MultiplicityCell {...props} />
|
<MultiplicityCell {...props} />
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const LABEL_MAPPINGS: Record<LabelType, HTMLInputTypeAttribute> = {
|
const LABEL_MAPPINGS: Record<LabelType, HTMLInputTypeAttribute> = {
|
||||||
@@ -219,8 +230,8 @@ function LabelInput({ inputId, ...props }: CellProps & { inputId: string }) {
|
|||||||
<label className="tn-checkbox">{inputNode}</label>
|
<label className="tn-checkbox">{inputNode}</label>
|
||||||
</div>
|
</div>
|
||||||
<label for={inputId}>{definition.promotedAlias ?? valueName}</label>
|
<label for={inputId}>{definition.promotedAlias ?? valueName}</label>
|
||||||
</>
|
</>;
|
||||||
} else {
|
}
|
||||||
return (
|
return (
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
{inputNode}
|
{inputNode}
|
||||||
@@ -241,7 +252,7 @@ function LabelInput({ inputId, ...props }: CellProps & { inputId: string }) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -282,7 +293,7 @@ function ColorPicker({ cell, onChange, inputId }: CellProps & {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function RelationInput({ inputId, ...props }: CellProps & { inputId: string }) {
|
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);
|
await updateAttribute(note, cell, componentId, value, setCells);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function MultiplicityCell({ cell, cells, setCells, setCellToFocus, note, componentId }: CellProps) {
|
function MultiplicityCell({ cell, cells, setCells, setCellToFocus, note, componentId }: CellProps) {
|
||||||
@@ -346,13 +357,13 @@ function MultiplicityCell({ cell, cells, setCells, setCellToFocus, note, compone
|
|||||||
name: cell.valueName,
|
name: cell.valueName,
|
||||||
value: ""
|
value: ""
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
setCells(cells.toSpliced(index, 1, ...newOnesToInsert));
|
setCells(cells.toSpliced(index, 1, ...newOnesToInsert));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</td>
|
</td>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PromotedActionButton({ icon, title, onClick }: {
|
function PromotedActionButton({ icon, title, onClick }: {
|
||||||
@@ -366,7 +377,7 @@ function PromotedActionButton({ icon, title, onClick }: {
|
|||||||
title={title}
|
title={title}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function InputButton({ icon, className, title, onClick }: {
|
function InputButton({ icon, className, title, onClick }: {
|
||||||
@@ -381,7 +392,7 @@ function InputButton({ icon, className, title, onClick }: {
|
|||||||
title={title}
|
title={title}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupTextLabelAutocomplete(el: HTMLInputElement, valueAttr: Attribute, onChangeListener: OnChangeListener) {
|
function setupTextLabelAutocomplete(el: HTMLInputElement, valueAttr: Attribute, onChangeListener: OnChangeListener) {
|
||||||
@@ -406,7 +417,7 @@ function setupTextLabelAutocomplete(el: HTMLInputElement, valueAttr: Attribute,
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
displayKey: "value",
|
displayKey: "value",
|
||||||
source: function (term, cb) {
|
source (term, cb) {
|
||||||
term = term.toLowerCase();
|
term = term.toLowerCase();
|
||||||
|
|
||||||
const filtered = attributeValues.filter((attr) => attr.value.toLowerCase().includes(term));
|
const filtered = attributeValues.filter((attr) => attr.value.toLowerCase().includes(term));
|
||||||
|
|||||||
@@ -5,16 +5,18 @@ import clsx from "clsx";
|
|||||||
import FNote from "../../entities/fnote";
|
import FNote from "../../entities/fnote";
|
||||||
import { t } from "../../services/i18n";
|
import { t } from "../../services/i18n";
|
||||||
import CollectionProperties from "../note_bars/CollectionProperties";
|
import CollectionProperties from "../note_bars/CollectionProperties";
|
||||||
|
import { PromotedAttributesContent, usePromotedAttributeData } from "../PromotedAttributes";
|
||||||
import Collapsible from "../react/Collapsible";
|
import Collapsible from "../react/Collapsible";
|
||||||
import { useNoteContext, useNoteProperty } from "../react/hooks";
|
import { useNoteContext, useNoteProperty } from "../react/hooks";
|
||||||
import SearchDefinitionTab from "../ribbon/SearchDefinitionTab";
|
import SearchDefinitionTab from "../ribbon/SearchDefinitionTab";
|
||||||
|
|
||||||
export default function NoteTitleActions() {
|
export default function NoteTitleActions() {
|
||||||
const { note, ntxId } = useNoteContext();
|
const { note, ntxId, componentId } = useNoteContext();
|
||||||
const isHiddenNote = note && note.noteId !== "_search" && note.noteId.startsWith("_");
|
const isHiddenNote = note && note.noteId !== "_search" && note.noteId.startsWith("_");
|
||||||
const noteType = useNoteProperty(note, "type");
|
const noteType = useNoteProperty(note, "type");
|
||||||
|
|
||||||
const items = [
|
const items = [
|
||||||
|
note && <PromotedAttributes note={note} componentId={componentId} />,
|
||||||
note && noteType === "search" && <SearchProperties note={note} ntxId={ntxId} />,
|
note && noteType === "search" && <SearchProperties note={note} ntxId={ntxId} />,
|
||||||
note && !isHiddenNote && noteType === "book" && <CollectionProperties note={note} />
|
note && !isHiddenNote && noteType === "book" && <CollectionProperties note={note} />
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
@@ -36,3 +38,16 @@ function SearchProperties({ note, ntxId }: { note: FNote, ntxId: string | null |
|
|||||||
</Collapsible>
|
</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