Skip to content

Commit 76c172d

Browse files
authored
Merge pull request #3699 from ilandikov/refactor/move-toolbar-rendering
refactor: move toolbar rendering to `QueryResultsRenderer`
2 parents 63bd504 + 0f93915 commit 76c172d

10 files changed

+97
-91
lines changed

src/Renderer/HtmlQueryResultsRenderer.ts

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1-
import { type App, type Component, Notice, type TFile, setIcon, setTooltip } from 'obsidian';
1+
import type { App, Component, TFile } from 'obsidian';
22
import { postponeButtonTitle, shouldShowPostponeButton } from '../DateTime/Postponer';
33
import { QueryLayout } from '../Layout/QueryLayout';
44
import { TaskLayout } from '../Layout/TaskLayout';
5-
import { State } from '../Obsidian/Cache';
65
import type { GroupDisplayHeading } from '../Query/Group/GroupDisplayHeading';
76
import type { QueryResult } from '../Query/QueryResult';
87
import type { ListItem } from '../Task/ListItem';
98
import type { Task } from '../Task/Task';
109
import { PostponeMenu } from '../ui/Menus/PostponeMenu';
1110
import { showMenu } from '../ui/Menus/TaskEditingMenu';
12-
import { MarkdownQueryResultsRenderer } from './MarkdownQueryResultsRenderer';
1311
import type { QueryRendererParameters } from './QueryResultsRenderer';
1412
import { QueryResultsRendererBase, type QueryResultsRendererGetters } from './QueryResultsRendererBase';
1513
import { TaskLineRenderer, type TextRenderer, createAndAppendElement } from './TaskLineRenderer';
@@ -40,8 +38,6 @@ export class HtmlQueryResultsRenderer extends QueryResultsRendererBase {
4038

4139
private readonly queryRendererParameters: QueryRendererParameters;
4240

43-
private readonly markdownRenderer: MarkdownQueryResultsRenderer;
44-
4541
constructor(
4642
renderMarkdown: (
4743
app: App,
@@ -71,16 +67,14 @@ export class HtmlQueryResultsRenderer extends QueryResultsRendererBase {
7167
taskLayoutOptions: this.getters.query().taskLayoutOptions,
7268
queryLayoutOptions: this.getters.query().queryLayoutOptions,
7369
});
74-
75-
this.markdownRenderer = new MarkdownQueryResultsRenderer(getters);
7670
}
7771

7872
protected beginRender(): void {
7973
return;
8074
}
8175

82-
protected renderSearchResultsHeader(queryResult: QueryResult): void {
83-
this.addToolbar(queryResult);
76+
protected renderSearchResultsHeader(_queryResult: QueryResult): void {
77+
return;
8478
}
8579

8680
protected renderSearchResultsFooter(queryResult: QueryResult): void {
@@ -102,28 +96,6 @@ export class HtmlQueryResultsRenderer extends QueryResultsRendererBase {
10296
explanationsBlock.textContent = explanation;
10397
}
10498

105-
private addToolbar(queryResult: QueryResult) {
106-
if (this.getters.query().queryLayoutOptions.hideToolbar) {
107-
return;
108-
}
109-
110-
const toolbar = createAndAppendElement('div', this.content);
111-
toolbar.classList.add('plugin-tasks-toolbar');
112-
this.addCopyButton(toolbar, queryResult);
113-
}
114-
115-
private addCopyButton(toolbar: HTMLDivElement, queryResult: QueryResult): void {
116-
const copyButton = createAndAppendElement('button', toolbar);
117-
setIcon(copyButton, 'lucide-copy');
118-
setTooltip(copyButton, 'Copy results');
119-
copyButton.addEventListener('click', async () => {
120-
// TODO reimplement this using QueryResult.asMarkdown() when it supports trees and list items.
121-
await this.markdownRenderer.renderQuery(State.Warm, queryResult);
122-
await navigator.clipboard.writeText(this.markdownRenderer.markdown);
123-
new Notice('Results copied to clipboard');
124-
});
125-
}
126-
12799
protected beginTaskList(): void {
128100
const isFirstTaskListInContainer = this.ulElementStack.length === 0;
129101
const taskListContainer = isFirstTaskListInContainer ? this.content : this.lastLIElement;

src/Renderer/QueryResultsRenderer.ts

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1-
import type { App, Component, TFile } from 'obsidian';
1+
import { type App, type Component, Notice, type TFile, setIcon, setTooltip } from 'obsidian';
22
import { GlobalQuery } from '../Config/GlobalQuery';
33
import type { IQuery } from '../IQuery';
44
import { PerformanceTracker } from '../lib/PerformanceTracker';
5-
import type { State } from '../Obsidian/Cache';
5+
import { State } from '../Obsidian/Cache';
66
import { getQueryForQueryRenderer } from '../Query/QueryRendererHelper';
7+
import type { QueryResult } from '../Query/QueryResult';
78
import type { TasksFile } from '../Scripting/TasksFile';
89
import type { Task } from '../Task/Task';
910
import { HtmlQueryResultsRenderer } from './HtmlQueryResultsRenderer';
10-
import type { TextRenderer } from './TaskLineRenderer';
11+
import { MarkdownQueryResultsRenderer } from './MarkdownQueryResultsRenderer';
12+
import { type TextRenderer, createAndAppendElement } from './TaskLineRenderer';
1113

1214
export type BacklinksEventHandler = (ev: MouseEvent, task: Task) => Promise<void>;
1315
export type EditButtonClickHandler = (event: MouseEvent, task: Task, allTasks: Task[]) => void;
@@ -47,6 +49,7 @@ export class QueryResultsRenderer {
4749
public readonly source: string;
4850

4951
private readonly htmlRenderer: HtmlQueryResultsRenderer;
52+
private readonly markdownRenderer: MarkdownQueryResultsRenderer;
5053

5154
// The path of the file that contains the instruction block, and cached data from that file.
5255
// This can be updated when the query file's frontmatter is modified.
@@ -90,18 +93,22 @@ export class QueryResultsRenderer {
9093
break;
9194
}
9295

96+
const getters = {
97+
source: () => this.source,
98+
tasksFile: () => this._tasksFile,
99+
query: () => this.query,
100+
};
101+
93102
this.htmlRenderer = new HtmlQueryResultsRenderer(
94103
renderMarkdown,
95104
obsidianComponent,
96105
obsidianApp,
97106
textRenderer,
98107
queryRendererParameters,
99-
{
100-
source: () => this.source,
101-
tasksFile: () => this._tasksFile,
102-
query: () => this.query,
103-
},
108+
getters,
104109
);
110+
111+
this.markdownRenderer = new MarkdownQueryResultsRenderer(getters);
105112
}
106113

107114
private makeQueryFromSourceAndTasksFile() {
@@ -147,8 +154,31 @@ export class QueryResultsRenderer {
147154

148155
const measureRender = new PerformanceTracker(`Render: ${this.query.queryId} - ${this.filePath}`);
149156
measureRender.start();
157+
this.addToolbar(queryResult, content);
150158
this.htmlRenderer.content = content;
151159
await this.htmlRenderer.renderQuery(state, queryResult);
152160
measureRender.finish();
153161
}
162+
163+
private addToolbar(queryResult: QueryResult, content: HTMLElement) {
164+
if (this.query.queryLayoutOptions.hideToolbar) {
165+
return;
166+
}
167+
168+
const toolbar = createAndAppendElement('div', content);
169+
toolbar.classList.add('plugin-tasks-toolbar');
170+
this.addCopyButton(toolbar, queryResult);
171+
}
172+
173+
private addCopyButton(toolbar: HTMLDivElement, queryResult: QueryResult) {
174+
const copyButton = createAndAppendElement('button', toolbar);
175+
setIcon(copyButton, 'lucide-copy');
176+
setTooltip(copyButton, 'Copy results');
177+
copyButton.addEventListener('click', async () => {
178+
// TODO reimplement this using QueryResult.asMarkdown() when it supports trees and list items.
179+
await this.markdownRenderer.renderQuery(State.Warm, queryResult);
180+
await navigator.clipboard.writeText(this.markdownRenderer.markdown);
181+
new Notice('Results copied to clipboard');
182+
});
183+
}
154184
}

tests/Renderer/HtmlQueryResultsRenderer.test.HtmlQueryResultsRenderer_tests_fully_populated_task.approved.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
-->
44

55
<div>
6-
<div class="plugin-tasks-toolbar"><button test-icon="lucide-copy" test-tooltip="Copy results"></button></div>
76
<ul class="contains-task-list plugin-tasks-query-result">
87
<li
98
class="task-list-item plugin-tasks-list-item"

tests/Renderer/HtmlQueryResultsRenderer.test.HtmlQueryResultsRenderer_tests_fully_populated_task_-_short_mode.approved.html

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
-->
44

55
<div>
6-
<div class="plugin-tasks-toolbar"><button test-icon="lucide-copy" test-tooltip="Copy results"></button></div>
76
<ul class="contains-task-list plugin-tasks-query-result tasks-layout-short-mode">
87
<li
98
class="task-list-item plugin-tasks-list-item"

tests/Renderer/HtmlQueryResultsRenderer.test.HtmlQueryResultsRenderer_tests_toolbar.approved.html

Lines changed: 0 additions & 30 deletions
This file was deleted.

tests/Renderer/HtmlQueryResultsRenderer.test.ts

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,8 @@ import { TasksFile } from '../../src/Scripting/TasksFile';
99
import type { Task } from '../../src/Task/Task';
1010
import { mockApp } from '../__mocks__/obsidian';
1111
import { readTasksFromSimulatedFile } from '../Obsidian/SimulatedFile';
12-
import { verifyWithFileExtension } from '../TestingTools/ApprovalTestHelpers';
13-
import { prettifyHTML } from '../TestingTools/HTMLHelpers';
1412
import { TaskBuilder } from '../TestingTools/TaskBuilder';
15-
import { toMarkdown } from '../TestingTools/TestHelpers';
16-
import { makeQueryRendererParameters, mockHTMLRenderer } from './RenderingTestHelpers';
13+
import { makeQueryRendererParameters, mockHTMLRenderer, verifyRenderedTasks } from './RenderingTestHelpers';
1714

1815
window.moment = moment;
1916

@@ -59,19 +56,9 @@ async function renderTasks(
5956
return container;
6057
}
6158

62-
function verifyRenderedTasks(container: HTMLDivElement, allTasks: Task[]): void {
63-
const taskAsMarkdown = `<!--
64-
${toMarkdown(allTasks)}
65-
-->\n\n`;
66-
67-
const prettyHTML = prettifyHTML(container.outerHTML);
68-
verifyWithFileExtension(taskAsMarkdown + prettyHTML, 'html');
69-
}
70-
7159
beforeEach(() => {
7260
jest.useFakeTimers();
7361
jest.setSystemTime(new Date('2023-07-05'));
74-
GlobalQuery.getInstance().set('hide toolbar');
7562
});
7663

7764
afterEach(() => {
@@ -97,11 +84,6 @@ describe('HtmlQueryResultsRenderer tests', () => {
9784
await verifyRenderedHtml(allTasks, 'scheduled 1970-01-01\nexplain');
9885
});
9986

100-
it('toolbar', async () => {
101-
const allTasks = [new TaskBuilder().path('sample.md').build()];
102-
await verifyRenderedHtml(allTasks, 'show toolbar');
103-
});
104-
10587
it('fully populated task', async () => {
10688
// The approved file from this test is embedded in the user documentation,
10789
// so we ignore any GlobalQuery, to avoid accidental changes to the docs:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<!--
2+
3+
-->
4+
5+
<div>
6+
<ul class="contains-task-list plugin-tasks-query-result tasks-layout-hide-urgency"></ul>
7+
<div class="task-count">0 tasks</div>
8+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!--
2+
3+
-->
4+
5+
<div>
6+
<div class="plugin-tasks-toolbar"><button test-icon="lucide-copy" test-tooltip="Copy results"></button></div>
7+
<ul class="contains-task-list plugin-tasks-query-result tasks-layout-hide-urgency"></ul>
8+
<div class="task-count">0 tasks</div>
9+
</div>

tests/Renderer/QueryResultsRenderer.test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@
44
import moment from 'moment';
55
import type { Task } from 'Task/Task';
66
import { resetSettings, updateSettings } from '../../src/Config/Settings';
7+
import { State } from '../../src/Obsidian/Cache';
78
import { QueryResultsRenderer } from '../../src/Renderer/QueryResultsRenderer';
89
import { TasksFile } from '../../src/Scripting/TasksFile';
910
import { mockApp } from '../__mocks__/obsidian';
10-
import { makeQueryRendererParameters, mockHTMLRenderer } from './RenderingTestHelpers';
11+
import { makeQueryRendererParameters, mockHTMLRenderer, verifyRenderedTasks } from './RenderingTestHelpers';
1112

1213
window.moment = moment;
1314

@@ -29,6 +30,30 @@ function makeQueryResultsRenderer(source: string, tasksFile: TasksFile, allTasks
2930
);
3031
}
3132

33+
describe('QueryResultsRenderer - rendering queries', () => {
34+
it('should render the toolbar', async () => {
35+
const source = 'show toolbar';
36+
const noTasks: Task[] = [];
37+
const renderer = makeQueryResultsRenderer(source, new TasksFile('file.md'), noTasks);
38+
const container = document.createElement('div');
39+
40+
await renderer.render(State.Warm, noTasks, container);
41+
42+
verifyRenderedTasks(container, noTasks);
43+
});
44+
45+
it('should not render the toolbar', async () => {
46+
const source = 'hide toolbar';
47+
const noTasks: Task[] = [];
48+
const renderer = makeQueryResultsRenderer(source, new TasksFile('file.md'), noTasks);
49+
const container = document.createElement('div');
50+
51+
await renderer.render(State.Warm, noTasks, container);
52+
53+
verifyRenderedTasks(container, noTasks);
54+
});
55+
});
56+
3257
describe('QueryResultsRenderer - responding to file edits', () => {
3358
it('should update the query when its file path is changed', () => {
3459
// Arrange

tests/Renderer/RenderingTestHelpers.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { MarkdownQueryResultsRenderer } from '../../src/Renderer/MarkdownQueryRe
66
import type { QueryRendererParameters } from '../../src/Renderer/QueryResultsRenderer';
77
import { TasksFile } from '../../src/Scripting/TasksFile';
88
import type { Task } from '../../src/Task/Task';
9+
import { verifyWithFileExtension } from '../TestingTools/ApprovalTestHelpers';
10+
import { prettifyHTML } from '../TestingTools/HTMLHelpers';
11+
import { toMarkdown } from '../TestingTools/TestHelpers';
912

1013
export const mockHTMLRenderer = async (_obsidianApp: App, text: string, element: HTMLSpanElement, _path: string) => {
1114
// Contrary to the default mockTextRenderer(),
@@ -56,3 +59,12 @@ export async function renderMarkdown(source: string, tasks: Task[]) {
5659
},
5760
};
5861
}
62+
63+
export function verifyRenderedTasks(container: HTMLDivElement, allTasks: Task[]): void {
64+
const taskAsMarkdown = `<!--
65+
${toMarkdown(allTasks)}
66+
-->\n\n`;
67+
68+
const prettyHTML = prettifyHTML(container.outerHTML);
69+
verifyWithFileExtension(taskAsMarkdown + prettyHTML, 'html');
70+
}

0 commit comments

Comments
 (0)