Skip to content

Commit 8646268

Browse files
committed
Hook up addTaskToNote()
1 parent 15efd1b commit 8646268

5 files changed

Lines changed: 118 additions & 34 deletions

File tree

.cursor/rules/np-programming-general.mdc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ CRITICAL: Never use dynamic imports. Rollup will not process them correctly. Alw
1616
- Use async/await and handle promises properly (no floating promises)
1717
- Follow existing naming patterns in the codebase
1818
- Proper error handling with try/catch blocks
19-
- Follow import order: external libs -> internal libs -> local files
19+
- Follow import order: external libs -> internal libs -> local files. Within that put in alphabetical order of source filename.
2020
- Keep code DRY and modular with clear function responsibilities
2121
- Add JSDoc comments for all functions
2222
- Always research the `helpers/` folder before writing new code

jgclark.Dashboard/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ For more details see the [plugin's documentation](https://github.com/NotePlan/pl
99
- TODO: fix isNoteFromAllowedFolder() for teamspace or possibly 2025-W21.md
1010
-->
1111

12+
## [2.4.0.b19] 2026-01-25
13+
- finish hooking up addTaskToNote
14+
- dev: refactor getOpenItemParasForTimePeriod() and add tests to the new smaller functions.
15+
1216
## [2.4.0.b18] 2026-01-24
1317
### New
1418
- New Section "Active Projects", which shows a list of all the currently-active projects from the separate Project & Reviews plugin. This includes any currently open projects (i.e. not completed, cancelled or paused) that match its settings, or are included in the current Perspective, if that option is set. It's designed to complement the existing Projects Section. It also shows the first of any 'Next Actions' as defined by the settings in the Projects plugin.

jgclark.Dashboard/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"plugin.description": "A Dashboard for NotePlan, that in one place shows:\n- a compact list of open tasks and checklists from today's note\n- scheduled open tasks and checklists from other notes.\n- similarly for yesterday's note, tomorrow's note, and the weekly, monthly and quarterly notes too (if used)\n- all overdue tasks\n- all open tasks and checklists that contain particular @tags or #mentions of your choosing\n- the next notes ready to review (if you use the 'Projects and Reviews' plugin).\nIt includes many other ways of speeding up managing your tasks: see the website for more details.",
99
"plugin.author": "@jgclark",
1010
"plugin.comment": "TODO: On full release, change minAppVersion down to 3.7?",
11-
"plugin.version": "2.4.0.b18",
11+
"plugin.version": "2.4.0.b19",
1212
"plugin.releaseStatus": "beta",
1313
"plugin.hidden": false,
1414
"plugin.lastUpdateInfo": "2.4.0: new 'Spaces to Include' setting which controls which (Team)Spaces you wish to include, plus whether or not to include the Private 'Space' (all notes not in a Space)\n2.3.3: new 'Year' section available.\n2.3.2: fix display when there are no priority items shown.\n2.3.1: fix for possible loss of settings error when upgrading.\n2.3.0: Support for NotePlan (Team)Spaces. Can re-order display of Sections.New '/backupSettings' command. Added 'noteTags' feature. Speeded up Tag/Mention sections. Layout improvements. Lots of other small fixes and improvements.\n2.2.1: Add new sorting option for Tag and Overdue sections.\n2.2.0: Add 'Search' section. New keyboard shortcuts. Plus many small improvements, bug fixes and performance improvements. See documentation for details.\n2.1.10: More move-under-heading options. Bug fixes and performance improvements.\n2.1.9: performance improvements and better UI for iPhone users.\n2.1.8: various fixes and small improvements.\n2.1.7: various fixes and small improvements.\n2.1.6: allow all current timeblocks to be shown, not just the first. Add new @repeat()s if using the extended syntax from the Repeat Extensions plugin. Bug fixes.\n2.1.5: fixes to time blocks and scheduling items.\n2.1.4: fix to Interactive Processing, and Edit All Perspectives dialog now shows unsaved changes.",

jgclark.Dashboard/src/react/components/Header/AddToAnyNote.jsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55

66
import React, { useState, useCallback, useMemo, useEffect, useRef } from 'react'
77
import { useAppContext } from '../AppContext.jsx'
8+
import { logTimer } from '@helpers/dev'
89
import DynamicDialog, { type TSettingItem } from '@helpers/react/DynamicDialog/DynamicDialog'
910
import type { NoteOption } from '@helpers/react/DynamicDialog/NoteChooser'
1011
import { logDebug, logError } from '@helpers/react/reactDev.js'
11-
import { logTimer } from '@helpers/dev'
1212
import { getElementCoordinates } from '@helpers/react/reactUtils.js'
1313
import './AddToAnyNote.css' // Import CSS for dialog positioning
1414

@@ -23,8 +23,7 @@ type Props = {
2323
* @param {Props} props
2424
* @returns {React$Node}
2525
*/
26-
const AddToAnyNote = React.memo(
27-
({ sendActionToPlugin }: Props): React$Node => {
26+
const AddToAnyNoteComponent = ({ sendActionToPlugin }: Props): React$Node => {
2827
const { dispatch } = useAppContext()
2928
// ----------------------------------------------------------------------
3029
// State
@@ -105,7 +104,14 @@ const AddToAnyNote = React.memo(
105104
pendingRequestsRef.current.delete(correlationId)
106105
clearTimeout(pending.timeoutId)
107106
if (success) {
108-
pending.resolve(data)
107+
// Resolve with full payload so caller can access success, message (via error field), and data
108+
// Note: The payload structure is { correlationId, success, data, error }
109+
// We reconstruct the RequestResponse object for the caller
110+
pending.resolve({
111+
success: true,
112+
message: error || undefined, // error field contains the message when success=true
113+
data: data,
114+
})
109115
} else {
110116
pending.reject(new Error(error || 'Request failed'))
111117
}
@@ -647,13 +653,17 @@ const AddToAnyNote = React.memo(
647653
)}
648654
</>
649655
)
650-
},
651-
(prevProps, nextProps) => {
652-
// Custom comparison: only re-render if sendActionToPlugin reference changes
653-
// This prevents re-renders when parent re-renders but props haven't changed
654-
return prevProps.sendActionToPlugin === nextProps.sendActionToPlugin
655-
},
656-
)
656+
}
657+
658+
// Custom comparison function for memoization
659+
const arePropsEqual = (prevProps: Props, nextProps: Props): boolean => {
660+
// Only re-render if sendActionToPlugin reference changes
661+
// This prevents re-renders when parent re-renders but props haven't changed
662+
return prevProps.sendActionToPlugin === nextProps.sendActionToPlugin
663+
}
664+
665+
// Memoize the component with custom comparison function
666+
const AddToAnyNote: React$ComponentType<Props> = React.memo(AddToAnyNoteComponent, arePropsEqual)
657667

658668
AddToAnyNote.displayName = 'AddToAnyNote'
659669

jgclark.Dashboard/src/requestHandlers/addTaskToNote.js

Lines changed: 91 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22
//--------------------------------------------------------------------------
33
// Request Handler: addTaskToNote
44
// Adds a task to a specified note
5+
// Last updated 2026-01-25 for v2.4.0.b19 by @jgclark
56
//--------------------------------------------------------------------------
67

7-
import pluginJson from '../../plugin.json'
8+
import { getDashboardSettings } from '../dashboardHelpers'
9+
import { isValidCalendarNoteFilename, convertISOToYYYYMMDD, isDailyDateStr } from '@helpers/dateTime'
810
import { logDebug, logError } from '@helpers/dev'
11+
import { coreAddTaskToNoteHeading } from '@helpers/NPAddItems'
12+
import { getNoteFromFilename } from '@helpers/NPnote'
13+
import { processChosenHeading } from '@helpers/userInput'
914

1015
// RequestResponse type definition
1116
export type RequestResponse = {
@@ -16,43 +21,108 @@ export type RequestResponse = {
1621

1722
/**
1823
* Add a task to a specified note
19-
* TODO(@jgclark): Implement actual task adding logic here
20-
* This could call doAddItem or similar function from clickHandlers
21-
*
2224
* @param {Object} params - Request parameters
2325
* @param {string} params.filename - Filename of the note to add the task to
2426
* @param {string} params.taskText - The task text to add
2527
* @param {string?} params.heading - Optional heading to add the task under
26-
* @param {string?} params.space - Optional space ID (for teamspace notes)
28+
* @param {string?} params.space - Optional space ID (empty string for Private, teamspace ID for teamspace notes)
2729
* @param {Object} pluginJson - Plugin JSON object for logging
2830
* @returns {RequestResponse}
2931
*/
30-
export function addTaskToNote(params: { filename: string, taskText: string, heading?: ?string, space?: ?string }, pluginJson: any): RequestResponse {
32+
export async function addTaskToNote(params: { filename: string, taskText: string, heading?: ?string, space?: ?string }, pluginJson: any): Promise<RequestResponse> {
3133
try {
3234
const { filename, taskText, heading, space } = params
3335
logDebug('Dashboard/requestHandlers] addTaskToNote', `Starting with filename="${filename}", taskText="${taskText}", heading="${heading || 'none'}", space="${space || 'private'}"`)
3436

35-
// TODO(@jgclark): Implement actual task adding logic here
36-
// This could call doAddItem or similar function from clickHandlers
37-
// Or import some code from /qath or something else -- you know best
37+
// Validate inputs
38+
if (!filename || !taskText || !taskText.trim()) {
39+
return {
40+
success: false,
41+
message: 'Filename and task text are required',
42+
data: null,
43+
}
44+
}
45+
46+
// Normalize filename: convert ISO date format (YYYY-MM-DD) to NotePlan format (YYYYMMDD) if needed
47+
// Check if filename (without extension) is a daily date string in ISO format
48+
const filenameWithoutExt = filename.replace(/\.(md|txt)$/, '')
49+
let normalizedFilename = filename
50+
if (isDailyDateStr(filenameWithoutExt)) {
51+
// Convert ISO format to NotePlan format for calendar notes
52+
const convertedDate = convertISOToYYYYMMDD(filenameWithoutExt)
53+
if (convertedDate !== filenameWithoutExt) {
54+
// Conversion happened - reconstruct filename with NotePlan format
55+
const ext = filename.match(/\.(md|txt)$/)?.[0] || '.md'
56+
normalizedFilename = `${convertedDate}${ext}`
57+
logDebug('[Dashboard/requestHandlers] addTaskToNote', `Converted ISO date filename "${filename}" to NotePlan format "${normalizedFilename}"`)
58+
}
59+
}
60+
61+
// Get dashboard settings for heading configuration
62+
const config = await getDashboardSettings()
63+
if (!config) {
64+
return {
65+
success: false,
66+
message: 'Failed to load dashboard settings',
67+
data: null,
68+
}
69+
}
70+
71+
// Get the note - handle teamspace notes if space is provided
72+
let destNote = null
73+
if (space && space !== '' && space !== 'Private') {
74+
// Teamspace note - use DataStore APIs with teamspace ID
75+
const isCalendarNote = isValidCalendarNoteFilename(normalizedFilename)
76+
if (isCalendarNote) {
77+
// Extract date string from normalized filename (without extension) for calendarNoteByDateString
78+
const dateStr = normalizedFilename.replace(/\.(md|txt)$/, '')
79+
// calendarNoteByDateString accepts both ISO (YYYY-MM-DD) and NotePlan (YYYYMMDD) formats,
80+
// but we've normalized to NotePlan format, so use that
81+
destNote = DataStore.calendarNoteByDateString(dateStr, space)
82+
} else {
83+
destNote = DataStore.noteByFilename(normalizedFilename, 'Notes', space)
84+
}
85+
} else {
86+
// Private note - use getNoteFromFilename helper which handles both regular and calendar notes
87+
// getNoteFromFilename should handle ISO format conversion internally, but we'll use normalized filename for consistency
88+
destNote = getNoteFromFilename(normalizedFilename)
89+
}
90+
91+
if (!destNote) {
92+
return {
93+
success: false,
94+
message: `Unable to locate note: ${filename}${space && space !== '' && space !== 'Private' ? ` in teamspace ${space}` : ''}`,
95+
data: null,
96+
}
97+
}
98+
99+
// Process heading if provided, otherwise use default from config
100+
const newHeadingLevel = config.newTaskSectionHeadingLevel || 2
101+
const headingToUse = heading
102+
? await processChosenHeading(destNote, heading, newHeadingLevel)
103+
: config.newTaskSectionHeading || ''
38104

39-
// you may want to trigger a refresh of the appropriate section before returning a value
105+
// Add the task to the note
106+
// logDebug('[Dashboard/requestHandlers] addTaskToNote', `Adding task to note: "${destNote?.title || '?'}" with heading: "${headingToUse}"`)
107+
const resultingPara = coreAddTaskToNoteHeading(destNote, headingToUse, taskText.trim(), newHeadingLevel, true)
108+
// logDebug('[Dashboard/requestHandlers] addTaskToNote', `Resulting paragraph: "${resultingPara?.rawContent || '?'}"`)
109+
110+
if (!resultingPara) {
111+
return {
112+
success: false,
113+
message: 'Failed to add task to note',
114+
data: null,
115+
}
116+
}
40117

41-
// For now, returning a message indicating it's not implemented yet, with the data so @jgclark knows what to implement
118+
logDebug('Dashboard/requestHandlers] addTaskToNote', `Successfully added task "${taskText}" to note "${filename}" under heading "${headingToUse || 'default'}"`)
42119
return {
43-
success: false,
44-
message: `⚠️ This request was sent to the backend but needs @jgclark to implement it in the new addTaskToNote() request handler. Data received:\n${JSON.stringify({
45-
filename,
46-
taskText,
47-
heading: heading || null,
48-
space: space || null,
49-
})}`,
50-
// Sending the data field back may not be strictly necessary unless you want to do an optomistic update
51-
// Just here as an example
120+
success: true,
121+
message: `Task added successfully to ${destNote.title || filename}`,
52122
data: {
53123
filename,
54124
taskText,
55-
heading: heading || null,
125+
heading: headingToUse || null,
56126
space: space || null,
57127
},
58128
}

0 commit comments

Comments
 (0)