Skip to content

Commit

Permalink
Merge pull request #2497 from claremacrae/postpone-experimentation
Browse files Browse the repository at this point in the history
feature: Experimental changes to (unreleased) Postpone menu options
  • Loading branch information
claremacrae authored Dec 11, 2023
2 parents 9f659ba + ad0c9bd commit ec4cb4b
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 13 deletions.
21 changes: 14 additions & 7 deletions src/QueryRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
type HappensDate,
createPostponedTask,
getDateFieldToPostpone,
postponeButtonTitle,
postponeMenuItemTitle,
postponementSuccessMessage,
shouldShowPostponeButton,
} from './Scripting/Postponer';
Expand Down Expand Up @@ -404,37 +406,42 @@ class QueryRenderChild extends MarkdownRenderChild {
}

private addPostponeButton(listItem: HTMLElement, task: Task, shortMode: boolean) {
const amount = 1;
const timeUnit = 'day';
const buttonTooltipText = postponeButtonTitle(task, amount, timeUnit);
const button = listItem.createEl('button', {
attr: {
id: 'postpone-button',
title: 'ℹ️ Postpone the task (right-click for more options)',
title: buttonTooltipText,
},
});

const classNames = shortMode ? ['internal-button', 'internal-button-short-mode'] : ['internal-button'];
button.addClasses(classNames);
button.setText(' ⏩');

button.addEventListener('click', () => this.postponeOnClickCallback(button, task, 1, 'days'));
button.addEventListener('click', () => this.postponeOnClickCallback(button, task, amount, timeUnit));

/** Open a context menu on right-click.
* Give a choice of postponing for a week, month, or quarter.
*/
button.addEventListener('contextmenu', async (ev: MouseEvent) => {
const menu = new Menu();
const commonTitle = 'Postpone for';

const postponeMenuItemCallback = (item: MenuItem, timeUnit: unitOfTime.DurationConstructor, amount = 1) => {
const amountOrArticle = amount > 1 ? amount : 'a';
item.setTitle(`${commonTitle} ${amountOrArticle} ${timeUnit}`).onClick(() =>
this.postponeOnClickCallback(button, task, amount, timeUnit),
);
const title = postponeMenuItemTitle(task, amount, timeUnit);
item.setTitle(title).onClick(() => this.postponeOnClickCallback(button, task, amount, timeUnit));
};

menu.addItem((item) => postponeMenuItemCallback(item, 'days', 2));
menu.addItem((item) => postponeMenuItemCallback(item, 'days', 3));
menu.addItem((item) => postponeMenuItemCallback(item, 'days', 4));
menu.addItem((item) => postponeMenuItemCallback(item, 'days', 5));
menu.addItem((item) => postponeMenuItemCallback(item, 'days', 6));
menu.addSeparator();
menu.addItem((item) => postponeMenuItemCallback(item, 'week'));
menu.addItem((item) => postponeMenuItemCallback(item, 'weeks', 2));
menu.addItem((item) => postponeMenuItemCallback(item, 'weeks', 3));
menu.addItem((item) => postponeMenuItemCallback(item, 'month'));

menu.showAtPosition({ x: ev.clientX, y: ev.clientY });
Expand Down
20 changes: 20 additions & 0 deletions src/Scripting/Postponer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,23 @@ export function postponementSuccessMessage(postponedDate: Moment, dateFieldToPos
const postponedDateString = postponedDate?.format('DD MMM YYYY');
return `Task's ${dateFieldToPostpone} postponed until ${postponedDateString}`;
}

export function postponeButtonTitle(task: Task, amount: number, timeUnit: unitOfTime.DurationConstructor) {
const buttonText = postponeMenuItemTitle(task, amount, timeUnit);
return `ℹ️ ${buttonText} (right-click for more options)`;
}

export function postponeMenuItemTitle(task: Task, amount: number, timeUnit: unitOfTime.DurationConstructor) {
function capitalizeFirstLetter(word: string) {
return word.charAt(0).toUpperCase() + word.slice(1);
}
const updatedDateType = getDateFieldToPostpone(task)!;
const dateToUpdate = task[updatedDateType] as Moment;
const updatedDateDisplayText = capitalizeFirstLetter(updatedDateType.replace('Date', ''));

const postponedDate = new TasksDate(dateToUpdate).postpone(timeUnit, amount);
const formattedNewDate = postponedDate.format('ddd Do MMM');

const amountOrArticle = amount > 1 ? amount : 'a';
return `${updatedDateDisplayText} in ${amountOrArticle} ${timeUnit}, on ${formattedNewDate}`;
}
35 changes: 29 additions & 6 deletions tests/Scripting/Postponer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
type HappensDate,
createPostponedTask,
getDateFieldToPostpone,
postponeButtonTitle,
postponeMenuItemTitle,
postponementSuccessMessage,
shouldShowPostponeButton,
} from '../../src/Scripting/Postponer';
Expand All @@ -16,6 +18,16 @@ import { TaskBuilder } from '../TestingTools/TaskBuilder';

window.moment = moment;

const today = '2023-12-03';
beforeEach(() => {
jest.useFakeTimers();
jest.setSystemTime(new Date(today));
});

afterEach(() => {
jest.useRealTimers();
});

describe('postpone - date field choice', () => {
function checkPostponeField(taskBuilder: TaskBuilder, expected: HappensDate | null) {
const task = taskBuilder.build();
Expand Down Expand Up @@ -146,16 +158,27 @@ describe('postpone - whether to show button', () => {
});
});

describe('postpone - new task creation', () => {
beforeEach(() => {
jest.useFakeTimers();
jest.setSystemTime(new Date('2023-12-03'));
describe('postpone - UI text', () => {
it('should include date type and new date in button tooltip', () => {
const task = new TaskBuilder().dueDate(today).build();
expect(postponeButtonTitle(task, 1, 'day')).toEqual(
'ℹ️ Due in a day, on Mon 4th Dec (right-click for more options)',
);
expect(postponeButtonTitle(task, 2, 'days')).toEqual(
'ℹ️ Due in 2 days, on Tue 5th Dec (right-click for more options)',
);
});

afterEach(() => {
jest.useRealTimers();
it('should include date type and new date in context menu labels', () => {
const task = new TaskBuilder().dueDate(today).build();
// TODO This text is misleading if the date is already in the future.
// In that case, it should still be 'Postpone'???
expect(postponeMenuItemTitle(task, 1, 'day')).toEqual('Due in a day, on Mon 4th Dec');
expect(postponeMenuItemTitle(task, 2, 'days')).toEqual('Due in 2 days, on Tue 5th Dec');
});
});

describe('postpone - new task creation', () => {
function testPostponedTaskAndDate(task: Task, expectedDateField: HappensDate, expectedPostponedDate: string) {
const { postponedDate, postponedTask } = createPostponedTask(task, expectedDateField, 'day', 1);
expect(postponedDate.format('YYYY-MM-DD')).toEqual(expectedPostponedDate);
Expand Down

0 comments on commit ec4cb4b

Please sign in to comment.