2025-08-30 15:44:49 +03:00
import { allViewTypes , ViewModeProps , ViewTypeOptions } from "./interface" ;
2025-08-30 15:11:49 +03:00
import { useNoteContext , useNoteLabel , useTriliumEvent } from "../react/hooks" ;
import FNote from "../../entities/fnote" ;
import "./NoteList.css" ;
2025-08-30 19:21:26 +03:00
import { ListView , GridView } from "./legacy/ListOrGridView" ;
2025-08-30 19:42:16 +03:00
import { useEffect , useRef , useState } from "preact/hooks" ;
2025-08-30 15:11:49 +03:00
2025-08-30 14:29:54 +03:00
interface NoteListProps {
2025-08-30 18:48:34 +03:00
note? : FNote | null ;
2025-08-30 14:29:54 +03:00
displayOnlyCollections? : boolean ;
2025-08-30 18:48:34 +03:00
highlightedTokens? : string [ ] | null ;
2025-08-30 14:29:54 +03:00
}
2025-08-30 19:48:05 +03:00
export default function NoteList ( { note : providedNote , highlightedTokens , displayOnlyCollections } : NoteListProps ) {
2025-08-30 19:42:16 +03:00
const widgetRef = useRef < HTMLDivElement > ( null ) ;
2025-08-30 19:48:05 +03:00
const { note : contextNote , noteContext } = useNoteContext ( ) ;
2025-08-30 18:48:34 +03:00
const note = providedNote ? ? contextNote ;
2025-08-30 15:11:49 +03:00
const viewType = useNoteViewType ( note ) ;
const noteIds = useNoteIds ( note , viewType ) ;
2025-08-30 19:24:32 +03:00
const isFullHeight = ( viewType !== "list" && viewType !== "grid" ) ;
2025-08-30 19:42:16 +03:00
const [ isIntersecting , setIsIntersecting ] = useState ( false ) ;
const shouldRender = ( isFullHeight || isIntersecting ) ;
2025-08-30 19:48:05 +03:00
const isEnabled = ( note && noteContext ? . hasNoteList ( ) && ! ! viewType && shouldRender ) ;
2025-08-30 19:42:16 +03:00
useEffect ( ( ) = > {
2025-08-30 19:48:05 +03:00
if ( isFullHeight || displayOnlyCollections ) {
// Double role: no need to check if the note list is visible if the view is full-height, but also prevent legacy views if `displayOnlyCollections` is true.
return ;
}
2025-08-30 19:42:16 +03:00
const observer = new IntersectionObserver (
( entries ) = > {
if ( ! isIntersecting ) {
setIsIntersecting ( entries [ 0 ] . isIntersecting ) ;
}
observer . disconnect ( ) ;
} ,
{
rootMargin : "50px" ,
threshold : 0.1
}
) ;
// there seems to be a race condition on Firefox which triggers the observer only before the widget is visible
// (intersection is false). https://github.com/zadam/trilium/issues/4165
setTimeout ( ( ) = > widgetRef . current && observer . observe ( widgetRef . current ) , 10 ) ;
return ( ) = > observer . disconnect ( ) ;
} , [ ] ) ;
2025-08-30 15:11:49 +03:00
return (
2025-08-30 19:42:16 +03:00
< div ref = { widgetRef } className = { ` note-list-widget ${ isFullHeight ? "full-height" : "" } ` } >
2025-08-30 15:11:49 +03:00
{ isEnabled && (
< div className = "note-list-widget-content" >
2025-08-30 18:48:34 +03:00
{ getComponentByViewType ( note , noteIds , viewType , highlightedTokens ) }
2025-08-30 15:11:49 +03:00
< / div >
) }
< / div >
) ;
}
2025-08-30 18:48:34 +03:00
function getComponentByViewType ( note : FNote , noteIds : string [ ] , viewType : ViewTypeOptions , highlightedTokens : string [ ] | null | undefined ) {
const props : ViewModeProps = { note , noteIds , highlightedTokens } ;
2025-08-30 17:21:22 +03:00
2025-08-30 15:11:49 +03:00
switch ( viewType ) {
case "list" :
2025-08-30 15:44:49 +03:00
return < ListView { ...props } / > ;
2025-08-30 17:21:22 +03:00
case "grid" :
return < GridView { ...props } / > ;
2025-08-30 15:11:49 +03:00
}
}
function useNoteViewType ( note? : FNote | null ) : ViewTypeOptions | undefined {
const [ viewType ] = useNoteLabel ( note , "viewType" ) ;
2025-08-30 17:21:22 +03:00
2025-08-30 15:11:49 +03:00
if ( ! note ) {
return undefined ;
} else if ( ! ( allViewTypes as readonly string [ ] ) . includes ( viewType || "" ) ) {
// when not explicitly set, decide based on the note type
return note . type === "search" ? "list" : "grid" ;
} else {
return viewType as ViewTypeOptions ;
}
}
function useNoteIds ( note : FNote | null | undefined , viewType : ViewTypeOptions | undefined ) {
const [ noteIds , setNoteIds ] = useState < string [ ] > ( [ ] ) ;
2025-08-30 17:21:22 +03:00
2025-08-30 15:11:49 +03:00
async function refreshNoteIds() {
if ( ! note ) {
setNoteIds ( [ ] ) ;
} else if ( viewType === "list" || viewType === "grid" ) {
2025-08-30 15:44:49 +03:00
console . log ( "Refreshed note IDs" ) ;
2025-08-30 15:11:49 +03:00
setNoteIds ( note . getChildNoteIds ( ) ) ;
} else {
2025-08-30 15:44:49 +03:00
console . log ( "Refreshed note IDs" ) ;
2025-08-30 15:11:49 +03:00
setNoteIds ( await note . getSubtreeNoteIds ( ) ) ;
}
}
// Refresh on note switch.
useEffect ( ( ) = > { refreshNoteIds ( ) } , [ note ] ) ;
// Refresh on alterations to the note subtree.
useTriliumEvent ( "entitiesReloaded" , ( { loadResults } ) = > {
if ( note && loadResults . getBranchRows ( ) . some ( branch = >
branch . parentNoteId === note . noteId
|| noteIds . includes ( branch . parentNoteId ? ? "" ) ) ) {
2025-08-30 17:21:22 +03:00
refreshNoteIds ( ) ;
2025-08-30 15:11:49 +03:00
}
} )
return noteIds ;
2025-08-30 17:21:22 +03:00
}