1- import { App , SuggestModal , Notice , MarkdownView } from 'obsidian' ;
2- import { SupernotePluginSettings } from './main' ;
1+ import { App , SuggestModal , Notice , MarkdownView , TFile } from 'obsidian' ;
2+ import SupernotePlugin from './main' ;
3+ import { SupernotePluginSettings , IP_VALIDATION_PATTERN } from 'settings' ;
34
45interface SupernoteFile {
56 name : string ;
@@ -19,20 +20,20 @@ interface SupernoteResponse {
1920 usedMemory : number ;
2021}
2122
22- export class FileListModal extends SuggestModal < SupernoteFile > {
23+ export abstract class FileListModal extends SuggestModal < SupernoteFile > {
2324 settings : SupernotePluginSettings ;
2425 files : SupernoteFile [ ] = [ ] ;
2526 currentPath : string = '/' ;
2627
27- constructor ( app : App , settings : SupernotePluginSettings ) {
28+ constructor ( app : App , plugin : SupernotePlugin ) {
2829 super ( app ) ;
29- this . settings = settings ;
30+ this . settings = plugin . settings ;
3031 this . setPlaceholder ( "Select a file to download or directory to open" ) ;
3132 }
3233
33- private async loadFiles ( ) {
34+ async loadFiles ( ) {
3435 try {
35- const response = await fetch ( `http://${ this . settings . mirrorIP } :8089${ this . currentPath } ` ) ;
36+ const response = await fetch ( `http://${ this . settings . directConnectIP } :8089${ this . currentPath } ` ) ;
3637 if ( ! response . ok ) {
3738 throw new Error ( `Failed to fetch file list: ${ response . statusText } ` ) ;
3839 }
@@ -91,6 +92,23 @@ export class FileListModal extends SuggestModal<SupernoteFile> {
9192 return ( bytes / 1073741824 ) . toFixed ( 2 ) + ' GB' ;
9293 }
9394
95+ async onChooseSuggestion ( file : SupernoteFile ) {
96+ if ( file . isDirectory ) {
97+ // Navigate into directory
98+ this . currentPath = file . uri ;
99+ await this . loadFiles ( ) ;
100+ // Reopen the modal to show new directory contents
101+ this . open ( ) ;
102+ }
103+ }
104+ }
105+
106+
107+ export class DownloadListModal extends FileListModal {
108+ constructor ( app : App , plugin : SupernotePlugin ) {
109+ super ( app , plugin ) ;
110+ }
111+
94112 async onChooseSuggestion ( file : SupernoteFile ) {
95113 if ( file . isDirectory ) {
96114 // Navigate into directory
@@ -100,7 +118,7 @@ export class FileListModal extends SuggestModal<SupernoteFile> {
100118 this . open ( ) ;
101119 } else {
102120 try {
103- const fileResponse = await fetch ( `http://${ this . settings . mirrorIP } :8089${ file . uri } ` ) ;
121+ const fileResponse = await fetch ( `http://${ this . settings . directConnectIP } :8089${ file . uri } ` ) ;
104122 if ( ! fileResponse . ok ) {
105123 throw new Error ( `Failed to download file: ${ fileResponse . statusText } ` ) ;
106124 }
@@ -120,3 +138,106 @@ export class FileListModal extends SuggestModal<SupernoteFile> {
120138 }
121139 }
122140}
141+ export class UploadListModal extends FileListModal {
142+ private sanitizePath ( path : string ) : string {
143+ return path . replace ( / \/ + / g, '/' ) . replace ( / \/ $ / , '' ) + '/' ;
144+ }
145+ private currentFile : TFile ;
146+
147+ constructor ( app : App , plugin : SupernotePlugin , file : TFile ) {
148+ super ( app , plugin ) ;
149+ this . currentFile = file ;
150+ }
151+
152+ override async getSuggestions ( query : string ) : Promise < SupernoteFile [ ] > {
153+ const suggestions = await super . getSuggestions ( query ) ;
154+
155+ // Add "Upload here" option when not at root
156+ if ( this . currentPath !== '/' ) {
157+ return [ {
158+ name : '[UPLOAD HERE]' ,
159+ size : 0 ,
160+ date : '' ,
161+ uri : this . currentPath ,
162+ extension : '' ,
163+ isDirectory : false
164+ } , ...suggestions ] ;
165+ }
166+ return suggestions ;
167+ }
168+
169+ override renderSuggestion ( file : SupernoteFile , el : HTMLElement ) {
170+ if ( file . name === '[UPLOAD HERE]' ) {
171+ el . createDiv ( { cls : "suggestion-item upload-here" } , container => {
172+ container . createSpan ( {
173+ cls : "suggestion-icon" ,
174+ text : "⬆️"
175+ } ) ;
176+ const content = container . createDiv ( { cls : "suggestion-content" } ) ;
177+ content . createDiv ( {
178+ cls : "suggestion-title" ,
179+ text : "Upload to current directory"
180+ } ) ;
181+ } ) ;
182+ } else {
183+ super . renderSuggestion ( file , el ) ;
184+ if ( file . isDirectory ) {
185+ const noteEl = el . querySelector ( ".suggestion-note" ) ;
186+ if ( noteEl ) {
187+ noteEl . textContent = "Select to enter directory" ;
188+ }
189+ }
190+ }
191+ }
192+
193+ override async onChooseSuggestion ( file : SupernoteFile ) {
194+ if ( file . name === '[UPLOAD HERE]' ) {
195+ try {
196+ if ( ! IP_VALIDATION_PATTERN . test ( this . settings . directConnectIP ) ) {
197+ new Notice ( "Invalid Supernote IP address configured" ) ;
198+ return ;
199+ }
200+
201+ const uploadURL = `http://${ this . settings . directConnectIP } :8089${ this . currentPath } ` ;
202+
203+ // Create FormData with file payload
204+ // Generate filename with .txt extension for markdown files
205+ const uploadFilename = this . currentFile . extension === "md"
206+ ? `${ this . currentFile . basename } .txt` // Change extension to .txt
207+ : this . currentFile . name ;
208+
209+ const formData = new FormData ( ) ;
210+ const fileContent = this . currentFile . extension === "md"
211+ ? await this . app . vault . read ( this . currentFile )
212+ : await this . app . vault . readBinary ( this . currentFile ) ;
213+
214+ const mimeType = this . currentFile . extension === "md"
215+ ? 'text/plain' // Use text/plain for compatibility
216+ : 'application/octet-stream' ;
217+
218+ // Use modified filename in the FormData
219+ formData . append ( 'file' , new Blob ( [ fileContent ] , { type : mimeType } ) , uploadFilename ) ;
220+
221+ const response = await fetch ( uploadURL , {
222+ method : "POST" ,
223+ mode : 'cors' ,
224+ body : formData
225+ } ) ;
226+
227+ if ( ! response . ok ) {
228+ const errorText = await response . text ( ) ;
229+ throw new Error ( `Upload failed: ${ errorText } ` ) ;
230+ }
231+
232+ new Notice ( `Successfully uploaded ${ uploadFilename } to Supernote` ) ;
233+ this . close ( ) ;
234+ } catch ( err ) {
235+ new Notice ( `Upload failed: ${ err . message } ` ) ;
236+ console . error ( 'Upload error:' , err ) ;
237+ }
238+ } else if ( file . isDirectory ) {
239+ // Navigate into directory using parent behavior
240+ await super . onChooseSuggestion ( file ) ;
241+ }
242+ }
243+ }
0 commit comments