@@ -4,6 +4,7 @@ import type { IQuery } from '../IQuery';
44import { PerformanceTracker } from '../lib/PerformanceTracker' ;
55import { State } from '../Obsidian/Cache' ;
66import { DescriptionField } from '../Query/Filter/DescriptionField' ;
7+ import { Query } from '../Query/Query' ;
78import { getQueryForQueryRenderer } from '../Query/QueryRendererHelper' ;
89import type { QueryResult } from '../Query/QueryResult' ;
910import type { TasksFile } from '../Scripting/TasksFile' ;
@@ -58,6 +59,8 @@ export class QueryResultsRenderer {
5859 private _tasksFile : TasksFile ;
5960
6061 public query : IQuery ;
62+ public queryResult : QueryResult ;
63+ public filteredQueryResult : QueryResult ;
6164 protected queryType : string ; // whilst there is only one query type, there is no point logging this value
6265
6366 constructor (
@@ -79,6 +82,10 @@ export class QueryResultsRenderer {
7982 this . source = source ;
8083 this . _tasksFile = tasksFile ;
8184
85+ // Store empty query result for now
86+ this . queryResult = new Query ( '' ) . applyQueryToTasks ( [ ] ) ;
87+ this . filteredQueryResult = this . queryResult ;
88+
8289 // The engine is chosen on the basis of the code block language. Currently,
8390 // there is only the main engine for the plugin, this allows others to be
8491 // added later.
@@ -148,18 +155,17 @@ export class QueryResultsRenderer {
148155 }
149156
150157 public async render ( state : State , tasks : Task [ ] , content : HTMLDivElement ) {
151- const queryResult = this . performSearch ( tasks ) ;
152-
153- this . addToolbar ( queryResult , content ) ;
154- await this . renderQueryResult ( state , queryResult , content ) ;
158+ this . performSearch ( tasks ) ;
159+ this . addToolbar ( content ) ;
160+ await this . renderQueryResult ( state , this . queryResult , content ) ;
155161 }
156162
157163 private performSearch ( tasks : Task [ ] ) {
158164 const measureSearch = new PerformanceTracker ( `Search: ${ this . query . queryId } - ${ this . filePath } ` ) ;
159165 measureSearch . start ( ) ;
160- const queryResult = this . query . applyQueryToTasks ( tasks ) ;
166+ this . queryResult = this . query . applyQueryToTasks ( tasks ) ;
167+ this . filteredQueryResult = this . queryResult ;
161168 measureSearch . finish ( ) ;
162- return queryResult ;
163169 }
164170
165171 private async renderQueryResult ( state : State , queryResult : QueryResult , content : HTMLDivElement ) {
@@ -170,58 +176,67 @@ export class QueryResultsRenderer {
170176 measureRender . finish ( ) ;
171177 }
172178
173- private addToolbar ( queryResult : QueryResult , content : HTMLDivElement ) {
179+ private addToolbar ( content : HTMLDivElement ) {
174180 if ( this . query . queryLayoutOptions . hideToolbar ) {
175181 return ;
176182 }
177183
178184 const toolbar = createAndAppendElement ( 'div' , content ) ;
179185 toolbar . classList . add ( 'plugin-tasks-toolbar' ) ;
180- this . addSearchBox ( toolbar , queryResult , content ) ;
181- this . addCopyButton ( toolbar , queryResult ) ;
186+ this . addSearchBox ( toolbar , content ) ;
187+ this . addCopyButton ( toolbar ) ;
182188 }
183189
184- private addSearchBox ( toolbar : HTMLDivElement , queryResult : QueryResult , content : HTMLDivElement ) {
190+ private addSearchBox ( toolbar : HTMLDivElement , content : HTMLDivElement ) {
185191 const label = createAndAppendElement ( 'label' , toolbar ) ;
186192 setIcon ( label , 'lucide-filter' ) ;
187193 const searchBox = createAndAppendElement ( 'input' , label ) ;
188194 searchBox . placeholder = 'Filter by description...' ;
189195 setTooltip ( searchBox , 'Filter results' ) ;
190196 searchBox . addEventListener ( 'input' , async ( ) => {
191- const { filter, error } = new DescriptionField ( ) . createFilterOrErrorMessage (
192- 'description includes ' + searchBox . value ,
193- ) ;
194- if ( error ) {
195- new Notice ( 'error searching for ' + searchBox . value + ': ' + error ) ;
196- return ;
197- }
197+ const filterString = searchBox . value ;
198+ await this . applySearchBoxFilter ( filterString , content ) ;
199+ } ) ;
200+ }
198201
199- // We want to retain the Toolbar, to not lose the search string.
200- // But we need to delete any pre-existing headings, tasks and task count.
201- // The following while loop relies on the Toolbar being the first element.
202- while ( content . firstElementChild !== content . lastElementChild ) {
203- const lastChild = content . lastChild ;
204- if ( lastChild === null ) {
205- break ;
206- }
202+ public async applySearchBoxFilter ( filterString : string , content : HTMLDivElement ) {
203+ const { filter , error } = new DescriptionField ( ) . createFilterOrErrorMessage (
204+ 'description includes ' + filterString ,
205+ ) ;
206+ if ( error ) {
207+ new Notice ( 'error searching for ' + filterString + ': ' + error ) ;
208+ return ;
209+ }
207210
208- lastChild . remove ( ) ;
211+ // We want to retain the Toolbar, to not lose the search string.
212+ // But we need to delete any pre-existing headings, tasks and task count.
213+ // The following while loop relies on the Toolbar being the first element.
214+ while ( content . firstElementChild !== content . lastElementChild ) {
215+ const lastChild = content . lastChild ;
216+ if ( lastChild === null ) {
217+ break ;
209218 }
210219
211- const filteredQueryResult = queryResult . applyFilter ( filter ! ) ;
212- await this . renderQueryResult ( State . Warm , filteredQueryResult , content ) ;
213- } ) ;
220+ lastChild . remove ( ) ;
221+ }
222+
223+ this . filteredQueryResult = this . queryResult . applyFilter ( filter ! ) ;
224+ await this . renderQueryResult ( State . Warm , this . filteredQueryResult , content ) ;
214225 }
215226
216- private addCopyButton ( toolbar : HTMLDivElement , queryResult : QueryResult ) {
227+ private addCopyButton ( toolbar : HTMLDivElement ) {
217228 const copyButton = createAndAppendElement ( 'button' , toolbar ) ;
218229 setIcon ( copyButton , 'lucide-copy' ) ;
219230 setTooltip ( copyButton , 'Copy results' ) ;
220231 copyButton . addEventListener ( 'click' , async ( ) => {
221- // TODO reimplement this using QueryResult.asMarkdown() when it supports trees and list items.
222- await this . markdownRenderer . renderQuery ( State . Warm , queryResult ) ;
223- await navigator . clipboard . writeText ( this . markdownRenderer . markdown ) ;
232+ const markdown = await this . resultsAsMarkdown ( ) ;
233+ await navigator . clipboard . writeText ( markdown ) ;
224234 new Notice ( 'Results copied to clipboard' ) ;
225235 } ) ;
226236 }
237+
238+ public async resultsAsMarkdown ( ) {
239+ await this . markdownRenderer . renderQuery ( State . Warm , this . filteredQueryResult ) ;
240+ return this . markdownRenderer . markdown ;
241+ }
227242}
0 commit comments