fix(library+workspace): arborescence dans Workspace tab, icônes drag+multi-select visibles, inline rename au clic
- Library: ajout /api/library/children/{id} pour charger les enfants à la demande
- Library: drag handle (.drag-handle) et checkbox (.lib-checkbox) toujours visibles
- Library: SortableJS init pour drag-and-drop réordonner les rows
- Library: toggleExpand() async avec chargement des enfants depuis l'API
- Local workspace: drag handle 6-dots + checkbox toujours visible dans les tree items
- Local workspace: renderChildren() inclut checkbox + drag handle + inline rename
- Local workspace: clic sur nom de fichier = inline rename (plus navigation directe)
- Local workspace: bouton Open pour ouvrir le fichier (double-clic aussi)
- CSS: .item-checkbox toujours visible (plus opacity:0)
- CSS: .drag-handle avec opacité .45 par défaut, 1.0 au hover
This commit is contained in:
+79
-10
@@ -58,17 +58,17 @@
|
||||
.lib-row .row-cell{display:flex;align-items:center;padding:0 8px;height:100%;overflow:hidden;}
|
||||
|
||||
/* Hover-only elements */
|
||||
.lib-row .hover-only{opacity:0;transition:opacity 0.1s;}
|
||||
.lib-row .hover-only{opacity:0.35;transition:opacity 0.15s;}
|
||||
.lib-row:hover .hover-only{opacity:1;}
|
||||
|
||||
/* Drag handle — 6 dots */
|
||||
.drag-handle{display:flex;flex-wrap:wrap;width:14px;height:14px;cursor:grab;align-content:space-between;justify-content:space-between;flex-shrink:0;}
|
||||
.drag-handle:hover{opacity:1!important;}
|
||||
.drag-handle{display:flex;flex-wrap:wrap;width:16px;height:16px;cursor:grab;align-content:space-between;justify-content:space-between;flex-shrink:0;opacity:0.45;}
|
||||
.lib-row:hover .drag-handle{opacity:1;}
|
||||
.drag-handle .dot{width:3px;height:3px;background:var(--text-tertiary);border-radius:50%;}
|
||||
|
||||
/* Checkbox */
|
||||
.lib-checkbox{width:14px;height:14px;border:1.5px solid var(--text-tertiary);border-radius:3px;cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;transition:all 0.1s;}
|
||||
.lib-checkbox:hover{border-color:var(--text-primary);}
|
||||
.lib-checkbox{width:15px;height:15px;border:1.5px solid var(--text-tertiary);border-radius:3px;cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;transition:all 0.15s;}
|
||||
.lib-row:hover .lib-checkbox{border-color:var(--text-secondary);}
|
||||
.lib-checkbox.checked{background:var(--accent);border-color:var(--accent);}
|
||||
.lib-checkbox.checked::after{content:'✓';color:#fff;font-size:10px;font-weight:700;}
|
||||
|
||||
@@ -204,7 +204,7 @@
|
||||
</div>
|
||||
|
||||
<!-- ── Table ── -->
|
||||
<div class="lib-table" x-show="!loading && flatItems.length > 0">
|
||||
<div class="lib-table" x-show="!loading && flatItems.length > 0" id="lib-table">
|
||||
<div class="lib-table-header">
|
||||
<div class="th lib-col-drag"></div>
|
||||
<div class="th lib-col-select">
|
||||
@@ -217,9 +217,9 @@
|
||||
<div class="th lib-col-visited sortable" @click="sortBy='visited';doSort()">Last visited</div>
|
||||
<div class="th lib-col-open"></div>
|
||||
</div>
|
||||
<div class="lib-table-body">
|
||||
<div class="lib-table-body" id="lib-table-body">
|
||||
<template x-for="(item, idx) in flatItems" :key="item.id">
|
||||
<div class="lib-row" :class="{ selected: !!selected[item.id] }">
|
||||
<div class="lib-row" :class="{ selected: !!selected[item.id] }" :data-lib-id="item.id">
|
||||
<div class="row-cell lib-col-drag">
|
||||
<div class="drag-handle hover-only" style="margin:auto;" title="Drag to move">
|
||||
<span class="dot"></span><span class="dot"></span>
|
||||
@@ -484,9 +484,39 @@ function libraryPage() {
|
||||
},
|
||||
|
||||
// ── Tree expand ──
|
||||
toggleExpand(item) {
|
||||
this.expanded[item.id] = !this.expanded[item.id];
|
||||
async toggleExpand(item) {
|
||||
if (this.expanded[item.id]) {
|
||||
// Collapse
|
||||
delete this.expanded[item.id];
|
||||
this.expanded = Object.assign({}, this.expanded);
|
||||
return;
|
||||
}
|
||||
// Expand — load children if not loaded yet
|
||||
this.expanded[item.id] = true;
|
||||
this.expanded = Object.assign({}, this.expanded);
|
||||
if (!item.children || !item.children.length) {
|
||||
try {
|
||||
var r = await fetch('/api/library/children/' + item.id);
|
||||
var d = await r.json();
|
||||
item.children = d.items || [];
|
||||
// Mark the tree for depth calculation
|
||||
if (typeof this._markChildren === 'function') {
|
||||
this._markChildren(item.children, (item.depth || 0) + 1);
|
||||
}
|
||||
} catch(e) {
|
||||
item.children = [];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_markChildren(nodes, depth) {
|
||||
if (!nodes) return;
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
nodes[i].depth = depth;
|
||||
if (nodes[i].children && nodes[i].children.length > 0) {
|
||||
this._markChildren(nodes[i].children, depth + 1);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// ── Selection ──
|
||||
@@ -712,5 +742,44 @@ 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>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user