Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 23 additions & 13 deletions src/Renderer/QueryResultsRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export class QueryResultsRenderer {
protected queryType: string; // whilst there is only one query type, there is no point logging this value
public queryResult: QueryResult;
public filteredQueryResult: QueryResult;
public filterString: string = '';

constructor(
className: string,
Expand Down Expand Up @@ -157,14 +158,14 @@ export class QueryResultsRenderer {
public async render(state: State, tasks: Task[], content: HTMLDivElement) {
this.performSearch(tasks);
this.addToolbar(content);
await this.renderQueryResult(state, this.queryResult, content);
await this.renderQueryResult(state, this.filteredQueryResult, content);
}

private performSearch(tasks: Task[]) {
const measureSearch = new PerformanceTracker(`Search: ${this.query.queryId} - ${this.filePath}`);
measureSearch.start();
this.queryResult = this.query.applyQueryToTasks(tasks);
this.filteredQueryResult = this.queryResult;
this.filterResults();
measureSearch.finish();
}

Expand All @@ -191,24 +192,21 @@ export class QueryResultsRenderer {
const label = createAndAppendElement('label', toolbar);
setIcon(label, 'lucide-filter');
const searchBox = createAndAppendElement('input', label);
searchBox.value = this.filterString;
searchBox.placeholder = 'Filter by description...';
setTooltip(searchBox, 'Filter results');
searchBox.addEventListener('input', async () => {
const filterString = searchBox.value;
await this.applySearchBoxFilter(filterString, content);
await this.applySearchBoxFilterAndRerender(filterString, content);
});
}

public async applySearchBoxFilter(filterString: string, content: HTMLDivElement) {
const { filter, error } = new DescriptionField().createFilterOrErrorMessage(
'description includes ' + filterString,
);
if (error) {
new Notice('error searching for ' + filterString + ': ' + error);
return;
}
public async applySearchBoxFilterAndRerender(filterString: string, content: HTMLDivElement) {
this.filterString = filterString;

this.filterResults();

// We want to retain the Toolbar, to not lose the search string.
// We want to retain the Toolbar, to not lose the cursor position in the search string.
// But we need to delete any pre-existing headings, tasks and task count.
// The following while loop relies on the Toolbar being the first element.
while (content.firstElementChild !== content.lastElementChild) {
Expand All @@ -220,10 +218,22 @@ export class QueryResultsRenderer {
lastChild.remove();
}

this.filteredQueryResult = this.queryResult.applyFilter(filter!);
await this.renderQueryResult(State.Warm, this.filteredQueryResult, content);
}

private filterResults() {
const { filter, error } = new DescriptionField().createFilterOrErrorMessage(
'description includes ' + this.filterString,
);
if (error) {
// If we can't create a filter, just silently show all the matching tasks
this.filteredQueryResult = this.queryResult;
return;
}

this.filteredQueryResult = this.queryResult.applyFilter(filter!);
}

private addCopyButton(toolbar: HTMLDivElement) {
const copyButton = createAndAppendElement('button', toolbar);
setIcon(copyButton, 'lucide-copy');
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<h2>Initial results:</h2>

<p>Results filter: ''</p>
<!--
- [ ] parent 📅 2025-12-01
- [ ] child 🆔 childID
Expand Down Expand Up @@ -70,6 +71,7 @@ <h2>Initial results:</h2>
</div>
<h2>Check that urgency is shown by global query:</h2>

<p>Results filter: ''</p>
<!--
- [ ] parent 📅 2025-12-01
- [ ] child 🆔 childID
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<h2>Initial results:</h2>

<p>Results filter: ''</p>
<!--
- [ ] parent 📅 2025-12-01
- [ ] child 🆔 childID
Expand Down Expand Up @@ -70,6 +71,7 @@ <h2>Initial results:</h2>
</div>
<h2>Check that due date is hidden by global query:</h2>

<p>Results filter: ''</p>
<!--
- [ ] parent 📅 2025-12-01
- [ ] child 🆔 childID
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<h2>Initial results:</h2>

<p>Results filter: ''</p>
<!--
- [ ] parent 📅 2025-12-01
- [ ] child 🆔 childID
-->

<div>
<div class="plugin-tasks-toolbar">
<label test-icon="lucide-filter">
<input placeholder="Filter by description..." test-tooltip="Filter results" />
</label>
<button test-icon="lucide-copy" test-tooltip="Copy results"></button>
</div>
<ul class="contains-task-list plugin-tasks-query-result tasks-layout-hide-urgency">
<li
class="task-list-item plugin-tasks-list-item"
data-task-due="today"
data-task-priority="normal"
data-task=""
data-line="0"
data-task-status-name="Todo"
data-task-status-type="TODO">
<input class="task-list-item-checkbox" type="checkbox" data-line="0" />
<span class="tasks-list-text">
<span class="task-description"><span>parent</span></span>
<span class="task-due" data-task-due="today" title="Click to edit due date, Right-click for more options">
<span>📅 2025-12-01</span>
</span>
</span>
<span class="task-extras">
<span class="tasks-backlink">
(
<a rel="noopener" target="_blank" class="internal-link">/</a>
)
</span>
<a class="tasks-edit" title="Edit task" href="#"></a>
<a class="tasks-postpone" title="ℹ️ Due tomorrow, on Tue 2nd Dec (right-click for more options)"></a>
</span>
</li>
<li
class="task-list-item plugin-tasks-list-item"
data-task-priority="normal"
data-task=""
data-line="1"
data-task-status-name="Todo"
data-task-status-type="TODO">
<input class="task-list-item-checkbox" type="checkbox" data-line="1" />
<span class="tasks-list-text">
<span class="task-description"><span>child</span></span>
<span class="task-id"><span>🆔 childID</span></span>
</span>
<span class="task-extras">
<span class="tasks-backlink">
(
<a rel="noopener" target="_blank" class="internal-link">/</a>
)
</span>
<a class="tasks-edit" title="Edit task" href="#"></a>
</span>
</li>
</ul>
<div class="task-count">2 tasks</div>
</div>
<h2>Filtered results (parent):</h2>

<p>Results filter: 'parent'</p>
<!--
- [ ] parent 📅 2025-12-01
- [ ] child 🆔 childID
-->

<div>
<div class="plugin-tasks-toolbar">
<label test-icon="lucide-filter">
<input placeholder="Filter by description..." test-tooltip="Filter results" />
</label>
<button test-icon="lucide-copy" test-tooltip="Copy results"></button>
</div>
<ul class="contains-task-list plugin-tasks-query-result tasks-layout-hide-urgency">
<li
class="task-list-item plugin-tasks-list-item"
data-task-due="today"
data-task-priority="normal"
data-task=""
data-line="0"
data-task-status-name="Todo"
data-task-status-type="TODO">
<input class="task-list-item-checkbox" type="checkbox" data-line="0" />
<span class="tasks-list-text">
<span class="task-description"><span>parent</span></span>
<span class="task-due" data-task-due="today" title="Click to edit due date, Right-click for more options">
<span>📅 2025-12-01</span>
</span>
</span>
<span class="task-extras">
<span class="tasks-backlink">
(
<a rel="noopener" target="_blank" class="internal-link">/</a>
)
</span>
<a class="tasks-edit" title="Edit task" href="#"></a>
<a class="tasks-postpone" title="ℹ️ Due tomorrow, on Tue 2nd Dec (right-click for more options)"></a>
</span>
</li>
</ul>
<div class="task-count">1 task</div>
</div>
<h2>Filtered results after editing Global Query:</h2>

<p>Results filter: 'parent'</p>
<!--
- [ ] parent 📅 2025-12-01
- [ ] child 🆔 childID
-->

<div>
<div class="plugin-tasks-toolbar">
<label test-icon="lucide-filter">
<input placeholder="Filter by description..." test-tooltip="Filter results" />
</label>
<button test-icon="lucide-copy" test-tooltip="Copy results"></button>
</div>
<ul class="contains-task-list plugin-tasks-query-result tasks-layout-hide-urgency">
<li
class="task-list-item plugin-tasks-list-item"
data-task-due="today"
data-task-priority="normal"
data-task=""
data-line="0"
data-task-status-name="Todo"
data-task-status-type="TODO">
<input class="task-list-item-checkbox" type="checkbox" data-line="0" />
<span class="tasks-list-text">
<span class="task-description"><span>parent</span></span>
<span class="task-due" data-task-due="today" title="Click to edit due date, Right-click for more options">
<span>📅 2025-12-01</span>
</span>
</span>
<span class="task-extras">
<span class="tasks-backlink">
(
<a rel="noopener" target="_blank" class="internal-link">/</a>
)
</span>
<a class="tasks-edit" title="Edit task" href="#"></a>
<a class="tasks-postpone" title="ℹ️ Due tomorrow, on Tue 2nd Dec (right-click for more options)"></a>
</span>
</li>
</ul>
<div class="task-count">1 task</div>
</div>
40 changes: 31 additions & 9 deletions tests/Renderer/QueryResultsRenderer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ describe('QueryResultsRenderer - accessing results', () => {

await renderer.render(State.Warm, twoTasks, document.createElement('div'));

await renderer.applySearchBoxFilter('another', document.createElement('div'));
await renderer.applySearchBoxFilterAndRerender('another', document.createElement('div'));

expect(renderer.queryResult.totalTasksCount).toEqual(2);
expect(renderer.filteredQueryResult.totalTasksCount).toEqual(1);
Expand Down Expand Up @@ -171,19 +171,25 @@ class RendererStoryboard {
}

/**
* This simulates QueryRenderer.renderResults()
* Returns the prettified rendered HTML, to allow 'expect' calls to be added.
* @param description
*/
public async addFrame(description: string): Promise<string> {
this.output += `<h2>${description}:</h2>\n\n`;

public async renderAndAddFrame(description: string) {
const container = document.createElement('div');
await this.renderer.render(State.Warm, this.allTasks, container);

return this.addFrame(description, container);
}

public addFrame(description: string, container: HTMLDivElement) {
this.output += `<h2>${description}:</h2>\n\n`;
this.output += `<p>Results filter: '${this.renderer.filterString}'</p>\n`;

const { tasksAsMarkdown, prettyHTML } = tasksMarkdownAndPrettifiedHtml(container, this.allTasks);
this.output += tasksAsMarkdown + prettyHTML;

return prettyHTML;
return { prettyHTML, container };
}

public verify() {
Expand All @@ -203,15 +209,15 @@ describe('QueryResultsRenderer - sequences', () => {
const dueDate = '📅 2025-12-01';

{
const prettyHTML = await storyboard.addFrame('Initial results');
const { prettyHTML } = await storyboard.renderAndAddFrame('Initial results');
expect(prettyHTML).toContain(dueDate);
}

GlobalQuery.getInstance().set('hide due date');
storyboard.renderer.rereadQueryFromFile();

{
const prettyHTML = await storyboard.addFrame('Check that due date is hidden by global query');
const { prettyHTML } = await storyboard.renderAndAddFrame('Check that due date is hidden by global query');
expect(prettyHTML).not.toContain(dueDate);
}

Expand All @@ -224,18 +230,34 @@ describe('QueryResultsRenderer - sequences', () => {
const urgency = '<span class="tasks-urgency">10.75</span>';

{
const prettyHTML = await storyboard.addFrame('Initial results');
const { prettyHTML } = await storyboard.renderAndAddFrame('Initial results');
expect(prettyHTML).not.toContain(urgency);
}

GlobalQuery.getInstance().set('show urgency');
storyboard.renderer.rereadQueryFromFile();

{
const prettyHTML = await storyboard.addFrame('Check that urgency is shown by global query');
const { prettyHTML } = await storyboard.renderAndAddFrame('Check that urgency is shown by global query');
expect(prettyHTML).toContain(urgency);
}

storyboard.verify();
});

it('rerendered results retain the filter', async () => {
const storyboard = new RendererStoryboard('', parentAndChild);

const { container } = await storyboard.renderAndAddFrame('Initial results');

await storyboard.renderer.applySearchBoxFilterAndRerender('parent', container);
storyboard.addFrame('Filtered results (parent)', container);

GlobalQuery.getInstance().set('sort by function reverse task.description.length');
storyboard.renderer.rereadQueryFromFile();

await storyboard.renderAndAddFrame('Filtered results after editing Global Query');

storyboard.verify();
});
});