Skip to content

Commit 7bf7ae3

Browse files
authored
Merge pull request #3118 from ilandikov/feat-render-parent-child
feat: add experimental rendering of child tasks and list items
2 parents 0128578 + 244ba7b commit 7bf7ae3

5 files changed

+400
-89
lines changed

src/Renderer/QueryResultsRenderer.ts

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Component, TFile } from 'obsidian';
22
import { GlobalFilter } from '../Config/GlobalFilter';
33
import { GlobalQuery } from '../Config/GlobalQuery';
4+
import { postponeButtonTitle, shouldShowPostponeButton } from '../DateTime/Postponer';
45
import type { IQuery } from '../IQuery';
56
import { QueryLayout } from '../Layout/QueryLayout';
67
import { TaskLayout } from '../Layout/TaskLayout';
@@ -10,9 +11,9 @@ import { State } from '../Obsidian/Cache';
1011
import type { GroupDisplayHeading } from '../Query/Group/GroupDisplayHeading';
1112
import type { TaskGroups } from '../Query/Group/TaskGroups';
1213
import type { QueryResult } from '../Query/QueryResult';
13-
import { postponeButtonTitle, shouldShowPostponeButton } from '../DateTime/Postponer';
1414
import type { TasksFile } from '../Scripting/TasksFile';
15-
import type { Task } from '../Task/Task';
15+
import type { ListItem } from '../Task/ListItem';
16+
import { Task } from '../Task/Task';
1617
import { PostponeMenu } from '../ui/Menus/PostponeMenu';
1718
import { TaskLineRenderer, type TextRenderer, createAndAppendElement } from './TaskLineRenderer';
1819

@@ -188,14 +189,16 @@ export class QueryResultsRenderer {
188189
// will be empty, and no headings will be added.
189190
await this.addGroupHeadings(content, group.groupHeadings);
190191

191-
await this.createTaskList(group.tasks, content, queryRendererParameters);
192+
const renderedTasks: Set<ListItem> = new Set();
193+
await this.createTaskList(group.tasks, content, queryRendererParameters, renderedTasks);
192194
}
193195
}
194196

195197
private async createTaskList(
196-
tasks: Task[],
197-
content: HTMLDivElement,
198+
tasks: ListItem[],
199+
content: HTMLElement,
198200
queryRendererParameters: QueryRendererParameters,
201+
renderedTasks: Set<ListItem>,
199202
): Promise<void> {
200203
const taskList = createAndAppendElement('ul', content);
201204

@@ -217,12 +220,80 @@ export class QueryResultsRenderer {
217220
});
218221

219222
for (const [taskIndex, task] of tasks.entries()) {
220-
await this.addTask(taskList, taskLineRenderer, task, taskIndex, queryRendererParameters);
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+
});
245+
}
221246
}
222247

223248
content.appendChild(taskList);
224249
}
225250

251+
private willBeRenderedLater(task: ListItem, renderedTasks: Set<ListItem>, tasks: ListItem[]) {
252+
// Try to find the closest parent that is a task
253+
let closestParentTask = task.parent;
254+
while (closestParentTask !== null && !(closestParentTask instanceof Task)) {
255+
closestParentTask = closestParentTask.parent;
256+
}
257+
258+
if (!closestParentTask) {
259+
return false;
260+
}
261+
262+
if (!renderedTasks.has(closestParentTask)) {
263+
// This task is a direct or indirect child of another task that we are waiting to draw,
264+
// so don't draw it yet, it will be done recursively later.
265+
if (tasks.includes(closestParentTask)) {
266+
return true;
267+
}
268+
}
269+
270+
return false;
271+
}
272+
273+
private alreadyRendered(task: ListItem, renderedTasks: Set<ListItem>) {
274+
return renderedTasks.has(task);
275+
}
276+
277+
private async addTaskOrListItem(
278+
taskList: HTMLUListElement,
279+
taskLineRenderer: TaskLineRenderer,
280+
task: ListItem,
281+
taskIndex: number,
282+
queryRendererParameters: QueryRendererParameters,
283+
) {
284+
if (task instanceof Task) {
285+
return await this.addTask(taskList, taskLineRenderer, task, taskIndex, queryRendererParameters);
286+
}
287+
288+
return this.addListItem(taskList, task);
289+
}
290+
291+
private addListItem(taskList: HTMLUListElement, listItem: ListItem) {
292+
const li = createAndAppendElement('li', taskList);
293+
li.textContent = listItem.originalMarkdown;
294+
return li;
295+
}
296+
226297
private async addTask(
227298
taskList: HTMLUListElement,
228299
taskLineRenderer: TaskLineRenderer,
@@ -259,6 +330,8 @@ export class QueryResultsRenderer {
259330
}
260331

261332
taskList.appendChild(listItem);
333+
334+
return listItem;
262335
}
263336

