@@ -92,6 +92,9 @@ class QueryRenderChild extends MarkdownRenderChild {
9292 private renderEventRef : EventRef | undefined ;
9393 private queryReloadTimeout : NodeJS . Timeout | undefined ;
9494
95+ private isCacheChangedSinceLastRedraw = false ;
96+ private observer : IntersectionObserver | null = null ;
97+
9598 private readonly queryResultsRenderer : QueryResultsRenderer ;
9699
97100 constructor ( {
@@ -162,6 +165,38 @@ class QueryRenderChild extends MarkdownRenderChild {
162165 this . handleMetadataOrFilePathChange ( tFile . path , fileCache ) ;
163166 } ) ,
164167 ) ;
168+
169+ this . setupVisibilityObserver ( ) ;
170+ }
171+
172+ private setupVisibilityObserver ( ) {
173+ if ( this . observer ) {
174+ return ;
175+ }
176+
177+ this . observer = new IntersectionObserver ( ( [ entry ] ) => {
178+ if ( ! this . containerEl . isShown ( ) ) {
179+ return ;
180+ }
181+
182+ // entry describes a single visibility change for the specific element we are observing.
183+ // It is safe to assume `entry.target === this.containerEl` here.
184+ if ( ! entry . isIntersecting ) {
185+ return ;
186+ }
187+
188+ this . queryResultsRenderer . query . debug (
189+ `[render][observer] Became visible, isCacheChangedSinceLastRedraw:${ this . isCacheChangedSinceLastRedraw } ` ,
190+ ) ;
191+ if ( this . isCacheChangedSinceLastRedraw ) {
192+ this . queryResultsRenderer . query . debug ( '[render][observer] ... updating search results' ) ;
193+ this . render ( { tasks : this . plugin . getTasks ( ) , state : this . plugin . getState ( ) } )
194+ . then ( )
195+ . catch ( ( e ) => console . error ( e ) ) ;
196+ }
197+ } ) ;
198+
199+ this . observer . observe ( this . containerEl ) ;
165200 }
166201
167202 private handleMetadataOrFilePathChange ( filePath : string , fileCache : CachedMetadata | null ) {
@@ -189,6 +224,9 @@ class QueryRenderChild extends MarkdownRenderChild {
189224 if ( this . queryReloadTimeout !== undefined ) {
190225 clearTimeout ( this . queryReloadTimeout ) ;
191226 }
227+
228+ this . observer ?. disconnect ( ) ;
229+ this . observer = null ;
192230 }
193231
194232 /**
@@ -219,6 +257,42 @@ class QueryRenderChild extends MarkdownRenderChild {
219257 }
220258
221259 private async render ( { tasks, state } : { tasks : Task [ ] ; state : State } ) {
260+ // We got here because the Cache reported a change in at least one task in the vault.
261+ // So note that any results we have already drawn are now out-of-date:
262+ this . isCacheChangedSinceLastRedraw = true ;
263+
264+ requestAnimationFrame ( async ( ) => {
265+ // We have to wrap the rendering inside requestAnimationFrame() to ensure
266+ // that we get correct values for isConnected and isShown().
267+ if ( ! this . containerEl . isConnected ) {
268+ // Example reasons why we might not be "connected":
269+ // - This Tasks query block is contained within another plugin's code block,
270+ // such as a Tabs plugin. The file is closed and that plugin has not correctly
271+ // tidied up, so we have not been deleted.
272+ this . queryResultsRenderer . query . debug (
273+ '[render] Ignoring redraw request, as code block is not connected.' ,
274+ ) ;
275+ return ;
276+ }
277+
278+ if ( ! this . containerEl . isShown ( ) ) {
279+ // Example reasons why we might not be "shown":
280+ // - We are in a collapsed callout.
281+ // - We are in a note which is obscured by another note.
282+ // - We are in a Tabs plugin, in a tab which is not at the front.
283+ // - The user has not yet scrolled to this code block's position in the file.
284+ this . queryResultsRenderer . query . debug ( '[render] Ignoring redraw request, as code block is not shown.' ) ;
285+ return ;
286+ }
287+
288+ await this . renderResults ( state , tasks ) ;
289+
290+ // Our results are now up-to-date:
291+ this . isCacheChangedSinceLastRedraw = false ;
292+ } ) ;
293+ }
294+
295+ private async renderResults ( state : State , tasks : Task [ ] ) {
222296 const content = createAndAppendElement ( 'div' , this . containerEl ) ;
223297 await this . queryResultsRenderer . render ( state , tasks , content , {
224298 allTasks : this . plugin . getTasks ( ) ,
0 commit comments