refactor: local-workspace UI → Library-style
CSS: - Ajouté: ws-container, ws-tabs-row, ws-tab, ws-icon-btn, ws-btn-primary - Ajouté: ws-dropdown, ws-search-bar, ws-select-bar, ws-row - Ajouté: row-name, row-icon, row-title, hover-only, ws-checkbox - Ajouté: tree-chevron, btn-open, more-menu (Library-style) - Conservé: ws-tree, ws-tree-item, ws-act, breadcrumb-row pour compatibilité Navbar: - Breadcrumb + toolbar fusionnés en une seule row propre - Dropdowns style Library (View, Sort) - Search toggle avec barre coulissante - Bulk bar → ws-select-bar avec icônes SVG Context menu: - .ctx-menu → .more-menu (style Library) - Toutes les refs JS mises à jour 143 tests passent
This commit is contained in:
@@ -17,12 +17,141 @@
|
||||
@drop.prevent="onDrop($event)"
|
||||
@keydown="onKeydown($event)">
|
||||
<style>
|
||||
.ws-layout{max-width:800px;margin:0 auto;padding:40px 24px;}/* deprecated by ws-split */
|
||||
@media (max-width: 768px) { .ws-layout { padding: 20px 12px; } }
|
||||
.ws-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px;flex-wrap:wrap;gap:12px;}
|
||||
.ws-header h1{font-size:24px;}
|
||||
.ws-toolbar{display:flex;gap:8px;}
|
||||
/* ── Library-style layout ── */
|
||||
.ws-container{max-width:1100px;margin:0 auto;padding:20px 24px;}
|
||||
.ws-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;}
|
||||
.ws-header h1{font-size:24px;font-weight:700;color:var(--text-primary);margin:0;}
|
||||
|
||||
/* ── Tabs row ── */
|
||||
.ws-tabs-row{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;padding:4px 0;}
|
||||
.ws-tabs{display:flex;gap:2px;align-items:center;overflow-x:auto;scrollbar-width:none;}
|
||||
.ws-tabs::-webkit-scrollbar{display:none;}
|
||||
.ws-tab{display:flex;align-items:center;gap:6px;padding:6px 12px;font-size:13px;font-weight:500;color:var(--text-secondary);background:transparent;border:none;border-radius:6px;cursor:pointer;transition:all 0.12s;white-space:nowrap;}
|
||||
.ws-tab:hover{color:var(--text-primary);background:var(--bg-hover);}
|
||||
.ws-tab.active{color:var(--text-primary);background:var(--bg-active);}
|
||||
.ws-tab svg{width:14px;height:14px;flex-shrink:0;}
|
||||
.ws-toolbar{display:flex;align-items:center;gap:2px;flex-shrink:0;}
|
||||
.ws-icon-btn{display:inline-flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:6px;background:transparent;border:none;color:var(--text-secondary);cursor:pointer;transition:background 0.1s,color 0.1s;}
|
||||
.ws-icon-btn:hover,.ws-icon-btn.active{background:var(--bg-hover);color:var(--text-primary);}
|
||||
.ws-icon-btn svg{width:16px;height:16px;}
|
||||
.ws-btn-primary{display:inline-flex;align-items:center;gap:6px;padding:6px 14px;font-size:13px;font-weight:500;color:#fff;background:var(--accent);border:none;border-radius:6px;cursor:pointer;transition:background 0.1s;}
|
||||
.ws-btn-primary:hover{background:var(--accent-hover);}
|
||||
.ws-btn-primary svg{width:14px;height:14px;}
|
||||
|
||||
/* ── Dropdowns ── */
|
||||
.ws-dropdown{position:absolute;top:calc(100% + 4px);right:0;min-width:210px;background:var(--bg-modal);border:1px solid var(--border);border-radius:8px;box-shadow:var(--shadow-popover);z-index:200;padding:6px;}
|
||||
.ws-dropdown .dd-label{padding:6px 8px 2px;font-size:11px;font-weight:600;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.3px;}
|
||||
.ws-dropdown .dd-item{padding:6px 8px;font-size:13px;color:var(--text-primary);cursor:pointer;border-radius:4px;display:flex;align-items:center;gap:8px;transition:background 0.1s;}
|
||||
.ws-dropdown .dd-item:hover{background:var(--bg-hover);}
|
||||
.ws-dropdown .dd-item.active{color:var(--accent);}
|
||||
.ws-dropdown .dd-item .check{width:14px;flex-shrink:0;color:var(--accent);}
|
||||
.lib-toolbar-wrap{position:relative;}
|
||||
|
||||
/* ── Search bar ── */
|
||||
.ws-search-bar{margin-bottom:10px;}
|
||||
.ws-search-bar input{width:100%;padding:8px 12px;border:1px solid var(--border);border-radius:8px;background:var(--bg-secondary);color:var(--text-primary);font-size:13px;outline:none;}
|
||||
.ws-search-bar input:focus{border-color:var(--accent);}
|
||||
|
||||
/* ── Multi-select bar ── */
|
||||
.ws-select-bar{display:flex;align-items:center;gap:4px;padding:6px 10px;background:var(--bg-secondary);border-radius:8px;margin-bottom:8px;font-size:13px;color:var(--text-secondary);min-height:38px;}
|
||||
.ws-select-bar .sel-count{font-weight:500;color:var(--text-primary);margin-right:8px;white-space:nowrap;}
|
||||
.ws-select-bar button{background:transparent;border:none;color:var(--text-secondary);cursor:pointer;padding:6px 8px;border-radius:6px;font-size:13px;display:flex;align-items:center;gap:6px;transition:all 0.12s;}
|
||||
.ws-select-bar button:hover{background:var(--bg-hover);color:var(--text-primary);}
|
||||
.ws-select-bar button svg{width:14px;height:14px;}
|
||||
|
||||
/* ── Table (Notion-style rows) ── */
|
||||
.ws-table{width:100%;}
|
||||
.ws-table-header{display:flex;align-items:center;border-bottom:1px solid var(--border);padding:0 4px;}
|
||||
.ws-table-header .th{display:flex;align-items:center;gap:5px;padding:6px 8px;font-size:12px;font-weight:500;color:var(--text-dim);user-select:none;white-space:nowrap;}
|
||||
.ws-table-header .th svg{width:13px;height:13px;flex-shrink:0;}
|
||||
|
||||
/* Column widths */
|
||||
.ws-col-name{flex:1;min-width:0;}
|
||||
.ws-col-type{width:80px;flex-shrink:0;}
|
||||
.ws-col-date{width:110px;flex-shrink:0;justify-content:flex-end;}
|
||||
|
||||
/* ── Row ── */
|
||||
.ws-row{display:flex;align-items:center;height:34px;transition:background 0.1s;position:relative;}
|
||||
.ws-row:hover{background:var(--bg-hover);}
|
||||
.ws-row.selected{background:rgba(35,131,226,0.08);}
|
||||
.ws-row.drag-over{outline:2px solid var(--accent);outline-offset:-2px;background:rgba(35,131,226,0.12);}
|
||||
.ws-row.dragging{opacity:0.4;}
|
||||
.ws-row .row-cell{display:flex;align-items:center;padding:0 8px;height:100%;overflow:hidden;}
|
||||
.ws-row .row-cell.ws-col-date{color:var(--text-secondary);font-size:13px;white-space:nowrap;}
|
||||
|
||||
/* ── Name cell ── */
|
||||
.row-name{display:flex;align-items:center;gap:6px;min-width:0;flex:1;position:relative;}
|
||||
.row-icon{font-size:15px;flex-shrink:0;width:20px;text-align:center;display:inline-flex;align-items:center;justify-content:center;}
|
||||
.row-icon svg{width:16px;height:16px;color:var(--text-dim);}
|
||||
.row-title{font-size:13px;font-weight:400;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;min-width:0;cursor:pointer;}
|
||||
.row-title:hover{text-decoration:underline;}
|
||||
.row-title input{width:100%;font-size:13px;padding:2px 4px;background:var(--bg-primary);border:1px solid var(--accent);border-radius:4px;color:var(--text-primary);outline:none;}
|
||||
|
||||
/* ── Hover-only elements ── */
|
||||
.hover-only{opacity:0;transition:opacity 0.12s;flex-shrink:0;}
|
||||
.ws-row:hover .hover-only,.ws-row.selected .hover-only{opacity:1;}
|
||||
|
||||
/* ── Drag handle ── */
|
||||
.drag-handle{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;cursor:grab;flex-shrink:0;margin-right:2px;}
|
||||
.drag-handle:active{cursor:grabbing;}
|
||||
.drag-handle svg{width:14px;height:14px;color:var(--text-dim);opacity:0.75;}
|
||||
|
||||
/* ── Checkbox ── */
|
||||
.ws-checkbox{width:15px;height:15px;border:1.5px solid var(--text-dim);border-radius:3px;cursor:pointer;flex-shrink:0;display:flex;align-items:center;justify-content:center;transition:border-color 0.1s;}
|
||||
.ws-checkbox:hover{border-color:var(--text-primary);}
|
||||
.ws-checkbox.checked{background:var(--accent);border-color:var(--accent);opacity:1!important;}
|
||||
.ws-checkbox.checked svg{width:10px;height:10px;color:#fff;}
|
||||
|
||||
/* ── OPEN button ── */
|
||||
.btn-open{display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border:1px solid var(--border);border-radius:4px;font-size:11px;font-weight:500;color:var(--text-primary);background:var(--bg-tertiary);cursor:pointer;white-space:nowrap;transition:all 0.1s;flex-shrink:0;text-decoration:none;margin-left:auto;}
|
||||
.btn-open:hover{background:var(--bg-active);}
|
||||
.btn-open svg{width:12px;height:12px;}
|
||||
|
||||
/* ── Expand chevron ── */
|
||||
.tree-chevron{width:18px;height:18px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;background:none;border:none;color:var(--text-dim);cursor:pointer;padding:0;border-radius:3px;transition:transform 0.15s,background 0.1s;}
|
||||
.tree-chevron:hover{color:var(--text-primary);background:var(--bg-active);}
|
||||
.tree-chevron svg{width:11px;height:11px;transition:transform 0.15s;}
|
||||
.tree-chevron.open svg{transform:rotate(90deg);}
|
||||
|
||||
/* Indent */
|
||||
.row-indent{flex-shrink:0;}
|
||||
|
||||
/* ── "Add new" row ── */
|
||||
.add-new-row{padding:8px 12px;color:var(--text-dim);font-size:13px;cursor:pointer;transition:color 0.15s;display:flex;align-items:center;gap:4px;}
|
||||
.add-new-row:hover{color:var(--text-primary);}
|
||||
.add-new-row svg{width:13px;height:13px;}
|
||||
.add-new-under{padding:5px 8px;color:var(--text-dim);font-size:12px;cursor:pointer;transition:color 0.15s;display:flex;align-items:center;gap:4px;}
|
||||
.add-new-under:hover{color:var(--text-primary);}
|
||||
.add-new-under svg{width:12px;height:12px;}
|
||||
|
||||
/* ── Context menu ── */
|
||||
.more-menu{position:fixed;background:var(--bg-modal);border:1px solid var(--border);border-radius:8px;box-shadow:var(--shadow-popover);z-index:600;padding:4px;}
|
||||
.more-menu .menu-item{padding:6px 10px;font-size:13px;color:var(--text-primary);cursor:pointer;border-radius:4px;display:flex;align-items:center;gap:8px;transition:background 0.1s;}
|
||||
.more-menu .menu-item:hover{background:var(--bg-hover);}
|
||||
.more-menu .menu-item svg{width:14px;height:14px;flex-shrink:0;}
|
||||
.more-menu .menu-item .shortcut{margin-left:auto;font-size:11px;color:var(--text-dim);}
|
||||
.more-menu .menu-danger{color:var(--danger);}
|
||||
.more-sep{height:1px;background:var(--border);margin:4px 8px;}
|
||||
|
||||
/* ── Keep existing functional styles ── */
|
||||
.breadcrumb-row{display:flex;align-items:center;gap:4px;padding:6px 8px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:8px;margin-bottom:10px;flex-wrap:wrap;}
|
||||
.breadcrumb-row .nav-arrow{width:26px;height:26px;padding:0;font-size:13px;}
|
||||
.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;}
|
||||
|
||||
/* ── Preserved styles ── */
|
||||
.ws-tree{list-style:none;margin:0;padding:0;}
|
||||
.ws-tree-item{display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:6px;cursor:pointer;transition:background 150ms;border:1px solid transparent;margin-bottom:2px;}
|
||||
.ws-tree-item:hover{background:var(--bg-tertiary);border-color:var(--border);}
|
||||
@@ -34,21 +163,18 @@
|
||||
.ws-tree-item .name.file:hover{color:var(--accent);}
|
||||
.ws-tree-item .actions{display:none;gap:2px;}
|
||||
.ws-tree-item:hover .actions{display:flex;}
|
||||
|
||||
/* Chevron in main tree */
|
||||
.ws-chevron{width:16px;height:16px;flex-shrink:0;display:inline-flex;align-items:center;justify-content:center;background:none;border:none;color:var(--text-tertiary);cursor:pointer;font-size:8px;padding:0;border-radius:3px;transition:transform 150ms,color 100ms;}
|
||||
.ws-chevron:hover{color:var(--text);background:var(--bg-hover);}
|
||||
.ws-chevron.open{transform:rotate(90deg);}
|
||||
|
||||
.ws-act{background:none;border:none;color:var(--text-tertiary);cursor:pointer;padding:3px 6px;border-radius:4px;font-size:11px;}
|
||||
.ws-act:hover{background:var(--bg-hover);color:var(--text);}
|
||||
.ws-act.danger:hover{color:var(--danger);}
|
||||
|
||||
.empty-state{text-align:center;padding:80px 20px;color:var(--text-tertiary);}
|
||||
.empty-state .empty-icon{font-size:64px;margin-bottom:16px;opacity:.5;}
|
||||
.empty-state h2{font-size:18px;color:var(--text-secondary);margin-bottom:8px;}
|
||||
.empty-state p{font-size:13px;margin-bottom:24px;max-width:400px;margin-left:auto;margin-right:auto;}
|
||||
.empty-actions{display:flex;gap:10px;justify-content:center;}
|
||||
.empty-state{text-align:center;padding:80px 20px;color:var(--text-dim);}
|
||||
.empty-state .empty-icon{font-size:48px;margin-bottom:12px;opacity:0.5;}
|
||||
.empty-state h3{font-size:16px;color:var(--text-secondary);margin:0 0 4px;}
|
||||
.empty-state p{font-size:13px;margin:0;}
|
||||
.empty-actions{display:flex;gap:10px;justify-content:center;margin-top:16px;}
|
||||
|
||||
.btn{padding:10px 20px;border-radius:8px;font-size:13px;cursor:pointer;border:none;font-weight:500;}
|
||||
.btn-primary{background:var(--accent);color:#fff;}
|
||||
@@ -270,7 +396,6 @@
|
||||
.view-menu-item.active{background:rgba(35,131,226,.12);color:var(--accent);font-weight:500;}
|
||||
|
||||
/* Context menu */
|
||||
.ctx-menu{position:fixed;background:var(--bg-modal);border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow-popover);min-width:200px;z-index:600;padding:4px 0;max-height:80vh;overflow-y:auto;}
|
||||
.ctx-item{display:flex;align-items:center;gap:8px;padding:7px 14px;font-size:13px;color:var(--text);cursor:pointer;transition:background 100ms;}
|
||||
.ctx-item:hover{background:var(--bg-hover);}
|
||||
.ctx-item.ctx-danger{color:var(--danger);}
|
||||
@@ -377,6 +502,8 @@ window._wsInitData = (function() {
|
||||
sortBy: '',
|
||||
viewMode: localStorage.getItem('fd_default_view') || 'tree',
|
||||
viewMenuOpen: false,
|
||||
sortOpen: false,
|
||||
searchOpen: false,
|
||||
viewIcons: {tree:'',list:'',details:'',title:'',content:''},
|
||||
viewLabels: {tree:'Tree',list:'List',details:'Details',title:'Cards',content:'Content'},
|
||||
|
||||
@@ -750,7 +877,7 @@ window._wsInitData = (function() {
|
||||
// ── Context menu ──
|
||||
onContextMenu(ev) {
|
||||
var el = ev.target.closest('[data-ws-id]');
|
||||
var menu = document.querySelector('.ctx-menu');
|
||||
var menu = document.querySelector('.more-menu');
|
||||
if (!el || !menu) { if (menu) menu.style.display = 'none'; return; }
|
||||
var id = parseInt(el.getAttribute('data-ws-id'));
|
||||
if (!id) { menu.style.display = 'none'; return; }
|
||||
@@ -791,7 +918,7 @@ window._wsInitData = (function() {
|
||||
c.ctxAvailTags = this.ctxAvailTags;
|
||||
c.ctxNewTagColor = this.ctxNewTagColor;
|
||||
// Also force Alpine update on the menu element
|
||||
var menuEl = document.querySelector('.ctx-menu');
|
||||
var menuEl = document.querySelector('.more-menu');
|
||||
if (menuEl && menuEl._x_dataStack && menuEl._x_dataStack[0]) {
|
||||
var d = menuEl._x_dataStack[0];
|
||||
d.ctxMenu = this.ctxMenu;
|
||||
@@ -848,24 +975,24 @@ window._wsInitData = (function() {
|
||||
|
||||
ctxMenuOpenPage() {
|
||||
if (this.ctxMenu.node) this.openPage(this.ctxMenu.node.id);
|
||||
document.querySelector('.ctx-menu').style.display = 'none';
|
||||
document.querySelector('.more-menu').style.display = 'none';
|
||||
},
|
||||
|
||||
ctxMenuOpenFolder() {
|
||||
if (this.ctxMenu.node) this.navigateToFolder(this.ctxMenu.node.id);
|
||||
document.querySelector('.ctx-menu').style.display = 'none';
|
||||
document.querySelector('.more-menu').style.display = 'none';
|
||||
},
|
||||
|
||||
ctxMenuRename() {
|
||||
if (this.ctxMenu.node) { this.startRename(this.ctxMenu.node); document.querySelector('.ctx-menu').style.display = 'none'; }
|
||||
if (this.ctxMenu.node) { this.startRename(this.ctxMenu.node); document.querySelector('.more-menu').style.display = 'none'; }
|
||||
},
|
||||
|
||||
ctxMenuDuplicate() {
|
||||
if (this.ctxMenu.node) { this.duplicateItem(this.ctxMenu.node); document.querySelector('.ctx-menu').style.display = 'none'; }
|
||||
if (this.ctxMenu.node) { this.duplicateItem(this.ctxMenu.node); document.querySelector('.more-menu').style.display = 'none'; }
|
||||
},
|
||||
|
||||
ctxMenuDelete() {
|
||||
if (this.ctxMenu.node) { this.deleteWithUndo(this.ctxMenu.node); document.querySelector('.ctx-menu').style.display = 'none'; }
|
||||
if (this.ctxMenu.node) { this.deleteWithUndo(this.ctxMenu.node); document.querySelector('.more-menu').style.display = 'none'; }
|
||||
},
|
||||
|
||||
ctxRemoveTag(tagId) {
|
||||
@@ -881,7 +1008,7 @@ window._wsInitData = (function() {
|
||||
window._ctxMenuData.ctxAvailTags = this.ctxAvailTags;
|
||||
window._ctxMenuData.ctxMenu = this.ctxMenu;
|
||||
// Also force Alpine update
|
||||
var menuEl = document.querySelector('.ctx-menu');
|
||||
var menuEl = document.querySelector('.more-menu');
|
||||
if (menuEl && menuEl._x_dataStack && menuEl._x_dataStack[0]) {
|
||||
menuEl._x_dataStack[0].ctxAvailTags = [...this.ctxAvailTags];
|
||||
menuEl._x_dataStack[0].ctxMenu = this.ctxMenu;
|
||||
@@ -914,13 +1041,13 @@ window._wsInitData = (function() {
|
||||
window._ctxMenuData.ctxAvailTags = self.ctxAvailTags;
|
||||
window._ctxMenuData.ctxMenu = self.ctxMenu;
|
||||
// Also force Alpine update
|
||||
var menuEl = document.querySelector('.ctx-menu');
|
||||
var menuEl = document.querySelector('.more-menu');
|
||||
if (menuEl && menuEl._x_dataStack && menuEl._x_dataStack[0]) {
|
||||
menuEl._x_dataStack[0].ctxAvailTags = [...self.ctxAvailTags];
|
||||
menuEl._x_dataStack[0].ctxMenu = self.ctxMenu;
|
||||
}
|
||||
// Close menu after successful add
|
||||
document.querySelector('.ctx-menu').style.display = 'none';
|
||||
document.querySelector('.more-menu').style.display = 'none';
|
||||
}); }, 200);
|
||||
}
|
||||
this.ctxTagAdding = false;
|
||||
@@ -2125,15 +2252,15 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
|
||||
<button class="undo-dismiss" @click="undoVisible=false">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Breadcrumb row -->
|
||||
<!-- Breadcrumb + toolbar row (Library-style) -->
|
||||
<div class="breadcrumb-row">
|
||||
<button class="btn-icon nav-arrow" x-show="sidebarCollapsed" x-cloak @click="sidebarCollapsed = false" title="Show sidebar" style="margin-right:2px;">{{ fd_icon("chevron-right",16) }}</button>
|
||||
<a href="/workspaces" class="btn-icon nav-arrow" title="All workspaces">{{ fd_icon("home",16) }}</a>
|
||||
<button class="btn-icon nav-arrow" @click="goBack()" title="Back" :disabled="!canGoBack">{{ fd_icon("chevron-left",16) }}</button>
|
||||
<button class="btn-icon nav-arrow" @click="goForward()" title="Forward" :disabled="!canGoForward">{{ fd_icon("chevron-right",16) }}</button>
|
||||
<button class="btn-icon nav-arrow" x-show="sidebarCollapsed" x-cloak @click="sidebarCollapsed = false" title="Show sidebar">{{ fd_icon("chevron-right",14) }}</button>
|
||||
<a href="/workspaces" class="btn-icon nav-arrow" title="All workspaces">{{ fd_icon("home",14) }}</a>
|
||||
<button class="btn-icon nav-arrow" @click="goBack()" title="Back" :disabled="!canGoBack">{{ fd_icon("chevron-left",14) }}</button>
|
||||
<button class="btn-icon nav-arrow" @click="goForward()" title="Forward" :disabled="!canGoForward">{{ fd_icon("chevron-right",14) }}</button>
|
||||
{% if breadcrumbs %}
|
||||
<div class="unified-breadcrumb">
|
||||
<span class="bc-icon">{{ fd_icon("folder",14) }}</span>
|
||||
<span class="bc-icon">{{ fd_icon("folder",13) }}</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;">
|
||||
@@ -2148,53 +2275,64 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
|
||||
@keydown.enter="navigateToPath(), editingPath=false"
|
||||
@keydown.escape="editingPath=false"
|
||||
placeholder="/workspace/...">
|
||||
<button class="bc-edit-go" @click="navigateToPath(), editingPath=false">{{ fd_icon("corner-down-right",14) }}</button>
|
||||
<button class="bc-edit-go" @click="navigateToPath(), editingPath=false">{{ fd_icon("corner-down-right",13) }}</button>
|
||||
</div>
|
||||
<button class="bc-edit-trigger" x-show="!editingPath" @click="startPathEdit()" title="Edit path">{{ fd_icon("edit",13) }}</button>
|
||||
<button class="bc-edit-trigger" x-show="!editingPath" @click="startPathEdit()" title="Edit path">{{ fd_icon("edit",11) }}</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Toolbar row -->
|
||||
<div class="toolbar-row">
|
||||
<div class="view-dropdown" @click.outside="viewMenuOpen=false" @keydown.escape="viewMenuOpen=false">
|
||||
<button class="view-dropdown-btn" @click="viewMenuOpen=!viewMenuOpen" title="View">
|
||||
<span x-text="viewLabels[viewMode] || 'Tree'"></span>
|
||||
<span class="dropdown-arrow">▾</span>
|
||||
</button>
|
||||
<div class="view-dropdown-menu" x-show="viewMenuOpen" x-transition @click="viewMenuOpen=false">
|
||||
<button class="view-menu-item" :class="{ active: viewMode==='tree' }" @click="viewMode='tree', previewItem=null">{{ fd_icon("folder",14) }} Tree</button>
|
||||
<button class="view-menu-item" :class="{ active: viewMode==='list' }" @click="viewMode='list', previewItem=null">{{ fd_icon("list",14) }} List</button>
|
||||
<button class="view-menu-item" :class="{ active: viewMode==='details' }" @click="viewMode='details', previewItem=null">{{ fd_icon("bar-chart",14) }} Details</button>
|
||||
<button class="view-menu-item" :class="{ active: viewMode==='title' }" @click="viewMode='title', previewItem=null">{{ fd_icon("grid",14) }} Cards</button>
|
||||
<button class="view-menu-item" :class="{ active: viewMode==='content' }" @click="viewMode='content', previewItem=null">{{ fd_icon("align-left",14) }} Content</button>
|
||||
<div class="ws-toolbar" style="margin-left:auto;">
|
||||
<!-- View dropdown -->
|
||||
<div class="lib-toolbar-wrap" style="position:relative;">
|
||||
<button class="ws-icon-btn" :class="{active: viewMenuOpen}" title="View" @click.stop="viewMenuOpen=!viewMenuOpen; sortOpen=false; searchOpen=false">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="4" y1="6" x2="20" y2="6"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="18" x2="20" y2="18"/></svg>
|
||||
</button>
|
||||
<div class="ws-dropdown" x-show="viewMenuOpen" @click.outside="viewMenuOpen=false" x-transition style="right:0;">
|
||||
<div class="dd-label">View</div>
|
||||
<div class="dd-item" :class="{active: viewMode==='tree'}" @click="viewMode='tree', previewItem=null, viewMenuOpen=false"><span class="check" x-text="viewMode==='tree'?'✓':''"></span>{{ fd_icon("folder",14) }} Tree</div>
|
||||
<div class="dd-item" :class="{active: viewMode==='list'}" @click="viewMode='list', previewItem=null, viewMenuOpen=false"><span class="check" x-text="viewMode==='list'?'✓':''"></span>{{ fd_icon("list",14) }} List</div>
|
||||
<div class="dd-item" :class="{active: viewMode==='details'}" @click="viewMode='details', previewItem=null, viewMenuOpen=false"><span class="check" x-text="viewMode==='details'?'✓':''"></span>{{ fd_icon("bar-chart",14) }} Details</div>
|
||||
<div class="dd-item" :class="{active: viewMode==='title'}" @click="viewMode='title', previewItem=null, viewMenuOpen=false"><span class="check" x-text="viewMode==='title'?'✓':''"></span>{{ fd_icon("grid",14) }} Cards</div>
|
||||
<div class="dd-item" :class="{active: viewMode==='content'}" @click="viewMode='content', previewItem=null, viewMenuOpen=false"><span class="check" x-text="viewMode==='content'?'✓':''"></span>{{ fd_icon("align-left",14) }} Content</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Sort -->
|
||||
<div class="lib-toolbar-wrap" style="position:relative;">
|
||||
<button class="ws-icon-btn" :class="{active: sortOpen}" title="Sort" @click.stop="sortOpen=!sortOpen; viewMenuOpen=false">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="4" y1="9" x2="14" y2="9"/><line x1="4" y1="15" x2="10" y2="15"/><polyline points="17 5 17 19"/><polyline points="13 16 17 20 21 16"/></svg>
|
||||
</button>
|
||||
<div class="ws-dropdown" x-show="sortOpen" @click.outside="sortOpen=false" x-transition style="right:0;">
|
||||
<div class="dd-label">Sort by</div>
|
||||
<div class="dd-item" :class="{active: sortBy==='name-asc'}" @click="sortBy='name-asc', doSort(), sortOpen=false"><span class="check" x-text="sortBy==='name-asc'?'✓':''"></span> Name A→Z</div>
|
||||
<div class="dd-item" :class="{active: sortBy==='name-desc'}" @click="sortBy='name-desc', doSort(), sortOpen=false"><span class="check" x-text="sortBy==='name-desc'?'✓':''"></span> Name Z→A</div>
|
||||
<div class="dd-item" :class="{active: sortBy==='type'}" @click="sortBy='type', doSort(), sortOpen=false"><span class="check" x-text="sortBy==='type'?'✓':''"></span> Type</div>
|
||||
<div class="dd-item" :class="{active: sortBy==='date-asc'}" @click="sortBy='date-asc', doSort(), sortOpen=false"><span class="check" x-text="sortBy==='date-asc'?'✓':''"></span> Oldest</div>
|
||||
<div class="dd-item" :class="{active: sortBy==='date-desc'}" @click="sortBy='date-desc', doSort(), sortOpen=false"><span class="check" x-text="sortBy==='date-desc'?'✓':''"></span> Newest</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Search toggle -->
|
||||
<button class="ws-icon-btn" :class="{active: searchOpen}" title="Search" @click.stop="searchOpen=!searchOpen; if(searchOpen) $nextTick(()=>$refs.searchInput?.focus())">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||||
</button>
|
||||
<!-- Expand/Collapse (tree only) -->
|
||||
<button class="ws-icon-btn" @click="expandAll()" title="Expand all" x-show="viewMode==='tree'"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"/></svg></button>
|
||||
<button class="ws-icon-btn" @click="collapseAll()" title="Collapse all" x-show="viewMode==='tree'"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 15 12 9 18 15"/></svg></button>
|
||||
<!-- Create buttons -->
|
||||
<button class="ws-btn-primary" onclick="window.FlowDeck.createPage()" title="New page">{{ fd_icon("file",14) }} New</button>
|
||||
</div>
|
||||
<div class="search-bar-inline">
|
||||
<span class="search-icon" x-html="getSvgIcon('search',16)"></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</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" x-show="viewMode==='tree'">⊞</button>
|
||||
<button class="btn-icon" @click="collapseAll()" title="Collapse" x-show="viewMode==='tree'">⊟</button>
|
||||
<button class="btn-icon btn-create" onclick="window.FlowDeck.createPage()" title="New file" x-html="getSvgIcon('file',16)"></button>
|
||||
<button class="btn-icon btn-create" onclick="window.FlowDeck.showCreateFolderModal()" title="New folder" x-html="getSvgIcon('folder',16)"></button>
|
||||
</div>
|
||||
<!-- Search bar (togglable) -->
|
||||
<div class="ws-search-bar" x-show="searchOpen" x-transition>
|
||||
<input x-ref="searchInput" type="text" placeholder="Filter files and folders..." x-model="searchQuery" @input="doFilter" autocomplete="off">
|
||||
</div>
|
||||
|
||||
<!-- Bulk actions bar -->
|
||||
<div class="bulk-bar" x-show="selectedIdsCount > 0" x-transition>
|
||||
<span x-text="selectedIdsCount + ' selected'"></span>
|
||||
<button class="btn btn-danger" @click="bulkDelete()">Delete</button>
|
||||
<button class="btn btn-secondary" @click="clearSelection()">{{ fd_icon("x",14) }} Clear</button>
|
||||
<div class="ws-select-bar" x-show="selectedIdsCount > 0" x-transition>
|
||||
<span class="sel-count" x-text="selectedIdsCount + ' selected'"></span>
|
||||
<button @click="clearSelection()" title="Clear"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg></button>
|
||||
<button @click="bulkDelete()" style="margin-left:auto;color:var(--danger);" title="Delete">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6"/><path d="M10 11v6M14 11v6"/></svg>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Tree -->
|
||||
@@ -2571,45 +2709,42 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
|
||||
|
||||
</div><!-- /ws-main -->
|
||||
|
||||
<!-- Context menu -->
|
||||
<div class="ctx-menu" x-data="_ctxMenuData" style="display:none"
|
||||
@click.outside="document.querySelector('.ctx-menu').style.display='none'"
|
||||
@keydown.escape="document.querySelector('.ctx-menu').style.display='none'"
|
||||
<!-- Context menu (Library-style) -->
|
||||
<div class="more-menu" x-data="_ctxMenuData" style="display:none"
|
||||
@click.outside="document.querySelector('.more-menu').style.display='none'"
|
||||
@keydown.escape="document.querySelector('.more-menu').style.display='none'"
|
||||
x-transition>
|
||||
<div class="ctx-item" @click="ctxMenuOpenPage()">{{ fd_icon("file",14) }} Open</div>
|
||||
<div class="ctx-item" x-show="ctxMenu.node && ctxMenu.node.is_folder" @click="ctxMenuOpenFolder()">{{ fd_icon("folder",14) }} Open folder</div>
|
||||
<div class="ctx-item" @click="ctxMenuRename()">{{ fd_icon("edit",14) }} Rename</div>
|
||||
<div class="ctx-item" @click="ctxMenuDuplicate()">{{ fd_icon("copy",14) }} Duplicate</div>
|
||||
<div class="ctx-sep"></div>
|
||||
<div class="ctx-section">{{ fd_icon("tag",14) }} Tags</div>
|
||||
<!-- Current tags with remove button -->
|
||||
<div class="menu-item" @click="ctxMenuOpenPage()">{{ fd_icon("file",14) }} Open</div>
|
||||
<div class="menu-item" x-show="ctxMenu.node && ctxMenu.node.is_folder" @click="ctxMenuOpenFolder()">{{ fd_icon("folder",14) }} Open folder</div>
|
||||
<div class="menu-item" @click="ctxMenuRename()">{{ fd_icon("edit",14) }} Rename</div>
|
||||
<div class="menu-item" @click="ctxMenuDuplicate()">{{ fd_icon("copy",14) }} Duplicate</div>
|
||||
<div class="more-sep"></div>
|
||||
<!-- Current tags -->
|
||||
<template x-for="t in (ctxMenu.tags||[])" :key="'ctx-tag'+t.id">
|
||||
<div class="ctx-item ctx-tag-item">
|
||||
<span class="ctx-tag-color" :style="{background: t.color}"></span>
|
||||
<span style="flex:1;font-size:13px;" x-text="t.name"></span>
|
||||
<div class="menu-item" style="justify-content:space-between;">
|
||||
<span style="display:flex;align-items:center;gap:8px;"><span class="ctx-tag-color" :style="{background: t.color}"></span><span style="font-size:13px;" x-text="t.name"></span></span>
|
||||
<button class="tag-chip rm-btn" @click.stop="ctxRemoveTag(t.id)" title="Remove">×</button>
|
||||
</div>
|
||||
</template>
|
||||
<div x-show="(ctxMenu.tags||[]).length === 0" style="padding:2px 14px;font-size:11px;color:var(--text-dim);">No tags</div>
|
||||
<div class="ctx-sep"></div>
|
||||
<!-- Add existing tag → sub-menu -->
|
||||
<div class="ctx-item ctx-has-sub" @click.stop="ctxTagExistingOpen=!ctxTagExistingOpen" x-show="ctxAvailTags.length > 0">
|
||||
<div x-show="(ctxMenu.tags||[]).length === 0" style="padding:4px 10px;font-size:11px;color:var(--text-dim);">No tags</div>
|
||||
<div class="more-sep"></div>
|
||||
<!-- Add existing tag -->
|
||||
<div class="menu-item" @click.stop="ctxTagExistingOpen=!ctxTagExistingOpen" x-show="ctxAvailTags.length > 0">
|
||||
<span style="flex:1;">Add existing tag</span>
|
||||
<span x-text="ctxTagExistingOpen ? '▾' : '▸'"></span>
|
||||
<span x-text="ctxTagExistingOpen ? '▾' : '▸'" style="font-size:11px;"></span>
|
||||
</div>
|
||||
<div x-show="ctxTagExistingOpen && ctxAvailTags.length > 0" style="padding:0 0 4px;background:var(--bg-tertiary);border-radius:6px;margin:2px 6px;">
|
||||
<template x-for="t in ctxAvailTags" :key="'ctx-avail'+t.id">
|
||||
<div class="ctx-item ctx-add-tag-item" @click.stop="ctxAddExistingTag(t), ctxTagExistingOpen=false">
|
||||
<span class="ctx-tag-color" :style="{background: t.color}"></span>
|
||||
<span style="flex:1;font-size:13px;" x-text="t.name"></span>
|
||||
<div class="menu-item" @click.stop="ctxAddExistingTag(t), ctxTagExistingOpen=false" style="justify-content:space-between;">
|
||||
<span style="display:flex;align-items:center;gap:8px;"><span class="ctx-tag-color" :style="{background: t.color}"></span><span style="font-size:13px;" x-text="t.name"></span></span>
|
||||
<span style="font-size:10px;color:var(--text-dim);" x-text="t.count||''"></span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- Create new tag -->
|
||||
<div class="ctx-item ctx-has-sub" @click.stop="ctxTagAdding=!ctxTagAdding">
|
||||
<div class="menu-item" @click.stop="ctxTagAdding=!ctxTagAdding">
|
||||
<span style="flex:1;">{{ fd_icon("plus",14) }} New tag</span>
|
||||
<span x-text="ctxTagAdding ? '▾' : '▸'"></span>
|
||||
<span x-text="ctxTagAdding ? '▾' : '▸'" style="font-size:11px;"></span>
|
||||
</div>
|
||||
<div x-show="ctxTagAdding" style="padding:6px 10px;background:var(--bg-tertiary);border-radius:6px;margin:2px 6px;">
|
||||
<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:4px;margin-bottom:6px;">
|
||||
@@ -2622,14 +2757,12 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
|
||||
<input class="tag-input" x-ref="ctxTagInput"
|
||||
@keydown.enter="ctxAddNewTag($event.target.value), $event.target.value=''"
|
||||
@keydown.escape="ctxTagAdding=false"
|
||||
@click.stop
|
||||
placeholder="Tag name..."
|
||||
style="flex:1;">
|
||||
@click.stop placeholder="Tag name..." style="flex:1;">
|
||||
<button class="btn btn-primary" @click.stop="ctxAddNewTag($refs.ctxTagInput.value), $refs.ctxTagInput.value=''" style="padding:2px 10px;font-size:12px;">Add</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ctx-sep"></div>
|
||||
<div class="ctx-item ctx-danger" @click="ctxMenuDelete()">{{ fd_icon("trash",14) }} Delete</div>
|
||||
<div class="more-sep"></div>
|
||||
<div class="menu-item menu-danger" @click="ctxMenuDelete()">{{ fd_icon("trash",14) }} Delete</div>
|
||||
</div>
|
||||
|
||||
<!-- Preview tooltip -->
|
||||
|
||||
Reference in New Issue
Block a user