Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Rename symbols in TaskFieldRenderer #2440

Merged
58 changes: 29 additions & 29 deletions src/TaskFieldRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,38 +5,38 @@ import type { TaskLayoutComponent } from './TaskLayout';

export type AttributesDictionary = { [key: string]: string };

export class FieldLayouts {
private readonly details = FieldLayoutDetails;
export class TaskFieldRenderer {
private readonly data = taskFieldHTMLData;

/**
* Searches for the component among the {@link FieldLayoutDetails} and gets its data attribute
* Searches for the component among the {@link taskFieldHTMLData} and gets its data attribute
* in a given task. The data attribute shall be added in the task's `<span>`.
* For example, a task with medium priority and done yesterday will have
* `data-task-priority="medium" data-task-due="past-1d" ` in its data attributes.
*
* If the data attribute is absent in the task, an empty {@link AttributesDictionary} is returned.
*
* For detailed calculation see {@link FieldLayoutDetail.dataAttribute}.
* For detailed calculation see {@link TaskFieldHTMLData.dataAttribute}.
*
* @param component the component of the task for which the data attribute has to be generated.
* @param task the task from which the data shall be taken
*/
public dataAttribute(component: TaskLayoutComponent, task: Task) {
return this.details[component].dataAttribute(component, task);
return this.data[component].dataAttribute(component, task);
}

/**
* @returns the component's CSS class describing what this component is (priority, due date etc.).
* @param component of the task.
*/
public className(component: TaskLayoutComponent) {
return this.details[component].className;
return this.data[component].className;
}
}

type AttributeValueCalculator = (component: TaskLayoutComponent, task: Task) => string;

export class FieldLayoutDetail {
export class TaskFieldHTMLData {
public readonly className: string;
private readonly attributeName: string;
private readonly attributeValueCalculator: AttributeValueCalculator;
Expand Down Expand Up @@ -86,14 +86,14 @@ export class FieldLayoutDetail {
* @param className CSS class that describes what the component is, e.g. a due date or a priority.
*
* @param attributeName if the component needs data attribute (`data-key="value"`) this is the key.
* Otherwise, set this to {@link FieldLayoutDetail.noAttributeName}.
* Otherwise, set this to {@link TaskFieldHTMLData.noAttributeName}.
*
* @param attributeValueCalculator And this is the value calculator.
* Set to {@link FieldLayoutDetail.noAttributeValueCalculator} if the component has no data attribute.
* Set to {@link TaskFieldHTMLData.noAttributeValueCalculator} if the component has no data attribute.
*
* There is a relation between {@link attributeName} and {@link attributeValueCalculator}.
* For a component to have the data attribute, both need to be set to values other than
* {@link FieldLayoutDetail.noAttributeName} and {@link FieldLayoutDetail.noAttributeValueCalculator} respectively.
* {@link TaskFieldHTMLData.noAttributeName} and {@link TaskFieldHTMLData.noAttributeValueCalculator} respectively.
* This means that having an empty data attribute (`data-key=""`) is not supported.
*/
constructor(className: string, attributeName: string, attributeValueCalculator: AttributeValueCalculator) {
Expand All @@ -107,54 +107,54 @@ export class FieldLayoutDetail {
}

/**
* Shall be called only by {@link FieldLayouts}. Use that class if you need the data attributes.
* Shall be called only by {@link TaskFieldRenderer}. Use that class if you need the data attributes.
*
* @returns the data attribute, associated to with a task's component, added in the task's `<span>`.
* For example, a task with medium priority and done yesterday will have
* `data-task-priority="medium" data-task-due="past-1d" ` in its data attributes.
*
* Calculation of the value is done with {@link FieldLayoutDetail.attributeValueCalculator}.
* Calculation of the value is done with {@link TaskFieldHTMLData.attributeValueCalculator}.
*
* @param component the component of the task for which the data attribute has to be generated.
* @param task the task from which the data shall be taken
*/
public dataAttribute(component: TaskLayoutComponent, task: Task) {
const dataAttribute: AttributesDictionary = {};

if (this.attributeName !== FieldLayoutDetail.noAttributeName) {
if (this.attributeName !== TaskFieldHTMLData.noAttributeName) {
dataAttribute[this.attributeName] = this.attributeValueCalculator(component, task);
}

return dataAttribute;
}
}

const FieldLayoutDetails: { [c in TaskLayoutComponent]: FieldLayoutDetail } = {
const taskFieldHTMLData: { [c in TaskLayoutComponent]: TaskFieldHTMLData } = {
// NEW_TASK_FIELD_EDIT_REQUIRED
createdDate: new FieldLayoutDetail('task-created', 'taskCreated', FieldLayoutDetail.dateAttributeCalculator),
dueDate: new FieldLayoutDetail('task-due', 'taskDue', FieldLayoutDetail.dateAttributeCalculator),
startDate: new FieldLayoutDetail('task-start', 'taskStart', FieldLayoutDetail.dateAttributeCalculator),
scheduledDate: new FieldLayoutDetail('task-scheduled', 'taskScheduled', FieldLayoutDetail.dateAttributeCalculator),
doneDate: new FieldLayoutDetail('task-done', 'taskDone', FieldLayoutDetail.dateAttributeCalculator),
createdDate: new TaskFieldHTMLData('task-created', 'taskCreated', TaskFieldHTMLData.dateAttributeCalculator),
dueDate: new TaskFieldHTMLData('task-due', 'taskDue', TaskFieldHTMLData.dateAttributeCalculator),
startDate: new TaskFieldHTMLData('task-start', 'taskStart', TaskFieldHTMLData.dateAttributeCalculator),
scheduledDate: new TaskFieldHTMLData('task-scheduled', 'taskScheduled', TaskFieldHTMLData.dateAttributeCalculator),
doneDate: new TaskFieldHTMLData('task-done', 'taskDone', TaskFieldHTMLData.dateAttributeCalculator),

description: new FieldLayoutDetail(
description: new TaskFieldHTMLData(
'task-description',
FieldLayoutDetail.noAttributeName,
FieldLayoutDetail.noAttributeValueCalculator,
TaskFieldHTMLData.noAttributeName,
TaskFieldHTMLData.noAttributeValueCalculator,
),
recurrenceRule: new FieldLayoutDetail(
recurrenceRule: new TaskFieldHTMLData(
'task-recurring',
FieldLayoutDetail.noAttributeName,
FieldLayoutDetail.noAttributeValueCalculator,
TaskFieldHTMLData.noAttributeName,
TaskFieldHTMLData.noAttributeValueCalculator,
),

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

blockLink: new FieldLayoutDetail(
blockLink: new TaskFieldHTMLData(
'task-block-link',
FieldLayoutDetail.noAttributeName,
FieldLayoutDetail.noAttributeValueCalculator,
TaskFieldHTMLData.noAttributeName,
TaskFieldHTMLData.noAttributeValueCalculator,
),
};
28 changes: 12 additions & 16 deletions src/TaskLineRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { TASK_FORMATS, getSettings } from './Config/Settings';
import { replaceTaskWithTasks } from './File';
import type { Task } from './Task';
import * as taskModule from './Task';
import { type AttributesDictionary, FieldLayouts } from './TaskFieldRenderer';
import { TaskFieldRenderer } from './TaskFieldRenderer';
import type { LayoutOptions, TaskLayoutComponent } from './TaskLayout';
import { TaskLayout } from './TaskLayout';

const fieldLayouts = new FieldLayouts();
const fieldRenderer = new TaskFieldRenderer();

/**
* The function used to render a Markdown task line into an existing HTML element.
Expand Down Expand Up @@ -97,8 +97,7 @@ export class TaskLineRenderer {
const textSpan = document.createElement('span');
li.appendChild(textSpan);
textSpan.classList.add('tasks-list-text');
const attributes = await this.taskToHtml(task, textSpan);
for (const key in attributes) li.dataset[key] = attributes[key];
await this.taskToHtml(task, textSpan, li);

// NOTE: this area is mentioned in `CONTRIBUTING.md` under "How does Tasks handle status changes". When
// moving the code, remember to update that reference too.
Expand Down Expand Up @@ -142,8 +141,7 @@ export class TaskLineRenderer {
return li;
}

private async taskToHtml(task: Task, parentElement: HTMLElement): Promise<AttributesDictionary> {
let allAttributes: AttributesDictionary = {};
private async taskToHtml(task: Task, parentElement: HTMLElement, li: HTMLLIElement): Promise<void> {
const taskLayout = new TaskLayout(this.layoutOptions);
const emojiSerializer = TASK_FORMATS.tasksPluginEmoji.taskSerializer;
// Render and build classes for all the task's visible components
Expand All @@ -167,34 +165,32 @@ export class TaskLineRenderer {
this.addInternalClasses(component, internalSpan);

// Add the component's CSS class describing what this component is (priority, due date etc.)
const componentClass = fieldLayouts.className(component);
const componentClass = fieldRenderer.className(component);
span.classList.add(...[componentClass]);

// Add the component's attribute ('priority-medium', 'due-past-1d' etc.)
const componentDataAttribute = fieldLayouts.dataAttribute(component, task);
const componentDataAttribute = fieldRenderer.dataAttribute(component, task);
for (const key in componentDataAttribute) span.dataset[key] = componentDataAttribute[key];
allAttributes = { ...allAttributes, ...componentDataAttribute };
for (const key in componentDataAttribute) li.dataset[key] = componentDataAttribute[key];
}
}
}

// Now build classes for the hidden task components without rendering them
for (const component of taskLayout.hiddenTaskLayoutComponents) {
const hiddenComponentDataAttribute = fieldLayouts.dataAttribute(component, task);
allAttributes = { ...allAttributes, ...hiddenComponentDataAttribute };
const hiddenComponentDataAttribute = fieldRenderer.dataAttribute(component, task);
for (const key in hiddenComponentDataAttribute) li.dataset[key] = hiddenComponentDataAttribute[key];
}

// If a task has no priority field set, its priority will not be rendered as part of the loop above and
// it will not be set a priority data attribute.
// In such a case we want the upper task LI element to mark the task has a 'normal' priority.
// So if the priority was not rendered, force it through the pipe of getting the component data for the
// priority field.
if (allAttributes.taskPriority === undefined) {
const priorityDataAttribute = fieldLayouts.dataAttribute('priority', task);
allAttributes = { ...allAttributes, ...priorityDataAttribute };
if (li.dataset.taskPriority === undefined) {
const priorityDataAttribute = fieldRenderer.dataAttribute('priority', task);
for (const key in priorityDataAttribute) li.dataset[key] = priorityDataAttribute[key];
}

return allAttributes;
}

/*
Expand Down
12 changes: 6 additions & 6 deletions tests/TaskFieldRenderer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @jest-environment jsdom
*/
import moment from 'moment';
import { FieldLayoutDetail, FieldLayouts } from '../src/TaskFieldRenderer';
import { TaskFieldHTMLData, TaskFieldRenderer } from '../src/TaskFieldRenderer';
import { TaskBuilder } from './TestingTools/TaskBuilder';

window.moment = moment;
Expand All @@ -18,7 +18,7 @@ describe('Field Layouts Container tests', () => {
});

it('should get the data attribute of an existing component (date)', () => {
const container = new FieldLayouts();
const container = new TaskFieldRenderer();
const task = new TaskBuilder().dueDate('2023-11-20').build();

const dueDateDataAttribute = container.dataAttribute('dueDate', task);
Expand All @@ -28,7 +28,7 @@ describe('Field Layouts Container tests', () => {
});

it('should get the data attribute of an existing component (not date)', () => {
const container = new FieldLayouts();
const container = new TaskFieldRenderer();
const task = TaskBuilder.createFullyPopulatedTask();

const dueDateDataAttribute = container.dataAttribute('priority', task);
Expand All @@ -38,7 +38,7 @@ describe('Field Layouts Container tests', () => {
});

it('should return empty data attributes dictionary for a missing component', () => {
const container = new FieldLayouts();
const container = new TaskFieldRenderer();
const task = new TaskBuilder().build();

const dueDateDataAttribute = container.dataAttribute('recurrenceRule', task);
Expand All @@ -49,14 +49,14 @@ describe('Field Layouts Container tests', () => {

describe('Field Layout Detail tests', () => {
it('should supply a class name and a data attribute name', () => {
const fieldLayoutDetail = new FieldLayoutDetail('stuff', 'taskAttribute', () => {
const fieldLayoutDetail = new TaskFieldHTMLData('stuff', 'taskAttribute', () => {
return '';
});
expect(fieldLayoutDetail.className).toEqual('stuff');
});

it('should return a data attribute', () => {
const fieldLayoutDetail = new FieldLayoutDetail('dataAttributeTest', 'aKey', () => {
const fieldLayoutDetail = new TaskFieldHTMLData('dataAttributeTest', 'aKey', () => {
return 'aValue';
});
const dataAttribute = fieldLayoutDetail.dataAttribute('description', new TaskBuilder().build());
Expand Down
Loading