Skip to content

Commit 53ac6a2

Browse files
added recursion to folders
1 parent 391138b commit 53ac6a2

File tree

2 files changed

+167
-160
lines changed

2 files changed

+167
-160
lines changed

frontend/app.js

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,13 +379,143 @@ function noteApp() {
379379
this.folderTree = { ...tree };
380380
},
381381

382+
// Render folder recursively (helper for deep nesting)
383+
renderFolderRecursive(folder, level = 0) {
384+
if (!folder) return '';
385+
386+
let html = '';
387+
const baseIndent = level * 12;
388+
389+
// First, render child folders (if any)
390+
if (folder.children && Object.keys(folder.children).length > 0) {
391+
const children = Object.entries(folder.children).sort((a, b) =>
392+
a[1].name.toLowerCase().localeCompare(b[1].name.toLowerCase())
393+
);
394+
395+
children.forEach(([childKey, childFolder]) => {
396+
const isExpanded = this.expandedFolders.has(childFolder.path);
397+
398+
// Folder header HTML (no margin - it's inside folder-contents which provides the indent)
399+
html += `
400+
<div>
401+
<div
402+
draggable="true"
403+
x-data="{}"
404+
@dragstart="onFolderDragStart('${childFolder.path.replace(/'/g, "\\'")}' )"
405+
@dragend="onFolderDragEnd()"
406+
@dragover.prevent
407+
@drop.stop="onFolderDrop('${childFolder.path.replace(/'/g, "\\'")}')"
408+
class="folder-item px-2 py-2 mb-1 text-sm rounded transition-all relative"
409+
style="color: var(--text-primary); cursor: pointer;"
410+
:class="draggedNote || draggedFolder ? 'border-2 border-dashed border-accent-primary bg-accent-light' : 'border-2 border-transparent'"
411+
@mouseover="if(!draggedNote && !draggedFolder) $el.style.backgroundColor='var(--bg-hover)'"
412+
@mouseout="if(!draggedNote && !draggedFolder) $el.style.backgroundColor='transparent'"
413+
@click="toggleFolder('${childFolder.path.replace(/'/g, "\\'")}')"
414+
>
415+
<div class="flex items-center gap-1">
416+
<button
417+
class="flex-shrink-0 w-4 h-4 flex items-center justify-center"
418+
style="color: var(--text-tertiary); cursor: pointer; transition: transform 0.2s; pointer-events: none; ${isExpanded ? 'transform: rotate(90deg);' : ''}"
419+
>
420+
<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor">
421+
<path d="M6 4l4 4-4 4V4z"/>
422+
</svg>
423+
</button>
424+
<span class="flex items-center gap-1 flex-1" style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500; pointer-events: none;">
425+
<span>${childFolder.name}</span>
426+
${childFolder.notes.length === 0 && (!childFolder.children || Object.keys(childFolder.children).length === 0) ? '<span class="text-xs" style="color: var(--text-tertiary); font-weight: 400;">(empty)</span>' : ''}
427+
</span>
428+
</div>
429+
<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>
430+
<button
431+
@click="createNoteInFolder('${childFolder.path.replace(/'/g, "\\'")}')"
432+
class="px-1.5 py-0.5 text-xs rounded hover:brightness-110"
433+
style="background-color: var(--bg-tertiary); color: var(--text-secondary);"
434+
title="New note in this folder"
435+
>📄</button>
436+
<button
437+
@click="createNewFolder('${childFolder.path.replace(/'/g, "\\'")}')"
438+
class="px-1.5 py-0.5 text-xs rounded hover:brightness-110"
439+
style="background-color: var(--bg-tertiary); color: var(--text-secondary);"
440+
title="New subfolder"
441+
>📁</button>
442+
<button
443+
@click="renameFolder('${childFolder.path.replace(/'/g, "\\'")}', '${childFolder.name.replace(/'/g, "\\'")}')"
444+
class="px-1.5 py-0.5 text-xs rounded hover:brightness-110"
445+
style="background-color: var(--bg-tertiary); color: var(--text-secondary);"
446+
title="Rename folder"
447+
>✏️</button>
448+
<button
449+
@click="deleteFolder('${childFolder.path.replace(/'/g, "\\'")}', '${childFolder.name.replace(/'/g, "\\'")}')"
450+
class="px-1 py-0.5 text-xs rounded hover:brightness-110"
451+
style="color: var(--error);"
452+
title="Delete folder and all contents"
453+
>
454+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
455+
<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>
456+
</svg>
457+
</button>
458+
</div>
459+
</div>
460+
`;
461+
462+
// If expanded, recursively render this folder's contents (notes + subfolders)
463+
if (isExpanded) {
464+
html += `<div class="folder-contents" style="padding-left: 12px;">`;
465+
html += this.renderFolderRecursive(childFolder, 0); // Reset level to 0 for relative positioning
466+
html += `</div>`;
467+
}
468+
469+
html += `</div>`;
470+
});
471+
}
472+
473+
// Then, render notes in this folder (after subfolders)
474+
if (folder.notes && folder.notes.length > 0) {
475+
folder.notes.forEach(note => {
476+
const isCurrentNote = this.currentNote === note.path;
477+
html += `
478+
<div
479+
draggable="true"
480+
x-data="{}"
481+
@dragstart="onNoteDragStart('${note.path.replace(/'/g, "\\'")}', $event)"
482+
@dragend="onNoteDragEnd()"
483+
@click="loadNote('${note.path.replace(/'/g, "\\'")}')"
484+
class="note-item px-3 py-2 mb-1 text-sm rounded relative"
485+
style="${isCurrentNote ? 'background-color: var(--accent-light); color: var(--accent-primary);' : 'color: var(--text-primary);'} cursor: pointer;"
486+
@mouseover="if('${note.path}' !== currentNote) $el.style.backgroundColor='var(--bg-hover)'"
487+
@mouseout="if('${note.path}' !== currentNote) $el.style.backgroundColor='transparent'"
488+
>
489+
<span class="truncate">${note.name}</span>
490+
<button
491+
@click.stop="deleteNote('${note.path.replace(/'/g, "\\'")}', '${note.name.replace(/'/g, "\\'")}')"
492+
class="note-delete-btn absolute right-2 top-1/2 transform -translate-y-1/2 px-1 py-0.5 text-xs rounded hover:brightness-110 transition-opacity"
493+
style="opacity: 0; color: var(--error);"
494+
title="Delete note"
495+
>
496+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
497+
<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>
498+
</svg>
499+
</button>
500+
</div>
501+
`;
502+
});
503+
}
504+
505+
return html;
506+
},
507+
382508
// Toggle folder expansion
383509
toggleFolder(folderPath) {
384510
if (this.expandedFolders.has(folderPath)) {
385511
this.expandedFolders.delete(folderPath);
386512
} else {
387513
this.expandedFolders.add(folderPath);
388514
}
515+
// Force Alpine reactivity by creating new Set reference
516+
this.expandedFolders = new Set(this.expandedFolders);
517+
// Also trigger folderTree reactivity to re-render x-html
518+
this.folderTree = { ...this.folderTree };
389519
},
390520

391521
// Check if folder is expanded

0 commit comments

Comments
 (0)