Skip to content

Commit e2211db

Browse files
authored
Merge pull request #2610 from ilandikov/remove-inheritance-tasklayout-querylayout
refactor: remove inheritance between `TaskLayout` and `QueryLayout`
2 parents 15b0cdc + 27fc35e commit e2211db

File tree

11 files changed

+127
-133
lines changed

11 files changed

+127
-133
lines changed

src/Layout/LayoutHelpers.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export function generateHiddenClassForTaskList(taskListHiddenClasses: string[], hide: boolean, component: string) {
2+
if (hide) {
3+
taskListHiddenClasses.push(hiddenComponentClassName(component));
4+
}
5+
}
6+
7+
function hiddenComponentClassName(component: string) {
8+
return `tasks-layout-hide-${component}`;
9+
}

src/Layout/QueryLayout.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { generateHiddenClassForTaskList } from './LayoutHelpers';
2+
import { QueryLayoutOptions } from './QueryLayoutOptions';
3+
4+
export class QueryLayout {
5+
protected queryLayoutOptions: QueryLayoutOptions;
6+
7+
constructor(queryLayoutOptions?: QueryLayoutOptions) {
8+
if (queryLayoutOptions) {
9+
this.queryLayoutOptions = queryLayoutOptions;
10+
} else {
11+
this.queryLayoutOptions = new QueryLayoutOptions();
12+
}
13+
}
14+
15+
public applyQueryLayoutOptions() {
16+
const taskListHiddenClasses: string[] = [];
17+
const componentsToGenerateClassesOnly: [boolean, string][] = [
18+
// The following components are handled in QueryRenderer.ts and thus are not part of the same flow that
19+
// hides TaskLayoutComponent items. However, we still want to have 'tasks-layout-hide' items for them
20+
// (see https://github.com/obsidian-tasks-group/obsidian-tasks/issues/1866).
21+
// This can benefit from some refactoring, i.e. render these components in a similar flow rather than
22+
// separately.
23+
[this.queryLayoutOptions.hideUrgency, 'urgency'],
24+
[this.queryLayoutOptions.hideBacklinks, 'backlinks'],
25+
[this.queryLayoutOptions.hideEditButton, 'edit-button'],
26+
[this.queryLayoutOptions.hidePostponeButton, 'postpone-button'],
27+
];
28+
for (const [hide, component] of componentsToGenerateClassesOnly) {
29+
generateHiddenClassForTaskList(taskListHiddenClasses, hide, component);
30+
}
31+
32+
if (this.queryLayoutOptions.shortMode) taskListHiddenClasses.push('tasks-layout-short-mode');
33+
34+
return taskListHiddenClasses;
35+
}
36+
}

src/Layout/TaskLayout.ts

