@@ -78,7 +78,8 @@ function handleAutoDateManagerTransaction(
7878 const dateOperations = determineDateOperations (
7979 oldStatus ,
8080 newStatus ,
81- plugin
81+ plugin ,
82+ doc . line ( lineNumber ) . text
8283 ) ;
8384
8485 if ( dateOperations . length === 0 ) {
@@ -165,12 +166,14 @@ function findTaskStatusChange(tr: Transaction): {
165166 * @param oldStatus The old task status
166167 * @param newStatus The new task status
167168 * @param plugin The plugin instance
169+ * @param lineText The current line text to check for existing dates
168170 * @returns Array of date operations to perform
169171 */
170172function determineDateOperations (
171173 oldStatus : string ,
172174 newStatus : string ,
173- plugin : TaskProgressBarPlugin
175+ plugin : TaskProgressBarPlugin ,
176+ lineText : string
174177) : DateOperation [ ] {
175178 const operations : DateOperation [ ] = [ ] ;
176179 const settings = plugin . settings . autoDateManager ;
@@ -185,27 +188,21 @@ function determineDateOperations(
185188 return operations ;
186189 }
187190
188- // Remove old status date if it exists and is managed
191+ // Remove old status date if it exists and is managed (but never remove start date)
189192 if ( settings . manageCompletedDate && oldStatusType === "completed" ) {
190193 operations . push ( {
191194 type : "remove" ,
192195 dateType : "completed" ,
193196 } ) ;
194197 }
195- if ( settings . manageStartDate && oldStatusType === "inProgress" ) {
196- operations . push ( {
197- type : "remove" ,
198- dateType : "start" ,
199- } ) ;
200- }
201198 if ( settings . manageCancelledDate && oldStatusType === "abandoned" ) {
202199 operations . push ( {
203200 type : "remove" ,
204201 dateType : "cancelled" ,
205202 } ) ;
206203 }
207204
208- // Add new status date if it should be managed
205+ // Add new status date if it should be managed and doesn't already exist
209206 if ( settings . manageCompletedDate && newStatusType === "completed" ) {
210207 operations . push ( {
211208 type : "add" ,
@@ -214,11 +211,14 @@ function determineDateOperations(
214211 } ) ;
215212 }
216213 if ( settings . manageStartDate && newStatusType === "inProgress" ) {
217- operations . push ( {
218- type : "add" ,
219- dateType : "start" ,
220- format : settings . startDateFormat || "YYYY-MM-DD" ,
221- } ) ;
214+ // Only add start date if it doesn't already exist
215+ if ( ! hasExistingDate ( lineText , "start" , plugin ) ) {
216+ operations . push ( {
217+ type : "add" ,
218+ dateType : "start" ,
219+ format : settings . startDateFormat || "YYYY-MM-DD" ,
220+ } ) ;
221+ }
222222 }
223223 if ( settings . manageCancelledDate && newStatusType === "abandoned" ) {
224224 operations . push ( {
@@ -231,6 +231,38 @@ function determineDateOperations(
231231 return operations ;
232232}
233233
234+ /**
235+ * Checks if a specific date type already exists in the line
236+ * @param lineText The task line text
237+ * @param dateType The type of date to check for
238+ * @param plugin The plugin instance
239+ * @returns True if the date already exists
240+ */
241+ function hasExistingDate (
242+ lineText : string ,
243+ dateType : string ,
244+ plugin : TaskProgressBarPlugin
245+ ) : boolean {
246+ const useDataviewFormat =
247+ plugin . settings . preferMetadataFormat === "dataview" ;
248+
249+ if ( useDataviewFormat ) {
250+ const fieldName = dateType === "start" ? "start" : dateType ;
251+ const pattern = new RegExp (
252+ `\\[${ fieldName } ::\\s*\\d{4}-\\d{2}-\\d{2}(?:\\s+\\d{2}:\\d{2}(?::\\d{2})?)?\\]`
253+ ) ;
254+ return pattern . test ( lineText ) ;
255+ } else {
256+ const dateMarker = getDateMarker ( dateType , plugin ) ;
257+ const pattern = new RegExp (
258+ `${ escapeRegex (
259+ dateMarker
260+ ) } \\s*\\d{4}-\\d{2}-\\d{2}(?:\\s+\\d{2}:\\d{2}(?::\\d{2})?)?`
261+ ) ;
262+ return pattern . test ( lineText ) ;
263+ }
264+ }
265+
234266/**
235267 * Gets the status type (completed, inProgress, etc.) for a given status character
236268 * @param status The status character
@@ -294,8 +326,23 @@ function applyDateOperations(
294326 dateText = ` ${ dateMarker } ${ dateString } ` ;
295327 }
296328
297- // Find the end of the task content (before any existing dates)
298- const insertPosition = findDateInsertPosition ( lineText , plugin ) ;
329+ // Find the appropriate insert position based on date type
330+ let insertPosition : number ;
331+ if ( operation . dateType === "completed" ) {
332+ // Completed date goes at the end (before block reference ID)
333+ insertPosition = findCompletedDateInsertPosition (
334+ lineText ,
335+ plugin
336+ ) ;
337+ } else {
338+ // Start date and cancelled date go after existing metadata but before completed date
339+ insertPosition = findMetadataInsertPosition (
340+ lineText ,
341+ plugin ,
342+ operation . dateType
343+ ) ;
344+ }
345+
299346 const absolutePosition = line . from + insertPosition ;
300347
301348 changes . push ( {
@@ -316,12 +363,10 @@ function applyDateOperations(
316363 let datePattern : RegExp ;
317364
318365 if ( useDataviewFormat ) {
319- // For dataview format: [completion::2024-01-01] or [start ::2024-01-01]
366+ // For dataview format: [completion::2024-01-01] or [cancelled ::2024-01-01]
320367 const fieldName =
321368 operation . dateType === "completed"
322369 ? "completion"
323- : operation . dateType === "start"
324- ? "start"
325370 : operation . dateType === "cancelled"
326371 ? "cancelled"
327372 : "unknown" ;
@@ -330,7 +375,7 @@ function applyDateOperations(
330375 "g"
331376 ) ;
332377 } else {
333- // For emoji format: ✅ 2024-01-01 or 🚀 2024-01-01
378+ // For emoji format: ✅ 2024-01-01 or ❌ 2024-01-01
334379 const dateMarker = getDateMarker ( operation . dateType , plugin ) ;
335380 datePattern = new RegExp (
336381 `\\s*${ escapeRegex (
@@ -442,45 +487,85 @@ function getDateMarker(
442487}
443488
444489/**
445- * Finds the position where a new date should be inserted
490+ * Finds the position where metadata (start date, cancelled date, etc.) should be inserted
446491 * @param lineText The task line text
447492 * @param plugin The plugin instance
448- * @returns The position index where the date should be inserted
493+ * @param dateType The type of date being inserted
494+ * @returns The position index where the metadata should be inserted
449495 */
450- function findDateInsertPosition (
496+ function findMetadataInsertPosition (
451497 lineText : string ,
452- plugin : TaskProgressBarPlugin
498+ plugin : TaskProgressBarPlugin ,
499+ dateType : string
453500) : number {
454- // Find the end of the task content, before any existing dates or metadata
501+ // Find the end of the task content, right after the task description
455502 const taskMatch = lineText . match ( / ^ [ \s | \t ] * ( [ - * + ] | \d + \. ) \s \[ .\] \s * / ) ;
456503 if ( ! taskMatch ) return lineText . length ;
457504
458505 let position = taskMatch [ 0 ] . length ;
459506
460- const useDataviewFormat =
461- plugin . settings . preferMetadataFormat === "dataview" ;
507+ // Find the main task content (description) before any metadata
508+ const contentMatch = lineText
509+ . slice ( position )
510+ . match ( / ^ [ ^ \[ # @ 📅 🚀 ✅ ❌ ] * (? = \s * [ \[ # @ 📅 🚀 ✅ ❌ ] | $ ) / ) ;
511+ if ( contentMatch ) {
512+ position += contentMatch [ 0 ] . trimEnd ( ) . length ;
513+ }
462514
463- if ( useDataviewFormat ) {
464- // For dataview format, find the main task content before any dataview fields, tags, or metadata
465- const contentMatch = lineText
466- . slice ( position )
467- . match ( / ^ [ ^ \[ # @ ] * (? = \s * [ \[ # @ ] | $ ) / ) ;
468- if ( contentMatch ) {
469- position += contentMatch [ 0 ] . trimEnd ( ) . length ;
470- }
471- } else {
472- // For emoji format, find the main task content before any emoji dates, tags, or metadata
473- const contentMatch = lineText
474- . slice ( position )
475- . match ( / ^ [ ^ 📅 🚀 ✅ ❌ # @ \[ ] * (? = \s * [ 📅 🚀 ✅ ❌ # @ \[ ] | $ ) / ) ;
476- if ( contentMatch ) {
477- position += contentMatch [ 0 ] . trimEnd ( ) . length ;
515+ // If we're inserting a cancelled date, we need to find the position after existing start dates
516+ if ( dateType === "cancelled" ) {
517+ const useDataviewFormat =
518+ plugin . settings . preferMetadataFormat === "dataview" ;
519+
520+ // Look for existing start dates and position after them
521+ const remainingText = lineText . slice ( position ) ;
522+ let startDateEnd = 0 ;
523+
524+ if ( useDataviewFormat ) {
525+ const startDateMatch = remainingText . match ( / ^ \s * \[ s t a r t : : [ ^ \] ] * \] / ) ;
526+ if ( startDateMatch ) {
527+ startDateEnd = startDateMatch [ 0 ] . length ;
528+ }
529+ } else {
530+ const startMarker = getDateMarker ( "start" , plugin ) ;
531+ const startDatePattern = new RegExp (
532+ `^\\s*${ escapeRegex (
533+ startMarker
534+ ) } \\s*\\d{4}-\\d{2}-\\d{2}(?:\\s+\\d{2}:\\d{2}(?::\\d{2})?)?`
535+ ) ;
536+ const startDateMatch = remainingText . match ( startDatePattern ) ;
537+ if ( startDateMatch ) {
538+ startDateEnd = startDateMatch [ 0 ] . length ;
539+ }
478540 }
541+
542+ position += startDateEnd ;
479543 }
480544
481545 return position ;
482546}
483547
548+ /**
549+ * Finds the position where completed date should be inserted (at the end, before block reference ID)
550+ * @param lineText The task line text
551+ * @param plugin The plugin instance
552+ * @returns The position index where the completed date should be inserted
553+ */
554+ function findCompletedDateInsertPosition (
555+ lineText : string ,
556+ plugin : TaskProgressBarPlugin
557+ ) : number {
558+ // Look for block reference ID pattern (^block-id) at the end
559+ const blockRefMatch = lineText . match ( / \s * \^ [ \w - ] + \s * $ / ) ;
560+ if ( blockRefMatch ) {
561+ // Insert before the block reference ID
562+ return lineText . length - blockRefMatch [ 0 ] . length ;
563+ }
564+
565+ // If no block reference, insert at the very end
566+ return lineText . length ;
567+ }
568+
484569/**
485570 * Escapes special regex characters
486571 * @param string The string to escape
0 commit comments