fixed drag & drop bug with many notes

This commit is contained in:
Gamosoft 2025-12-09 12:08:12 +01:00
parent 800b9f384c
commit 4085bad479
2 changed files with 63 additions and 39 deletions

View File

@ -277,6 +277,16 @@ function noteApp() {
// Initialize app // Initialize app
async init() { async init() {
// Store global reference for native event handlers in x-html content
window.$root = this;
// ESC key to cancel drag operations
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && (this.draggedNote || this.draggedFolder || this.draggedItem)) {
this.cancelDrag();
}
});
await this.loadConfig(); await this.loadConfig();
await this.loadThemes(); await this.loadThemes();
await this.initTheme(); await this.initTheme();
@ -976,30 +986,24 @@ function noteApp() {
const isExpanded = this.expandedFolders.has(folder.path); const isExpanded = this.expandedFolders.has(folder.path);
// Render this folder's header // Render this folder's header
// Note: Using native event handlers (ondragstart, onclick, etc.) instead of Alpine directives
// because x-html doesn't process Alpine directives in dynamically generated content
const escapedPath = folder.path.replace(/'/g, "\\'").replace(/\\/g, "\\\\");
html += ` html += `
<div> <div>
<div <div
draggable="true" draggable="true"
x-data="{}" ondragstart="window.$root.onFolderDragStart('${escapedPath}', event)"
@dragstart="onFolderDragStart('${folder.path.replace(/'/g, "\\'")}', $event)" ondragend="window.$root.onFolderDragEnd()"
@dragend="onFolderDragEnd()" ondragover="event.preventDefault(); window.$root.dragOverFolder = '${escapedPath}'; this.classList.add('drag-over')"
@dragover.prevent="dragOverFolder = '${folder.path.replace(/'/g, "\\'")}'" ondragenter="event.preventDefault(); window.$root.dragOverFolder = '${escapedPath}'; this.classList.add('drag-over')"
@dragenter.prevent="dragOverFolder = '${folder.path.replace(/'/g, "\\'")}'" ondragleave="window.$root.dragOverFolder = null; this.classList.remove('drag-over')"
@dragleave="dragOverFolder = null" ondrop="event.stopPropagation(); this.classList.remove('drag-over'); window.$root.onFolderDrop('${escapedPath}')"
@drop.stop="onFolderDrop('${folder.path.replace(/'/g, "\\'")}' )" onclick="window.$root.toggleFolder('${escapedPath}')"
onmouseover="if(!window.$root.draggedNote && !window.$root.draggedFolder) this.style.backgroundColor='var(--bg-hover)'"
onmouseout="if(!window.$root.draggedNote && !window.$root.draggedFolder) this.style.backgroundColor='transparent'"
class="folder-item px-2 py-1 text-sm relative" class="folder-item px-2 py-1 text-sm relative"
style="color: var(--text-primary); cursor: pointer;" style="color: var(--text-primary); cursor: pointer;"
:class="{
'border-2 border-dashed bg-accent-light': (draggedNote || draggedFolder) && dragOverFolder === '${folder.path.replace(/'/g, "\\'")}',
'border-2 border-dashed': (draggedNote || draggedFolder) && dragOverFolder !== '${folder.path.replace(/'/g, "\\'")}'
}"
:style="{
'border-color': (draggedNote || draggedFolder) && dragOverFolder === '${folder.path.replace(/'/g, "\\'")}' ? 'var(--accent-primary)' : 'var(--border-secondary)',
'background-color': dragOverFolder === '${folder.path.replace(/'/g, "\\'")}' ? 'var(--accent-light)' : ''
}"
@mouseover="if(!draggedNote && !draggedFolder) $el.style.backgroundColor='var(--bg-hover)'"
@mouseout="if(!draggedNote && !draggedFolder && dragOverFolder !== '${folder.path.replace(/'/g, "\\'")}' ) $el.style.backgroundColor='transparent'"
@click="toggleFolder('${folder.path.replace(/'/g, "\\'")}')"
> >
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<button <button
@ -1015,21 +1019,21 @@ function noteApp() {
${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>' : ''} ${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>' : ''}
</span> </span>
</div> </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> <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;" onclick="event.stopPropagation()">
<button <button
@click="dropdownTargetFolder = '${folder.path.replace(/'/g, "\\'")}'; toggleNewDropdown($event)" onclick="event.stopPropagation(); window.$root.dropdownTargetFolder = '${escapedPath}'; window.$root.toggleNewDropdown(event)"
class="px-1.5 py-0.5 text-xs rounded hover:brightness-110" class="px-1.5 py-0.5 text-xs rounded hover:brightness-110"
style="background-color: var(--bg-tertiary); color: var(--text-secondary);" style="background-color: var(--bg-tertiary); color: var(--text-secondary);"
title="Add item here" title="Add item here"
>+</button> >+</button>
<button <button
@click="renameFolder('${folder.path.replace(/'/g, "\\'")}', '${folder.name.replace(/'/g, "\\'")}')" onclick="event.stopPropagation(); window.$root.renameFolder('${escapedPath}', '${folder.name.replace(/'/g, "\\'").replace(/\\/g, "\\\\")}')"
class="px-1.5 py-0.5 text-xs rounded hover:brightness-110" class="px-1.5 py-0.5 text-xs rounded hover:brightness-110"
style="background-color: var(--bg-tertiary); color: var(--text-secondary);" style="background-color: var(--bg-tertiary); color: var(--text-secondary);"
title="Rename folder" title="Rename folder"
></button> ></button>
<button <button
@click="deleteFolder('${folder.path.replace(/'/g, "\\'")}', '${folder.name.replace(/'/g, "\\'")}')" onclick="event.stopPropagation(); window.$root.deleteFolder('${escapedPath}', '${folder.name.replace(/'/g, "\\'").replace(/\\/g, "\\\\")}')"
class="px-1 py-0.5 text-xs rounded hover:brightness-110" class="px-1 py-0.5 text-xs rounded hover:brightness-110"
style="color: var(--error);" style="color: var(--error);"
title="Delete folder" title="Delete folder"
@ -1069,29 +1073,32 @@ function noteApp() {
// Different icon for images // Different icon for images
const icon = isImage ? '🖼️' : ''; const icon = isImage ? '🖼️' : '';
// Escape paths for use in native event handlers
const escapedNotePath = note.path.replace(/'/g, "\\'").replace(/\\/g, "\\\\");
const escapedNoteName = note.name.replace(/'/g, "\\'").replace(/\\/g, "\\\\");
// Click handler // Click handler
const clickHandler = `openItem('${note.path.replace(/'/g, "\\'")}', '${note.type}')`; const clickHandler = `window.$root.openItem('${escapedNotePath}', '${note.type}')`;
// Delete handler // Delete handler
const deleteHandler = isImage const deleteHandler = isImage
? `deleteImage('${note.path.replace(/'/g, "\\'")}')` ? `event.stopPropagation(); window.$root.deleteImage('${escapedNotePath}')`
: `deleteNote('${note.path.replace(/'/g, "\\'")}', '${note.name.replace(/'/g, "\\'")}')`; : `event.stopPropagation(); window.$root.deleteNote('${escapedNotePath}', '${escapedNoteName}')`;
html += ` html += `
<div <div
draggable="true" draggable="true"
x-data="{}" ondragstart="window.$root.onNoteDragStart('${escapedNotePath}', event)"
@dragstart="onNoteDragStart('${note.path.replace(/'/g, "\\'")}', $event)" ondragend="window.$root.onNoteDragEnd()"
@dragend="onNoteDragEnd()" onclick="${clickHandler}"
@click="${clickHandler}"
class="note-item px-2 py-1 text-sm relative" class="note-item px-2 py-1 text-sm relative"
style="${isCurrent ? 'background-color: var(--accent-light); color: var(--accent-primary);' : 'color: var(--text-primary);'} ${isImage ? 'opacity: 0.85;' : ''} cursor: pointer;" style="${isCurrent ? 'background-color: var(--accent-light); color: var(--accent-primary);' : 'color: var(--text-primary);'} ${isImage ? 'opacity: 0.85;' : ''} cursor: pointer;"
@mouseover="if('${note.path}' !== currentNote && '${note.path}' !== currentImage) $el.style.backgroundColor='var(--bg-hover)'" onmouseover="if('${escapedNotePath}' !== window.$root.currentNote && '${escapedNotePath}' !== window.$root.currentImage) this.style.backgroundColor='var(--bg-hover)'"
@mouseout="if('${note.path}' !== currentNote && '${note.path}' !== currentImage) $el.style.backgroundColor='transparent'" onmouseout="if('${escapedNotePath}' !== window.$root.currentNote && '${escapedNotePath}' !== window.$root.currentImage) this.style.backgroundColor='transparent'"
> >
<span class="truncate" style="display: block; padding-right: 30px;">${icon}${icon ? ' ' : ''}${note.name}</span> <span class="truncate" style="display: block; padding-right: 30px;">${icon}${icon ? ' ' : ''}${note.name}</span>
<button <button
@click.stop="${deleteHandler}" onclick="${deleteHandler}"
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" 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"
style="opacity: 0; color: var(--error);" style="opacity: 0; color: var(--error);"
title="${isImage ? 'Delete image' : 'Delete note'}" title="${isImage ? 'Delete image' : 'Delete note'}"
@ -1233,9 +1240,9 @@ function noteApp() {
this.dropTarget = null; this.dropTarget = null;
this.dragOverFolder = null; this.dragOverFolder = null;
// Reset opacity of all note items // Reset opacity of all note items
document.querySelectorAll('.note-item').forEach(el => { document.querySelectorAll('.note-item').forEach(el => el.style.opacity = '1');
el.style.opacity = '1'; // Reset drag-over class (more efficient than querying all folder items)
}); document.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));
}, },
// Handle dragover on editor to show cursor position // Handle dragover on editor to show cursor position
@ -1570,10 +1577,22 @@ function noteApp() {
this.draggedFolder = null; this.draggedFolder = null;
this.dropTarget = null; this.dropTarget = null;
this.dragOverFolder = null; this.dragOverFolder = null;
// Reset opacity of all folder items // Reset styles - only query elements with drag-over class (more efficient)
document.querySelectorAll('.folder-item').forEach(el => { document.querySelectorAll('.folder-item').forEach(el => el.style.opacity = '1');
el.style.opacity = '1'; document.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));
}); },
cancelDrag() {
// Cancel any active drag operation (triggered by ESC key)
this.draggedNote = null;
this.draggedFolder = null;
this.draggedItem = null;
this.dropTarget = null;
this.dragOverFolder = null;
// Reset styles - only query elements with drag-over class (more efficient)
document.querySelectorAll('.folder-item').forEach(el => el.style.opacity = '1');
document.querySelectorAll('.note-item').forEach(el => el.style.opacity = '1');
document.querySelectorAll('.drag-over').forEach(el => el.classList.remove('drag-over'));
}, },
async onFolderDrop(targetFolderPath) { async onFolderDrop(targetFolderPath) {

View File

@ -453,6 +453,11 @@
background-color: var(--bg-hover); background-color: var(--bg-hover);
} }
.folder-item.drag-over {
background-color: var(--accent-light) !important;
border: 2px dashed var(--accent-primary) !important;
}
.folder-expanded { .folder-expanded {
font-weight: 600; font-weight: 600;
} }