chore(react/collections): add intersection observer

This commit is contained in:
Elian Doran 2025-08-30 19:42:16 +03:00
parent 2689b22674
commit 6e575df40b
No known key found for this signature in database
2 changed files with 26 additions and 54 deletions

View File

@ -3,7 +3,7 @@ import { useNoteContext, useNoteLabel, useTriliumEvent } from "../react/hooks";
import FNote from "../../entities/fnote"; import FNote from "../../entities/fnote";
import "./NoteList.css"; import "./NoteList.css";
import { ListView, GridView } from "./legacy/ListOrGridView"; import { ListView, GridView } from "./legacy/ListOrGridView";
import { useEffect, useState } from "preact/hooks"; import { useEffect, useRef, useState } from "preact/hooks";
interface NoteListProps { interface NoteListProps {
note?: FNote | null; note?: FNote | null;
@ -12,15 +12,38 @@ interface NoteListProps {
} }
export default function NoteList({ note: providedNote, highlightedTokens }: NoteListProps) { export default function NoteList({ note: providedNote, highlightedTokens }: NoteListProps) {
const widgetRef = useRef<HTMLDivElement>(null);
const { note: contextNote } = useNoteContext(); const { note: contextNote } = useNoteContext();
const note = providedNote ?? contextNote; const note = providedNote ?? contextNote;
const viewType = useNoteViewType(note); const viewType = useNoteViewType(note);
const noteIds = useNoteIds(note, viewType); const noteIds = useNoteIds(note, viewType);
const isEnabled = (note && !!viewType);
const isFullHeight = (viewType !== "list" && viewType !== "grid"); const isFullHeight = (viewType !== "list" && viewType !== "grid");
const [ isIntersecting, setIsIntersecting ] = useState(false);
const shouldRender = (isFullHeight || isIntersecting);
const isEnabled = (note && !!viewType && shouldRender);
useEffect(() => {
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();
}, []);
return ( return (
<div className={`note-list-widget ${isFullHeight ? "full-height" : ""}`}> <div ref={widgetRef} className={`note-list-widget ${isFullHeight ? "full-height" : ""}`}>
{isEnabled && ( {isEnabled && (
<div className="note-list-widget-content"> <div className="note-list-widget-content">
{getComponentByViewType(note, noteIds, viewType, highlightedTokens)} {getComponentByViewType(note, noteIds, viewType, highlightedTokens)}

View File

@ -7,7 +7,6 @@ import type ViewMode from "../view_widgets/view_mode.js";
export default class NoteListWidget extends NoteContextAwareWidget { export default class NoteListWidget extends NoteContextAwareWidget {
private $content!: JQuery<HTMLElement>; private $content!: JQuery<HTMLElement>;
private isIntersecting?: boolean;
private noteIdRefreshed?: string; private noteIdRefreshed?: string;
private shownNoteId?: string | null; private shownNoteId?: string | null;
private viewMode?: ViewMode<any> | null; private viewMode?: ViewMode<any> | null;
@ -33,56 +32,6 @@ export default class NoteListWidget extends NoteContextAwareWidget {
return this.noteContext?.hasNoteList(); return this.noteContext?.hasNoteList();
} }
doRender() {
this.$widget = $(TPL);
this.contentSized();
this.$content = this.$widget.find(".note-list-widget-content");
const observer = new IntersectionObserver(
(entries) => {
this.isIntersecting = entries[0].isIntersecting;
this.checkRenderStatus();
},
{
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(() => observer.observe(this.$widget[0]), 10);
}
checkRenderStatus() {
// console.log("this.isIntersecting", this.isIntersecting);
// console.log(`${this.noteIdRefreshed} === ${this.noteId}`, this.noteIdRefreshed === this.noteId);
// console.log("this.shownNoteId !== this.noteId", this.shownNoteId !== this.noteId);
if (this.note && this.isIntersecting && this.noteIdRefreshed === this.noteId && this.shownNoteId !== this.noteId) {
this.shownNoteId = this.noteId;
this.renderNoteList(this.note);
}
}
async renderNoteList(note: FNote) {
const noteListRenderer = new NoteListRenderer({
$parent: this.$content,
parentNote: note,
parentNotePath: this.notePath
});
this.$widget.toggleClass("full-height", noteListRenderer.isFullHeight);
await noteListRenderer.renderList();
this.viewMode = noteListRenderer.viewMode;
}
async refresh() {
this.shownNoteId = null;
await super.refresh();
}
async refreshNoteListEvent({ noteId }: EventData<"refreshNoteList">) { async refreshNoteListEvent({ noteId }: EventData<"refreshNoteList">) {
if (this.isNote(noteId) && this.note) { if (this.isNote(noteId) && this.note) {
await this.renderNoteList(this.note); await this.renderNoteList(this.note);