@@ -88,6 +88,9 @@ function noteApp() {
8888 editorWidth : 50 , // percentage
8989 isResizingSplit : false ,
9090
91+ // Dropdown state
92+ showNewDropdown : false ,
93+
9194 // DOM element cache (to avoid repeated querySelector calls)
9295 _domCache : {
9396 editor : null ,
@@ -170,7 +173,13 @@ function noteApp() {
170173 // Ctrl/Cmd + Alt + N for new note
171174 if ( ( e . ctrlKey || e . metaKey ) && e . altKey && e . key === 'n' ) {
172175 e . preventDefault ( ) ;
173- this . createNewNote ( ) ;
176+ this . createNote ( ) ;
177+ }
178+
179+ // Ctrl/Cmd + Alt + F for new folder
180+ if ( ( e . ctrlKey || e . metaKey ) && e . altKey && e . key === 'f' ) {
181+ e . preventDefault ( ) ;
182+ this . createFolder ( ) ;
174183 }
175184
176185 // Ctrl/Cmd + Z for undo
@@ -430,13 +439,13 @@ function noteApp() {
430439 </div>
431440 <div class="hover-buttons flex gap-1 transition-opacity absolute right-2 top-1/2 transform -translate-y-1/2" style="opacity: 0; pointer-events: none; background: linear-gradient(to right, transparent, var(--bg-hover) 20%, var(--bg-hover)); padding-left: 20px;" @click.stop>
432441 <button
433- @click="createNoteInFolder ('${ folder . path . replace ( / ' / g, "\\'" ) } ')"
442+ @click="createNote ('${ folder . path . replace ( / ' / g, "\\'" ) } ')"
434443 class="px-1.5 py-0.5 text-xs rounded hover:brightness-110"
435444 style="background-color: var(--bg-tertiary); color: var(--text-secondary);"
436- title="New note in this folder "
445+ title="New note here "
437446 >📄</button>
438447 <button
439- @click="createNewFolder ('${ folder . path . replace ( / ' / g, "\\'" ) } ')"
448+ @click="createFolder ('${ folder . path . replace ( / ' / g, "\\'" ) } ')"
440449 class="px-1.5 py-0.5 text-xs rounded hover:brightness-110"
441450 style="background-color: var(--bg-tertiary); color: var(--text-secondary);"
442451 title="New subfolder"
@@ -451,7 +460,7 @@ function noteApp() {
451460 @click="deleteFolder('${ folder . path . replace ( / ' / g, "\\'" ) } ', '${ folder . name . replace ( / ' / g, "\\'" ) } ')"
452461 class="px-1 py-0.5 text-xs rounded hover:brightness-110"
453462 style="color: var(--error);"
454- title="Delete folder and all contents "
463+ title="Delete folder"
455464 >
456465 <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
457466 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
@@ -1164,9 +1173,30 @@ function noteApp() {
11641173 this . currentMatchIndex = - 1 ;
11651174 } ,
11661175
1167- // Create a new note
1168- async createNewNote ( ) {
1169- const noteName = prompt ( 'Enter note name (you can use folder/name):' ) ;
1176+ // =====================================================
1177+ // DROPDOWN MENU SYSTEM
1178+ // =====================================================
1179+
1180+ toggleNewDropdown ( ) {
1181+ this . showNewDropdown = ! this . showNewDropdown ;
1182+ } ,
1183+
1184+ closeDropdown ( ) {
1185+ this . showNewDropdown = false ;
1186+ } ,
1187+
1188+ // =====================================================
1189+ // UNIFIED CREATION FUNCTIONS (reusable from anywhere)
1190+ // =====================================================
1191+
1192+ async createNote ( folderPath = '' ) {
1193+ this . closeDropdown ( ) ;
1194+
1195+ const promptText = folderPath
1196+ ? `Create note in "${ folderPath } ".\nEnter note name:`
1197+ : 'Enter note name (you can use folder/name):' ;
1198+
1199+ const noteName = prompt ( promptText ) ;
11701200 if ( ! noteName ) return ;
11711201
11721202 const sanitizedName = noteName . trim ( ) . replace ( / [ ^ a - z A - Z 0 - 9 -_ \s \/ ] / g, '' ) ;
@@ -1175,8 +1205,19 @@ function noteApp() {
11751205 return ;
11761206 }
11771207
1178- // Add .md extension if not present
1179- const notePath = sanitizedName . endsWith ( '.md' ) ? sanitizedName : `${ sanitizedName } .md` ;
1208+ let notePath ;
1209+ if ( folderPath ) {
1210+ notePath = `${ folderPath } /${ sanitizedName } .md` ;
1211+ } else {
1212+ notePath = sanitizedName . endsWith ( '.md' ) ? sanitizedName : `${ sanitizedName } .md` ;
1213+ }
1214+
1215+ // CRITICAL: Check if note already exists
1216+ const existingNote = this . notes . find ( note => note . path === notePath ) ;
1217+ if ( existingNote ) {
1218+ alert ( `A note named "${ sanitizedName } " already exists in this location.\nPlease choose a different name.` ) ;
1219+ return ;
1220+ }
11801221
11811222 try {
11821223 const response = await fetch ( `/api/notes/${ notePath } ` , {
@@ -1186,6 +1227,9 @@ function noteApp() {
11861227 } ) ;
11871228
11881229 if ( response . ok ) {
1230+ if ( folderPath ) {
1231+ this . expandedFolders . add ( folderPath ) ;
1232+ }
11891233 await this . loadNotes ( ) ;
11901234 await this . loadNote ( notePath ) ;
11911235 } else {
@@ -1196,13 +1240,14 @@ function noteApp() {
11961240 }
11971241 } ,
11981242
1199- // Create a new folder with context
1200- async createNewFolder ( parentPath = '' ) {
1201- const prompt_text = parentPath
1243+ async createFolder ( parentPath = '' ) {
1244+ this . closeDropdown ( ) ;
1245+
1246+ const promptText = parentPath
12021247 ? `Create subfolder in "${ parentPath } ".\nEnter folder name:`
12031248 : 'Create new folder.\nEnter folder path (e.g., "Projects" or "Work/2025"):' ;
12041249
1205- const folderName = prompt ( prompt_text ) ;
1250+ const folderName = prompt ( promptText ) ;
12061251 if ( ! folderName ) return ;
12071252
12081253 const sanitizedName = folderName . trim ( ) . replace ( / [ ^ a - z A - Z 0 - 9 -_ \s \/ ] / g, '' ) ;
@@ -1211,9 +1256,15 @@ function noteApp() {
12111256 return ;
12121257 }
12131258
1214- // Construct full path
12151259 const folderPath = parentPath ? `${ parentPath } /${ sanitizedName } ` : sanitizedName ;
12161260
1261+ // Check if folder already exists
1262+ const existingFolder = this . allFolders . find ( folder => folder === folderPath ) ;
1263+ if ( existingFolder ) {
1264+ alert ( `A folder named "${ sanitizedName } " already exists in this location.\nPlease choose a different name.` ) ;
1265+ return ;
1266+ }
1267+
12171268 try {
12181269 const response = await fetch ( '/api/folders' , {
12191270 method : 'POST' ,
@@ -1222,10 +1273,10 @@ function noteApp() {
12221273 } ) ;
12231274
12241275 if ( response . ok ) {
1225- // Automatically expand parent folder
12261276 if ( parentPath ) {
12271277 this . expandedFolders . add ( parentPath ) ;
12281278 }
1279+ this . expandedFolders . add ( folderPath ) ;
12291280 await this . loadNotes ( ) ;
12301281 } else {
12311282 ErrorHandler . handle ( 'create folder' , new Error ( 'Server returned error' ) ) ;
@@ -1320,39 +1371,6 @@ function noteApp() {
13201371 }
13211372 } ,
13221373
1323- // Create note in specific folder
1324- async createNoteInFolder ( folderPath ) {
1325- const noteName = prompt ( `Create note in "${ folderPath } ".\nEnter note name:` ) ;
1326- if ( ! noteName ) return ;
1327-
1328- const sanitizedName = noteName . trim ( ) . replace ( / [ ^ a - z A - Z 0 - 9 - _ \s] / g, '' ) ;
1329- if ( ! sanitizedName ) {
1330- alert ( 'Invalid note name.' ) ;
1331- return ;
1332- }
1333-
1334- const notePath = `${ folderPath } /${ sanitizedName } .md` ;
1335-
1336- try {
1337- const response = await fetch ( `/api/notes/${ notePath } ` , {
1338- method : 'POST' ,
1339- headers : { 'Content-Type' : 'application/json' } ,
1340- body : JSON . stringify ( { content : '' } )
1341- } ) ;
1342-
1343- if ( response . ok ) {
1344- this . expandedFolders . add ( folderPath ) ;
1345- await this . loadNotes ( ) ;
1346- await this . loadNote ( notePath ) ;
1347- } else {
1348- alert ( 'Failed to create note.' ) ;
1349- }
1350- } catch ( error ) {
1351- console . error ( 'Failed to create note:' , error ) ;
1352- alert ( 'Failed to create note.' ) ;
1353- }
1354- } ,
1355-
13561374 // Auto-save with debounce
13571375 autoSave ( ) {
13581376 if ( this . saveTimeout ) {
0 commit comments