@@ -3,6 +3,7 @@ import { GlobalQuery } from '../Config/GlobalQuery';
33import type { IQuery } from '../IQuery' ;
44import { PerformanceTracker } from '../lib/PerformanceTracker' ;
55import { State } from '../Obsidian/Cache' ;
6+ import { DescriptionField } from '../Query/Filter/DescriptionField' ;
67import { getQueryForQueryRenderer } from '../Query/QueryRendererHelper' ;
78import type { QueryResult } from '../Query/QueryResult' ;
89import type { TasksFile } from '../Scripting/TasksFile' ;
@@ -147,29 +148,71 @@ export class QueryResultsRenderer {
147148 }
148149
149150 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 ) ;
155+ }
156+
157+ private performSearch ( tasks : Task [ ] ) {
150158 const measureSearch = new PerformanceTracker ( `Search: ${ this . query . queryId } - ${ this . filePath } ` ) ;
151159 measureSearch . start ( ) ;
152160 const queryResult = this . query . applyQueryToTasks ( tasks ) ;
153161 measureSearch . finish ( ) ;
162+ return queryResult ;
163+ }
154164
165+ private async renderQueryResult ( state : State , queryResult : QueryResult , content : HTMLDivElement ) {
155166 const measureRender = new PerformanceTracker ( `Render: ${ this . query . queryId } - ${ this . filePath } ` ) ;
156167 measureRender . start ( ) ;
157- this . addToolbar ( queryResult , content ) ;
158168 this . htmlRenderer . content = content ;
159169 await this . htmlRenderer . renderQuery ( state , queryResult ) ;
160170 measureRender . finish ( ) ;
161171 }
162172
163- private addToolbar ( queryResult : QueryResult , content : HTMLElement ) {
173+ private addToolbar ( queryResult : QueryResult , content : HTMLDivElement ) {
164174 if ( this . query . queryLayoutOptions . hideToolbar ) {
165175 return ;
166176 }
167177
168178 const toolbar = createAndAppendElement ( 'div' , content ) ;
169179 toolbar . classList . add ( 'plugin-tasks-toolbar' ) ;
180+ this . addSearchBox ( toolbar , queryResult , content ) ;
170181 this . addCopyButton ( toolbar , queryResult ) ;
171182 }
172183
184+ private addSearchBox ( toolbar : HTMLDivElement , queryResult : QueryResult , content : HTMLDivElement ) {
185+ const label = createAndAppendElement ( 'label' , toolbar ) ;
186+ setIcon ( label , 'lucide-filter' ) ;
187+ const searchBox = createAndAppendElement ( 'input' , label ) ;
188+ searchBox . placeholder = 'Filter by description...' ;
189+ setTooltip ( searchBox , 'Filter results' ) ;
190+ 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+ }
198+
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+ }
207+
208+ lastChild . remove ( ) ;
209+ }
210+
211+ const filteredQueryResult = queryResult . applyFilter ( filter ! ) ;
212+ await this . renderQueryResult ( State . Warm , filteredQueryResult , content ) ;
213+ } ) ;
214+ }
215+
173216 private addCopyButton ( toolbar : HTMLDivElement , queryResult : QueryResult ) {
174217 const copyButton = createAndAppendElement ( 'button' , toolbar ) ;
175218 setIcon ( copyButton , 'lucide-copy' ) ;
0 commit comments