Skip to content

Commit 1f1a24c

Browse files
committed
Add command to start and stop the tracker, and once it stops, automatically update YAML metadata of note and its ascendants
1 parent 1a2530f commit 1f1a24c

21 files changed

+335
-147
lines changed

src/main.ts

Lines changed: 107 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,4 @@
1-
import {
2-
Plugin,
3-
TFile,
4-
Notice,
5-
Editor,
6-
MarkdownSectionInformation,
7-
ButtonComponent,
8-
} from "obsidian";
1+
import { Plugin, TFile, Notice, Editor, MarkdownView } from "obsidian";
92
import { defaultSettings, TimeTreeSettings } from "./settings";
103
import { TimeTreeSettingsTab } from "./settings-tab";
114
import { FrontMatterManager } from "./front-matter-manager";
@@ -17,6 +10,7 @@ export default class TimeTreePlugin extends Plugin {
1710
private frontMatterManager: FrontMatterManager;
1811
private calculator: TimeTreeCalculator;
1912
private computeIntervalHandle: any;
13+
private buttonObserver: MutationObserver | null = null;
2014

2115
async onload(): Promise<void> {
2216
await this.loadSettings();
@@ -62,10 +56,38 @@ export default class TimeTreePlugin extends Plugin {
6256
},
6357
});
6458

59+
this.buttonObserver = new MutationObserver((mutations) => {
60+
mutations.forEach((mutation) => {
61+
mutation.addedNodes.forEach((node) => {
62+
if (node instanceof HTMLElement) {
63+
const btn = node.querySelector(
64+
".simple-time-tracker-btn"
65+
) as HTMLButtonElement | null;
66+
if (btn) {
67+
btn.addEventListener("click", () => {
68+
const btnStatus =
69+
btn.getAttribute("aria-label");
70+
if (btnStatus === "End") {
71+
this.elapsedTime();
72+
}
73+
});
74+
}
75+
}
76+
});
77+
});
78+
});
79+
this.buttonObserver.observe(document.body, {
80+
childList: true,
81+
subtree: true,
82+
});
83+
6584
this.scheduleComputeTimeTree();
6685
}
6786

