1
- import { Plugin , TFile , App } from "obsidian" ;
1
+ import { Plugin , TFile , App , TFolder } from "obsidian" ;
2
2
import { defaultSettings , TimeTreeSettings } from "./settings" ;
3
3
import { TimeTreeSettingsTab } from "./settings-tab" ;
4
4
import { FrontMatterManager } from "./front-matter-manager" ;
5
5
import { TimeTreeHandler } from "./command-handler" ;
6
- import { delay } from "./utils" ;
6
+ import { delay , isValidDateFormat } from "./utils" ;
7
7
8
8
interface AppWithPlugins extends App {
9
9
plugins : {
@@ -21,6 +21,20 @@ export default class TimeTreePlugin extends Plugin {
21
21
private commandHandler : TimeTreeHandler ;
22
22
private pluginLoadTime = 0 ;
23
23
24
+ private scheduleComputeTimeTree ( ) : void {
25
+ // Clear any existing interval
26
+ if ( this . computeIntervalHandle ) {
27
+ clearInterval ( this . computeIntervalHandle ) ;
28
+ }
29
+ // Only schedule if the compute interval is greater than 0 (enabled)
30
+ if ( this . settings . computeIntervalMinutes > 0 ) {
31
+ const intervalMs = this . settings . computeIntervalMinutes * 60 * 1000 ;
32
+ this . computeIntervalHandle = setInterval ( async ( ) => {
33
+ await this . commandHandler . computeTimeTree ( ) ;
34
+ } , intervalMs ) ;
35
+ }
36
+ }
37
+
24
38
private async loadPlugins ( verbose = false ) : Promise < void > {
25
39
// Load Simple Time Tracker API
26
40
const simpleTimeTrackerPlugin = ( this . app as AppWithPlugins ) . plugins . plugins [ "simple-time-tracker" ] ;
@@ -41,6 +55,89 @@ export default class TimeTreePlugin extends Plugin {
41
55
}
42
56
}
43
57
58
+ private async getAllFilesInTimeFolder ( ) : Promise < string [ ] > {
59
+ const timeFolder = this . app . vault . getAbstractFileByPath ( this . settings . TimeFolderPath ) ;
60
+ if ( timeFolder && timeFolder instanceof TFolder ) {
61
+ const files : string [ ] = [ ] ;
62
+ timeFolder . children . forEach ( ( file ) => {
63
+ if ( file instanceof TFile && file . extension === "md" && isValidDateFormat ( file . name , true ) ) {
64
+ files . push ( file . path ) ;
65
+ }
66
+ } ) ;
67
+ files . sort ( ( a , b ) => a . localeCompare ( b ) ) ;
68
+ return files ;
69
+ } else {
70
+ console . error ( "TimeFolderPath is not a valid folder:" , this . settings . TimeFolderPath ) ;
71
+ return [ ] ;
72
+ }
73
+ }
74
+
75
+ private async logRootFileCreatedDate ( rootFile : TFile ) : Promise < void > {
76
+ const lastTrackerTime = await this . frontMatterManager . getLastTrackerTimeRegex ( rootFile ) as string ;
77
+ this . pluginLoadTime = lastTrackerTime ? new Date ( lastTrackerTime ) . getTime ( ) : Date . now ( ) ;
78
+ const files = await this . getAllFilesInTimeFolder ( ) ;
79
+ for ( let i = 0 ; i < files . length ; i ++ ) {
80
+ const filePath = files [ i ] ;
81
+ const file = this . app . vault . getAbstractFileByPath ( filePath ) as TFile ;
82
+ await this . handleFileCreation ( file , i === files . length - 1 ) ;
83
+ }
84
+ this . registerEvent ( this . app . vault . on ( "create" , this . handleFileCreation . bind ( this ) ) ) ;
85
+ }
86
+
87
+ private async initializeRootFileChecker ( rootNotePath : string ) : Promise < void > {
88
+ const tryGetRootFile = ( ) : TFile | null => {
89
+ if ( ! rootNotePath ) {
90
+ return null ;
91
+ }
92
+ const rootFile = this . app . vault . getAbstractFileByPath ( rootNotePath ) ;
93
+ return rootFile instanceof TFile ? rootFile : null ;
94
+ } ;
95
+
96
+ let rootFile : TFile | null = null ;
97
+ while ( ! rootFile ) {
98
+ rootFile = tryGetRootFile ( ) ;
99
+ if ( rootFile ) {
100
+ await this . logRootFileCreatedDate ( rootFile ) ;
101
+ console . log ( "Root file found:" , rootFile . path ) ;
102
+ break ;
103
+ }
104
+ await delay ( 1000 ) ;
105
+ }
106
+ }
107
+
108
+ private async handleFileCreation ( file : TFile , updateRootNoteTracker = true ) : Promise < void > {
109
+ if ( file instanceof TFile && file . stat ) {
110
+ if ( file . stat . ctime > this . pluginLoadTime ) {
111
+ await delay ( 0 ) ;
112
+ await this . commandHandler . handleFileCreation ( file , updateRootNoteTracker ) ;
113
+ console . log ( "File handled:" , file . path ) ;
114
+ }
115
+ }
116
+ }
117
+
118
+ private initializeButtonObserver ( ) : void {
119
+ this . buttonObserver = new MutationObserver ( ( mutations ) => {
120
+ mutations . forEach ( ( mutation ) => {
121
+ mutation . addedNodes . forEach ( ( node ) => {
122
+ if ( node instanceof HTMLElement ) {
123
+ const btn = node . querySelector (
124
+ ".simple-time-tracker-btn"
125
+ ) as HTMLButtonElement | null ;
126
+ if ( btn ) {
127
+ btn . addEventListener ( "click" , async ( ) => {
128
+ await this . commandHandler . handleTrackerButtonClick ( btn ) ;
129
+ } ) ;
130
+ }
131
+ }
132
+ } ) ;
133
+ } ) ;
134
+ } ) ;
135
+ this . buttonObserver . observe ( document . body , {
136
+ childList : true ,
137
+ subtree : true ,
138
+ } ) ;
139
+ }
140
+
44
141
async onload ( ) : Promise < void > {
45
142
await this . loadSettings ( ) ;
46
143
await this . loadPlugins ( ) ;
@@ -163,46 +260,8 @@ export default class TimeTreePlugin extends Plugin {
163
260
} ,
164
261
} ) ;
165
262
166
- this . buttonObserver = new MutationObserver ( ( mutations ) => {
167
- mutations . forEach ( ( mutation ) => {
168
- mutation . addedNodes . forEach ( ( node ) => {
169
- if ( node instanceof HTMLElement ) {
170
- const btn = node . querySelector (
171
- ".simple-time-tracker-btn"
172
- ) as HTMLButtonElement | null ;
173
- if ( btn ) {
174
- btn . addEventListener ( "click" , async ( ) => {
175
- await this . commandHandler . handleTrackerButtonClick ( btn ) ;
176
- } ) ;
177
- }
178
- }
179
- } ) ;
180
- } ) ;
181
- } ) ;
182
- this . buttonObserver . observe ( document . body , {
183
- childList : true ,
184
- subtree : true ,
185
- } ) ;
186
-
187
- // const rootFile = await getRootFile(this.app, this.settings.rootNotePath) as TFile; // | null;
188
- // if (!rootFile) {
189
- // // TODO: file exists but was not loaded yet
190
- // console.error("Root file not found at path:", this.settings.rootNotePath);
191
- // return; // Exit early if the root file is not found
192
- // }
193
- const lastTrackerTime = null ; // await this.frontMatterManager.getLastTrackerTimeRegex(rootFile) as string;
194
- this . pluginLoadTime = lastTrackerTime ? new Date ( lastTrackerTime ) . getTime ( ) : Date . now ( ) ;
195
- this . registerEvent (
196
- this . app . vault . on ( "create" , async ( file : TFile ) => {
197
- if ( file instanceof TFile && file . stat ) {
198
- if ( file . stat . ctime > this . pluginLoadTime ) {
199
- await delay ( 0 ) ;
200
- await this . commandHandler . handleFileCreation ( file ) ;
201
- }
202
- }
203
- } )
204
- ) ;
205
-
263
+ this . initializeButtonObserver ( ) ;
264
+ this . initializeRootFileChecker ( this . settings . rootNotePath ) ;
206
265
this . scheduleComputeTimeTree ( ) ;
207
266
}
208
267
@@ -227,18 +286,4 @@ export default class TimeTreePlugin extends Plugin {
227
286
await this . saveData ( this . settings ) ;
228
287
this . scheduleComputeTimeTree ( ) ;
229
288
}
230
-
231
- scheduleComputeTimeTree ( ) : void {
232
- // Clear any existing interval
233
- if ( this . computeIntervalHandle ) {
234
- clearInterval ( this . computeIntervalHandle ) ;
235
- }
236
- // Only schedule if the compute interval is greater than 0 (enabled)
237
- if ( this . settings . computeIntervalMinutes > 0 ) {
238
- const intervalMs = this . settings . computeIntervalMinutes * 60 * 1000 ;
239
- this . computeIntervalHandle = setInterval ( async ( ) => {
240
- await this . commandHandler . computeTimeTree ( ) ;
241
- } , intervalMs ) ;
242
- }
243
- }
244
289
}
0 commit comments