Skip to content

Commit 7287275

Browse files
committed
fix(timer): resolve timer widget display and subtask detection issues
- Fix timer not starting after block ID insertion - Improve parent task detection to find all subtasks, not just immediate next line - Add proper state updates to refresh widget after block ID creation - Fix TypeScript errors with generateBlockId visibility and elapsed property - Ensure timer controls update correctly after inserting block reference
1 parent f6676b0 commit 7287275

3 files changed

Lines changed: 61 additions & 27 deletions

File tree

src/editor-ext/taskTimer.ts

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
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";
1010
import { TFile, App, MetadataCache, editorInfoField, editorEditorField } from "obsidian";
1111
import { TaskTimerSettings } from "../common/setting-definition";
1212
import { 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
*/
569601
export function taskTimerExtension(
570-
app: App,
602+
_app: App,
571603
settings: TaskTimerSettings,
572604
metadataCache: MetadataCache
573605
) {

src/utils/TaskTimerManager.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@ export class TaskTimerManager {
2828

2929
/**
3030
* Generate a unique block reference ID
31+
* @param prefix Optional prefix to use (defaults to settings)
3132
* @returns Generated block ID
3233
*/
33-
private generateBlockId(): string {
34+
public generateBlockId(prefix?: string): string {
35+
const actualPrefix = prefix || this.settings.blockRefPrefix;
3436
const timestamp = Date.now().toString().slice(-6); // Last 6 digits of timestamp
3537
const random = Math.floor(Math.random() * 10000).toString().padStart(4, '0');
36-
return `${this.settings.blockRefPrefix}-${timestamp}-${random}`;
38+
return `${actualPrefix}-${timestamp}-${random}`;
3739
}
3840

3941
/**

styles.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)