2025-09-19 17:31:10 +03:00
|
|
|
import { NoteType } from "@triliumnext/commons";
|
2025-09-19 16:53:31 +03:00
|
|
|
import { useNoteContext } from "./react/hooks"
|
2025-09-19 17:31:10 +03:00
|
|
|
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";
|
2025-09-19 18:08:21 +03:00
|
|
|
import Empty from "./type_widgets/Empty";
|
|
|
|
|
import { VNode } from "preact";
|
2025-09-19 18:32:45 +03:00
|
|
|
import Doc from "./type_widgets/Doc";
|
|
|
|
|
import { TypeWidgetProps } from "./type_widgets/type_widget";
|
2025-09-19 18:55:04 +03:00
|
|
|
import ProtectedSession from "./type_widgets/ProtectedSession";
|
2025-09-19 19:03:31 +03:00
|
|
|
import Book from "./type_widgets/Book";
|
2025-09-19 21:18:09 +03:00
|
|
|
import ContentWidget from "./type_widgets/ContentWidget";
|
2025-09-19 21:27:45 +03:00
|
|
|
import WebView from "./type_widgets/WebView";
|
2025-09-19 21:40:35 +03:00
|
|
|
import "./NoteDetail.css";
|
2025-09-19 22:22:45 +03:00
|
|
|
import File from "./type_widgets/File";
|
2025-09-19 22:41:18 +03:00
|
|
|
import Image from "./type_widgets/Image";
|
2025-09-19 17:31:10 +03:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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<NoteType, "launcher" | "text" | "code"> | "empty" | "readOnlyCode" | "readOnlyText" | "editableText" | "editableCode" | "attachmentDetail" | "attachmentList" | "protectedSession" | "aiChat";
|
2025-09-19 16:53:31 +03:00
|
|
|
|
2025-09-19 17:40:24 +03:00
|
|
|
/**
|
|
|
|
|
* 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.
|
|
|
|
|
*/
|
2025-09-19 16:53:31 +03:00
|
|
|
export default function NoteDetail() {
|
2025-09-19 21:40:35 +03:00
|
|
|
const { note, type, noteContext } = useNoteInfo();
|
|
|
|
|
const { ntxId, viewScope } = noteContext ?? {};
|
2025-09-19 18:08:21 +03:00
|
|
|
const [ correspondingWidget, setCorrespondingWidget ] = useState<VNode>();
|
2025-09-19 21:40:35 +03:00
|
|
|
const isFullHeight = checkFullHeight(noteContext, type);
|
2025-09-19 17:31:10 +03:00
|
|
|
|
2025-09-19 18:32:45 +03:00
|
|
|
const props: TypeWidgetProps = {
|
|
|
|
|
note: note!,
|
|
|
|
|
viewScope,
|
|
|
|
|
ntxId
|
|
|
|
|
};
|
|
|
|
|
useEffect(() => setCorrespondingWidget(getCorrespondingWidget(type, props)), [ note, viewScope, type ]);
|
2025-09-19 18:08:21 +03:00
|
|
|
|
|
|
|
|
return (
|
2025-09-19 21:40:35 +03:00
|
|
|
<div class={`note-detail ${isFullHeight ? "full-height" : ""}`}>
|
2025-09-19 18:08:21 +03:00
|
|
|
{correspondingWidget || <p>Note detail goes here! {note?.title} of {type}</p>}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2025-09-19 17:31:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Manages both note changes and changes to the widget type, which are asynchronous. */
|
|
|
|
|
function useNoteInfo() {
|
|
|
|
|
const { note: actualNote, noteContext } = useNoteContext();
|
|
|
|
|
const [ note, setNote ] = useState<FNote | null | undefined>();
|
|
|
|
|
const [ type, setType ] = useState<ExtendedNoteType>();
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
getWidgetType(actualNote, noteContext).then(type => {
|
|
|
|
|
setNote(actualNote);
|
|
|
|
|
setType(type);
|
|
|
|
|
});
|
|
|
|
|
}, [ actualNote, noteContext ]);
|
|
|
|
|
|
2025-09-19 21:40:35 +03:00
|
|
|
return { note, type, noteContext };
|
2025-09-19 17:31:10 +03:00
|
|
|
}
|
|
|
|
|
|
2025-09-19 18:32:45 +03:00
|
|
|
function getCorrespondingWidget(noteType: ExtendedNoteType | undefined, props: TypeWidgetProps) {
|
2025-09-19 18:08:21 +03:00
|
|
|
switch (noteType) {
|
2025-09-19 18:55:04 +03:00
|
|
|
case "empty": return <Empty />
|
|
|
|
|
case "doc": return <Doc {...props} />
|
|
|
|
|
case "search": return <div className="note-detail-none note-detail-printable" />
|
|
|
|
|
case "protectedSession": return <ProtectedSession />
|
2025-09-19 19:03:31 +03:00
|
|
|
case "book": return <Book {...props} />
|
2025-09-19 21:18:09 +03:00
|
|
|
case "contentWidget": return <ContentWidget {...props} />
|
2025-09-19 21:27:45 +03:00
|
|
|
case "webView": return <WebView {...props} />
|
2025-09-19 22:22:45 +03:00
|
|
|
case "file": return <File {...props} />
|
2025-09-19 22:41:18 +03:00
|
|
|
case "image": return <Image {...props} />
|
2025-09-19 18:55:04 +03:00
|
|
|
default: break;
|
2025-09-19 18:08:21 +03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-19 17:31:10 +03:00
|
|
|
async function getWidgetType(note: FNote | null | undefined, noteContext: NoteContext | undefined): Promise<ExtendedNoteType> {
|
|
|
|
|
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;
|
2025-09-19 16:53:31 +03:00
|
|
|
}
|
2025-09-19 21:40:35 +03:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|