fix: arborescence dédoublée + dossiers fermés visibles + preview pages FlowDeck
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped

1. Tree affichage: displayTree contient maintenant les racines uniquement
   (plus de flatten). _markTree ajoute depth/db_id sans aplatir, et
   renderChildren gère la récursion. Les enfants n'apparaissent plus
   quand le dossier est fermé, et plus de dédoublement à l'ouverture.

2. Filtre: _filterTree travaille sur l'arbre hiérarchique, clone les
   nodes pour préserver la structure.

3. Preview pages FlowDeck: nouvelle API GET /api/local-workspace/page-content/{id}.
   showPreview détecte content_format='file' → /api/files/, sinon → API page-content.
   Plus de 'file not found' sur les fichiers créés dans FlowDeck.

4. API tree: ajout content_format dans la réponse pour distinguer
   fichiers uploadés vs pages FlowDeck.
This commit is contained in:
2026-07-11 10:08:33 -04:00
parent 8bd737cef6
commit 722c7103de
2 changed files with 59 additions and 27 deletions
+41 -25
View File
@@ -326,25 +326,25 @@ document.addEventListener('alpine:init', function() {
}
var r = await fetch(url);
var d = await r.json();
this.tree = this.addDepths(d.tree || [], 0);
this.displayTree = this.tree;
// Keep tree hierarchical, only roots in displayTree
var roots = d.tree || [];
this._markTree(roots, 0);
this.tree = roots;
this.displayTree = roots;
this.breadcrumb = d.breadcrumb || [];
this.loaded = true;
} catch(e) { console.error('Tree load error:', e); this.loaded = true; }
},
addDepths(nodes, depth) {
var result = [];
_markTree(nodes, depth) {
for (var i=0; i<nodes.length; i++) {
var n = nodes[i];
n.depth = depth;
n.db_id = n.id; // Ensure db_id for consistency with renderChildren
result.push(n);
n.db_id = n.id;
if (n.children && n.children.length > 0) {
result = result.concat(this.addDepths(n.children, depth+1));
this._markTree(n.children, depth + 1);
}
}
return result;
},
renderChildren(children, depth) {
@@ -402,19 +402,21 @@ document.addEventListener('alpine:init', function() {
doFilter() {
var q = (this.searchQuery || '').toLowerCase().trim();
if (!q) { this.displayTree = this.tree; return; }
var self = this;
this.displayTree = this.tree.filter(function(n) {
return self._itemMatches(n, q);
});
this.displayTree = this._filterTree(this.tree, q);
},
_itemMatches(node, query) {
if ((node.name || '').toLowerCase().indexOf(query) !== -1) return true;
if (node.children && node.children.length > 0) {
for (var i = 0; i < node.children.length; i++) {
if (this._itemMatches(node.children[i], query)) return true;
_filterTree(nodes, query) {
var result = [];
for (var i=0; i<nodes.length; i++) {
var n = nodes[i];
var filteredChildren = n.children ? this._filterTree(n.children, query) : [];
var selfMatches = (n.name || '').toLowerCase().indexOf(query) !== -1;
if (selfMatches || filteredChildren.length > 0) {
var clone = Object.assign({}, n);
clone.children = filteredChildren;
result.push(clone);
}
}
return false;
return result;
},
// ── Preview on hover ──
@@ -426,20 +428,34 @@ document.addEventListener('alpine:init', function() {
this.previewVisible = true;
var self = this;
var wsId = {{ workspace_id }} || 1;
var url = '/api/files/' + wsId + '/' + encodeURIComponent(node.name);
// Check if image
// Image preview
var imgExts = ['.png','.jpg','.jpeg','.gif','.webp','.svg','.bmp','.ico'];
var isImg = false;
for (var i=0; i<imgExts.length; i++) {
if ((node.name||'').toLowerCase().endsWith(imgExts[i])) { isImg = true; break; }
}
if (isImg) {
this.previewContent = '<img src="'+url+'" style="max-width:380px;max-height:180px;display:block;">';
if (node.content_format === 'file') {
// Uploaded file: serve from disk
var url = '/api/files/' + wsId + '/' + encodeURIComponent(node.name);
if (isImg) {
this.previewContent = '<img src="'+url+'" style="max-width:380px;max-height:180px;display:block;">';
} else {
this.previewContent = 'Loading...';
fetch(url)
.then(function(r) { return r.text(); })
.then(function(t) { self.previewContent = t.substring(0, 500); })
.catch(function() { self.previewContent = '(preview not available)'; });
}
} else {
// FlowDeck page: fetch content via API
this.previewContent = 'Loading...';
fetch(url)
.then(function(r) { return r.text(); })
.then(function(t) { self.previewContent = t.substring(0, 500); })
fetch('/api/local-workspace/page-content/' + node.id)
.then(function(r) { return r.json(); })
.then(function(d) {
self.previewContent = (d.content || '').substring(0, 500);
})
.catch(function() { self.previewContent = '(preview not available)'; });
}
},