refactor: barre de navigation unifiée — breadcrumb éditable + recherche intégrée
- Barre unique remplaçant breadcrumb + path bar + header: back/forward + chemin + toolbar - Chemin cliquable avec navigation, double-clic pour rename inline - Édition du path intégrée: clic ✎ → input inline au même endroit - Barre de recherche intégrée dans la toolbar (compacte, à côté du sélecteur de vue) - Filtres compactés: icônes seules, ligne unique avec bouton clear - Gain d'espace vertical: ~80px libérés pour le contenu
This commit is contained in:
@@ -222,6 +222,40 @@
|
||||
.ws-table-compact td{padding:5px 10px;border-bottom:1px solid var(--border);}
|
||||
.ws-table-compact tr:hover td{background:var(--bg-hover);}
|
||||
|
||||
/* Unified navigation bar */
|
||||
.unified-nav{display:flex;align-items:center;gap:6px;padding:6px 8px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:10px;margin-bottom:6px;flex-wrap:wrap;}
|
||||
.nav-arrow{width:28px;height:28px;font-size:13px;flex-shrink:0;}
|
||||
.unified-breadcrumb{flex:1;display:flex;align-items:center;gap:2px;min-width:0;font-size:13px;overflow:hidden;}
|
||||
.unified-breadcrumb .bc-icon{font-size:15px;flex-shrink:0;}
|
||||
.unified-breadcrumb .bc-root{color:var(--text-secondary);text-decoration:none;white-space:nowrap;font-weight:500;flex-shrink:0;}
|
||||
.unified-breadcrumb .bc-root:hover{color:var(--accent);}
|
||||
.unified-breadcrumb .bc-sep{color:var(--text-tertiary);margin:0 2px;flex-shrink:0;}
|
||||
.unified-breadcrumb a{color:var(--text-secondary);text-decoration:none;white-space:nowrap;}
|
||||
.unified-breadcrumb a:hover{color:var(--accent);}
|
||||
.unified-breadcrumb .bc-current{color:var(--text);font-weight:600;}
|
||||
.bc-edit-trigger{background:none;border:none;color:var(--text-tertiary);cursor:pointer;font-size:11px;padding:2px 4px;border-radius:3px;flex-shrink:0;opacity:.5;transition:opacity 150ms;}
|
||||
.bc-edit-trigger:hover{opacity:1;color:var(--accent);background:var(--bg-hover);}
|
||||
.bc-edit-wrap{display:flex;align-items:center;gap:4px;flex-shrink:0;}
|
||||
.bc-edit-input{width:200px;background:var(--bg);border:1px solid var(--accent);border-radius:5px;color:var(--text);font-size:12px;padding:3px 8px;outline:none;font-family:'Fira Code',monospace;}
|
||||
.bc-edit-go{background:var(--accent);color:#fff;border:none;padding:3px 8px;border-radius:4px;cursor:pointer;font-size:12px;}
|
||||
.bc-edit-go:hover{opacity:.85;}
|
||||
.unified-actions{display:flex;align-items:center;gap:6px;flex-shrink:0;flex-wrap:wrap;}
|
||||
|
||||
/* Search bar inline (in unified nav) */
|
||||
.search-bar-inline{display:flex;align-items:center;gap:4px;padding:2px 8px;background:var(--bg);border:1px solid var(--border);border-radius:6px;min-width:140px;}
|
||||
.search-bar-inline:focus-within{border-color:var(--accent);}
|
||||
.search-bar-inline .search-icon{font-size:12px;color:var(--text-tertiary);}
|
||||
.search-bar-inline .search-input{width:80px;background:none;border:none;color:var(--text);font-size:12px;outline:none;}
|
||||
.search-bar-inline .search-input::placeholder{color:var(--text-tertiary);}
|
||||
.search-bar-inline .search-clear{background:none;border:none;color:var(--text-tertiary);cursor:pointer;font-size:14px;padding:0 2px;}
|
||||
.search-bar-inline .search-clear:hover{color:var(--text);}
|
||||
|
||||
/* Compact filter row */
|
||||
.filter-row{display:flex;align-items:center;gap:4px;padding:2px 0;margin-bottom:4px;flex-wrap:wrap;}
|
||||
.filter-row .filter-chip{font-size:12px;padding:3px 8px;}
|
||||
.filter-clear{font-size:11px;color:var(--text-tertiary);cursor:pointer;padding:2px 6px;border-radius:4px;}
|
||||
.filter-clear:hover{color:var(--danger);background:var(--bg-hover);}
|
||||
|
||||
/* View selector */
|
||||
.view-selector{display:flex;gap:2px;background:var(--bg-tertiary);border-radius:8px;padding:2px;}
|
||||
.view-btn{width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;background:none;border:none;border-radius:6px;cursor:pointer;font-size:14px;color:var(--text-tertiary);transition:all 150ms;}
|
||||
@@ -310,56 +344,57 @@
|
||||
<button class="undo-dismiss" @click="undoVisible=false">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Breadcrumb -->
|
||||
<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="#" @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="folderStack.length > 0">
|
||||
<span class="path-icon">📂</span>
|
||||
<input class="path-input" :value="pathValue"
|
||||
@keydown.enter="navigateToPath()"
|
||||
@focus="$event.target.select()"
|
||||
placeholder="/workspace/...">
|
||||
<button class="path-go" @click="navigateToPath()" title="Go">→</button>
|
||||
</div>
|
||||
|
||||
<div class="ws-header">
|
||||
<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>
|
||||
<button class="btn-icon" @click="goForward()" title="Forward" :disabled="!canGoForward">→</button>
|
||||
<!-- Unified navigation bar: breadcrumb + inline path edit + back/forward -->
|
||||
<div class="unified-nav">
|
||||
<button class="btn-icon nav-arrow" @click="goBack()" title="Back" :disabled="!canGoBack">←</button>
|
||||
<button class="btn-icon nav-arrow" @click="goForward()" title="Forward" :disabled="!canGoForward">→</button>
|
||||
<div class="unified-breadcrumb">
|
||||
<span class="bc-icon">📁</span>
|
||||
<a href="/local-workspace" @click.prevent="navigateToRoot()" class="bc-root">{{ workspace_name }}</a>
|
||||
<template x-for="(b, i) in folderStack" :key="'bc'+b.id">
|
||||
<span style="display:contents;">
|
||||
<span class="bc-sep">/</span>
|
||||
<a href="#" @click.prevent="navigateToBreadcrumb(i)" x-text="b.name"
|
||||
:class="{ 'bc-current': i === folderStack.length - 1 }"
|
||||
@dblclick.prevent="startInlineRename(b, $event)"></a>
|
||||
</span>
|
||||
</template>
|
||||
<!-- Inline path editor (shown on click) -->
|
||||
<div class="bc-edit-wrap" x-show="editingPath" @click.outside="editingPath=false">
|
||||
<input class="bc-edit-input" x-ref="pathEdit" :value="pathValue"
|
||||
@keydown.enter="navigateToPath(); editingPath=false"
|
||||
@keydown.escape="editingPath=false"
|
||||
placeholder="/workspace/...">
|
||||
<button class="bc-edit-go" @click="navigateToPath(); editingPath=false">→</button>
|
||||
</div>
|
||||
<button class="bc-edit-trigger" x-show="!editingPath" @click="startPathEdit()" title="Edit path">✎</button>
|
||||
</div>
|
||||
<div class="unified-actions">
|
||||
<div class="view-selector">
|
||||
<button class="view-btn" :class="{ active: viewMode==='tree' }" @click="viewMode='tree'; previewItem=null" title="Tree view">🌳</button>
|
||||
<button class="view-btn" :class="{ active: viewMode==='list' }" @click="viewMode='list'; previewItem=null" title="List view">📋</button>
|
||||
<button class="view-btn" :class="{ active: viewMode==='details' }" @click="viewMode='details'; previewItem=null" title="Details view">📊</button>
|
||||
<button class="view-btn" :class="{ active: viewMode==='title' }" @click="viewMode='title'; previewItem=null" title="Title cards">🏷️</button>
|
||||
<button class="view-btn" :class="{ active: viewMode==='content' }" @click="viewMode='content'; previewItem=null" title="Content preview">📝</button>
|
||||
<button class="view-btn" :class="{ active: viewMode==='tree' }" @click="viewMode='tree'; previewItem=null" title="Tree">🌳</button>
|
||||
<button class="view-btn" :class="{ active: viewMode==='list' }" @click="viewMode='list'; previewItem=null" title="List">📋</button>
|
||||
<button class="view-btn" :class="{ active: viewMode==='details' }" @click="viewMode='details'; previewItem=null" title="Details">📊</button>
|
||||
<button class="view-btn" :class="{ active: viewMode==='title' }" @click="viewMode='title'; previewItem=null" title="Cards">🏷️</button>
|
||||
<button class="view-btn" :class="{ active: viewMode==='content' }" @click="viewMode='content'; previewItem=null" title="Content">📝</button>
|
||||
</div>
|
||||
<div class="search-bar-inline">
|
||||
<span class="search-icon">🔍</span>
|
||||
<input class="search-input" type="text" placeholder="Filter..."
|
||||
x-model="searchQuery" @input="doFilter" autocomplete="off">
|
||||
<button class="search-clear" x-show="searchQuery" @click="searchQuery='';doFilter()" title="Clear">×</button>
|
||||
</div>
|
||||
<select class="sort-select" x-model="sortBy" @change="doSort()">
|
||||
<option value="">Sort by...</option>
|
||||
<option value="name-asc">Name A→Z</option>
|
||||
<option value="name-desc">Name Z→A</option>
|
||||
<option value="">Sort</option>
|
||||
<option value="name-asc">A→Z</option>
|
||||
<option value="name-desc">Z→A</option>
|
||||
<option value="type">Type</option>
|
||||
<option value="date-asc">Oldest</option>
|
||||
<option value="date-desc">Newest</option>
|
||||
</select>
|
||||
<button class="btn-icon" @click="expandAll()" title="Expand all" x-show="viewMode==='tree'">⊞</button>
|
||||
<button class="btn-icon" @click="collapseAll()" title="Collapse all" x-show="viewMode==='tree'">⊟</button>
|
||||
<button class="btn-icon" @click="openModal('file')" title="New file">📄</button>
|
||||
<button class="btn-icon" @click="openModal('folder')" title="New folder">📁</button>
|
||||
<button class="btn-icon" @click="expandAll()" title="Expand" x-show="viewMode==='tree'">⊞</button>
|
||||
<button class="btn-icon" @click="collapseAll()" title="Collapse" x-show="viewMode==='tree'">⊟</button>
|
||||
<button class="btn-icon btn-create" @click="openModal('file')" title="New file">📄</button>
|
||||
<button class="btn-icon btn-create" @click="openModal('folder')" title="New folder">📁</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -372,22 +407,16 @@
|
||||
|
||||
<!-- Tree -->
|
||||
<div x-show="viewMode==='tree'" style="display:contents;">
|
||||
<!-- Search bar -->
|
||||
<div class="search-bar" x-show="tree.length>0 || searchQuery">
|
||||
<span class="search-icon">🔍</span>
|
||||
<input class="search-input" type="text" placeholder="Filter files & folders..."
|
||||
x-model="searchQuery" @input="doFilter" autocomplete="off">
|
||||
<button class="search-clear" x-show="searchQuery" @click="searchQuery='';doFilter()" title="Clear">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Filter chips -->
|
||||
<div class="filter-chips" x-show="tree.length>0">
|
||||
<button class="filter-chip" :class="{ active: filterType === 'folder' }" @click="filterByType('folder')" title="Folders only">📁 Folders</button>
|
||||
<button class="filter-chip" :class="{ active: filterType === 'page' }" @click="filterByType('page')" title="FlowDeck pages">📝 Pages</button>
|
||||
<button class="filter-chip" :class="{ active: filterType === 'pdf' }" @click="filterByType('pdf')" title="PDF">📕 PDF</button>
|
||||
<button class="filter-chip" :class="{ active: filterType === 'image' }" @click="filterByType('image')" title="Images">🖼️ Img</button>
|
||||
<button class="filter-chip" :class="{ active: filterType === 'code' }" @click="filterByType('code')" title="Code">📜 Code</button>
|
||||
<button class="filter-chip" :class="{ active: filterType === 'text' }" @click="filterByType('text')" title="Text">📄 Text</button>
|
||||
<!-- Compact filter row -->
|
||||
<div class="filter-row" x-show="tree.length>0 || searchQuery || filterType">
|
||||
<button class="filter-chip" :class="{ active: filterType === 'folder' }" @click="filterByType('folder')" title="Folders">📁</button>
|
||||
<button class="filter-chip" :class="{ active: filterType === 'page' }" @click="filterByType('page')" title="Pages">📝</button>
|
||||
<button class="filter-chip" :class="{ active: filterType === 'pdf' }" @click="filterByType('pdf')" title="PDF">📕</button>
|
||||
<button class="filter-chip" :class="{ active: filterType === 'image' }" @click="filterByType('image')" title="Images">🖼️</button>
|
||||
<button class="filter-chip" :class="{ active: filterType === 'code' }" @click="filterByType('code')" title="Code">📜</button>
|
||||
<button class="filter-chip" :class="{ active: filterType === 'text' }" @click="filterByType('text')" title="Text">📄</button>
|
||||
<span x-show="filterType || searchQuery" class="filter-clear" @click="filterType='';searchQuery='';doFilter()">✕ clear</span>
|
||||
</div>
|
||||
|
||||
<ul class="ws-tree" x-show="viewMode==='tree' && (displayTree.length>0 || (currentFolder > 0))" @click="handleTreeAction($event)">
|
||||
@@ -748,6 +777,7 @@ document.addEventListener('alpine:init', function() {
|
||||
currentFolder: {{ current_folder_id }},
|
||||
searchQuery: '',
|
||||
filterType: '',
|
||||
editingPath: false,
|
||||
folderStack: [],
|
||||
|
||||
// Inline rename
|
||||
@@ -809,6 +839,15 @@ document.addEventListener('alpine:init', function() {
|
||||
await this._loadFolder(entry ? entry.id : 0);
|
||||
},
|
||||
|
||||
startPathEdit() {
|
||||
this.editingPath = true;
|
||||
var self = this;
|
||||
this.$nextTick(function() {
|
||||
var inp = self.$refs.pathEdit;
|
||||
if (inp) { inp.focus(); inp.select(); }
|
||||
});
|
||||
},
|
||||
|
||||
// Drag indicators
|
||||
dragCount: 0,
|
||||
dragOverParentId: null,
|
||||
|
||||
Reference in New Issue
Block a user