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" ;
92import { defaultSettings , TimeTreeSettings } from "./settings" ;
103import { TimeTreeSettingsTab } from "./settings-tab" ;
114import { 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 ) {
0 commit comments