Merge pull request #21 from gamosoft/bugfix/note-folder-existing
Bugfix/note folder existing
This commit is contained in:
commit
114a87c12b
|
|
@ -112,6 +112,7 @@ $$
|
|||
|---------------|-----|--------|
|
||||
| `Ctrl+S` | `Cmd+S` | Save note |
|
||||
| `Ctrl+Alt+N` | `Cmd+Option+N` | New note |
|
||||
| `Ctrl+Alt+F` | `Cmd+Option+F` | New folder |
|
||||
| `Ctrl+Z` | `Cmd+Z` | Undo |
|
||||
| `Ctrl+Y` or `Ctrl+Shift+Z` | `Cmd+Y` or `Cmd+Shift+Z` | Redo |
|
||||
| `F3` | `F3` | Next search match |
|
||||
|
|
|
|||
116
frontend/app.js
116
frontend/app.js
|
|
@ -88,6 +88,9 @@ function noteApp() {
|
|||
editorWidth: 50, // percentage
|
||||
isResizingSplit: false,
|
||||
|
||||
// Dropdown state
|
||||
showNewDropdown: false,
|
||||
|
||||
// DOM element cache (to avoid repeated querySelector calls)
|
||||
_domCache: {
|
||||
editor: null,
|
||||
|
|
@ -170,7 +173,13 @@ function noteApp() {
|
|||
// Ctrl/Cmd + Alt + N for new note
|
||||
if ((e.ctrlKey || e.metaKey) && e.altKey && e.key === 'n') {
|
||||
e.preventDefault();
|
||||
this.createNewNote();
|
||||
this.createNote();
|
||||
}
|
||||
|
||||
// Ctrl/Cmd + Alt + F for new folder
|
||||
if ((e.ctrlKey || e.metaKey) && e.altKey && e.key === 'f') {
|
||||
e.preventDefault();
|
||||
this.createFolder();
|
||||
}
|
||||
|
||||
// Ctrl/Cmd + Z for undo
|
||||
|
|
@ -430,13 +439,13 @@ function noteApp() {
|
|||
</div>
|
||||
<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>
|
||||
<button
|
||||
@click="createNoteInFolder('${folder.path.replace(/'/g, "\\'")}')"
|
||||
@click="createNote('${folder.path.replace(/'/g, "\\'")}')"
|
||||
class="px-1.5 py-0.5 text-xs rounded hover:brightness-110"
|
||||
style="background-color: var(--bg-tertiary); color: var(--text-secondary);"
|
||||
title="New note in this folder"
|
||||
title="New note here"
|
||||
>📄</button>
|
||||
<button
|
||||
@click="createNewFolder('${folder.path.replace(/'/g, "\\'")}')"
|
||||
@click="createFolder('${folder.path.replace(/'/g, "\\'")}')"
|
||||
class="px-1.5 py-0.5 text-xs rounded hover:brightness-110"
|
||||
style="background-color: var(--bg-tertiary); color: var(--text-secondary);"
|
||||
title="New subfolder"
|
||||
|
|
@ -451,7 +460,7 @@ function noteApp() {
|
|||
@click="deleteFolder('${folder.path.replace(/'/g, "\\'")}', '${folder.name.replace(/'/g, "\\'")}')"
|
||||
class="px-1 py-0.5 text-xs rounded hover:brightness-110"
|
||||
style="color: var(--error);"
|
||||
title="Delete folder and all contents"
|
||||
title="Delete folder"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<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() {
|
|||
this.currentMatchIndex = -1;
|
||||
},
|
||||
|
||||
// Create a new note
|
||||
async createNewNote() {
|
||||
const noteName = prompt('Enter note name (you can use folder/name):');
|
||||
// =====================================================
|
||||
// DROPDOWN MENU SYSTEM
|
||||
// =====================================================
|
||||
|
||||
toggleNewDropdown() {
|
||||
this.showNewDropdown = !this.showNewDropdown;
|
||||
},
|
||||
|
||||
closeDropdown() {
|
||||
this.showNewDropdown = false;
|
||||
},
|
||||
|
||||
// =====================================================
|
||||
// UNIFIED CREATION FUNCTIONS (reusable from anywhere)
|
||||
// =====================================================
|
||||
|
||||
async createNote(folderPath = '') {
|
||||
this.closeDropdown();
|
||||
|
||||
const promptText = folderPath
|
||||
? `Create note in "${folderPath}".\nEnter note name:`
|
||||
: 'Enter note name (you can use folder/name):';
|
||||
|
||||
const noteName = prompt(promptText);
|
||||
if (!noteName) return;
|
||||
|
||||
const sanitizedName = noteName.trim().replace(/[^a-zA-Z0-9-_\s\/]/g, '');
|
||||
|
|
@ -1175,8 +1205,19 @@ function noteApp() {
|
|||
return;
|
||||
}
|
||||
|
||||
// Add .md extension if not present
|
||||
const notePath = sanitizedName.endsWith('.md') ? sanitizedName : `${sanitizedName}.md`;
|
||||
let notePath;
|
||||
if (folderPath) {
|
||||
notePath = `${folderPath}/${sanitizedName}.md`;
|
||||
} else {
|
||||
notePath = sanitizedName.endsWith('.md') ? sanitizedName : `${sanitizedName}.md`;
|
||||
}
|
||||
|
||||
// CRITICAL: Check if note already exists
|
||||
const existingNote = this.notes.find(note => note.path === notePath);
|
||||
if (existingNote) {
|
||||
alert(`A note named "${sanitizedName}" already exists in this location.\nPlease choose a different name.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/notes/${notePath}`, {
|
||||
|
|
@ -1186,6 +1227,9 @@ function noteApp() {
|
|||
});
|
||||
|
||||
if (response.ok) {
|
||||
if (folderPath) {
|
||||
this.expandedFolders.add(folderPath);
|
||||
}
|
||||
await this.loadNotes();
|
||||
await this.loadNote(notePath);
|
||||
} else {
|
||||
|
|
@ -1196,13 +1240,14 @@ function noteApp() {
|
|||
}
|
||||
},
|
||||
|
||||
// Create a new folder with context
|
||||
async createNewFolder(parentPath = '') {
|
||||
const prompt_text = parentPath
|
||||
async createFolder(parentPath = '') {
|
||||
this.closeDropdown();
|
||||
|
||||
const promptText = parentPath
|
||||
? `Create subfolder in "${parentPath}".\nEnter folder name:`
|
||||
: 'Create new folder.\nEnter folder path (e.g., "Projects" or "Work/2025"):';
|
||||
|
||||
const folderName = prompt(prompt_text);
|
||||
const folderName = prompt(promptText);
|
||||
if (!folderName) return;
|
||||
|
||||
const sanitizedName = folderName.trim().replace(/[^a-zA-Z0-9-_\s\/]/g, '');
|
||||
|
|
@ -1211,9 +1256,15 @@ function noteApp() {
|
|||
return;
|
||||
}
|
||||
|
||||
// Construct full path
|
||||
const folderPath = parentPath ? `${parentPath}/${sanitizedName}` : sanitizedName;
|
||||
|
||||
// Check if folder already exists
|
||||
const existingFolder = this.allFolders.find(folder => folder === folderPath);
|
||||
if (existingFolder) {
|
||||
alert(`A folder named "${sanitizedName}" already exists in this location.\nPlease choose a different name.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/folders', {
|
||||
method: 'POST',
|
||||
|
|
@ -1222,10 +1273,10 @@ function noteApp() {
|
|||
});
|
||||
|
||||
if (response.ok) {
|
||||
// Automatically expand parent folder
|
||||
if (parentPath) {
|
||||
this.expandedFolders.add(parentPath);
|
||||
}
|
||||
this.expandedFolders.add(folderPath);
|
||||
await this.loadNotes();
|
||||
} else {
|
||||
ErrorHandler.handle('create folder', new Error('Server returned error'));
|
||||
|
|
@ -1320,39 +1371,6 @@ function noteApp() {
|
|||
}
|
||||
},
|
||||
|
||||
// Create note in specific folder
|
||||
async createNoteInFolder(folderPath) {
|
||||
const noteName = prompt(`Create note in "${folderPath}".\nEnter note name:`);
|
||||
if (!noteName) return;
|
||||
|
||||
const sanitizedName = noteName.trim().replace(/[^a-zA-Z0-9-_\s]/g, '');
|
||||
if (!sanitizedName) {
|
||||
alert('Invalid note name.');
|
||||
return;
|
||||
}
|
||||
|
||||
const notePath = `${folderPath}/${sanitizedName}.md`;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/notes/${notePath}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ content: '' })
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
this.expandedFolders.add(folderPath);
|
||||
await this.loadNotes();
|
||||
await this.loadNote(notePath);
|
||||
} else {
|
||||
alert('Failed to create note.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to create note:', error);
|
||||
alert('Failed to create note.');
|
||||
}
|
||||
},
|
||||
|
||||
// Auto-save with debounce
|
||||
autoSave() {
|
||||
if (this.saveTimeout) {
|
||||
|
|
|
|||
|
|
@ -779,26 +779,58 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- New Note & Folder Buttons -->
|
||||
<div class="p-3 border-b flex gap-2" style="border-color: var(--border-primary);">
|
||||
<!-- New Item Dropdown -->
|
||||
<div class="p-3 border-b relative" style="border-color: var(--border-primary);">
|
||||
<button
|
||||
@click="createNewNote()"
|
||||
class="flex-1 px-4 py-2 text-sm font-medium text-white rounded-lg focus:outline-none focus:ring-2"
|
||||
@click="toggleNewDropdown()"
|
||||
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"
|
||||
style="background-color: var(--accent-primary);"
|
||||
onmouseover="this.style.backgroundColor='var(--accent-hover)'"
|
||||
onmouseout="this.style.backgroundColor='var(--accent-primary)'"
|
||||
>
|
||||
+ Note
|
||||
<span>+ New</span>
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Dropdown Menu -->
|
||||
<div
|
||||
x-show="showNewDropdown"
|
||||
@click.outside="closeDropdown()"
|
||||
x-transition:enter="transition ease-out duration-100"
|
||||
x-transition:enter-start="transform opacity-0 scale-95"
|
||||
x-transition:enter-end="transform opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="transform opacity-100 scale-100"
|
||||
x-transition:leave-end="transform opacity-0 scale-95"
|
||||
class="absolute left-3 right-3 mt-2 rounded-lg shadow-lg z-50"
|
||||
style="background-color: var(--bg-secondary); border: 1px solid var(--border-primary);"
|
||||
>
|
||||
<div class="py-1">
|
||||
<button
|
||||
@click="createNote()"
|
||||
class="w-full px-4 py-2.5 text-sm text-left flex items-center gap-3 transition-colors"
|
||||
style="color: var(--text-primary);"
|
||||
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
|
||||
onmouseout="this.style.backgroundColor='transparent'"
|
||||
>
|
||||
<span class="text-lg">📝</span>
|
||||
<span>New Note</span>
|
||||
</button>
|
||||
<button
|
||||
@click="createNewFolder()"
|
||||
class="px-4 py-2 text-sm font-medium rounded-lg focus:outline-none focus:ring-2"
|
||||
style="background-color: var(--bg-tertiary); color: var(--text-primary);"
|
||||
title="Create folder"
|
||||
@click="createFolder()"
|
||||
class="w-full px-4 py-2.5 text-sm text-left flex items-center gap-3 transition-colors"
|
||||
style="color: var(--text-primary);"
|
||||
onmouseover="this.style.backgroundColor='var(--bg-hover)'"
|
||||
onmouseout="this.style.backgroundColor='transparent'"
|
||||
>
|
||||
📁
|
||||
<span class="text-lg">📁</span>
|
||||
<span>New Folder</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notes List -->
|
||||
<div class="flex-1 overflow-y-auto custom-scrollbar">
|
||||
|
|
@ -940,7 +972,7 @@
|
|||
<h2 class="text-2xl font-bold mb-2" style="color: var(--text-primary);" x-text="appName"></h2>
|
||||
<p class="mb-6" style="color: var(--text-secondary);" x-text="appTagline"></p>
|
||||
<button
|
||||
@click="createNewNote()"
|
||||
@click="createNote()"
|
||||
class="px-6 py-3 text-sm font-medium text-white rounded-lg"
|
||||
style="background-color: var(--accent-primary);"
|
||||
onmouseover="this.style.backgroundColor='var(--accent-hover)'"
|
||||
|
|
|
|||
Loading…
Reference in New Issue