Skip to content

Commit 861b850

Browse files
authored
Merge pull request #21 from gamosoft/bugfix/note-folder-existing
Bugfix/note folder existing
2 parents d6897d5 + b7477d6 commit 861b850

File tree

3 files changed

+113
-62
lines changed

3 files changed

+113
-62
lines changed

documentation/FEATURES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ $$
112112
|---------------|-----|--------|
113113
| `Ctrl+S` | `Cmd+S` | Save note |
114114
| `Ctrl+Alt+N` | `Cmd+Option+N` | New note |
115+
| `Ctrl+Alt+F` | `Cmd+Option+F` | New folder |
115116
| `Ctrl+Z` | `Cmd+Z` | Undo |
116117
| `Ctrl+Y` or `Ctrl+Shift+Z` | `Cmd+Y` or `Cmd+Shift+Z` | Redo |
117118
| `F3` | `F3` | Next search match |

frontend/app.js

Lines changed: 67 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -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-zA-Z0-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-zA-Z0-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-zA-Z0-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) {

frontend/index.html

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -779,25 +779,57 @@ <h1 class="text-xl font-bold" style="color: var(--text-primary);" x-text="appNam
779779
</div>
780780
</div>
781781

782-
<!-- New Note & Folder Buttons -->
783-
<div class="p-3 border-b flex gap-2" style="border-color: var(--border-primary);">
782+
<!-- New Item Dropdown -->
783+
<div class="p-3 border-b relative" style="border-color: var(--border-primary);">
784784
<button
785-
@click="createNewNote()"
786-
class="flex-1 px-4 py-2 text-sm font-medium text-white rounded-lg focus:outline-none focus:ring-2"
785+
@click="toggleNewDropdown()"
786+
class="w-full px-4 py-2 text-sm font-medium text-white rounded-lg focus:outline-none focus:ring-2 flex items-center justify-center gap-2"
787787
style="background-color: var(--accent-primary);"
788788
onmouseover="this.style.backgroundColor='var(--accent-hover)'"
789789
onmouseout="this.style.backgroundColor='var(--accent-primary)'"
790790
>
791-
+ Note
791+
<span>+ New</span>
792+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
793+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
794+
</svg>
792795
</button>
793-
<button
794-
@click="createNewFolder()"
795-
class="px-4 py-2 text-sm font-medium rounded-lg focus:outline-none focus:ring-2"
796-
style="background-color: var(--bg-tertiary); color: var(--text-primary);"
797-
title="Create folder"
796+
797+
<!-- Dropdown Menu -->
798+
<div
799+
x-show="showNewDropdown"
800+
@click.outside="closeDropdown()"
801+
x-transition:enter="transition ease-out duration-100"
802+
x-transition:enter-start="transform opacity-0 scale-95"
803+
x-transition:enter-end="transform opacity-100 scale-100"
804+
x-transition:leave="transition ease-in duration-75"
805+
x-transition:leave-start="transform opacity-100 scale-100"
806+
x-transition:leave-end="transform opacity-0 scale-95"
807+
class="absolute left-3 right-3 mt-2 rounded-lg shadow-lg z-50"
808+
style="background-color: var(--bg-secondary); border: 1px solid var(--border-primary);"
798809
>
799-
📁
800-
</button>
810+
<div class="py-1">
811+
<button
812+
@click="createNote()"
813+
class="w-full px-4 py-2.5 text-sm text-left flex items-center gap-3 transition-colors"
814+
style="color: var(--text-primary);"
815+
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
816+
onmouseout="this.style.backgroundColor='transparent'"
817+
>
818+
<span class="text-lg">📝</span>
819+
<span>New Note</span>
820+
</button>
821+
<button
822+
@click="createFolder()"
823+
class="w-full px-4 py-2.5 text-sm text-left flex items-center gap-3 transition-colors"
824+
style="color: var(--text-primary);"
825+
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
826+
onmouseout="this.style.backgroundColor='transparent'"
827+
>
828+
<span class="text-lg">📁</span>
829+
<span>New Folder</span>
830+
</button>
831+
</div>
832+
</div>
801833
</div>
802834

803835
<!-- Notes List -->
@@ -940,7 +972,7 @@ <h1 class="text-xl font-bold" style="color: var(--text-primary);" x-text="appNam
940972
<h2 class="text-2xl font-bold mb-2" style="color: var(--text-primary);" x-text="appName"></h2>
941973
<p class="mb-6" style="color: var(--text-secondary);" x-text="appTagline"></p>
942974
<button
943-
@click="createNewNote()"
975+
@click="createNote()"
944976
class="px-6 py-3 text-sm font-medium text-white rounded-lg"
945977
style="background-color: var(--accent-primary);"
946978
onmouseover="this.style.backgroundColor='var(--accent-hover)'"

0 commit comments

Comments
 (0)