66 ViewUpdate ,
77 WidgetType ,
88} from "@codemirror/view" ;
9- import { EditorState , Range , StateField } from "@codemirror/state" ;
9+ import { EditorState , Range , StateField , StateEffect } from "@codemirror/state" ;
1010import { TFile , App , MetadataCache , editorInfoField , editorEditorField } from "obsidian" ;
1111import { TaskTimerSettings } from "../common/setting-definition" ;
1212import { TaskTimerMetadataDetector } from "../utils/TaskTimerMetadataDetector" ;
@@ -38,7 +38,7 @@ class TaskTimerWidget extends WidgetType {
3838 private readonly lineFrom : number ,
3939 private readonly lineTo : number ,
4040 private readonly filePath : string ,
41- private readonly existingBlockId ?: string
41+ private existingBlockId ?: string
4242 ) {
4343 super ( ) ;
4444 }
@@ -91,7 +91,7 @@ class TaskTimerWidget extends WidgetType {
9191 } ) ;
9292 } else {
9393 // Show elapsed time
94- const elapsedMs = Date . now ( ) - this . timerState . startTime + ( this . timerState . elapsed || 0 ) ;
94+ const elapsedMs = Date . now ( ) - this . timerState . startTime + this . timerState . totalPausedDuration ;
9595 const formattedTime = TaskTimerFormatter . formatDuration ( elapsedMs , this . settings . timeFormat ) ;
9696 const timeSpan = this . dom . createSpan ( ) ;
9797 timeSpan . setText ( `⏱ ${ formattedTime } ` ) ;
@@ -175,10 +175,20 @@ class TaskTimerWidget extends WidgetType {
175175 this . existingBlockId = blockId ;
176176 taskId = `${ this . filePath } #^${ blockId } ` ;
177177
178- // Force widget refresh after successful insertion
178+ // Start the timer after inserting block ID
179+ console . log ( `[TaskTimer] Starting timer for newly created task: ${ taskId } ` ) ;
180+ this . timerManager . startTimer ( taskId ) ;
181+ this . startRealtimeUpdates ( ) ;
182+ this . updateTimerState ( ) ;
183+
184+ // Force CodeMirror to recreate decorations with new block ID
179185 setTimeout ( ( ) => {
180- this . updateTimerState ( ) ;
181- this . refreshUI ( ) ;
186+ // Trigger a state update to recreate decorations
187+ if ( view ) {
188+ view . dispatch ( {
189+ effects : StateEffect . appendConfig . of ( [ ] )
190+ } ) ;
191+ }
182192 } , 100 ) ;
183193 } catch ( err ) {
184194 console . error ( "[TaskTimer] Error dispatching change:" , err ) ;
@@ -203,6 +213,17 @@ class TaskTimerWidget extends WidgetType {
203213
204214 this . existingBlockId = blockId ;
205215 taskId = `${ this . filePath } #^${ blockId } ` ;
216+
217+ // Start timer for the fallback path as well
218+ console . log ( `[TaskTimer] Starting timer for newly created task (fallback): ${ taskId } ` ) ;
219+ this . timerManager . startTimer ( taskId ) ;
220+ this . startRealtimeUpdates ( ) ;
221+ this . updateTimerState ( ) ;
222+
223+ // Force recreation through a timeout since we can't dispatch effects
224+ setTimeout ( ( ) => {
225+ this . refreshUI ( ) ;
226+ } , 100 ) ;
206227 } catch ( err ) {
207228 console . error ( "[TaskTimer] Fallback also failed:" , err ) ;
208229 return ;
@@ -311,9 +332,8 @@ class TaskTimerWidget extends WidgetType {
311332 return ;
312333 }
313334
314- // Calculate elapsed time
315- const elapsedMs = this . timerManager . completeTimer ( taskId ) ;
316- const formattedDuration = TaskTimerFormatter . formatDuration ( elapsedMs , this . settings . timeFormat ) ;
335+ // Complete the timer and get the formatted duration
336+ const formattedDuration = this . timerManager . completeTimer ( taskId ) ;
317337
318338 // Get EditorView to modify document
319339 let view = this . state . field ( editorEditorField , false ) ;
@@ -444,13 +464,14 @@ const taskTimerStateField = StateField.define<DecorationSet>({
444464 create ( state : EditorState ) : DecorationSet {
445465 return createTaskTimerDecorations ( state ) ;
446466 } ,
447- update ( decorations : DecorationSet , transaction ) : DecorationSet {
448- if ( transaction . docChanged ) {
467+ update ( decorations : DecorationSet , transaction : any ) : DecorationSet {
468+ // Recreate decorations on doc changes or state effects
469+ if ( transaction . docChanged || transaction . effects . length > 0 ) {
449470 return createTaskTimerDecorations ( transaction . state ) ;
450471 }
451472 return decorations ;
452473 } ,
453- provide : ( field ) => EditorView . decorations . from ( field )
474+ provide : ( field : StateField < DecorationSet > ) => EditorView . decorations . from ( field )
454475} ) ;
455476
456477/**
@@ -506,18 +527,30 @@ function createTaskTimerDecorations(state: EditorState): DecorationSet {
506527 if ( isTaskLine ( lineText ) ) {
507528 console . log ( "[TaskTimer] Found task line:" , lineText . trim ( ) ) ;
508529
509- // Check if next line exists and has greater indentation (simple check)
510- if ( i < doc . lines ) {
511- const nextLine = doc . line ( i + 1 ) ;
512- const nextLineText = nextLine . text ;
530+ // Check if this task has any subtasks (not just immediate next line)
531+ const currentIndent = lineText . match ( / ^ ( \s * ) / ) ?. [ 1 ] . length || 0 ;
532+ let hasSubtasks = false ;
533+
534+ // Look ahead for subtasks with greater indentation
535+ for ( let j = i + 1 ; j <= doc . lines ; j ++ ) {
536+ const checkLine = doc . line ( j ) ;
537+ const checkLineText = checkLine . text ;
538+ const checkIndent = checkLineText . match ( / ^ ( \s * ) / ) ?. [ 1 ] . length || 0 ;
513539
514- // Simple indentation check
515- const currentIndent = lineText . match ( / ^ ( \s * ) / ) ?. [ 1 ] . length || 0 ;
516- const nextIndent = nextLineText . match ( / ^ ( \s * ) / ) ?. [ 1 ] . length || 0 ;
540+ // If we hit a line with same or less indentation, stop checking
541+ if ( checkLineText . trim ( ) && checkIndent <= currentIndent ) {
542+ break ;
543+ }
517544
518- // If next line has more indentation, this is a parent task
519- if ( nextIndent > currentIndent && nextLineText . trim ( ) ) {
520- console . log ( "[TaskTimer] Found parent task with subtasks (next line indent:" , nextIndent , "vs" , currentIndent , ")" ) ;
545+ // If we find a task line with greater indentation, this is a parent
546+ if ( checkIndent > currentIndent && isTaskLine ( checkLineText ) ) {
547+ hasSubtasks = true ;
548+ break ;
549+ }
550+ }
551+
552+ if ( hasSubtasks ) {
553+ console . log ( "[TaskTimer] Found parent task with subtasks at line" , i ) ;
521554 // Extract existing block reference if present
522555 const existingBlockId = extractBlockRef ( lineText ) ;
523556
@@ -539,7 +572,6 @@ function createTaskTimerDecorations(state: EditorState): DecorationSet {
539572 // Add decoration at the start of the line (this will appear above the task)
540573 decorations . push ( timerDeco . range ( line . from ) ) ;
541574 console . log ( "[TaskTimer] Added timer decoration for line:" , i ) ;
542- }
543575 }
544576 }
545577 }
@@ -567,7 +599,7 @@ function extractBlockRef(lineText: string): string | undefined {
567599 * Creates a StateField-based extension for proper block decorations
568600 */
569601export function taskTimerExtension (
570- app : App ,
602+ _app : App ,
571603 settings : TaskTimerSettings ,
572604 metadataCache : MetadataCache
573605) {
0 commit comments