11const obsidian = require ( 'obsidian' ) ;
2+ const { exec } = require ( 'child_process' ) ;
3+ const { promisify } = require ( 'util' ) ;
4+
5+ const execPromise = promisify ( exec ) ;
6+
7+ /**
8+ * Modal for Git commit messages
9+ */
10+ class GitCommitModal extends obsidian . Modal {
11+ constructor ( app , plugin ) {
12+ super ( app ) ;
13+ this . plugin = plugin ;
14+ }
15+
16+ onOpen ( ) {
17+ const { contentEl } = this ;
18+ contentEl . createEl ( 'h2' , { text : 'Git Commit & Push' } ) ;
19+
20+ const commitMsgContainer = contentEl . createDiv ( ) ;
21+ commitMsgContainer . createEl ( 'label' , { text : 'Commit Message:' } ) ;
22+
23+ const inputEl = commitMsgContainer . createEl ( 'textarea' ) ;
24+ inputEl . style . width = '100%' ;
25+ inputEl . style . height = '100px' ;
26+ inputEl . style . marginBottom = '10px' ;
27+
28+ const buttonContainer = contentEl . createDiv ( ) ;
29+ buttonContainer . style . display = 'flex' ;
30+ buttonContainer . style . justifyContent = 'flex-end' ;
31+
32+ const cancelButton = buttonContainer . createEl ( 'button' , { text : 'Cancel' } ) ;
33+ cancelButton . addEventListener ( 'click' , ( ) => this . close ( ) ) ;
34+
35+ const commitButton = buttonContainer . createEl ( 'button' , { text : 'Commit & Push' } ) ;
36+ commitButton . style . marginLeft = '10px' ;
37+ commitButton . addEventListener ( 'click' , async ( ) => {
38+ const message = inputEl . value . trim ( ) ;
39+ if ( message ) {
40+ await this . plugin . commitAndPush ( message ) ;
41+ this . close ( ) ;
42+ } else {
43+ new obsidian . Notice ( 'Please enter a commit message' ) ;
44+ }
45+ } ) ;
46+ }
47+
48+ onClose ( ) {
49+ const { contentEl } = this ;
50+ contentEl . empty ( ) ;
51+ }
52+ }
253
354/**
455 * @class DocsViewerPlugin
@@ -13,19 +64,116 @@ class DocsViewerPlugin extends obsidian.Plugin {
1364 async onload ( ) {
1465 console . log ( 'Loading DocsViewer plugin' ) ;
1566
67+ // Store Git status and ribbon icon reference
68+ this . hasUncommittedChanges = false ;
69+ this . gitRibbonIcon = null ;
70+
1671 this . updateAllTitles ( ) ;
1772
1873 this . app . workspace . onLayoutReady ( ( ) => {
1974 this . registerEvents ( ) ;
2075
2176 setTimeout ( ( ) => {
2277 this . updateAllTitles ( ) ;
78+ this . updateGitStatus ( ) ;
2379 } , 500 ) ;
2480 } ) ;
2581
2682 this . registerInterval (
2783 window . setInterval ( ( ) => this . updateAllTitles ( ) , 5000 )
2884 ) ;
85+
86+ // Add Git commit button to the left ribbon
87+ this . gitRibbonIcon = this . addRibbonIcon ( 'git-pull-request' , 'Git Commit & Push' , async ( ) => {
88+ try {
89+ const hasChanges = await this . hasGitChanges ( ) ;
90+
91+ if ( hasChanges ) {
92+ new GitCommitModal ( this . app , this ) . open ( ) ;
93+ } else {
94+ new obsidian . Notice ( 'No changes to commit' ) ;
95+ }
96+ } catch ( error ) {
97+ console . error ( 'Git error:' , error ) ;
98+ new obsidian . Notice ( `Git error: ${ error . message } ` ) ;
99+ }
100+ } ) ;
101+
102+ // Add periodic Git status check
103+ this . registerInterval (
104+ window . setInterval ( ( ) => this . updateGitStatus ( ) , 30000 ) // Check every 30 seconds
105+ ) ;
106+
107+ // Initial Git status check
108+ this . updateGitStatus ( ) ;
109+ }
110+
111+ /**
112+ * @description Updates the Git status indicator based on uncommitted changes
113+ * @returns {Promise<void> }
114+ */
115+ async updateGitStatus ( ) {
116+ try {
117+ const hasChanges = await this . hasGitChanges ( ) ;
118+
119+ if ( hasChanges && ! this . hasUncommittedChanges ) {
120+ // Add notification indicator
121+ this . gitRibbonIcon . addClass ( 'git-notification-indicator' ) ;
122+ this . hasUncommittedChanges = true ;
123+ } else if ( ! hasChanges && this . hasUncommittedChanges ) {
124+ // Remove notification indicator
125+ this . gitRibbonIcon . removeClass ( 'git-notification-indicator' ) ;
126+ this . hasUncommittedChanges = false ;
127+ }
128+ } catch ( error ) {
129+ console . error ( 'Error updating Git status:' , error ) ;
130+ }
131+ }
132+
133+ /**
134+ * @description Check if there are uncommitted changes in the repository
135+ * @returns {Promise<boolean> } True if there are changes to commit
136+ */
137+ async hasGitChanges ( ) {
138+ try {
139+ const vaultPath = this . app . vault . adapter . basePath ;
140+ const { stdout } = await execPromise ( 'git status --porcelain' , { cwd : vaultPath } ) ;
141+ return stdout . trim ( ) . length > 0 ;
142+ } catch ( error ) {
143+ console . error ( 'Error checking git status:' , error ) ;
144+ throw error ;
145+ }
146+ }
147+
148+ /**
149+ * @description Commit and push changes to the Git repository
150+ * @param {string } message - The commit message
151+ * @returns {Promise<void> }
152+ */
153+ async commitAndPush ( message ) {
154+ try {
155+ const vaultPath = this . app . vault . adapter . basePath ;
156+
157+ // Add all changes
158+ await execPromise ( 'git add .' , { cwd : vaultPath } ) ;
159+
160+ // Commit with the provided message
161+ await execPromise ( `git commit -m "${ message . replace ( / " / g, '\\"' ) } "` , { cwd : vaultPath } ) ;
162+
163+ // Push to remote
164+ new obsidian . Notice ( 'Committing changes...' ) ;
165+ const { stdout } = await execPromise ( 'git push' , { cwd : vaultPath } ) ;
166+
167+ // Update Git status after commit
168+ this . updateGitStatus ( ) ;
169+
170+ new obsidian . Notice ( 'Successfully committed and pushed changes' ) ;
171+ console . log ( 'Git push result:' , stdout ) ;
172+ } catch ( error ) {
173+ console . error ( 'Error in git operations:' , error ) ;
174+ new obsidian . Notice ( `Git error: ${ error . message } ` ) ;
175+ throw error ;
176+ }
29177 }
30178
31179 /**
@@ -213,6 +361,11 @@ class DocsViewerPlugin extends obsidian.Plugin {
213361 document . querySelectorAll ( '.nav-file, .nav-file-title' ) . forEach ( el => {
214362 el . style . order = '' ;
215363 } ) ;
364+
365+ // Clean up Git notification indicator if it exists
366+ if ( this . gitRibbonIcon ) {
367+ this . gitRibbonIcon . removeClass ( 'git-notification-indicator' ) ;
368+ }
216369 }
217370}
218371
0 commit comments