fix: vues workspace affichent répertoire courant + navigation sans rechargement
- Vues list/details/title/content utilisent displayTree (enfants du dossier courant) au lieu de flatTree (arbre entier aplati) → comportement explorateur de fichiers - Navigation sans rechargement: navigateToFolder/goToParent/goBack/goForward utilisent fetch AJAX + _loadFolder() au lieu de window.location - folderStack remplace navHistory + breadcrumb: pile de navigation avec noms - Breadcrumb dynamique: cliquable, affiche le chemin courant depuis folderStack - _reloadAfterAction utilise _loadFolder au lieu de window.location.reload - La vue choisie (tree/list/details/title/content) est préservée pendant la navigation
This commit is contained in:
@@ -311,20 +311,20 @@
|
||||
</div>
|
||||
|
||||
<!-- Breadcrumb -->
|
||||
<div class="breadcrumb-bar" x-show="breadcrumb.length > 0">
|
||||
<a href="/local-workspace">📁 {{ workspace_name }}</a>
|
||||
<template x-for="(b, i) in breadcrumb" :key="b.id">
|
||||
<div class="breadcrumb-bar" x-show="folderStack.length > 0">
|
||||
<a href="/local-workspace" @click.prevent="navigateToRoot()">📁 {{ workspace_name }}</a>
|
||||
<template x-for="(b, i) in folderStack" :key="'bc'+b.id">
|
||||
<span style="display:contents;">
|
||||
<span class="sep">/</span>
|
||||
<a :href="'/local-workspace?folder=' + b.id" x-text="b.name"
|
||||
:class="{ current: i === breadcrumb.length - 1 }"
|
||||
<a href="#" @click.prevent="navigateToBreadcrumb(i)" x-text="b.name"
|
||||
:class="{ current: i === folderStack.length - 1 }"
|
||||
@dblclick.prevent="startInlineRename(b, $event)"></a>
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- Path bar -->
|
||||
<div class="path-bar" x-show="breadcrumb.length > 0">
|
||||
<div class="path-bar" x-show="folderStack.length > 0">
|
||||
<span class="path-icon">📂</span>
|
||||
<input class="path-input" :value="pathValue"
|
||||
@keydown.enter="navigateToPath()"
|
||||
@@ -334,9 +334,9 @@
|
||||
</div>
|
||||
|
||||
<div class="ws-header">
|
||||
<h1 x-show="breadcrumb.length===0">📁 {{ workspace_name }}</h1>
|
||||
<h1 x-show="breadcrumb.length>0">
|
||||
📁 <span x-text="breadcrumb.length > 0 ? breadcrumb[breadcrumb.length-1].name : ''"></span>
|
||||
<h1 x-show="folderStack.length===0 || (folderStack.length===1 && folderStack[0].id===0)">📁 {{ workspace_name }}</h1>
|
||||
<h1 x-show="folderStack.length>0 && !(folderStack.length===1 && folderStack[0].id===0)">
|
||||
📁 <span x-text="folderStack[folderStack.length-1].name"></span>
|
||||
</h1>
|
||||
<div class="ws-toolbar">
|
||||
<button class="btn-icon" @click="goBack()" title="Back" :disabled="!canGoBack">←</button>
|
||||
@@ -392,7 +392,7 @@
|
||||
|
||||
<ul class="ws-tree" x-show="viewMode==='tree' && (displayTree.length>0 || (currentFolder > 0))" @click="handleTreeAction($event)">
|
||||
<!-- Parent folder ".." -->
|
||||
<li x-show="currentFolder > 0">
|
||||
<li x-show="currentFolder > 0" style="list-style:none;">
|
||||
<div class="ws-tree-item" @click="goToParent()" style="padding-left:4px;">
|
||||
<span style="width:16px;flex-shrink:0;"></span>
|
||||
<span class="icon">📂</span>
|
||||
@@ -534,7 +534,7 @@
|
||||
</table>
|
||||
|
||||
<!-- List view — compact flat list -->
|
||||
<table class="ws-table-compact" x-show="viewMode==='list' && !searchQuery && !filterType && !tagFilter && flatTree.length>0">
|
||||
<table class="ws-table-compact" x-show="viewMode==='list' && !searchQuery && !filterType && !tagFilter && displayTree.length>0">
|
||||
<thead><tr>
|
||||
<th style="width:24px"></th>
|
||||
<th>Name</th>
|
||||
@@ -543,7 +543,7 @@
|
||||
<th style="width:100px">Modified</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<template x-for="node in flatTree" :key="'l'+node.id">
|
||||
<template x-for="node in displayTree" :key="'l'+node.id">
|
||||
<tr @click="node.is_folder ? navigateToFolder(node.id) : selectForPreview(node)" style="cursor:pointer">
|
||||
<td><span x-text="_fileIcon(node.name, node.is_folder, node.content_format)"></span></td>
|
||||
<td class="col-name" x-text="node.name"></td>
|
||||
@@ -556,7 +556,7 @@
|
||||
</table>
|
||||
|
||||
<!-- Details view — full table with all columns -->
|
||||
<table class="ws-table-enhanced" x-show="viewMode==='details' && !searchQuery && !filterType && !tagFilter && flatTree.length>0">
|
||||
<table class="ws-table-enhanced" x-show="viewMode==='details' && !searchQuery && !filterType && !tagFilter && displayTree.length>0">
|
||||
<thead><tr>
|
||||
<th style="width:24px"></th>
|
||||
<th>Name</th>
|
||||
@@ -568,7 +568,7 @@
|
||||
<th>Tags</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<template x-for="node in flatTree" :key="'d'+node.id">
|
||||
<template x-for="node in displayTree" :key="'d'+node.id">
|
||||
<tr @click="node.is_folder ? navigateToFolder(node.id) : selectForPreview(node)" style="cursor:pointer"
|
||||
:class="{ 'selected-row': previewItem && previewItem.id === node.id }">
|
||||
<td><span class="table-icon" x-text="_fileIcon(node.name, node.is_folder, node.content_format)"></span></td>
|
||||
@@ -591,8 +591,8 @@
|
||||
</table>
|
||||
|
||||
<!-- Title view — card grid -->
|
||||
<div class="title-grid" x-show="viewMode==='title' && !searchQuery && !filterType && !tagFilter && flatTree.length>0">
|
||||
<template x-for="node in flatTree" :key="'g'+node.id">
|
||||
<div class="title-grid" x-show="viewMode==='title' && !searchQuery && !filterType && !tagFilter && displayTree.length>0">
|
||||
<template x-for="node in displayTree" :key="'g'+node.id">
|
||||
<div class="title-card" @click="node.is_folder ? navigateToFolder(node.id) : selectForPreview(node)"
|
||||
:class="{ 'selected-card': previewItem && previewItem.id === node.id }">
|
||||
<div class="title-card-icon" x-text="node.is_folder ? '📁' : _fileIcon(node.name, node.is_folder, node.content_format)"></div>
|
||||
@@ -606,8 +606,8 @@
|
||||
</div>
|
||||
|
||||
<!-- Content view — list with inline preview -->
|
||||
<div class="content-list" x-show="viewMode==='content' && !searchQuery && !filterType && !tagFilter && flatTree.length>0">
|
||||
<template x-for="node in flatTree" :key="'c'+node.id">
|
||||
<div class="content-list" x-show="viewMode==='content' && !searchQuery && !filterType && !tagFilter && displayTree.length>0">
|
||||
<template x-for="node in displayTree" :key="'c'+node.id">
|
||||
<div class="content-item" @click="node.is_folder ? navigateToFolder(node.id) : selectForPreview(node)"
|
||||
:class="{ 'selected-row': previewItem && previewItem.id === node.id }">
|
||||
<div class="content-item-header">
|
||||
@@ -654,7 +654,7 @@
|
||||
<div class="modal-box">
|
||||
<h3><span x-text="createType==='folder'?'📁':'📄'"></span> <span x-text="createType==='folder'?'New Folder':'New File'"></span></h3>
|
||||
<p class="modal-sub">
|
||||
In <strong>{{ workspace_name }}</strong>
|
||||
In <strong>{{ workspace_name }}</strong> <span x-show="folderStack.length>1">/ <span x-text="folderStack[folderStack.length-1].name"></span></span>
|
||||
<span x-show="parentFolder"> / <span x-text="parentFolder?.name"></span></span>
|
||||
</p>
|
||||
<input class="modal-input" x-model="newName" :placeholder="createType==='folder'?'Folder name':'File name'" @keydown.enter="doCreate" x-ref="cinp">
|
||||
@@ -748,6 +748,7 @@ document.addEventListener('alpine:init', function() {
|
||||
currentFolder: {{ current_folder_id }},
|
||||
searchQuery: '',
|
||||
filterType: '',
|
||||
folderStack: [],
|
||||
|
||||
// Inline rename
|
||||
renamingId: null,
|
||||
@@ -790,7 +791,22 @@ document.addEventListener('alpine:init', function() {
|
||||
|
||||
// Path bar
|
||||
get pathValue() {
|
||||
return '/workspace/' + this.breadcrumb.map(function(b){return b.name}).join('/');
|
||||
return '/workspace/' + this.folderStack.filter(function(b){return b.id > 0}).map(function(b){return b.name}).join('/');
|
||||
},
|
||||
|
||||
async navigateToRoot() {
|
||||
this.folderStack = [{id: 0, name: 'Root'}];
|
||||
this.navIndex = 0;
|
||||
this._saveExpandedState();
|
||||
await this._loadFolder(0);
|
||||
},
|
||||
|
||||
async navigateToBreadcrumb(idx) {
|
||||
this.folderStack = this.folderStack.slice(0, idx + 1);
|
||||
this.navIndex = idx;
|
||||
this._saveExpandedState();
|
||||
var entry = this.folderStack[idx];
|
||||
await this._loadFolder(entry ? entry.id : 0);
|
||||
},
|
||||
|
||||
// Drag indicators
|
||||
@@ -808,7 +824,7 @@ document.addEventListener('alpine:init', function() {
|
||||
navHistory: [],
|
||||
navIndex: -1,
|
||||
get canGoBack() { return this.navIndex > 0; },
|
||||
get canGoForward() { return this.navIndex < this.navHistory.length - 1; },
|
||||
get canGoForward() { return this.navIndex < this.folderStack.length - 1; },
|
||||
|
||||
// Preview popup
|
||||
previewVisible: false,
|
||||
@@ -841,9 +857,17 @@ document.addEventListener('alpine:init', function() {
|
||||
this.displayTree = roots;
|
||||
this.breadcrumb = d.breadcrumb || [];
|
||||
this.loaded = true;
|
||||
// Initialize nav history
|
||||
this.navHistory = [this.currentFolder || 0];
|
||||
this.navIndex = 0;
|
||||
// Initialize folder stack from breadcrumb
|
||||
this.folderStack = (this.breadcrumb || []).map(function(b) { return {id: b.id, name: b.name}; });
|
||||
if (this.currentFolder && this.currentFolder > 0 && this.folderStack.length === 0) {
|
||||
this.folderStack = [{id: 0, name: 'Root'}];
|
||||
// Add current folder name from tree if present
|
||||
if (this.tree.length > 0) {
|
||||
// The current folder's children are in breadcrumb, parent is the last
|
||||
}
|
||||
}
|
||||
this.navIndex = this.folderStack.length > 0 ? this.folderStack.length - 1 : 0;
|
||||
if (this.folderStack.length === 0) this.folderStack = [{id: 0, name: 'Root'}];
|
||||
// Expose globally for renderChildren onclick handlers
|
||||
window._wsData = this;
|
||||
// Listen for online/offline
|
||||
@@ -1159,37 +1183,69 @@ document.addEventListener('alpine:init', function() {
|
||||
}
|
||||
},
|
||||
|
||||
navigateToFolder(id) {
|
||||
// Push to nav history
|
||||
if (this.navIndex < this.navHistory.length - 1) {
|
||||
this.navHistory = this.navHistory.slice(0, this.navIndex + 1);
|
||||
async navigateToFolder(id) {
|
||||
// Find the folder name for the stack
|
||||
var folderNode = this._findNodeById(this.tree, id);
|
||||
var folderName = folderNode ? folderNode.name : 'Folder';
|
||||
// Push to folder stack for breadcrumb
|
||||
if (this.navIndex < this.folderStack.length - 1) {
|
||||
this.folderStack = this.folderStack.slice(0, this.navIndex + 1);
|
||||
}
|
||||
this.navHistory.push(id);
|
||||
this.navIndex = this.navHistory.length - 1;
|
||||
this._saveExpandedState();
|
||||
window.location = '/local-workspace?folder=' + id;
|
||||
this.folderStack.push({id: id, name: folderName});
|
||||
this.navIndex = this.folderStack.length - 1;
|
||||
// Fetch children of this folder
|
||||
await this._loadFolder(id);
|
||||
},
|
||||
|
||||
goBack() {
|
||||
async goToParent() {
|
||||
if (this.folderStack.length <= 1) { await this._loadFolder(0); return; }
|
||||
this.folderStack.pop();
|
||||
this.navIndex = this.folderStack.length - 1;
|
||||
var parentId = this.folderStack.length > 0 ? this.folderStack[this.folderStack.length - 1].id : 0;
|
||||
await this._loadFolder(parentId);
|
||||
},
|
||||
|
||||
async _loadFolder(folderId) {
|
||||
var url = '/api/local-workspace/tree';
|
||||
if (folderId && folderId > 0) url += '?folder=' + folderId;
|
||||
try {
|
||||
var r = await fetch(url);
|
||||
var d = await r.json();
|
||||
var roots = d.tree || [];
|
||||
this._markTree(roots, 0);
|
||||
this.tree = roots;
|
||||
this.displayTree = [...roots];
|
||||
this.breadcrumb = d.breadcrumb || [];
|
||||
this.currentFolder = folderId || 0;
|
||||
this._saveExpandedState();
|
||||
this.previewItem = null;
|
||||
} catch(e) { console.error('Load folder error:', e); }
|
||||
},
|
||||
|
||||
_findNodeById(nodes, id) {
|
||||
for (var i=0; i<nodes.length; i++) {
|
||||
if (nodes[i].id === id) return nodes[i];
|
||||
if (nodes[i].children) {
|
||||
var found = this._findNodeById(nodes[i].children, id);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
async goBack() {
|
||||
if (this.navIndex <= 0) return;
|
||||
this.navIndex--;
|
||||
var id = this.navHistory[this.navIndex];
|
||||
var entry = this.folderStack[this.navIndex];
|
||||
this._saveExpandedState();
|
||||
window.location = id ? '/local-workspace?folder=' + id : '/local-workspace';
|
||||
await this._loadFolder(entry ? entry.id : 0);
|
||||
},
|
||||
goForward() {
|
||||
if (this.navIndex >= this.navHistory.length - 1) return;
|
||||
async goForward() {
|
||||
if (this.navIndex >= this.folderStack.length - 1) return;
|
||||
this.navIndex++;
|
||||
var id = this.navHistory[this.navIndex];
|
||||
var entry = this.folderStack[this.navIndex];
|
||||
this._saveExpandedState();
|
||||
window.location = id ? '/local-workspace?folder=' + id : '/local-workspace';
|
||||
},
|
||||
goToParent() {
|
||||
var bc = this.breadcrumb;
|
||||
if (bc.length <= 1) { window.location = '/local-workspace'; return; }
|
||||
var parentId = bc[bc.length - 2].id;
|
||||
this._saveExpandedState();
|
||||
window.location = '/local-workspace?folder=' + parentId;
|
||||
await this._loadFolder(entry ? entry.id : 0);
|
||||
},
|
||||
|
||||
_saveExpandedState() {
|
||||
@@ -1198,7 +1254,7 @@ document.addEventListener('alpine:init', function() {
|
||||
},
|
||||
_reloadAfterAction() {
|
||||
this._saveExpandedState();
|
||||
window.location.reload();
|
||||
this._loadFolder(this.currentFolder);
|
||||
},
|
||||
|
||||
// ── Inline Rename ──
|
||||
|
||||
Reference in New Issue
Block a user