264337
private addEditButton(listItem: HTMLElement, task: Task, queryRendererParameters: QueryRendererParameters) {

tests/Renderer/QueryResultsRenderer.test.QueryResultsRenderer_tests_parent-child_items.approved.html

Lines changed: 98 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -19,86 +19,104 @@
1919
</span>
2020
<a class="tasks-edit" title="Edit task" href="#"></a>
2121
</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>
22+
<ul class="contains-task-list plugin-tasks-query-result tasks-layout-hide-urgency">
23+
<li
24+
class="task-list-item plugin-tasks-list-item"
25+
data-task-priority="normal"
26+
data-task=""
27+
data-line="0"
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="0" />
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+
<ul class="contains-task-list plugin-tasks-query-result tasks-layout-hide-urgency">
43+
<li
44+
class="task-list-item plugin-tasks-list-item"
45+
data-task-priority="normal"
46+
data-task=""
47+
data-line="0"
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="0" />
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+
<ul class="contains-task-list plugin-tasks-query-result tasks-layout-hide-urgency">
63+
<li>- list item grand grand child</li>
64+
</ul>
65+
</li>
66+
</ul>
67+
</li>
68+
<li
69+
class="task-list-item plugin-tasks-list-item"
70+
data-task-priority="normal"
71+
data-task=""
72+
data-line="1"
73+
data-task-status-name="Todo"
74+
data-task-status-type="TODO">
75+
<input class="task-list-item-checkbox" type="checkbox" title="Right-click for options" data-line="1" />
76+
<span class="tasks-list-text">
77+
<span class="task-description"><span>child 2</span></span>
78+
</span>
79+
<span class="task-extras">
80+
<span class="tasks-backlink">
81+
(
82+
<a rel="noopener" target="_blank" class="internal-link">inheritance_rendering_sample</a>
83+
)
84+
</span>
85+
<a class="tasks-edit" title="Edit task" href="#"></a>
86+
</span>
87+
<ul class="contains-task-list plugin-tasks-query-result tasks-layout-hide-urgency">
88+
<li>
89+
- non task grandchild
90+
<ul class="contains-task-list plugin-tasks-query-result tasks-layout-hide-urgency">
91+
<li
92+
class="task-list-item plugin-tasks-list-item"
93+
data-task-priority="normal"
94+
data-task=""
95+
data-line="0"
96+
data-task-status-name="Todo"
97+
data-task-status-type="TODO">
98+
<input
99+
class="task-list-item-checkbox"
100+
type="checkbox"
101+
title="Right-click for options"
102+
data-line="0" />
103+
<span class="tasks-list-text">
104+
<span class="task-description"><span>grand grand child</span></span>
105+
</span>
106+
<span class="task-extras">
107+
<span class="tasks-backlink">
108+
(
109+
<a rel="noopener" target="_blank" class="internal-link">inheritance_rendering_sample</a>
110+
)
111+
</span>
112+
<a class="tasks-edit" title="Edit task" href="#"></a>
113+
</span>
114+
</li>
115+
</ul>
116+
</li>
117+
</ul>
118+
</li>
119+
</ul>
102120
</li>
103121
<li
104122
class="task-list-item plugin-tasks-list-item"

0 commit comments

Comments
 (0)