Lines changed: 9 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,34 @@
1+
import { generateHiddenClassForTaskList } from './LayoutHelpers';
12
import { TaskLayoutOptions } from './TaskLayoutOptions';
2-
import { QueryLayoutOptions } from './QueryLayoutOptions';
3-
4-
export type TaskLayoutComponent =
5-
// NEW_TASK_FIELD_EDIT_REQUIRED
6-
| 'description'
7-
| 'priority'
8-
| 'recurrenceRule'
9-
| 'createdDate'
10-
| 'startDate'
11-
| 'scheduledDate'
12-
| 'dueDate'
13-
| 'doneDate'
14-
| 'cancelledDate'
15-
| 'blockedBy'
16-
| 'id'
17-
| 'blockLink';
18-
19-
// The order here determines the order that task fields are rendered and written to markdown.
20-
export const taskLayoutComponents: TaskLayoutComponent[] = [
21-
// NEW_TASK_FIELD_EDIT_REQUIRED
22-
'description',
23-
'id',
24-
'blockedBy',
25-
'priority',
26-
'recurrenceRule',
27-
'createdDate',
28-
'startDate',
29-
'scheduledDate',
30-
'dueDate',
31-
'cancelledDate',
32-
'doneDate',
33-
'blockLink',
34-
];
35-
36-
export class QueryLayout {
37-
protected queryLayoutOptions: QueryLayoutOptions;
38-
39-
constructor(queryLayoutOptions?: QueryLayoutOptions) {
40-
if (queryLayoutOptions) {
41-
this.queryLayoutOptions = queryLayoutOptions;
42-
} else {
43-
this.queryLayoutOptions = new QueryLayoutOptions();
44-
}
45-
}
46-
47-
protected applyQueryLayoutOptions(taskListHiddenClasses: string[]) {
48-
const componentsToGenerateClassesOnly: [boolean, string][] = [
49-
// The following components are handled in QueryRenderer.ts and thus are not part of the same flow that
50-
// hides TaskLayoutComponent items. However, we still want to have 'tasks-layout-hide' items for them
51-
// (see https://github.com/obsidian-tasks-group/obsidian-tasks/issues/1866).
52-
// This can benefit from some refactoring, i.e. render these components in a similar flow rather than
53-
// separately.
54-
[this.queryLayoutOptions.hideUrgency, 'urgency'],
55-
[this.queryLayoutOptions.hideBacklinks, 'backlinks'],
56-
[this.queryLayoutOptions.hideEditButton, 'edit-button'],
57-
[this.queryLayoutOptions.hidePostponeButton, 'postpone-button'],
58-
];
59-
for (const [hide, component] of componentsToGenerateClassesOnly) {
60-
generateHiddenClassForTaskList(taskListHiddenClasses, hide, component);
61-
}
62-
63-
if (this.queryLayoutOptions.shortMode) taskListHiddenClasses.push('tasks-layout-short-mode');
64-
}
65-
}
66-
67-
function generateHiddenClassForTaskList(taskListHiddenClasses: string[], hide: boolean, component: string) {
68-
if (hide) {
69-
taskListHiddenClasses.push(hiddenComponentClassName(component));
70-
}
71-
}
72-
73-
function hiddenComponentClassName(component: string) {
74-
return `tasks-layout-hide-${component}`;
75-
}
763

