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