Skip to content

Commit d732bbb

Browse files
authored
Merge pull request #3128 from obsidian-tasks-group/add-show-hide-children
feat: Add experimental 'show/hide tree' instruction - disabled by default
2 parents d216a16 + 10cf7bd commit d732bbb

File tree

7 files changed

+224
-26
lines changed

7 files changed

+224
-26
lines changed

resources/sample_vaults/Tasks-Demo/Other Plugins/Dataview/Parent-Child relationships - Searches - Tasks.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
folder includes {{query.file.folder}}
99
sort by function task.lineNumber
1010
hide backlinks
11+
show tree
1112
```
1213

1314
Note: Tasks uses the due date in its default sort order,<br>so we override that by sorting by line number.
@@ -20,6 +21,7 @@ Note: Tasks uses the due date in its default sort order,<br>so we override that
2021
folder includes {{query.file.folder}}
2122
done
2223
hide backlinks
24+
show tree
2325
```
2426

2527
### 2.2 Fully completed
@@ -31,6 +33,7 @@ folder includes {{query.file.folder}}
3133
# There is no Tasks instruction for "fully done" yet
3234
done
3335
hide backlinks
36+
show tree
3437
```
3538

3639
### 2.3 Not completed
@@ -39,6 +42,7 @@ hide backlinks
3942
folder includes {{query.file.folder}}
4043
not done
4144
hide backlinks
45+
show tree
4246
```
4347

4448
### 2.4 Not fully completed
@@ -50,6 +54,7 @@ folder includes {{query.file.folder}}
5054
# There is no Tasks instruction for "not fully done" yet
5155
not done
5256
hide backlinks
57+
show tree
5358
```
5459

5560
## 3 Sorting
@@ -62,6 +67,7 @@ hide backlinks
6267
folder includes {{query.file.folder}}
6368
sort by function task.lineNumber
6469
hide backlinks
70+
show tree
6571
```
6672

6773
### 3.2 Reverse Order
@@ -70,6 +76,7 @@ hide backlinks
7076
folder includes {{query.file.folder}}
7177
sort by function reverse task.lineNumber
7278
hide backlinks
79+
show tree
7380
```
7481

7582
## 4 Grouping
@@ -83,6 +90,7 @@ folder includes {{query.file.folder}}
8390
done
8491
group by heading
8592
hide backlinks
93+
show tree
8694
```
8795

8896
### 4.2 Fully completed
@@ -95,6 +103,7 @@ folder includes {{query.file.folder}}
95103
done
96104
group by heading
97105
hide backlinks
106+
show tree
98107
```
99108

100109
### 4.3 Not completed
@@ -104,6 +113,7 @@ folder includes {{query.file.folder}}
104113
not done
105114
group by heading
106115
hide backlinks
116+
show tree
107117
```
108118

