mirror of
https://github.com/zadam/trilium.git
synced 2025-12-17 05:39:55 +01:00
feat(layout): automatically collapse promoted attributes in full-height notes
This commit is contained in:
@@ -1,16 +1,18 @@
|
|||||||
import { useNoteContext, useTriliumEvent } from "./react/hooks"
|
|
||||||
import FNote from "../entities/fnote";
|
|
||||||
import protected_session_holder from "../services/protected_session_holder";
|
|
||||||
import { useEffect, useRef, useState } from "preact/hooks";
|
|
||||||
import NoteContext from "../components/note_context";
|
|
||||||
import { isValidElement, VNode } from "preact";
|
|
||||||
import { TypeWidgetProps } from "./type_widgets/type_widget";
|
|
||||||
import "./NoteDetail.css";
|
import "./NoteDetail.css";
|
||||||
|
|
||||||
|
import { isValidElement, VNode } from "preact";
|
||||||
|
import { useEffect, useRef, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
import NoteContext from "../components/note_context";
|
||||||
|
import FNote from "../entities/fnote";
|
||||||
import attributes from "../services/attributes";
|
import attributes from "../services/attributes";
|
||||||
import { ExtendedNoteType, TYPE_MAPPINGS, TypeWidget } from "./note_types";
|
|
||||||
import { dynamicRequire, isElectron, isMobile } from "../services/utils";
|
|
||||||
import toast from "../services/toast.js";
|
|
||||||
import { t } from "../services/i18n";
|
import { t } from "../services/i18n";
|
||||||
|
import protected_session_holder from "../services/protected_session_holder";
|
||||||
|
import toast from "../services/toast.js";
|
||||||
|
import { dynamicRequire, isElectron, isMobile } from "../services/utils";
|
||||||
|
import { ExtendedNoteType, TYPE_MAPPINGS, TypeWidget } from "./note_types";
|
||||||
|
import { useNoteContext, useTriliumEvent } from "./react/hooks";
|
||||||
|
import { TypeWidgetProps } from "./type_widgets/type_widget";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The note detail is in charge of rendering the content of a note, by determining its type (e.g. text, code) and using the appropriate view widget.
|
* The note detail is in charge of rendering the content of a note, by determining its type (e.g. text, code) and using the appropriate view widget.
|
||||||
@@ -80,7 +82,7 @@ export default function NoteDetail() {
|
|||||||
parentComponent.handleEvent("noteTypeMimeChanged", { noteId: note.noteId });
|
parentComponent.handleEvent("noteTypeMimeChanged", { noteId: note.noteId });
|
||||||
} else if (note.noteId
|
} else if (note.noteId
|
||||||
&& loadResults.isNoteReloaded(note.noteId, parentComponent.componentId)
|
&& loadResults.isNoteReloaded(note.noteId, parentComponent.componentId)
|
||||||
&& (type !== (await getWidgetType(note, noteContext)) || mime !== note?.mime)) {
|
&& (type !== (await getExtendedWidgetType(note, noteContext)) || mime !== note?.mime)) {
|
||||||
// this needs to have a triggerEvent so that e.g., note type (not in the component subtree) is updated
|
// this needs to have a triggerEvent so that e.g., note type (not in the component subtree) is updated
|
||||||
parentComponent.triggerEvent("noteTypeMimeChanged", { noteId: note.noteId });
|
parentComponent.triggerEvent("noteTypeMimeChanged", { noteId: note.noteId });
|
||||||
} else {
|
} else {
|
||||||
@@ -212,7 +214,7 @@ export default function NoteDetail() {
|
|||||||
isVisible={type === itemType}
|
isVisible={type === itemType}
|
||||||
isFullHeight={isFullHeight}
|
isFullHeight={isFullHeight}
|
||||||
props={props}
|
props={props}
|
||||||
/>
|
/>;
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -254,7 +256,7 @@ function useNoteInfo() {
|
|||||||
const [ mime, setMime ] = useState<string>();
|
const [ mime, setMime ] = useState<string>();
|
||||||
|
|
||||||
function refresh() {
|
function refresh() {
|
||||||
getWidgetType(actualNote, noteContext).then(type => {
|
getExtendedWidgetType(actualNote, noteContext).then(type => {
|
||||||
setNote(actualNote);
|
setNote(actualNote);
|
||||||
setType(type);
|
setType(type);
|
||||||
setMime(actualNote?.mime);
|
setMime(actualNote?.mime);
|
||||||
@@ -282,12 +284,12 @@ async function getCorrespondingWidget(type: ExtendedNoteType): Promise<null | Ty
|
|||||||
} else if (isValidElement(result)) {
|
} else if (isValidElement(result)) {
|
||||||
// Direct VNode provided.
|
// Direct VNode provided.
|
||||||
return result;
|
return result;
|
||||||
} else {
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getWidgetType(note: FNote | null | undefined, noteContext: NoteContext | undefined): Promise<ExtendedNoteType | undefined> {
|
export async function getExtendedWidgetType(note: FNote | null | undefined, noteContext: NoteContext | undefined): Promise<ExtendedNoteType | undefined> {
|
||||||
if (!noteContext) return undefined;
|
if (!noteContext) return undefined;
|
||||||
if (!note) {
|
if (!note) {
|
||||||
// If the note is null, then it's a new tab. If it's undefined, then it's not loaded yet.
|
// If the note is null, then it's a new tab. If it's undefined, then it's not loaded yet.
|
||||||
@@ -324,7 +326,7 @@ async function getWidgetType(note: FNote | null | undefined, noteContext: NoteCo
|
|||||||
return resultingType;
|
return resultingType;
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkFullHeight(noteContext: NoteContext | undefined, type: ExtendedNoteType | undefined) {
|
export function checkFullHeight(noteContext: NoteContext | undefined, type: ExtendedNoteType | undefined) {
|
||||||
if (!noteContext) return false;
|
if (!noteContext) return false;
|
||||||
|
|
||||||
// https://github.com/zadam/trilium/issues/2522
|
// https://github.com/zadam/trilium/issues/2522
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
import "./NoteTitleActions.css";
|
import "./NoteTitleActions.css";
|
||||||
|
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
import NoteContext from "../../components/note_context";
|
||||||
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 { checkFullHeight, getExtendedWidgetType } from "../NoteDetail";
|
||||||
import { PromotedAttributesContent, usePromotedAttributeData } from "../PromotedAttributes";
|
import { PromotedAttributesContent, usePromotedAttributeData } from "../PromotedAttributes";
|
||||||
import Collapsible from "../react/Collapsible";
|
import Collapsible, { ExternallyControlledCollapsible } 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, componentId } = useNoteContext();
|
const { note, ntxId, componentId, noteContext } = 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 && <PromotedAttributes note={note} componentId={componentId} noteContext={noteContext} />,
|
||||||
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);
|
||||||
@@ -39,15 +42,29 @@ function SearchProperties({ note, ntxId }: { note: FNote, ntxId: string | null |
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function PromotedAttributes({ note, componentId }: { note: FNote | null | undefined, componentId: string }) {
|
function PromotedAttributes({ note, componentId, noteContext }: {
|
||||||
|
note: FNote | null | undefined,
|
||||||
|
componentId: string,
|
||||||
|
noteContext: NoteContext | undefined
|
||||||
|
}) {
|
||||||
const [ cells, setCells ] = usePromotedAttributeData(note, componentId);
|
const [ cells, setCells ] = usePromotedAttributeData(note, componentId);
|
||||||
if (!cells?.length) return false;
|
const [ expanded, setExpanded ] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getExtendedWidgetType(note, noteContext).then(extendedNoteType => {
|
||||||
|
const fullHeight = checkFullHeight(noteContext, extendedNoteType);
|
||||||
|
setExpanded(!fullHeight);
|
||||||
|
});
|
||||||
|
}, [ note, noteContext ]);
|
||||||
|
|
||||||
|
if (!cells?.length) return false;
|
||||||
return (note && (
|
return (note && (
|
||||||
<Collapsible
|
<ExternallyControlledCollapsible
|
||||||
|
key={note.noteId}
|
||||||
title={t("promoted_attributes.promoted_attributes")}
|
title={t("promoted_attributes.promoted_attributes")}
|
||||||
|
expanded={expanded} setExpanded={setExpanded}
|
||||||
>
|
>
|
||||||
<PromotedAttributesContent note={note} componentId={componentId} cells={cells} setCells={setCells} />
|
<PromotedAttributesContent note={note} componentId={componentId} cells={cells} setCells={setCells} />
|
||||||
</Collapsible>
|
</ExternallyControlledCollapsible>
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,17 @@ interface CollapsibleProps extends Pick<HTMLAttributes<HTMLDivElement>, "classNa
|
|||||||
initiallyExpanded?: boolean;
|
initiallyExpanded?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Collapsible({ title, children, className, initiallyExpanded }: CollapsibleProps) {
|
export default function Collapsible({ initiallyExpanded, ...restProps }: CollapsibleProps) {
|
||||||
|
const [ expanded, setExpanded ] = useState(initiallyExpanded);
|
||||||
|
return <ExternallyControlledCollapsible {...restProps} expanded={expanded} setExpanded={setExpanded} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ExternallyControlledCollapsible({ title, children, className, expanded, setExpanded }: Omit<CollapsibleProps, "initiallyExpanded"> & {
|
||||||
|
expanded: boolean | undefined;
|
||||||
|
setExpanded: (expanded: boolean) => void
|
||||||
|
}) {
|
||||||
const bodyRef = useRef<HTMLDivElement>(null);
|
const bodyRef = useRef<HTMLDivElement>(null);
|
||||||
const innerRef = useRef<HTMLDivElement>(null);
|
const innerRef = useRef<HTMLDivElement>(null);
|
||||||
const [ expanded, setExpanded ] = useState(initiallyExpanded);
|
|
||||||
const { height } = useElementSize(innerRef) ?? {};
|
const { height } = useElementSize(innerRef) ?? {};
|
||||||
const contentId = useUniqueName();
|
const contentId = useUniqueName();
|
||||||
|
|
||||||
@@ -48,5 +55,4 @@ export default function Collapsible({ title, children, className, initiallyExpan
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user