Skip to content

Commit b783741

Browse files
authored
Merge pull request #3463 from obsidian-tasks-group/trigger-search-result-redraws
feat: Editing any Includes setting redraws open Tasks searches
2 parents 5889789 + c3ebe9d commit b783741

File tree

8 files changed

+175
-12
lines changed

8 files changed

+175
-12
lines changed

resources/sample_vaults/Tasks-Demo/How To/Reuse instructions across the vault.md

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
---
22
TQ_explain: false
33
TQ_extra_instructions: |-
4+
(filename includes Tasks Format) OR (tag includes #context)
45
ignore global query
5-
limit 10
6+
limit 20
67
---
78
# Reuse instructions across the vault
89

@@ -16,6 +17,12 @@ explain: `INPUT[toggle:TQ_explain]`
1617

1718
(assuming there is not one called `xxxx`!)
1819

20+
````text
21+
```tasks
22+
include xxxx
23+
```
24+
````
25+
1926
```tasks
2027
include xxxx
2128
```
@@ -24,13 +31,25 @@ include xxxx
2431

2532
explain: `INPUT[toggle:TQ_explain]`
2633

34+
````text
35+
```tasks
36+
```
37+
````
38+
2739
```tasks
2840
```
2941

3042
## Hide all the fields
3143

3244
explain: `INPUT[toggle:TQ_explain]`
3345

46+
````text
47+
```tasks
48+
include hide_all_dates
49+
include hide_other_fields
50+
```
51+
````
52+
3453
```tasks
3554
include hide_all_dates
3655
include hide_other_fields
@@ -40,24 +59,59 @@ include hide_other_fields
4059

4160
explain: `INPUT[toggle:TQ_explain]`
4261

62+
````text
63+
```tasks
64+
include hide_buttons
65+
```
66+
````
67+
4368
```tasks
4469
include hide_buttons
4570
```
4671

47-
## Hide everything, including tags
72+
## Show only the description and any tags
4873

4974
explain: `INPUT[toggle:TQ_explain]`
5075

76+
````text
77+
```tasks
78+
include just_the_description_and_tags
79+
```
80+
````
81+
5182
```tasks
5283
include just_the_description_and_tags
5384
```
5485

86+
## Just show the description, without tags
87+
88+
explain: `INPUT[toggle:TQ_explain]`
89+
90+
````text
91+
```tasks
92+
include just_the_description
93+
```
94+
````
95+
96+
```tasks
97+
include just_the_description
98+
```
99+
55100
## Advanced use: return a function, that takes a parameter from the query source
56101

57102
### Has context 'home' - and group by the Include text - version 1
58103

59104
explain: `INPUT[toggle:TQ_explain]`
60105

106+
````text
107+
```tasks
108+
# For debug/explanatory purposes, show the source of the Include as a group name:
109+
group by function const x = "{{includes.filter_by_context}}"; return x
110+
111+
{{includes.filter_by_context}}('home')
112+
```
113+
````
114+
61115
```tasks
62116
# For debug/explanatory purposes, show the source of the Include as a group name:
63117
group by function const x = "{{includes.filter_by_context}}"; return x
@@ -69,6 +123,16 @@ group by function const x = "{{includes.filter_by_context}}"; return x
69123

70124
explain: `INPUT[toggle:TQ_explain]`
71125

126+
````text
127+
```tasks
128+
# For debug/explanatory purposes, show the source of the Include as a group name:
129+
group by function const x = "{{includes.extract_contexts_1}}"; return x
130+
131+
# includes.extract_contexts_1 value needs to be surrounded by parentheses ():
132+
filter by function return ({{includes.extract_contexts_1}})('home')
133+
```
134+
````
135+
72136
```tasks
73137
# For debug/explanatory purposes, show the source of the Include as a group name:
74138
group by function const x = "{{includes.extract_contexts_1}}"; return x
@@ -81,6 +145,16 @@ filter by function return ({{includes.extract_contexts_1}})('home')
81145

82146
explain: `INPUT[toggle:TQ_explain]`
83147

148+
````text
149+
```tasks
150+
# For debug/explanatory purposes, show the source of the Include as a group name:
151+
group by function const x = "{{includes.extract_contexts_2}}"; return x
152+
153+
# includes.extract_contexts_1 value has the parentheses, to simplify use:
154+
filter by function {{includes.extract_contexts_2}}('home')
155+
```
156+
````
157+
84158
```tasks
85159
# For debug/explanatory purposes, show the source of the Include as a group name:
86160
group by function const x = "{{includes.extract_contexts_2}}"; return x

src/Config/IncludesSettingsUI.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Setting, TextAreaComponent } from 'obsidian';
22
import type TasksPlugin from '../main';
3+
import type { TasksEvents } from '../Obsidian/TasksEvents';
34
import { IncludesSettingsService, type RenamesInProgress } from './IncludesSettingsService';
45
import { type IncludesMap, type Settings, getSettings, updateSettings } from './Settings';
56