774
/**
785
* This represents the desired layout of tasks when they are rendered in a given configuration.
796
* The layout is used when flattening the task to a string and when rendering queries, and can be
807
* modified by applying {@link TaskLayoutOptions} objects.
818
*/
82-
export class TaskLayout extends QueryLayout {
83-
public taskListHiddenClasses(): string[] {
84-
return this._taskListHiddenClasses;
85-
}
9+
export class TaskLayout {
8610
private taskLayoutOptions: TaskLayoutOptions;
87-
private _taskListHiddenClasses: string[] = [];
88-
89-
constructor(taskLayoutOptions?: TaskLayoutOptions, queryLayoutOptions?: QueryLayoutOptions) {
90-
super(queryLayoutOptions);
9111

12+
constructor(taskLayoutOptions?: TaskLayoutOptions) {
9213
if (taskLayoutOptions) {
9314
this.taskLayoutOptions = taskLayoutOptions;
9415
} else {
9516
this.taskLayoutOptions = new TaskLayoutOptions();
9617
}
97-
this.applyTaskLayoutOptions();
98-
this.applyQueryLayoutOptions(this._taskListHiddenClasses);
9918
}
100-
private applyTaskLayoutOptions() {
19+
public applyTaskLayoutOptions() {
20+
const taskListHiddenClasses: string[] = [];
10121
this.taskLayoutOptions.toggleableComponents.forEach((component) => {
10222
generateHiddenClassForTaskList(
103-
this._taskListHiddenClasses,
23+
taskListHiddenClasses,
10424
!this.taskLayoutOptions.isShown(component),
10525
component,
10626
);
10727
});
10828

10929
// Tags are hidden, rather than removed. See tasks-layout-hide-tags in styles.css.
110-
generateHiddenClassForTaskList(this._taskListHiddenClasses, !this.taskLayoutOptions.areTagsShown(), 'tags');
30+
generateHiddenClassForTaskList(taskListHiddenClasses, !this.taskLayoutOptions.areTagsShown(), 'tags');
31+
32+
return taskListHiddenClasses;
11133
}
11234
}

src/Layout/TaskLayoutOptions.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,34 @@
1-
import { type TaskLayoutComponent, taskLayoutComponents } from './TaskLayout';
1+
export type TaskLayoutComponent =
2+
// NEW_TASK_FIELD_EDIT_REQUIRED
3+
| 'description'
4+
| 'priority'
5+
| 'recurrenceRule'
6+
| 'createdDate'
7+
| 'startDate'
8+
| 'scheduledDate'
9+
| 'dueDate'
10+
| 'doneDate'
11+
| 'cancelledDate'
12+
| 'blockedBy'
13+
| 'id'
14+
| 'blockLink';
15+
16+
// The order here determines the order that task fields are rendered and written to markdown.
17+
export const taskLayoutComponents: TaskLayoutComponent[] = [
18+
// NEW_TASK_FIELD_EDIT_REQUIRED
19+
'description',
20+
'id',
21+
'blockedBy',
22+
'priority',
23+
'recurrenceRule',
24+
'createdDate',
25+
'startDate',
26+
'scheduledDate',
27+
'dueDate',
28+
'cancelledDate',
29+
'doneDate',
30+
'blockLink',
31+
];
232

333
/**
434
* Various rendering options of tasks in a query.

src/Renderer/QueryRenderer.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { EventRef, MarkdownPostProcessorContext } from 'obsidian';
22
import { App, Keymap, MarkdownRenderChild, MarkdownRenderer, TFile } from 'obsidian';
33
import { GlobalFilter } from '../Config/GlobalFilter';
44
import { GlobalQuery } from '../Config/GlobalQuery';
5+
import { QueryLayout } from '../Layout/QueryLayout';
56
import { DateFallback } from '../Task/DateFallback';
67

78
import type { IQuery } from '../IQuery';
@@ -221,10 +222,11 @@ class QueryRenderChild extends MarkdownRenderChild {
221222
}
222223

223224
private async createTaskList(tasks: Task[], content: HTMLDivElement): Promise<void> {
224-
const layout = new TaskLayout(this.query.taskLayoutOptions, this.query.queryLayoutOptions);
225+
const layout = new TaskLayout(this.query.taskLayoutOptions);
226+
const queryLayout = new QueryLayout(this.query.queryLayoutOptions);
225227
const taskList = content.createEl('ul');
226228
taskList.addClasses(['contains-task-list', 'plugin-tasks-query-result']);
227-
taskList.addClasses(layout.taskListHiddenClasses());
229+
taskList.addClasses([...layout.applyTaskLayoutOptions(), ...queryLayout.applyQueryLayoutOptions()]);
228230
const groupingAttribute = this.getGroupingAttribute();
229231
if (groupingAttribute && groupingAttribute.length > 0) taskList.dataset.taskGroupBy = groupingAttribute;
230232

src/Renderer/TaskFieldRenderer.ts

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { Moment } from 'moment';
2+
3+
import type { TaskLayoutComponent } from '../Layout/TaskLayoutOptions';
24
import { PriorityTools } from '../lib/PriorityTools';
35
import type { Task } from '../Task/Task';
4-
import type { TaskLayoutComponent } from '../Layout/TaskLayout';
56

67
export class TaskFieldRenderer {
78
private readonly data = taskFieldHTMLData;
@@ -139,43 +140,34 @@ export class TaskFieldHTMLData {
139140
}
140141
}
141142

142-
const taskFieldHTMLData: { [c in TaskLayoutComponent]: TaskFieldHTMLData } = {
143-
// NEW_TASK_FIELD_EDIT_REQUIRED
144-
createdDate: new TaskFieldHTMLData('task-created', 'taskCreated', TaskFieldHTMLData.dateAttributeCalculator),
145-
dueDate: new TaskFieldHTMLData('task-due', 'taskDue', TaskFieldHTMLData.dateAttributeCalculator),
146-
startDate: new TaskFieldHTMLData('task-start', 'taskStart', TaskFieldHTMLData.dateAttributeCalculator),
147-
scheduledDate: new TaskFieldHTMLData('task-scheduled', 'taskScheduled', TaskFieldHTMLData.dateAttributeCalculator),
148-
doneDate: new TaskFieldHTMLData('task-done', 'taskDone', TaskFieldHTMLData.dateAttributeCalculator),
149-
cancelledDate: new TaskFieldHTMLData('task-cancelled', 'taskCancelled', TaskFieldHTMLData.dateAttributeCalculator),
150-
151-
description: new TaskFieldHTMLData(
152-
'task-description',
153-
TaskFieldHTMLData.noAttributeName,
154-
TaskFieldHTMLData.noAttributeValueCalculator,
155-
),
156-
recurrenceRule: new TaskFieldHTMLData(
157-
'task-recurring',
143+
function createFieldWithoutDataAttributes(className: string) {
144+
return new TaskFieldHTMLData(
145+
className,
158146
TaskFieldHTMLData.noAttributeName,
159147
TaskFieldHTMLData.noAttributeValueCalculator,
160-
),
148+
);
149+
}
150+
151+
function createDateField(className: string, attributeName: string) {
152+
return new TaskFieldHTMLData(className, attributeName, TaskFieldHTMLData.dateAttributeCalculator);
153+
}
154+
155+
const taskFieldHTMLData: { [c in TaskLayoutComponent]: TaskFieldHTMLData } = {
156+
// NEW_TASK_FIELD_EDIT_REQUIRED
157+
createdDate: createDateField('task-created', 'taskCreated'),
158+
dueDate: createDateField('task-due', 'taskDue'),
159+
startDate: createDateField('task-start', 'taskStart'),
160+
scheduledDate: createDateField('task-scheduled', 'taskScheduled'),
161+
doneDate: createDateField('task-done', 'taskDone'),
162+
cancelledDate: createDateField('task-cancelled', 'taskCancelled'),
161163

162164
priority: new TaskFieldHTMLData('task-priority', 'taskPriority', (_component, task) => {
163165
return PriorityTools.priorityNameUsingNormal(task.priority).toLocaleLowerCase();
164166
}),
165167

166-
blockedBy: new TaskFieldHTMLData(
167-
'task-blockedBy',
168-
TaskFieldHTMLData.noAttributeName,
169-
TaskFieldHTMLData.noAttributeValueCalculator,
170-
),
171-
id: new TaskFieldHTMLData(
172-
'task-id',
173-
TaskFieldHTMLData.noAttributeName,
174-
TaskFieldHTMLData.noAttributeValueCalculator,
175-
),
176-
blockLink: new TaskFieldHTMLData(
177-
'task-block-link',
178-
TaskFieldHTMLData.noAttributeName,
179-
TaskFieldHTMLData.noAttributeValueCalculator,
180-
),
168+
description: createFieldWithoutDataAttributes('task-description'),
169+
recurrenceRule: createFieldWithoutDataAttributes('task-recurring'),
170+
blockedBy: createFieldWithoutDataAttributes('task-blockedBy'),
171+
id: createFieldWithoutDataAttributes('task-id'),
172+
blockLink: createFieldWithoutDataAttributes('task-block-link'),
181173
};

src/Renderer/TaskLineRenderer.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@ import { Component, MarkdownRenderer } from 'obsidian';
33
import { GlobalFilter } from '../Config/GlobalFilter';
44
import { TASK_FORMATS, getSettings } from '../Config/Settings';
55
import { replaceTaskWithTasks } from '../Obsidian/File';
6-
import type { TaskLayoutOptions } from '../Layout/TaskLayoutOptions';
6+
import type { TaskLayoutComponent, TaskLayoutOptions } from '../Layout/TaskLayoutOptions';
77
import type { QueryLayoutOptions } from '../Layout/QueryLayoutOptions';
88
import type { Task } from '../Task/Task';
99
import * as taskModule from '../Task/Task';
10-
import type { TaskLayoutComponent } from '../Layout/TaskLayout';
1110
import { StatusMenu } from '../ui/Menus/StatusMenu';
1211
import { StatusRegistry } from '../Statuses/StatusRegistry';
1312
import { TaskFieldRenderer } from './TaskFieldRenderer';

src/TaskSerializer/DataviewTaskSerializer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { TaskLayoutComponent } from '../Layout/TaskLayout';
1+
import type { TaskLayoutComponent } from '../Layout/TaskLayoutOptions';
22
import type { Task } from '../Task/Task';
33
import { Priority } from '../Task/Task';
44
import { DefaultTaskSerializer } from './DefaultTaskSerializer';

src/TaskSerializer/DefaultTaskSerializer.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { Moment } from 'moment';
2-
import { TaskLayoutOptions } from '../Layout/TaskLayoutOptions';
3-
import type { TaskLayoutComponent } from '../Layout/TaskLayout';
2+
import { type TaskLayoutComponent, TaskLayoutOptions } from '../Layout/TaskLayoutOptions';
43
import { Recurrence } from '../Task/Recurrence';
54
import { Priority, Task, TaskRegularExpressions } from '../Task/Task';
65
import type { TaskDetails, TaskSerializer } from '.';

tests/Layout/TaskLayout.test.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,18 @@
22
* @jest-environment jsdom
33
*/
44

5+
import { QueryLayout } from '../../src/Layout/QueryLayout';
56
import { TaskLayoutOptions } from '../../src/Layout/TaskLayoutOptions';
67
import { QueryLayoutOptions } from '../../src/Layout/QueryLayoutOptions';
78
import { TaskLayout } from '../../src/Layout/TaskLayout';
89

910
describe('TaskLayout tests', () => {
1011
it('should generate expected CSS classes for default layout', () => {
1112
const taskLayout = new TaskLayout();
12-
expect(taskLayout.taskListHiddenClasses().join('\n')).toMatchInlineSnapshot('"tasks-layout-hide-urgency"');
13+
const queryLayout = new QueryLayout();
14+
15+
const hiddenClasses = [...taskLayout.applyTaskLayoutOptions(), ...queryLayout.applyQueryLayoutOptions()];
16+
expect(hiddenClasses.join('\n')).toMatchInlineSnapshot('"tasks-layout-hide-urgency"');
1317
});
1418

1519
it('should generate expected CSS classes with all default options reversed', () => {
@@ -23,9 +27,11 @@ describe('TaskLayout tests', () => {
2327
queryLayoutOptions[key2] = !queryLayoutOptions[key2];
2428
});
2529

26-
const taskLayout = new TaskLayout(taskLayoutOptions, queryLayoutOptions);
30+
const taskLayout = new TaskLayout(taskLayoutOptions);
31+
const queryLayout = new QueryLayout(queryLayoutOptions);
2732

28-
expect(taskLayout.taskListHiddenClasses().join('\n')).toMatchInlineSnapshot(`
33+
const hiddenClasses = [...taskLayout.applyTaskLayoutOptions(), ...queryLayout.applyQueryLayoutOptions()];
34+
expect(hiddenClasses.join('\n')).toMatchInlineSnapshot(`
2935
"tasks-layout-hide-id
3036
tasks-layout-hide-blockedBy
3137
tasks-layout-hide-priority

tests/Renderer/TaskLineRenderer.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ import moment from 'moment';
55
import { DebugSettings } from '../../src/Config/DebugSettings';
66
import { GlobalFilter } from '../../src/Config/GlobalFilter';
77
import { resetSettings, updateSettings } from '../../src/Config/Settings';
8-
import { TaskLayoutOptions } from '../../src/Layout/TaskLayoutOptions';
8+
import { type TaskLayoutComponent, TaskLayoutOptions, taskLayoutComponents } from '../../src/Layout/TaskLayoutOptions';
99
import { DateParser } from '../../src/Query/DateParser';
1010
import { QueryLayoutOptions } from '../../src/Layout/QueryLayoutOptions';
1111
import type { Task } from '../../src/Task/Task';
1212
import { TaskRegularExpressions } from '../../src/Task/Task';
13-
import { type TaskLayoutComponent, taskLayoutComponents } from '../../src/Layout/TaskLayout';
1413
import type { TextRenderer } from '../../src/Renderer/TaskLineRenderer';
1514
import { TaskLineRenderer } from '../../src/Renderer/TaskLineRenderer';
1615
import { fromLine } from '../TestingTools/TestHelpers';

0 commit comments

Comments
 (0)