Skip to content

Commit adace93

Browse files
authored
Merge pull request #1958 from ilandikov/feat-next-recurrence-on-line-below
feat: Optionally put next recurrence below current task
2 parents 8b7adf5 + a69c364 commit adace93

File tree

10 files changed

+129
-8
lines changed

10 files changed

+129
-8
lines changed

docs/Getting Started/Recurring Tasks.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ For example: `🔁 every weekday` means the task will repeat every week on Monda
1212
Every recurrence rule has to start with the word `every`.
1313

1414
When you toggle the status of a recurring task to anything but "todo" (i.e. "done"), the original task that you wanted to toggle will be marked as done and get the done date appended to it, like any other task.
15-
In addition, _a new task will be put one line above the original task._
15+
16+
In addition, _a new task will be put one line above the original task_ by default.
17+
18+
See [[#Location of the new task]] below, for how to change this behaviour and make the new task appear _on the line below the original task_.
19+
1620
The new task will have updated dates based off the original task.
1721

1822
### Basic Example
@@ -70,6 +74,17 @@ You can validate that tasks understands your rule by using the `Tasks: Create or
7074

7175
---
7276

77+
## Location of the new task
78+
79+
Use this setting to control where the recurring task is inserted. The default is to put the new task before the original one.
80+
81+
![Setting for next recurrence appearance](../../images/settings-recurrence-location.png)
82+
83+
> [!released]
84+
> Control of the location of the new task was introduced in Tasks X.Y.Z
85+
86+
---
87+
7388
## Repeating a Task Based on the Original Due Date or the Completion Date
7489

7590
When you create a recurring task, you can decide whether the next occurrence should be based on the original dates or the date when you completed the task.

docs/Getting Started/Settings.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ For convenience, here is a list of all those documentation pages (in the order t
2626
- [[Global Query]]
2727
- [[Status Settings]]
2828
- [[Dates#Date-tracking settings]]
29+
- [[Recurring Tasks#Location of the new task]]
2930
- [[Auto-Suggest]]
3031
- [[Create or edit Task#Turning off keyboard shortcuts]]
29.8 KB
Loading

src/Commands/ToggleDone.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ export const toggleLine = (line: string, path: string): EditorInsertion => {
7272
fallbackDate: null, // We don't need this to toggle it here in the editor.
7373
});
7474
if (task !== null) {
75-
const lines = task.toggle().map((t) => t.toFileLineString());
75+
const lines = task.toggleWithRecurrenceInUsersOrder().map((t) => t.toFileLineString());
7676
return { text: lines.join('\n'), moveTo: { line: lines.length - 1 } };
7777
} else {
7878
// If the task is null this means that we have one of:

src/Config/Settings.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export interface Settings {
6262
provideAccessKeys: boolean;
6363
useFilenameAsScheduledDate: boolean;
6464
filenameAsDateFolders: string[];
65+
recurrenceOnNextLine: boolean;
6566

6667
// The custom status states.
6768
statusSettings: StatusSettings;
@@ -91,6 +92,7 @@ const defaultSettings: Settings = {
9192
provideAccessKeys: true,
9293
useFilenameAsScheduledDate: false,
9394
filenameAsDateFolders: [],
95+
recurrenceOnNextLine: false,
9496
statusSettings: new StatusSettings(),
9597
features: Feature.settingsFlags,
9698
generalSettings: {

src/Config/SettingsTab.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,23 @@ export class SettingsTab extends PluginSettingTab {
223223
});
224224
});
225225

226+
// ---------------------------------------------------------------------------
227+
containerEl.createEl('h4', { text: 'Recurring task Settings' });
228+
// ---------------------------------------------------------------------------
229+
230+
new Setting(containerEl)
231+
.setName('Next recurrence appears on the line below')
232+
.setDesc(
233+
'Enabling this will make the next recurrence of a task appear on the line below the completed task. Otherwise the next recurrence will appear before the completed one.',
234+
)
235+
.addToggle((toggle) => {
236+
const { recurrenceOnNextLine: recurrenceOnNextLine } = getSettings();
237+
toggle.setValue(recurrenceOnNextLine).onChange(async (value) => {
238+
updateSettings({ recurrenceOnNextLine: value });
239+
await this.plugin.saveSettings();
240+
});
241+
});
242+
226243
// ---------------------------------------------------------------------------
227244
containerEl.createEl('h4', { text: 'Auto-suggest Settings' });
228245
// ---------------------------------------------------------------------------

src/LivePreviewExtension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ class LivePreviewExtension implements PluginValue {
7575
event.preventDefault();
7676

7777
// Clicked on a task's checkbox. Toggle the task and set it.
78-
const toggled = task.toggle();
78+
const toggled = task.toggleWithRecurrenceInUsersOrder();
7979
const toggledString = toggled.map((t) => t.toFileLineString()).join(state.lineBreak);
8080

8181
// Creates a CodeMirror transaction in order to update the document.

src/Task.ts

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,12 +291,20 @@ export class Task {
291291
}
292292

293293
/**
294-
* Toggles this task and returns the resulting tasks.
294+
* Toggles this task and returns the resulting task(s).
295+
*
296+
* Use this method if you need to know which is the original (completed)
297+
* task and which is the new recurrence.
298+
*
299+
* If the task is not recurring, it will return `[toggled]`.
295300
*
296301
* Toggling can result in more than one returned task in the case of
297-
* recurrence. If it is a recurring task, the toggled task will be returned
298-
* together with the next occurrence in the order `[next, toggled]`. If the
299-
* task is not recurring, it will return `[toggled]`.
302+
* recurrence. In this case, the toggled task will be returned
303+
* together with the next occurrence in the order `[next, toggled]`.
304+
*
305+
* There is a possibility to use user set order `[next, toggled]`
306+
* or `[toggled, next]` - {@link toggleWithRecurrenceInUsersOrder}.
307+
*
300308
*/
301309
public toggle(): Task[] {
302310
const newStatus = StatusRegistry.getInstance().getNextStatusOrCreate(this.status);
@@ -356,6 +364,30 @@ export class Task {
356364
return newTasks;
357365
}
358366

367+
/**
368+
* Toggles this task and returns the resulting task(s).
369+
*
370+
* Use this method if the updated task(s) are to be saved,
371+
* as this honours the user setting to control the order
372+
* the tasks should be saved in.
373+
*
374+
* If the task is not recurring, it will return `[toggled]`.
375+
*
376+
* Toggling can result in more than one returned task in the case of
377+
* recurrence. In this case, the toggled task will be returned in
378+
* user set order `[next, toggled]` or `[toggled, next]` depending
379+
* on {@link Settings}.
380+
*
381+
* If there is no need to consider user settings call {@link toggle}.
382+
*
383+
*/
384+
public toggleWithRecurrenceInUsersOrder(): Task[] {
385+
const newTasks = this.toggle();
386+
387+
const { recurrenceOnNextLine: recurrenceOnNextLine } = getSettings();
388+
return recurrenceOnNextLine ? newTasks.reverse() : newTasks;
389+
}
390+
359391
public get urgency(): number {
360392
if (this._urgency === null) {
361393
this._urgency = Urgency.calculate(this);

src/TaskLineRenderer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ export async function renderTaskLine(
8989

9090
// Should be re-rendered as enabled after update in file.
9191
checkbox.disabled = true;
92-
const toggledTasks = task.toggle();
92+
const toggledTasks = task.toggleWithRecurrenceInUsersOrder();
9393
replaceTaskWithTasks({
9494
originalTask: task,
9595
newTasks: toggledTasks,

tests/Task.test.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,6 +1122,60 @@ describe('set correct created date on reccurence task', () => {
11221122
});
11231123
});
11241124

1125+
describe('next task recurrence appearance', () => {
1126+
beforeAll(() => {
1127+
jest.useFakeTimers();
1128+
jest.setSystemTime(new Date(2023, 5 - 1, 16));
1129+
resetSettings();
1130+
});
1131+
1132+
afterAll(() => {
1133+
jest.useRealTimers();
1134+
resetSettings();
1135+
});
1136+
1137+
it('new task shall appear on previous line by default', () => {
1138+
// Arrange
1139+
const task = fromLine({ line: '- [ ] this is a recurring task 🔁 every day' });
1140+
1141+
// Act
1142+
const lines = task.toggleWithRecurrenceInUsersOrder().map((t) => t.toFileLineString());
1143+
1144+
// Assert
1145+
expect(lines.length).toEqual(2);
1146+
expect(lines[0]).toMatchInlineSnapshot('"- [ ] this is a recurring task 🔁 every day"');
1147+
expect(lines[1]).toMatchInlineSnapshot('"- [x] this is a recurring task 🔁 every day ✅ 2023-05-16"');
1148+
});
1149+
1150+
it('new task shall appear on next line with the setting set to false', () => {
1151+
// Arrange
1152+
const task = fromLine({ line: '- [ ] this is a recurring task 🔁 every day' });
1153+
updateSettings({ recurrenceOnNextLine: false });
1154+
1155+
// Act
1156+
const lines = task.toggleWithRecurrenceInUsersOrder().map((t) => t.toFileLineString());
1157+
1158+
// Assert
1159+
expect(lines.length).toEqual(2);
1160+
expect(lines[0]).toMatchInlineSnapshot('"- [ ] this is a recurring task 🔁 every day"');
1161+
expect(lines[1]).toMatchInlineSnapshot('"- [x] this is a recurring task 🔁 every day ✅ 2023-05-16"');
1162+
});
1163+
1164+
it('new task shall appear on next line with the setting set to true', () => {
1165+
// Arrange
1166+
const task = fromLine({ line: '- [ ] this is a recurring task 🔁 every day' });
1167+
updateSettings({ recurrenceOnNextLine: true });
1168+
1169+
// Act
1170+
const lines = task.toggleWithRecurrenceInUsersOrder().map((t) => t.toFileLineString());
1171+
1172+
// Assert
1173+
expect(lines.length).toEqual(2);
1174+
expect(lines[0]).toMatchInlineSnapshot('"- [x] this is a recurring task 🔁 every day ✅ 2023-05-16"');
1175+
expect(lines[1]).toMatchInlineSnapshot('"- [ ] this is a recurring task 🔁 every day"');
1176+
});
1177+
});
1178+
11251179
declare global {
11261180
namespace jest {
11271181
interface Matchers<R> {

0 commit comments

Comments
 (0)