fix(library): rendu DOM direct (innerHTML) + suppression SortableJS
Abandon complet d'Alpine pour le rendu du tableau : - _renderTable() fait innerHTML directement sur #lib-table-body - Plus de x-for, plus de x-html, plus de getters - SortableJS supprimé (causait removeEventListener sur null) - Fix startRename/commitRenameById pour utiliser les IDs DOM
This commit is contained in:
+26
-65
@@ -220,7 +220,7 @@
|
|||||||
<div class="th lib-col-edited sortable" @click="sortBy='edited';doSort()">Last edited</div>
|
<div class="th lib-col-edited sortable" @click="sortBy='edited';doSort()">Last edited</div>
|
||||||
<div class="th lib-col-visited sortable" @click="sortBy='visited';doSort()">Last visited</div>
|
<div class="th lib-col-visited sortable" @click="sortBy='visited';doSort()">Last visited</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="lib-table-body" id="lib-table-body" x-html="tableHtml">
|
<div class="lib-table-body" id="lib-table-body">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -313,12 +313,13 @@ function libraryPage() {
|
|||||||
peekFavorited: false,
|
peekFavorited: false,
|
||||||
peekMoreOpen: false,
|
peekMoreOpen: false,
|
||||||
|
|
||||||
// ── Computed flat list (NOT a getter — Alpine 3.14.9 bug) ──
|
// ── Computed flat list ──
|
||||||
flatItems: [],
|
flatItems: [],
|
||||||
tableHtml: '',
|
|
||||||
|
|
||||||
_buildTableHtml() {
|
_renderTable() {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
var body = document.getElementById('lib-table-body');
|
||||||
|
if (!body) return;
|
||||||
var html = '';
|
var html = '';
|
||||||
for (var i = 0; i < this.flatItems.length; i++) {
|
for (var i = 0; i < this.flatItems.length; i++) {
|
||||||
var item = this.flatItems[i];
|
var item = this.flatItems[i];
|
||||||
@@ -346,7 +347,7 @@ function libraryPage() {
|
|||||||
html += '<span class="row-icon">' + (item.icon || '\ud83d\udcc4') + '</span>';
|
html += '<span class="row-icon">' + (item.icon || '\ud83d\udcc4') + '</span>';
|
||||||
// Title or rename input
|
// Title or rename input
|
||||||
if (isRenaming) {
|
if (isRenaming) {
|
||||||
html += '<input value="' + self._escAttr(self.renameValue) + '" onkeydown="if(event.key===\'Enter\')window._libData.commitRenameById(' + item.id + ')" onblur="window._libData.commitRenameById(' + item.id + ')" style="flex:1;min-width:80px;font-size:13px;padding:2px 4px;background:var(--bg-primary);border:1px solid var(--accent);border-radius:4px;color:var(--text-primary);outline:none;" onclick="event.stopPropagation()">';
|
html += '<input id="rename-inp-' + item.id + '" value="' + self._escAttr(self.renameValue) + '" onkeydown="if(event.key===\'Enter\')window._libData.commitRenameById(' + item.id + ')" onblur="window._libData.commitRenameById(' + item.id + ')" style="flex:1;min-width:80px;font-size:13px;padding:2px 4px;background:var(--bg-primary);border:1px solid var(--accent);border-radius:4px;color:var(--text-primary);outline:none;" onclick="event.stopPropagation()">';
|
||||||
} else {
|
} else {
|
||||||
html += '<span class="row-title" onclick="window._libData.startRenameById(' + item.id + ')" ondblclick="window._libData.openItemById(' + item.id + ')" title="Click to rename, double-click to open">' + self._escHtml(item.title) + '</span>';
|
html += '<span class="row-title" onclick="window._libData.startRenameById(' + item.id + ')" ondblclick="window._libData.openItemById(' + item.id + ')" title="Click to rename, double-click to open">' + self._escHtml(item.title) + '</span>';
|
||||||
}
|
}
|
||||||
@@ -365,7 +366,7 @@ function libraryPage() {
|
|||||||
html += '<div class="row-cell lib-col-visited"><span class="row-time">' + self.formatTime(item.visited_at) + '</span></div>';
|
html += '<div class="row-cell lib-col-visited"><span class="row-time">' + self.formatTime(item.visited_at) + '</span></div>';
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
}
|
}
|
||||||
this.tableHtml = html;
|
body.innerHTML = html;
|
||||||
},
|
},
|
||||||
|
|
||||||
_escHtml(s) { var d=document.createElement('div'); d.textContent = s||''; return d.innerHTML; },
|
_escHtml(s) { var d=document.createElement('div'); d.textContent = s||''; return d.innerHTML; },
|
||||||
@@ -384,7 +385,7 @@ function libraryPage() {
|
|||||||
this.flatItems.splice(0, this.flatItems.length, ...result);
|
this.flatItems.splice(0, this.flatItems.length, ...result);
|
||||||
this.selectedCount = Object.keys(this.selected).length;
|
this.selectedCount = Object.keys(this.selected).length;
|
||||||
this.allSelected = this.flatItems.length > 0 && this.selectedCount === this.flatItems.length;
|
this.allSelected = this.flatItems.length > 0 && this.selectedCount === this.flatItems.length;
|
||||||
this._buildTableHtml();
|
this._renderTable();
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── Global dispatch (bypasses Alpine x-for reactivity issues) ──
|
// ── Global dispatch (bypasses Alpine x-for reactivity issues) ──
|
||||||
@@ -405,7 +406,10 @@ function libraryPage() {
|
|||||||
},
|
},
|
||||||
commitRenameById(id) {
|
commitRenameById(id) {
|
||||||
var item = this._findItem(id);
|
var item = this._findItem(id);
|
||||||
if (!item) { this.renamingId = null; this._buildTableHtml(); return; }
|
if (!item) { this.renamingId = null; this._renderTable(); return; }
|
||||||
|
// Read value from the input
|
||||||
|
var inp = document.getElementById('rename-inp-' + id);
|
||||||
|
if (inp) this.renameValue = inp.value;
|
||||||
this.commitRename(item);
|
this.commitRename(item);
|
||||||
},
|
},
|
||||||
openItemById(id) {
|
openItemById(id) {
|
||||||
@@ -524,7 +528,7 @@ function libraryPage() {
|
|||||||
console.log('[Library] loaded ' + this.items.length + ' items');
|
console.log('[Library] loaded ' + this.items.length + ' items');
|
||||||
this._markTree(this.items, 0);
|
this._markTree(this.items, 0);
|
||||||
this._recomputeFlatItems();
|
this._recomputeFlatItems();
|
||||||
this._buildTableHtml();
|
this._renderTable();
|
||||||
console.log('[Library] flatItems after recompute: ' + this.flatItems.length);
|
console.log('[Library] flatItems after recompute: ' + this.flatItems.length);
|
||||||
// Force Alpine to re-render (known issue with x-for in 3.14.9)
|
// Force Alpine to re-render (known issue with x-for in 3.14.9)
|
||||||
var self = this;
|
var self = this;
|
||||||
@@ -568,7 +572,7 @@ function libraryPage() {
|
|||||||
delete this.expanded[item.id];
|
delete this.expanded[item.id];
|
||||||
this.expanded = Object.assign({}, this.expanded);
|
this.expanded = Object.assign({}, this.expanded);
|
||||||
this._recomputeFlatItems();
|
this._recomputeFlatItems();
|
||||||
this._buildTableHtml();
|
this._renderTable();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Expand — load children if not loaded yet
|
// Expand — load children if not loaded yet
|
||||||
@@ -586,7 +590,7 @@ function libraryPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._recomputeFlatItems();
|
this._recomputeFlatItems();
|
||||||
this._buildTableHtml();
|
this._renderTable();
|
||||||
},
|
},
|
||||||
|
|
||||||
_markChildren(nodes, depth) {
|
_markChildren(nodes, depth) {
|
||||||
@@ -608,7 +612,7 @@ function libraryPage() {
|
|||||||
}
|
}
|
||||||
this.selected = Object.assign({}, this.selected);
|
this.selected = Object.assign({}, this.selected);
|
||||||
this._recomputeFlatItems();
|
this._recomputeFlatItems();
|
||||||
this._buildTableHtml();
|
this._renderTable();
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleSelectAll() {
|
toggleSelectAll() {
|
||||||
@@ -622,14 +626,14 @@ function libraryPage() {
|
|||||||
this.selected = s;
|
this.selected = s;
|
||||||
}
|
}
|
||||||
this._recomputeFlatItems();
|
this._recomputeFlatItems();
|
||||||
this._buildTableHtml();
|
this._renderTable();
|
||||||
},
|
},
|
||||||
|
|
||||||
clearSelection() {
|
clearSelection() {
|
||||||
this.selected = {};
|
this.selected = {};
|
||||||
this.moreOpen = false;
|
this.moreOpen = false;
|
||||||
this._recomputeFlatItems();
|
this._recomputeFlatItems();
|
||||||
this._buildTableHtml();
|
this._renderTable();
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── Multi-select actions ──
|
// ── Multi-select actions ──
|
||||||
@@ -678,22 +682,18 @@ function libraryPage() {
|
|||||||
startRename(item) {
|
startRename(item) {
|
||||||
this.renamingId = item.id;
|
this.renamingId = item.id;
|
||||||
this.renameValue = item.title;
|
this.renameValue = item.title;
|
||||||
this._buildTableHtml();
|
this._renderTable();
|
||||||
var self = this;
|
// Focus the rename input
|
||||||
this.$nextTick(function() {
|
setTimeout(function() {
|
||||||
var inp = document.querySelector('.row-title input, [onkeydown*=\"commitRenameById\"]');
|
var inp = document.getElementById('rename-inp-' + item.id);
|
||||||
// Try to find the rename input that was just created
|
if (inp) { inp.focus(); inp.select(); }
|
||||||
var inputs = document.querySelectorAll('#lib-table-body input');
|
}, 50);
|
||||||
for (var i = 0; i < inputs.length; i++) {
|
|
||||||
if (inputs[i].value === item.title) { inputs[i].focus(); inputs[i].select(); break; }
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async commitRename(item) {
|
async commitRename(item) {
|
||||||
var newName = (this.renameValue || '').trim();
|
var newName = (this.renameValue || '').trim();
|
||||||
this.renamingId = null;
|
this.renamingId = null;
|
||||||
if (!newName || newName === item.title) { this._buildTableHtml(); return; }
|
if (!newName || newName === item.title) { this._renderTable(); return; }
|
||||||
try {
|
try {
|
||||||
var r = await fetch('/api/pages/' + item.id + '/rename', {
|
var r = await fetch('/api/pages/' + item.id + '/rename', {
|
||||||
method:'PUT', headers:{'Content-Type':'application/json'},
|
method:'PUT', headers:{'Content-Type':'application/json'},
|
||||||
@@ -703,7 +703,7 @@ function libraryPage() {
|
|||||||
item.title = newName;
|
item.title = newName;
|
||||||
}
|
}
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
this._buildTableHtml();
|
this._renderTable();
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── Add new ──
|
// ── Add new ──
|
||||||
@@ -834,44 +834,5 @@ function libraryPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// SortableJS — drag-and-drop reorder in library table
|
|
||||||
document.addEventListener('alpine:init', function() {
|
|
||||||
var initSortable = function() {
|
|
||||||
var body = document.getElementById('lib-table-body');
|
|
||||||
if (!body || typeof Sortable === 'undefined') return;
|
|
||||||
// Clean up any existing instance
|
|
||||||
if (body._sortable) body._sortable.destroy();
|
|
||||||
body._sortable = new Sortable(body, {
|
|
||||||
handle: '.drag-handle',
|
|
||||||
animation: 150,
|
|
||||||
ghostClass: 'sortable-ghost',
|
|
||||||
onEnd: async function(evt) {
|
|
||||||
var itemEl = evt.item;
|
|
||||||
var itemId = itemEl.getAttribute('data-lib-id');
|
|
||||||
var prevEl = evt.item.previousElementSibling;
|
|
||||||
var nextEl = evt.item.nextElementSibling;
|
|
||||||
var prevId = prevEl ? prevEl.getAttribute('data-lib-id') : null;
|
|
||||||
var nextId = nextEl ? nextEl.getAttribute('data-lib-id') : null;
|
|
||||||
if (!itemId) return;
|
|
||||||
try {
|
|
||||||
await fetch('/api/pages/' + itemId + '/move', {
|
|
||||||
method: 'PUT',
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
body: JSON.stringify({before_id: nextId || null, after_id: prevId || null})
|
|
||||||
});
|
|
||||||
} catch(e) {}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
// Watch for table body updates with MutationObserver
|
|
||||||
var target = document.getElementById('lib-table');
|
|
||||||
if (target) {
|
|
||||||
var observer = new MutationObserver(function() {
|
|
||||||
setTimeout(initSortable, 200);
|
|
||||||
});
|
|
||||||
observer.observe(target, {childList: true, subtree: true});
|
|
||||||
}
|
|
||||||
initSortable();
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
Reference in New Issue
Block a user