import { NoteType } from "@triliumnext/commons"; import { useNoteContext, useTriliumEvent } from "./react/hooks" import FNote from "../entities/fnote"; import protected_session_holder from "../services/protected_session_holder"; import { useEffect, useState } from "preact/hooks"; import NoteContext from "../components/note_context"; import Empty from "./type_widgets/Empty"; import { VNode } from "preact"; import Doc from "./type_widgets/Doc"; import { TypeWidgetProps } from "./type_widgets/type_widget"; import ProtectedSession from "./type_widgets/ProtectedSession"; import Book from "./type_widgets/Book"; import ContentWidget from "./type_widgets/ContentWidget"; import WebView from "./type_widgets/WebView"; import "./NoteDetail.css"; import File from "./type_widgets/File"; import Image from "./type_widgets/Image"; import { ReadOnlyCode, EditableCode } from "./type_widgets/code/Code"; import Mermaid from "./type_widgets/Mermaid"; import MindMap from "./type_widgets/MindMap"; import { AttachmentDetail, AttachmentList } from "./type_widgets/Attachment"; import ReadOnlyText from "./type_widgets/text/ReadOnlyText"; import attributes from "../services/attributes"; /** * A `NoteType` altered by the note detail widget, taking into consideration whether the note is editable or not and adding special note types such as an empty one, * for protected session or attachment information. */ type ExtendedNoteType = Exclude | "empty" | "readOnlyCode" | "readOnlyText" | "editableText" | "editableCode" | "attachmentDetail" | "attachmentList" | "protectedSession" | "aiChat"; /** * 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. * * Apart from that: * - It applies a full-height style depending on the content type (e.g. canvas notes). * - Focuses the content when switching tabs. */ export default function NoteDetail() { const { note, type, mime, noteContext, parentComponent } = useNoteInfo(); const { ntxId, viewScope } = noteContext ?? {}; const [ correspondingWidget, setCorrespondingWidget ] = useState(); const isFullHeight = checkFullHeight(noteContext, type); const props: TypeWidgetProps = { note: note!, viewScope, ntxId, parentComponent, noteContext }; useEffect(() => setCorrespondingWidget(getCorrespondingWidget(type, props)), [ note, viewScope, type ]); // Detect note type changes. useTriliumEvent("entitiesReloaded", async ({ loadResults }) => { if (!note) return; // we're detecting note type change on the note_detail level, but triggering the noteTypeMimeChanged // globally, so it gets also to e.g. ribbon components. But this means that the event can be generated multiple // times if the same note is open in several tabs. if (note.noteId && loadResults.isNoteContentReloaded(note.noteId, parentComponent.componentId)) { // probably incorrect event // calling this.refresh() is not enough since the event needs to be propagated to children as well // FIXME: create a separate event to force hierarchical refresh // this uses handleEvent to make sure that the ordinary content updates are propagated only in the subtree // to avoid the problem in #3365 parentComponent.handleEvent("noteTypeMimeChanged", { noteId: note.noteId }); } else if (note.noteId && loadResults.isNoteReloaded(note.noteId, parentComponent.componentId) && (type !== (await getWidgetType(note, noteContext)) || mime !== note?.mime)) { // 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 }); } else { const attrs = loadResults.getAttributeRows(); const label = attrs.find( (attr) => attr.type === "label" && ["readOnly", "autoReadOnlyDisabled", "cssClass", "displayRelations", "hideRelations"].includes(attr.name ?? "") && attributes.isAffecting(attr, note) ); const relation = attrs.find((attr) => attr.type === "relation" && ["template", "inherit", "renderNote"] .includes(attr.name ?? "") && attributes.isAffecting(attr, note)); if (note.noteId && (label || relation)) { // probably incorrect event // calling this.refresh() is not enough since the event needs to be propagated to children as well parentComponent.triggerEvent("noteTypeMimeChanged", { noteId: note.noteId }); } } }); // Automatically focus the editor. useTriliumEvent("activeNoteChanged", () => { // Restore focus to the editor when switching tabs, but only if the note tree is not already focused. if (!document.activeElement?.classList.contains("fancytree-title")) { parentComponent.triggerCommand("focusOnDetail", { ntxId }); } }); return (
{correspondingWidget ||

Note detail goes here! {note?.title} of {type}

}
); } /** Manages both note changes and changes to the widget type, which are asynchronous. */ function useNoteInfo() { const { note: actualNote, noteContext, parentComponent } = useNoteContext(); const [ note, setNote ] = useState(); const [ type, setType ] = useState(); const [ mime, setMime ] = useState(); function refresh() { getWidgetType(actualNote, noteContext).then(type => { setNote(actualNote); setType(type); setMime(actualNote?.mime); }); } useEffect(refresh, [ actualNote, noteContext]); useTriliumEvent("readOnlyTemporarilyDisabled", ({ noteContext: eventNoteContext }) => { if (eventNoteContext?.ntxId !== noteContext?.ntxId) return; refresh(); }); useTriliumEvent("noteTypeMimeChanged", refresh); return { note, type, mime, noteContext, parentComponent }; } function getCorrespondingWidget(noteType: ExtendedNoteType | undefined, props: TypeWidgetProps) { switch (noteType) { case "empty": return case "doc": return case "search": return
case "protectedSession": return case "book": return case "contentWidget": return case "webView": return case "file": return case "image": return case "readOnlyCode": return case "editableCode": return case "mermaid": return case "mindMap": return case "attachmentList": return case "attachmentDetail": return case "readOnlyText": return default: break; } } async function getWidgetType(note: FNote | null | undefined, noteContext: NoteContext | undefined): Promise { if (!note) { return "empty"; } const type = note.type; let resultingType: ExtendedNoteType; if (noteContext?.viewScope?.viewMode === "source") { resultingType = "readOnlyCode"; } else if (noteContext?.viewScope && noteContext.viewScope.viewMode === "attachments") { resultingType = noteContext.viewScope.attachmentId ? "attachmentDetail" : "attachmentList"; } else if (type === "text" && (await noteContext?.isReadOnly())) { resultingType = "readOnlyText"; } else if ((type === "code" || type === "mermaid") && (await noteContext?.isReadOnly())) { resultingType = "readOnlyCode"; } else if (type === "text") { resultingType = "editableText"; } else if (type === "code") { resultingType = "editableCode"; } else if (type === "launcher") { resultingType = "doc"; } else { resultingType = type; } if (note.isProtected && !protected_session_holder.isProtectedSessionAvailable()) { resultingType = "protectedSession"; } return resultingType; } function checkFullHeight(noteContext: NoteContext | undefined, type: ExtendedNoteType | undefined) { if (!noteContext) return false; // https://github.com/zadam/trilium/issues/2522 const isBackendNote = noteContext?.noteId === "_backendLog"; const isSqlNote = noteContext.note?.mime === "text/x-sqlite;schema=trilium"; const isFullHeightNoteType = ["canvas", "webView", "noteMap", "mindMap", "mermaid", "file"].includes(type ?? ""); return (!noteContext?.hasNoteList() && isFullHeightNoteType && !isSqlNote) || noteContext?.viewScope?.viewMode === "attachments" || isBackendNote; }