Skip to content

Commit

Permalink
Merge pull request #2440 from ilandikov/create-TaskFieldRenderer
Browse files Browse the repository at this point in the history
refactor: Rename symbols in TaskFieldRenderer
  • Loading branch information
claremacrae authored Nov 22, 2023
2 parents cce227f + 1625bcd commit 2e26b69
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 99 deletions.
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

0 comments on commit 2e26b69

Please sign in to comment.