diff --git a/__mocks__/DataStore.mock.js b/__mocks__/DataStore.mock.js index b96a9a459..6003f57c2 100644 --- a/__mocks__/DataStore.mock.js +++ b/__mocks__/DataStore.mock.js @@ -97,6 +97,9 @@ export const DataStore = { ] } }], */ // async referencedBlocks() { return null }, + updateCache(note, shouldUpdateTags = false) { + return note + }, // async saveData() { return null }, async saveJSON(object, filename) { __json = object diff --git a/dwertheimer.TaskAutomations/__tests__/NPTaskScanAndProcess.test.js b/dwertheimer.TaskAutomations/__tests__/NPTaskScanAndProcess.test.js index 4fd408093..293bf8231 100644 --- a/dwertheimer.TaskAutomations/__tests__/NPTaskScanAndProcess.test.js +++ b/dwertheimer.TaskAutomations/__tests__/NPTaskScanAndProcess.test.js @@ -152,13 +152,60 @@ describe(`${PLUGIN_NAME}`, () => { */ describe('handleArrowDatesAction()' /* function */, () => { test("should change the date to the user's choice of >date", async () => { - const before = { content: '>2022-12-31 task content' } + const note = new Note({ filename: 'folder/note.md', type: 'Notes' }) + const before = { content: '>2022-12-31 task content', type: 'open', note } const userChoice = '>2023-01-01' const result = await handleArrowDatesAction(before, userChoice) expect(result).toEqual(CONTINUE) expect(before.content).toEqual('task content >2023-01-01') }) + + test('should leave the type alone for an item in a regular note (lite method)', async () => { + const note = new Note({ filename: 'folder/note.md', type: 'Notes' }) + const before = { content: '>2022-12-31 task content', type: 'open', note } + + await handleArrowDatesAction(before, '>2023-01-01') + expect(before.type).toEqual('open') + }) + + test('should leave the type alone in a calendar note when useLiteScheduleMethod is set', async () => { + DataStore.settings = { ...DataStore.settings, useLiteScheduleMethod: true } + const note = new Note({ filename: '20221231.md', type: 'Calendar' }) + const before = { content: '>2022-12-31 task content', type: 'open', note } + + await handleArrowDatesAction(before, '>2023-01-01') + expect(before.content).toEqual('task content >2023-01-01') + expect(before.type).toEqual('open') + DataStore.settings = { ...DataStore.settings, useLiteScheduleMethod: false } + }) + + test("should mark a calendar note item scheduled and add a back-linked copy in the destination note (NotePlan's full method)", async () => { + const originNote = new Note({ filename: '20221231.md', type: 'Calendar' }) + const destNote = new Note({ filename: '20230101.md', type: 'Calendar' }) + destNote.insertParagraph = jest.fn() + DataStore.calendarNoteByDateString = jest.fn().mockReturnValue(destNote) + const before = { content: '>2022-12-31 task content', type: 'open', note: originNote } + + const result = await handleArrowDatesAction(before, '>2023-01-01') + expect(result).toEqual(CONTINUE) + // original is marked [>] and points at the new date + expect(before.content).toEqual('task content >2023-01-01') + expect(before.type).toEqual('scheduled') + // and a copy back-linking to the original date lands in the destination note + expect(destNote.insertParagraph).toHaveBeenCalledWith('task content <2022-12-31', expect.any(Number), 'open') + }) + + test('should use checklistScheduled for a checklist item taking the full method', async () => { + const originNote = new Note({ filename: '20221231.md', type: 'Calendar' }) + const destNote = new Note({ filename: '20230101.md', type: 'Calendar' }) + destNote.insertParagraph = jest.fn() + DataStore.calendarNoteByDateString = jest.fn().mockReturnValue(destNote) + const before = { content: '>2022-12-31 task content', type: 'checklist', note: originNote } + + await handleArrowDatesAction(before, '>2023-01-01') + expect(before.type).toEqual('checklistScheduled') + }) }) }) }) diff --git a/dwertheimer.TaskAutomations/changelog.md b/dwertheimer.TaskAutomations/changelog.md index 369b629f9..ac7863417 100644 --- a/dwertheimer.TaskAutomations/changelog.md +++ b/dwertheimer.TaskAutomations/changelog.md @@ -7,8 +7,11 @@ NOTE: A COUPLE OF RELEASES AFTER 3.0.0 DELETE THE SETTINGS THAT PERTAIN TO TASKS -## [3.1.1] (upcoming) +## [3.2.0] (upcoming) +- Fix (#741): rescheduling an overdue item to a `>date` now uses NotePlan's normal scheduling method for items in calendar notes: the original is marked `[>]` (`scheduled`/`checklistScheduled`) **and** a copy back-linking to the original date (`date` was rewritten, so the item never showed the `[>]` marker. Note: marking the original `[>]` *without* adding the copy would make the item vanish from the new date, so the two always happen together. +- New setting "Use simplified (re)scheduling method?" — if set, rescheduling just updates the `>date` in place (the previous behaviour) with no `[>]` marker and no copy. Items in regular (non-calendar) notes always use this simplified method, matching NotePlan's "Schedule by linking in regular notes" preference. +- New settings "Section heading to add rescheduled tasks under" and "Heading level for new headings", controlling where the copy is placed in the destination note. These mirror the equivalent settings in the Dashboard plugin. - Fix: "This week" and "Next week" in the follow-up date picker now respect NotePlan's "Start Week On" setting. Previously, when the week started on Monday (or any day other than Sunday), "this week" pointed to the previous week and "next week" to the current week (fix in shared `helpers/NPdateTime.js` getWeekOptions). ## [3.0.0] @dwertheimer 2024-03-26 diff --git a/dwertheimer.TaskAutomations/plugin.json b/dwertheimer.TaskAutomations/plugin.json index 7f2b70836..a97939e22 100644 --- a/dwertheimer.TaskAutomations/plugin.json +++ b/dwertheimer.TaskAutomations/plugin.json @@ -5,8 +5,8 @@ "plugin.name": "✅ Overdue Task Processing", "plugin.description": "Automations for handling Tasks:\n- Overdue/Forgotten task scanning\n- Task sorting within a note\n- Copying #tags/@mentions from one task to another\n- Mark all tasks in note open/completed\n- Automatically opening URLs of task lines", "plugin.author": "@dwertheimer", - "plugin.version": "3.1.1", - "plugin.lastUpdateInfo": "Fix: 'This week' and 'Next week' in the follow-up date picker now respect NotePlan's 'Start Week On' setting. Previously, when the week started on Monday (or any day other than Sunday), 'this week' pointed to the previous week and 'next week' to the current week.", + "plugin.version": "3.2.0", + "plugin.lastUpdateInfo": "Rescheduling an overdue item in a calendar note now uses NotePlan's normal scheduling method: the original is marked '[>]' and a back-linked copy is added to the destination note. Set 'Use simplified (re)scheduling method?' to keep the previous in-place behaviour. Also: 'This week' and 'Next week' in the follow-up date picker now respect NotePlan's 'Start Week On' setting.", "DELETE_ME": "DON'T DELETE commandMigrationMessage or offerToDownloadPlugin until they are documented somewhere", "commandMigrationMessage": "NOTE: Task Sorting commands have been moved from the Task Automations plugin to the Task Sorting plugin. This plugin has been renamed to 'Overdue Task Processing'.", "offerToDownloadPlugin": { @@ -428,6 +428,30 @@ "type": "heading", "title": "Overdue Task Scan Settings" }, + { + "key": "useLiteScheduleMethod", + "title": "Use simplified (re)scheduling method?", + "description": "When you reschedule a task to a >date during a review, if set then the item simply has its '>date' updated in the note it is in. It does not show with the special 🕓 [>] task icon, and a copy isn't added into the date it's being scheduled to. If not set, NotePlan's normal method is used: the original is marked '[>] task >newDate' and a copy '[ ] task >' or '<>' to always add at the top/bottom, or '<>' to maintain the current hierarchy of headings in the new note.", + "type": "string", + "default": "", + "required": false + }, + { + "key": "newTaskSectionHeadingLevel", + "title": "Heading level for new headings", + "description": "Heading level (1-5) to use when adding new headings in notes. Note: you can also set this to 0, which means add the task under the heading, but only if it already exists.", + "type": "number", + "default": 2, + "required": true + }, { "key": "numLastUsedChoices", "title": "Show last N frequently used rescheduling choices at top", diff --git a/dwertheimer.TaskAutomations/src/NPTaskScanAndProcess.js b/dwertheimer.TaskAutomations/src/NPTaskScanAndProcess.js index 48e8a046f..90db7d0bb 100644 --- a/dwertheimer.TaskAutomations/src/NPTaskScanAndProcess.js +++ b/dwertheimer.TaskAutomations/src/NPTaskScanAndProcess.js @@ -4,6 +4,7 @@ import moment from 'moment/min/moment-with-locales' import pluginJson from '../plugin.json' import { moveParagraphToNote } from '@helpers/NPMoveItems' import { getOverdueParagraphs } from '@helpers/NPParagraph' +import { scheduleItem, scheduleItemLiteMethod } from '@helpers/NPScheduleItems' import { getNPWeekData, getWeekOptions } from '@helpers/NPdateTime' import { filterNotesAgainstExcludeFolders, noteType } from '@helpers/note' import { getReferencedParagraphs, getTodaysReferences } from '@helpers/NPnote' @@ -320,12 +321,30 @@ export async function handleOpenTaskAction(origPara: TParagraph): Promisedate export async function handleArrowDatesAction(origPara: TParagraph, userChoice: string, optionChosen?: CommandBarChoice): Promise { const cmdPressed = optionChosen ? optionChosen.keyModifiers?.length && optionChosen.keyModifiers.includes('cmd') : false - origPara.content = replaceArrowDatesInString(origPara.content, cmdPressed ? '' : userChoice) - updateParagraph(origPara) // Note: after origPara is updated, the pointer is no longer good in Obj-C if (cmdPressed) { + // CMD means "move this task to that note", so strip the >date here and let processCmdKey() do the move + origPara.content = replaceArrowDatesInString(origPara.content, '') + updateParagraph(origPara) // Note: after origPara is updated, the pointer is no longer good in Obj-C logDebug(pluginJson, `handleArrowDatesAction: keyModifiers: ${optionChosen ? optionChosen.keyModifiers.toString() : ''}`) const updatedPara = origPara.note?.paragraphs[origPara.lineIndex] || origPara // get the updated paragraph await processCmdKey(updatedPara, userChoice) + return CONTINUE + } + + // Two ways to (re)schedule, matching jgclark.Dashboard::doRescheduleItem(): + // - 'lite': just update the >date in place, leaving the type as open/checklist so the item still shows on the new date. + // - NotePlan's full method: mark the original 'scheduled'/'checklistScheduled' ([>]) *and* add a copy in the destination + // calendar note that back-links to the original date ('/, '') + const useLite = origPara.note?.type === 'Notes' || useLiteScheduleMethod + logDebug(pluginJson, `handleArrowDatesAction: scheduling to '${dateStrToAdd}' using ${useLite ? 'lite' : 'NP full'} method`) + if (useLite) { + scheduleItemLiteMethod(origPara, dateStrToAdd) + } else { + scheduleItem(origPara, dateStrToAdd, newTaskSectionHeading ?? '', Number(newTaskSectionHeadingLevel ?? 2)) } return CONTINUE }