109119
### 4.4 Not fully completed
@@ -116,4 +126,5 @@ folder includes {{query.file.folder}}
116126
not done
117127
group by heading
118128
hide backlinks
129+
show tree
119130
```

src/Layout/QueryLayoutOptions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export class QueryLayoutOptions {
99
hideBacklinks: boolean = false;
1010
hideEditButton: boolean = false;
1111
hideUrgency: boolean = true;
12+
hideTree: boolean = true; // WARNING: undocumented, and not yet ready for release.
1213
shortMode: boolean = false;
1314
explainQuery: boolean = false;
1415
}

src/Query/Query.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ export class Query implements IQuery {
3636
private _ignoreGlobalQuery: boolean = false;
3737

3838
private readonly hideOptionsRegexp =
39-
/^(hide|show) (task count|backlink|priority|cancelled date|created date|start date|scheduled date|done date|due date|recurrence rule|edit button|postpone button|urgency|tags|depends on|id|on completion)/i;
39+
/^(hide|show) (task count|backlink|priority|cancelled date|created date|start date|scheduled date|done date|due date|recurrence rule|edit button|postpone button|urgency|tags|depends on|id|on completion|tree)/i;
4040
private readonly shortModeRegexp = /^short/i;
4141
private readonly fullModeRegexp = /^full/i;
4242
private readonly explainQueryRegexp = /^explain/i;
@@ -309,6 +309,10 @@ ${statement.explainStatement(' ')}
309309
const option = hideOptionsMatch[2].toLowerCase();
310310

311311
switch (option) {
312+
case 'tree':
313+
// WARNING: undocumented, and not yet ready for release.
314+
this._queryLayoutOptions.hideTree = hide;
315+
break;
312316
case 'task count':
313317
this._queryLayoutOptions.hideTaskCount = hide;
314318
break;

src/Renderer/QueryResultsRenderer.ts

Lines changed: 64 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -220,28 +220,36 @@ export class QueryResultsRenderer {
220220
});
221221

222222
for (const [taskIndex, task] of tasks.entries()) {
223-
if (this.alreadyRendered(task, renderedTasks)) {
224-
continue;
225-
}
226-
227-
if (this.willBeRenderedLater(task, renderedTasks, tasks)) {
228-
continue;
229-
}
230-
231-
const listItem = await this.addTaskOrListItem(
232-
taskList,
233-
taskLineRenderer,
234-
task,
235-
taskIndex,
236-
queryRendererParameters,
237-
);
238-
renderedTasks.add(task);
239-
240-
if (task.children.length > 0) {
241-
await this.createTaskList(task.children, listItem, queryRendererParameters, renderedTasks);
242-
task.children.forEach((childTask) => {
243-
renderedTasks.add(childTask);
244-
});
223+
if (this.query.queryLayoutOptions.hideTree) {
224+
/* Old-style rendering of tasks:
225+
* - What is rendered:
226+
* - Only task lines that match the query are rendered, as a flat list
227+
* - The order that lines are rendered:
228+
* - Tasks are rendered in the order specified in 'sort by' instructions and default sort order.
229+
*/
230+
if (task instanceof Task) {
231+
await this.addTask(taskList, taskLineRenderer, task, taskIndex, queryRendererParameters);
232+
}
233+
} else {
234+
/* New-style rendering of tasks:
235+
* - What is rendered:
236+
* - Task lines that match the query are rendered, as a tree.
237+
* - Currently, all child tasks and list items of the found tasks are shown,
238+
* including any child tasks that did not match the query.
239+
* - The order that lines are rendered:
240+
* - The top-level/outermost tasks are sorted in the order specified in 'sort by'
241+
* instructions and default sort order.
242+
* - Child tasks (and list items) are shown in their original order in their Markdown file.
243+
*/
244+
await this.addTaskOrListItemAndChildren(
245+
taskList,
246+
taskLineRenderer,
247+
task,
248+
taskIndex,
249+
queryRendererParameters,
250+
tasks,
251+
renderedTasks,
252+
);
245253
}
246254
}
247255

@@ -274,6 +282,40 @@ export class QueryResultsRenderer {
274282
return renderedTasks.has(task);
275283
}
276284

285+
private async addTaskOrListItemAndChildren(
286+
taskList: HTMLUListElement,
287+
taskLineRenderer: TaskLineRenderer,
288+
task: ListItem,
289+
taskIndex: number,
290+
queryRendererParameters: QueryRendererParameters,
291+
tasks: ListItem[],
292+
renderedTasks: Set<ListItem>,
293+
) {
294+
if (this.alreadyRendered(task, renderedTasks)) {
295+
return;
296+
}
297+
298+
if (this.willBeRenderedLater(task, renderedTasks, tasks)) {
299+
return;
300+
}
301+
302+
const listItem = await this.addTaskOrListItem(
303+
taskList,
304+
taskLineRenderer,
305+
task,
306+
taskIndex,
307+
queryRendererParameters,
308+
);
309+
renderedTasks.add(task);
310+
311+
if (task.children.length > 0) {
312+
await this.createTaskList(task.children, listItem, queryRendererParameters, renderedTasks);
313+
task.children.forEach((childTask) => {
314+
renderedTasks.add(childTask);
315+
});
316+
}
317+
}
318+
277319
private async addTaskOrListItem(
278320
taskList: HTMLUListElement,
279321
taskLineRenderer: TaskLineRenderer,

tests/Query/Query.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,7 @@ describe('Query parsing', () => {
466466
'hide start date',
467467
'hide tags',
468468
'hide task count',
469+
'hide tree',
469470
'hide urgency',
470471
'ignore global query',
471472
'limit 42',
@@ -489,6 +490,7 @@ describe('Query parsing', () => {
489490
'show start date',
490491
'show tags',
491492
'show task count',
493+
'show tree',
492494
'show urgency',
493495
];
494496
test.concurrent.each<string>(filters)('recognises %j', (filter) => {
@@ -1302,6 +1304,11 @@ describe('Query', () => {
13021304
const query = new Query('hide on completion');
13031305
expect(query.taskLayoutOptions.isShown(TaskLayoutComponent.OnCompletion)).toEqual(false);
13041306
});
1307+
1308+
it('should hide "tree" by default', () => {
1309+
const query = new Query('');
1310+
expect(query.queryLayoutOptions.hideTree).toEqual(true);
1311+
});
13051312
});
13061313

13071314
describe('sorting', () => {
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<div>
2+
<ul class="contains-task-list plugin-tasks-query-result tasks-layout-hide-urgency">
3+
<li
4+
class="task-list-item plugin-tasks-list-item"
5+
data-task-priority="normal"
6+
data-task=""
7+
data-line="0"
8+
data-task-status-name="Todo"
9+
data-task-status-type="TODO">
10+
<input class="task-list-item-checkbox" type="checkbox" title="Right-click for options" data-line="0" />
11+
<span class="tasks-list-text">
12+
<span class="task-description"><span>parent 1</span></span>
13+
</span>
14+
<span class="task-extras">
15+
<span class="tasks-backlink">
16+
(
17+
<a rel="noopener" target="_blank" class="internal-link">inheritance_rendering_sample</a>
18+
)
19+
</span>
20+
<a class="tasks-edit" title="Edit task" href="#"></a>
21+
</span>
22+
</li>
23+
<li
24+
class="task-list-item plugin-tasks-list-item"
25+
data-task-priority="normal"
26+
data-task=""
27+
data-line="1"
28+
data-task-status-name="Todo"
29+
data-task-status-type="TODO">
30+
<input class="task-list-item-checkbox" type="checkbox" title="Right-click for options" data-line="1" />
31+
<span class="tasks-list-text">
32+
<span class="task-description"><span>child 1</span></span>
33+
</span>
34+
<span class="task-extras">
35+
<span class="tasks-backlink">
36+
(
37+
<a rel="noopener" target="_blank" class="internal-link">inheritance_rendering_sample</a>
38+
)
39+
</span>
40+
<a class="tasks-edit" title="Edit task" href="#"></a>
41+
</span>
42+
</li>
43+
<li
44+
class="task-list-item plugin-tasks-list-item"
45+
data-task-priority="normal"
46+
data-task=""
47+
data-line="2"
48+
data-task-status-name="Todo"
49+
data-task-status-type="TODO">
50+
<input class="task-list-item-checkbox" type="checkbox" title="Right-click for options" data-line="2" />
51+
<span class="tasks-list-text">
52+
<span class="task-description"><span>grandchild 1</span></span>
53+
</span>
54+
<span class="task-extras">
55+
<span class="tasks-backlink">
56+
(
57+
<a rel="noopener" target="_blank" class="internal-link">inheritance_rendering_sample</a>
58+
)
59+
</span>
60+
<a class="tasks-edit" title="Edit task" href="#"></a>
61+
</span>
62+
</li>
63+
<li
64+
class="task-list-item plugin-tasks-list-item"
65+
data-task-priority="normal"
66+
data-task=""
67+
data-line="3"
68+
data-task-status-name="Todo"
69+
data-task-status-type="TODO">
70+
<input class="task-list-item-checkbox" type="checkbox" title="Right-click for options" data-line="3" />
71+
<span class="tasks-list-text">
72+
<span class="task-description"><span>child 2</span></span>
73+
</span>
74+
<span class="task-extras">
75+
<span class="tasks-backlink">
76+
(
77+
<a rel="noopener" target="_blank" class="internal-link">inheritance_rendering_sample</a>
78+
)
79+
</span>
80+
<a class="tasks-edit" title="Edit task" href="#"></a>
81+
</span>
82+
</li>
83+
<li
84+
class="task-list-item plugin-tasks-list-item"
85+
data-task-priority="normal"
86+
data-task=""
87+
data-line="4"
88+
data-task-status-name="Todo"
89+
data-task-status-type="TODO">
90+
<input class="task-list-item-checkbox" type="checkbox" title="Right-click for options" data-line="4" />
91+
<span class="tasks-list-text">
92+
<span class="task-description"><span>grand grand child</span></span>
93+
</span>
94+
<span class="task-extras">
95+
<span class="tasks-backlink">
96+
(
97+
<a rel="noopener" target="_blank" class="internal-link">inheritance_rendering_sample</a>
98+
)
99+
</span>
100+
<a class="tasks-edit" title="Edit task" href="#"></a>
101+
</span>
102+
</li>
103+
<li
104+
class="task-list-item plugin-tasks-list-item"
105+
data-task-priority="normal"
106+
data-task=""
107+
data-line="5"
108+
data-task-status-name="Todo"
109+
data-task-status-type="TODO">
110+
<input class="task-list-item-checkbox" type="checkbox" title="Right-click for options" data-line="5" />
111+
<span class="tasks-list-text">
112+
<span class="task-description"><span>parent 2</span></span>
113+
</span>
114+
<span class="task-extras">
115+
<span class="tasks-backlink">
116+
(
117+
<a rel="noopener" target="_blank" class="internal-link">inheritance_rendering_sample</a>
118+
)
119+
</span>
120+
<a class="tasks-edit" title="Edit task" href="#"></a>
121+
</span>
122+
</li>
123+
</ul>
124+
<div class="task-count">6 tasks</div>
125+
</div>

tests/Renderer/QueryResultsRenderer.test.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,19 +55,27 @@ describe('QueryResultsRenderer tests', () => {
5555
await verifyRenderedTasksHTML(allTasks);
5656
});
5757

58+
const showTree = 'show tree\n';
59+
const hideTree = 'hide tree\n';
60+
61+
it('parent-child items hidden', async () => {
62+
const allTasks = readTasksFromSimulatedFile(inheritance_rendering_sample);
63+
await verifyRenderedTasksHTML(allTasks, hideTree + 'sort by function task.lineNumber');
64+
});
65+
5866
it('parent-child items', async () => {
5967
const allTasks = readTasksFromSimulatedFile(inheritance_rendering_sample);
60-
await verifyRenderedTasksHTML(allTasks, 'sort by function task.lineNumber');
68+
await verifyRenderedTasksHTML(allTasks, showTree + 'sort by function task.lineNumber');
6169
});
6270

6371
it('parent-child items reverse sorted', async () => {
6472
const allTasks = readTasksFromSimulatedFile(inheritance_rendering_sample);
65-
await verifyRenderedTasksHTML(allTasks, 'sort by function reverse task.lineNumber');
73+
await verifyRenderedTasksHTML(allTasks, showTree + 'sort by function reverse task.lineNumber');
6674
});
6775

6876
it('should render tasks without their parents', async () => {
6977
// example chosen to match subtasks whose parents do not match the query
7078
const allTasks = readTasksFromSimulatedFile(inheritance_task_2listitem_3task);
71-
await verifyRenderedTasksHTML(allTasks, 'description includes grandchild');
79+
await verifyRenderedTasksHTML(allTasks, showTree + 'description includes grandchild');
7280
});
7381
});

0 commit comments

Comments
 (0)