6887
onunload(): void {
88+
if (this.buttonObserver) {
89+
this.buttonObserver.disconnect();
90+
}
6991
if (this.computeIntervalHandle) {
7092
clearInterval(this.computeIntervalHandle);
7193
}
@@ -84,7 +106,76 @@ export default class TimeTreePlugin extends Plugin {
84106
this.scheduleComputeTimeTree();
85107
}
86108

109+
async adjustCursorOutsideTracker(editor: Editor): Promise<void> {
110+
// Get the full content and split it into lines.
111+
const content = editor.getValue();
112+
const lines = content.split("\n");
113+
114+
// Determine the end of YAML front matter if present. Line L is the last line of YAML metadata.
115+
let yamlEnd = 0;
116+
if (lines[0].trim() === "---") {
117+
for (let i = 1; i < lines.length; i++) {
118+
if (lines[i].trim() === "---") {
119+
yamlEnd = i + 1; // YAML metadata ends at line L
120+
break;
121+
}
122+
}
123+
}
124+
125+
// Collect tracker block boundaries, but only after the YAML metadata.
126+
const trackerBlocks: { start: number; end: number }[] = [];
127+
for (let i = yamlEnd; i < lines.length; i++) {
128+
if (lines[i].trimEnd() === "```simple-time-tracker") {
129+
const blockStart = i;
130+
// Look for the closing marker.
131+
for (let j = i + 1; j < lines.length; j++) {
132+
if (lines[j].trimEnd() === "```") {
133+
trackerBlocks.push({ start: blockStart, end: j });
134+
i = j; // Skip the rest of this block
135+
break;
136+
}
137+
}
138+
}
139+
}
140+
141+
// Define a helper to check if a given line index is inside any tracker block.
142+
const isLineInTracker = (line: number): boolean => {
143+
return trackerBlocks.some(
144+
(block) => line >= block.start && line <= block.end
145+
);
146+
};
147+
148+
// Find the first line after YAML metadata that is not inside any tracker block.
149+
let targetLine = yamlEnd;
150+
while (targetLine < lines.length && isLineInTracker(targetLine)) {
151+
targetLine++;
152+
}
153+
if (targetLine >= lines.length) {
154+
targetLine = lines.length - 1;
155+
}
156+
157+
// Get the current cursor position.
158+
const cursor = editor.getCursor();
159+
160+
// If the cursor is not inside any tracker block, do nothing.
161+
const cursorInTracker = trackerBlocks.some(
162+
(block) => cursor.line >= block.start && cursor.line <= block.end
163+
);
164+
if (!cursorInTracker) {
165+
return;
166+
}
167+
168+
// Move the cursor to the first line after YAML metadata that is not part of any tracker block.
169+
editor.setCursor({ line: targetLine, ch: 0 });
170+
}
171+
87172
async startStopTracker(): Promise<void> {
173+
const activeView = this.app.workspace.getActiveViewOfType(MarkdownView);
174+
if (activeView) {
175+
await this.adjustCursorOutsideTracker(activeView.editor);
176+
} else {
177+
new Notice("No active Markdown editor found.");
178+
}
88179
const btn = document.querySelector(
89180
".simple-time-tracker-btn"
90181
) as HTMLButtonElement | null;
@@ -103,6 +194,14 @@ export default class TimeTreePlugin extends Plugin {
103194
}
104195
let elapsed = 0;
105196
elapsed = await this.calculator.calculateElapsedTime(activeFile);
197+
await new Promise((resolve) => setTimeout(resolve, 10));
198+
await this.frontMatterManager.updateProperty(
199+
activeFile,
200+
(frontmatter) => {
201+
frontmatter.elapsed = elapsed;
202+
return frontmatter;
203+
}
204+
);
106205
await this.calculator.communicateAscendants(activeFile);
107206
const rootPath = this.settings.rootNotePath;
108207
if (rootPath) {

src/settings.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
export const defaultSettings: TimeTreeSettings = {
2-
onlyFirstTracker: false,
2+
onlyFirstTracker: true,
33
rootNotePath: "",
44
RootFolderPath: "/",
5-
considerSubdirs: false,
6-
computeIntervalMinutes: 0, // New property: 0 means disabled by default
5+
considerSubdirs: true,
6+
computeIntervalMinutes: 0, // 0 means disabled by default
77
};
88

99
export interface TimeTreeSettings {
1010
onlyFirstTracker: boolean;
1111
rootNotePath: string;
1212
RootFolderPath: string;
1313
considerSubdirs: boolean;
14-
computeIntervalMinutes: number; // New property
14+
computeIntervalMinutes: number;
1515
}

src/time-tree-calculator.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,15 @@ export class TimeTreeCalculator {
3535
}
3636
}
3737
}
38-
await this.frontMatterManager.updateProperty(file, (frontmatter) => {
39-
frontmatter.elapsed = localElapsed;
40-
return frontmatter;
41-
});
4238
return localElapsed;
4339
}
4440

4541
async calculateRecursiveElapsedTime(file: TFile): Promise<number> {
4642
let localElapsed = await this.calculateElapsedTime(file);
43+
await this.frontMatterManager.updateProperty(file, (frontmatter) => {
44+
frontmatter.elapsed = localElapsed;
45+
return frontmatter;
46+
});
4747
const fileCache = this.app.metadataCache.getFileCache(file);
4848
if (fileCache && fileCache.links && fileCache.links.length > 0) {
4949
for (const link of fileCache.links) {
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[
2+
"custom-node-size",
23
"simple-time-tracker",
3-
"time-tree",
4-
"custom-node-size"
4+
"time-tree"
55
]

test-vault/.obsidian/graph.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@
3232
"repelStrength": 20,
3333
"linkStrength": 1,
3434
"linkDistance": 184,
35-
"scale": 0.4016008904932634,
35+
"scale": 0.38564307862722413,
3636
"close": true
3737
}
Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
{
2-
"id": "simple-time-tracker",
3-
"name": "Super Simple Time Tracker",
4-
"version": "1.0.3",
5-
"minAppVersion": "1.2.8",
6-
"description": "Multi-purpose time trackers for your notes!",
7-
"author": "Ellpeck",
8-
"authorUrl": "https://ellpeck.de",
9-
"fundingUrl": "https://ellpeck.de/support",
10-
"isDesktopOnly": false
11-
}
1+
{
2+
"id": "simple-time-tracker",
3+
"name": "Super Simple Time Tracker",
4+
"version": "1.0.3",
5+
"minAppVersion": "1.2.8",
6+
"description": "Multi-purpose time trackers for your notes!",
7+
"author": "Ellpeck",
8+
"authorUrl": "https://ellpeck.de",
9+
"isDesktopOnly": false
10+
}

0 commit comments

Comments
 (0)