@@ -12,15 +13,18 @@ type RefreshViewCallback = () => void;
1213
*/
1314
export class IncludesSettingsUI {
1415
private readonly plugin: TasksPlugin;
16+
private readonly events: TasksEvents;
1517
private readonly includesSettingsService = new IncludesSettingsService();
1618
private readonly nameFields: Map<string, { inputEl: HTMLInputElement; originalKey: string }> = new Map();
1719

1820
/**
1921
* Creates a new instance of IncludesSettingsUI
2022
* @param plugin The Tasks plugin instance
23+
* @param events The plugin's events object
2124
*/
22-
constructor(plugin: TasksPlugin) {
25+
constructor(plugin: TasksPlugin, events: TasksEvents) {
2326
this.plugin = plugin;
27+
this.events = events;
2428
}
2529

2630
/**
@@ -219,5 +223,7 @@ export class IncludesSettingsUI {
219223
if (refreshView) {
220224
refreshView();
221225
}
226+
227+
this.events.triggerReloadOpenSearchResults();
222228
}
223229
}

src/Config/SettingsTab.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Status } from '../Statuses/Status';
66
import type { StatusCollection } from '../Statuses/StatusCollection';
77
import { createStatusRegistryReport } from '../Statuses/StatusRegistryReport';
88
import { i18n } from '../i18n/i18n';
9+
import type { TasksEvents } from '../Obsidian/TasksEvents';
910
import * as Themes from './Themes';
1011
import {
1112
type HeadingState,
@@ -34,11 +35,11 @@ export class SettingsTab extends PluginSettingTab {
3435
private readonly plugin: TasksPlugin;
3536
private readonly includesSettingsUI;
3637

37-
constructor({ plugin }: { plugin: TasksPlugin }) {
38+
constructor({ plugin, events }: { plugin: TasksPlugin; events: TasksEvents }) {
3839
super(plugin.app, plugin);
3940

4041
this.plugin = plugin;
41-
this.includesSettingsUI = new IncludesSettingsUI(plugin);
42+
this.includesSettingsUI = new IncludesSettingsUI(plugin, events);
4243
}
4344

4445
private static createFragmentWithHTML = (html: string) =>

src/Obsidian/TasksEvents.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { State } from './Cache';
77
enum Event {
88
CacheUpdate = 'obsidian-tasks-plugin:cache-update',
99
RequestCacheUpdate = 'obsidian-tasks-plugin:request-cache-update',
10+
ReloadOpenSearchResults = 'obsidian-tasks-plugin:reload-open-search-results',
1011
}
1112

1213
interface CacheUpdateData {
@@ -22,30 +23,52 @@ export class TasksEvents {
2223
this.obsidianEvents = obsidianEvents;
2324
}
2425

26+
// ------------------------------------------------------------------------
27+
// CacheUpdate event
28+
2529
public onCacheUpdate(handler: (cacheData: CacheUpdateData) => void): EventRef {
2630
this.logger.debug('TasksEvents.onCacheUpdate()');
31+
const name = Event.CacheUpdate;
2732
// @ts-expect-error: error TS2345: Argument of type '(cacheData: CacheUpdateData) => void'
2833
// is not assignable to parameter of type '(...data: unknown[]) => unknown'.
29-
return this.obsidianEvents.on(Event.CacheUpdate, handler);
34+
return this.obsidianEvents.on(name, handler);
3035
}
3136

3237
public triggerCacheUpdate(cacheData: CacheUpdateData): void {
3338
this.logger.debug('TasksEvents.triggerCacheUpdate()');
3439
this.obsidianEvents.trigger(Event.CacheUpdate, cacheData);
3540
}
3641

42+
// ------------------------------------------------------------------------
43+
// RequestCacheUpdate event
44+
3745
public onRequestCacheUpdate(handler: (fn: (cacheData: CacheUpdateData) => void) => void): EventRef {
3846
this.logger.debug('TasksEvents.onRequestCacheUpdate()');
47+
const name = Event.RequestCacheUpdate;
3948
// @ts-expect-error: error TS2345: Argument of type '(cacheData: CacheUpdateData) => void'
4049
// is not assignable to parameter of type '(...data: unknown[]) => unknown'.
41-
return this.obsidianEvents.on(Event.RequestCacheUpdate, handler);
50+
return this.obsidianEvents.on(name, handler);
4251
}
4352

4453
public triggerRequestCacheUpdate(fn: (cacheData: CacheUpdateData) => void): void {
4554
this.logger.debug('TasksEvents.triggerRequestCacheUpdate()');
4655
this.obsidianEvents.trigger(Event.RequestCacheUpdate, fn);
4756
}
4857

58+
// ------------------------------------------------------------------------
59+
// ReloadOpenSearchResults event
60+
61+
public onReloadOpenSearchResults(handler: () => void): EventRef {
62+
this.logger.debug('TasksEvents.onReloadOpenSearchResults()');
63+
const name = Event.ReloadOpenSearchResults;
64+
return this.obsidianEvents.on(name, handler);
65+
}
66+
67+
public triggerReloadOpenSearchResults(): void {
68+
this.logger.debug('TasksEvents.triggerReloadOpenSearchResults()');
69+
this.obsidianEvents.trigger(Event.ReloadOpenSearchResults);
70+
}
71+
4972
public off(eventRef: EventRef): void {
5073
this.logger.debug('TasksEvents.off()');
5174
this.obsidianEvents.offref(eventRef);

src/Renderer/QueryRenderer.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import {
22
type CachedMetadata,
3+
type Debouncer,
34
type EventRef,
45
type MarkdownPostProcessorContext,
56
MarkdownRenderChild,
67
MarkdownRenderer,
78
type TAbstractFile,
89
TFile,
10+
debounce,
911
} from 'obsidian';
1012
import { App, Keymap } from 'obsidian';
1113
import { GlobalQuery } from '../Config/GlobalQuery';
@@ -21,6 +23,8 @@ import type { Task } from '../Task/Task';
2123
import { type BacklinksEventHandler, type EditButtonClickHandler, QueryResultsRenderer } from './QueryResultsRenderer';
2224
import { createAndAppendElement } from './TaskLineRenderer';
2325

26+
type RenderParams = { tasks: Task[]; state: State };
27+
2428
/**
2529
* `QueryRenderer` is responsible for rendering queries in Markdown code blocks
2630
* annotated with the 'tasks' processor.
@@ -90,12 +94,14 @@ class QueryRenderChild extends MarkdownRenderChild {
9094
private readonly events: TasksEvents;
9195

9296
private renderEventRef: EventRef | undefined;
97+
private reloadSearchResultsEventRef: EventRef | undefined;
9398
private queryReloadTimeout: NodeJS.Timeout | undefined;
9499

95100
private isCacheChangedSinceLastRedraw = false;
96101
private observer: IntersectionObserver | null = null;
97102

98103
private readonly queryResultsRenderer: QueryResultsRenderer;
104+
private readonly debouncedRenderFn: Debouncer<[RenderParams], void>;
99105

100106
constructor({
101107
app,
@@ -127,15 +133,19 @@ class QueryRenderChild extends MarkdownRenderChild {
127133
this.app = app;
128134
this.plugin = plugin;
129135
this.events = events;
136+
137+
this.debouncedRenderFn = debounce((params: RenderParams) => this.render(params), 300, true);
130138
}
131139

132140
onload() {
133141
this.queryResultsRenderer.query.debug('[render] QueryRenderChild.onload() entered');
134142

135143
// Process the current cache state:
136144
this.events.triggerRequestCacheUpdate(this.render.bind(this));
137-
// Listen to future cache changes:
145+
146+
// Listen to future changes:
138147
this.renderEventRef = this.events.onCacheUpdate(this.render.bind(this));
148+
this.reloadSearchResultsEventRef = this.events.onReloadOpenSearchResults(this.rereadQueryFromFile.bind(this));
139149

140150
this.reloadQueryAtMidnight();
141151

@@ -221,10 +231,17 @@ class QueryRenderChild extends MarkdownRenderChild {
221231
this.events.off(this.renderEventRef);
222232
}
223233

234+
if (this.reloadSearchResultsEventRef !== undefined) {
235+
this.events.off(this.reloadSearchResultsEventRef);
236+
}
237+
224238
if (this.queryReloadTimeout !== undefined) {
225239
clearTimeout(this.queryReloadTimeout);
226240
}
227241

242+
// Cancel any pending debounced renders
243+
this.debouncedRenderFn.cancel();
244+
228245
this.observer?.disconnect();
229246
this.observer = null;
230247
}
@@ -256,7 +273,11 @@ class QueryRenderChild extends MarkdownRenderChild {
256273
}, millisecondsToMidnight + 1000); // Add buffer to be sure to run after midnight.
257274
}
258275

259-
private async render({ tasks, state }: { tasks: Task[]; state: State }) {
276+
private debouncedRender(params: RenderParams): void {
277+
this.debouncedRenderFn(params);
278+
}
279+
280+
private async render({ tasks, state }: RenderParams) {
260281
// We got here because the Cache reported a change in at least one task in the vault.
261282
// So note that any results we have already drawn are now out-of-date:
262283
this.isCacheChangedSinceLastRedraw = true;
@@ -304,6 +325,12 @@ class QueryRenderChild extends MarkdownRenderChild {
304325

305326
this.containerEl.firstChild?.replaceWith(content);
306327
}
328+
329+
private rereadQueryFromFile() {
330+
this.queryResultsRenderer.rereadQueryFromFile();
331+
this.isCacheChangedSinceLastRedraw = true;
332+
this.debouncedRender({ tasks: this.plugin.getTasks(), state: this.plugin.getState() });
333+
}
307334
}
308335

309336
function createEditTaskPencilClickHandler(app: App): EditButtonClickHandler {

src/Renderer/QueryResultsRenderer.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,20 @@ export class QueryResultsRenderer {
112112
*/
113113
public setTasksFile(newFile: TasksFile) {
114114
this._tasksFile = newFile;
115+
this.rereadQueryFromFile();
116+
}
117+
118+
/**
119+
* Reads the query from the source file and tasks file.
120+
*
121+
* This is for when some change in the vault invalidates the current
122+
* Query object, and so it needs to be reloaded.
123+
*
124+
* For example, the user edited their Tasks plugin settings in some
125+
* way that changes how the query is interpreted, such as changing an
126+
* 'includes' definition.
127+
*/
128+
public rereadQueryFromFile() {
115129
this.query = this.makeQueryFromSourceAndTasksFile();
116130
}
117131

0 commit comments

Comments
 (0)