feat(workspace): Notion-style multi-select, select/unselect all, Library-style bulk action bar with move-to modal
This commit is contained in:
@@ -279,6 +279,10 @@
|
|||||||
<!-- ── Multi-select bar ── -->
|
<!-- ── Multi-select bar ── -->
|
||||||
<div class="lib-select-bar" x-show="selectedCount > 0" x-transition>
|
<div class="lib-select-bar" x-show="selectedCount > 0" x-transition>
|
||||||
<span class="sel-count" x-text="selectedCount + ' selected'"></span>
|
<span class="sel-count" x-text="selectedCount + ' selected'"></span>
|
||||||
|
<button @click="toggleSelectAll()" :title="allSelected ? 'Clear selection' : 'Select all'">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" x-show="!allSelected"><polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/></svg>
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" x-show="allSelected" x-cloak><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||||
|
</button>
|
||||||
<button @click="clearSelection()" title="Clear selection">
|
<button @click="clearSelection()" title="Clear selection">
|
||||||
<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>
|
<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>
|
||||||
@@ -329,6 +333,7 @@
|
|||||||
<div class="lib-table" id="lib-table" :class="{'show-author': showAuthor, 'show-source': showSource}">
|
<div class="lib-table" id="lib-table" :class="{'show-author': showAuthor, 'show-source': showSource}">
|
||||||
<div class="lib-table-header">
|
<div class="lib-table-header">
|
||||||
<div class="th lib-col-name">
|
<div class="th lib-col-name">
|
||||||
|
<input type="checkbox" style="width:14px;height:14px;accent-color:var(--accent);cursor:pointer;margin-left:2px;margin-right:6px;" :checked="allSelected" @click="toggleSelectAll()" title="Select / unselect all">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
|
||||||
Page name
|
Page name
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -59,6 +59,19 @@
|
|||||||
.ws-select-bar button:hover{background:var(--bg-hover);color:var(--text-primary);}
|
.ws-select-bar button:hover{background:var(--bg-hover);color:var(--text-primary);}
|
||||||
.ws-select-bar button svg{width:14px;height:14px;}
|
.ws-select-bar button svg{width:14px;height:14px;}
|
||||||
|
|
||||||
|
/* ── Multi-select "…" dropdown (Library-style) ── */
|
||||||
|
.ws-select-menu{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:300;padding:6px;}
|
||||||
|
.ws-select-menu .menu-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-select-menu .menu-item:hover{background:var(--bg-hover);}
|
||||||
|
.ws-select-menu .menu-item svg{width:14px;height:14px;flex-shrink:0;}
|
||||||
|
.ws-select-menu .menu-item .shortcut{margin-left:auto;font-size:11px;color:var(--text-dim);}
|
||||||
|
.ws-select-menu .menu-danger{color:var(--danger);}
|
||||||
|
.ws-select-menu .more-sep{height:1px;background:var(--border);margin:4px 8px;}
|
||||||
|
|
||||||
|
/* ── Move-to modal list items ── */
|
||||||
|
.move-modal-item{padding:7px 10px;font-size:13px;color:var(--text-primary);cursor:pointer;border-radius:6px;display:flex;align-items:center;gap:8px;}
|
||||||
|
.move-modal-item:hover{background:var(--bg-hover);}
|
||||||
|
|
||||||
/* ── Table (Notion-style rows) ── */
|
/* ── Table (Notion-style rows) ── */
|
||||||
.ws-table{width:100%;}
|
.ws-table{width:100%;}
|
||||||
.ws-table-header{display:flex;align-items:center;border-bottom:1px solid var(--border);padding:0 4px;}
|
.ws-table-header{display:flex;align-items:center;border-bottom:1px solid var(--border);padding:0 4px;}
|
||||||
@@ -534,6 +547,15 @@ window._wsInitData = (function() {
|
|||||||
selectedIds: {},
|
selectedIds: {},
|
||||||
selectedIdsCount: 0,
|
selectedIdsCount: 0,
|
||||||
lastSelectedId: null,
|
lastSelectedId: null,
|
||||||
|
allSelected: false,
|
||||||
|
moreOpen: false,
|
||||||
|
|
||||||
|
// Move-to modal
|
||||||
|
moveModalOpen: false,
|
||||||
|
moveIds: [],
|
||||||
|
moveSearchQuery: '',
|
||||||
|
moveCandidates: [],
|
||||||
|
_moveAllCandidates: [],
|
||||||
|
|
||||||
// Undo
|
// Undo
|
||||||
undoVisible: false,
|
undoVisible: false,
|
||||||
@@ -685,6 +707,7 @@ window._wsInitData = (function() {
|
|||||||
this.flatTree = ft;
|
this.flatTree = ft;
|
||||||
// filteredTableItems
|
// filteredTableItems
|
||||||
this._computeFilteredTableItems();
|
this._computeFilteredTableItems();
|
||||||
|
this._syncSelection();
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── Pin/Favorite items ──
|
// ── Pin/Favorite items ──
|
||||||
@@ -702,7 +725,8 @@ window._wsInitData = (function() {
|
|||||||
|
|
||||||
// ── Multi-select ──
|
// ── Multi-select ──
|
||||||
toggleSelect(node, ev) {
|
toggleSelect(node, ev) {
|
||||||
if (ev.shiftKey && this.lastSelectedId) {
|
var e = ev || {};
|
||||||
|
if (e.shiftKey && this.lastSelectedId) {
|
||||||
// Range select
|
// Range select
|
||||||
var allIds = this._collectVisibleIds(this.displayTree);
|
var allIds = this._collectVisibleIds(this.displayTree);
|
||||||
var start = allIds.indexOf(this.lastSelectedId);
|
var start = allIds.indexOf(this.lastSelectedId);
|
||||||
@@ -711,18 +735,19 @@ window._wsInitData = (function() {
|
|||||||
var from = Math.min(start, end), to = Math.max(start, end);
|
var from = Math.min(start, end), to = Math.max(start, end);
|
||||||
for (var i = from; i <= to; i++) this.selectedIds[allIds[i]] = true;
|
for (var i = from; i <= to; i++) this.selectedIds[allIds[i]] = true;
|
||||||
}
|
}
|
||||||
} else if (ev.ctrlKey || ev.metaKey) {
|
} else if (e.ctrlKey || e.metaKey) {
|
||||||
// Toggle single
|
// Toggle single, keep others
|
||||||
if (!!this.selectedIds[node.id]) delete this.selectedIds[node.id];
|
if (!!this.selectedIds[node.id]) delete this.selectedIds[node.id];
|
||||||
else this.selectedIds[node.id] = true;
|
else this.selectedIds[node.id] = true;
|
||||||
} else {
|
} else {
|
||||||
// Replace selection
|
// Notion-style: toggle this item, keep the rest of the selection
|
||||||
for (var k in this.selectedIds) delete this.selectedIds[k];
|
if (!!this.selectedIds[node.id]) delete this.selectedIds[node.id];
|
||||||
this.selectedIds[node.id] = true;
|
else this.selectedIds[node.id] = true;
|
||||||
}
|
}
|
||||||
this.lastSelectedId = node.id;
|
this.lastSelectedId = node.id;
|
||||||
// Force reactivity
|
// Force reactivity
|
||||||
this.selectedIds = Object.assign({}, this.selectedIds); this.selectedIdsCount = Object.keys(this.selectedIds).length;
|
this.selectedIds = Object.assign({}, this.selectedIds);
|
||||||
|
this._syncSelection();
|
||||||
},
|
},
|
||||||
_collectVisibleIds(nodes) {
|
_collectVisibleIds(nodes) {
|
||||||
var ids = [];
|
var ids = [];
|
||||||
@@ -755,16 +780,19 @@ window._wsInitData = (function() {
|
|||||||
clearSelection() {
|
clearSelection() {
|
||||||
for (var k in this.selectedIds) delete this.selectedIds[k];
|
for (var k in this.selectedIds) delete this.selectedIds[k];
|
||||||
this.selectedIds = {};
|
this.selectedIds = {};
|
||||||
this.selectedIdsCount = 0;
|
this.moreOpen = false;
|
||||||
|
this._syncSelection();
|
||||||
},
|
},
|
||||||
selectAll(ev) {
|
selectAll(ev) {
|
||||||
if (ev.target.checked) {
|
// Toggle: select / unselect all currently visible items
|
||||||
|
if (this.allSelected) {
|
||||||
|
for (var k in this.selectedIds) delete this.selectedIds[k];
|
||||||
|
} else {
|
||||||
var all = this._collectVisibleIds(this.displayTree);
|
var all = this._collectVisibleIds(this.displayTree);
|
||||||
for (var i=0; i<all.length; i++) this.selectedIds[all[i]] = true;
|
for (var i=0; i<all.length; i++) this.selectedIds[all[i]] = true;
|
||||||
} else {
|
|
||||||
for (var k in this.selectedIds) delete this.selectedIds[k];
|
|
||||||
}
|
}
|
||||||
this.selectedIds = Object.assign({}, this.selectedIds); this.selectedIdsCount = Object.keys(this.selectedIds).length;
|
this.selectedIds = Object.assign({}, this.selectedIds);
|
||||||
|
this._syncSelection();
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── Table view data ──
|
// ── Table view data ──
|
||||||
@@ -1352,6 +1380,8 @@ window._wsInitData = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var self = this;
|
||||||
|
this.$nextTick(function() { self._syncCheckboxes(); });
|
||||||
},
|
},
|
||||||
|
|
||||||
async navigateToFolder(id) {
|
async navigateToFolder(id) {
|
||||||
@@ -1392,6 +1422,8 @@ window._wsInitData = (function() {
|
|||||||
this._saveExpandedState();
|
this._saveExpandedState();
|
||||||
this.previewItem = null;
|
this.previewItem = null;
|
||||||
this._recompute();
|
this._recompute();
|
||||||
|
var self = this;
|
||||||
|
this.$nextTick(function() { self._syncCheckboxes(); });
|
||||||
} catch(e) { console.error('Load folder error:', e); }
|
} catch(e) { console.error('Load folder error:', e); }
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1466,6 +1498,8 @@ window._wsInitData = (function() {
|
|||||||
|
|
||||||
// ── Keyboard shortcuts ──
|
// ── Keyboard shortcuts ──
|
||||||
onKeydown(ev) {
|
onKeydown(ev) {
|
||||||
|
var tag = ev.target && ev.target.tagName;
|
||||||
|
if (tag === 'INPUT' || tag === 'TEXTAREA') return; // Don't hijack typing
|
||||||
if (this.renamingId) return; // Don't intercept during rename
|
if (this.renamingId) return; // Don't intercept during rename
|
||||||
if (ev.key === 'Delete' && !ev.ctrlKey && !ev.metaKey) {
|
if (ev.key === 'Delete' && !ev.ctrlKey && !ev.metaKey) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
@@ -2304,7 +2338,86 @@ window._wsInitData = (function() {
|
|||||||
for (var k in this.selectedIds) delete this.selectedIds[k];
|
for (var k in this.selectedIds) delete this.selectedIds[k];
|
||||||
}
|
}
|
||||||
this.selectedIds = Object.assign({}, this.selectedIds);
|
this.selectedIds = Object.assign({}, this.selectedIds);
|
||||||
|
this._syncSelection();
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Selection sync (keeps checkboxes & counters in sync) ──
|
||||||
|
_syncSelection() {
|
||||||
this.selectedIdsCount = Object.keys(this.selectedIds).length;
|
this.selectedIdsCount = Object.keys(this.selectedIds).length;
|
||||||
|
var total = this._collectVisibleIds(this.displayTree).length;
|
||||||
|
this.allSelected = total > 0 && this.selectedIdsCount >= total;
|
||||||
|
this._syncCheckboxes();
|
||||||
|
},
|
||||||
|
_syncCheckboxes() {
|
||||||
|
var boxes = document.querySelectorAll('input.item-checkbox[data-ws-id]');
|
||||||
|
for (var i = 0; i < boxes.length; i++) {
|
||||||
|
boxes[i].checked = !!this.selectedIds[parseInt(boxes[i].getAttribute('data-ws-id'))];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Multi-select actions (Library-style select bar) ──
|
||||||
|
_selectedIds() {
|
||||||
|
return Object.keys(this.selectedIds).map(Number);
|
||||||
|
},
|
||||||
|
copySelectedLinks() {
|
||||||
|
this.moreOpen = false;
|
||||||
|
var ids = Object.keys(this.selectedIds);
|
||||||
|
if (!ids.length) return;
|
||||||
|
var links = ids.map(function(id) { return window.location.origin + '/pages/' + id; });
|
||||||
|
navigator.clipboard.writeText(links.join('\n'));
|
||||||
|
if (window.showToast) window.showToast('Links copied', 'success');
|
||||||
|
},
|
||||||
|
async openMovePicker() {
|
||||||
|
var ids = this._selectedIds();
|
||||||
|
if (!ids.length) return;
|
||||||
|
this.moreOpen = false;
|
||||||
|
this.moveIds = ids;
|
||||||
|
this.moveSearchQuery = '';
|
||||||
|
this.moveModalOpen = true;
|
||||||
|
try {
|
||||||
|
// Fetch the full workspace tree so every folder is a valid destination
|
||||||
|
var r = await fetch('/api/local-workspace/tree');
|
||||||
|
var d = await r.json();
|
||||||
|
var folders = [];
|
||||||
|
this._collectFolders(d.tree || [], folders, 0);
|
||||||
|
this._moveAllCandidates = folders;
|
||||||
|
this.moveCandidates = folders.slice(0, 50);
|
||||||
|
} catch(e) {
|
||||||
|
this._moveAllCandidates = [];
|
||||||
|
this.moveCandidates = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_collectFolders(nodes, out, depth) {
|
||||||
|
for (var i = 0; i < nodes.length; i++) {
|
||||||
|
var n = nodes[i];
|
||||||
|
if (n.is_folder) out.push({ id: n.id, name: n.name, depth: depth });
|
||||||
|
if (n.children) this._collectFolders(n.children, out, depth + 1);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doMoveSearch() {
|
||||||
|
var q = (this.moveSearchQuery || '').toLowerCase().trim();
|
||||||
|
if (!q) { this.moveCandidates = this._moveAllCandidates.slice(0, 50); return; }
|
||||||
|
this.moveCandidates = this._moveAllCandidates.filter(function(it) {
|
||||||
|
return (it.name || '').toLowerCase().indexOf(q) !== -1;
|
||||||
|
}).slice(0, 50);
|
||||||
|
},
|
||||||
|
async confirmBulkMove(parentId) {
|
||||||
|
this.moveModalOpen = false;
|
||||||
|
var ids = this.moveIds;
|
||||||
|
this.moveIds = [];
|
||||||
|
if (!ids || !ids.length) return;
|
||||||
|
for (var i = 0; i < ids.length; i++) {
|
||||||
|
try {
|
||||||
|
await fetch('/api/local-workspace/items/' + ids[i] + '/move', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {'Content-Type':'application/json'},
|
||||||
|
body: JSON.stringify({ parent_id: parentId || null })
|
||||||
|
});
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
this.clearSelection();
|
||||||
|
this._reloadAfterAction();
|
||||||
|
if (window.showToast) window.showToast('Item' + (ids.length > 1 ? 's' : '') + ' moved', 'success');
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -2470,14 +2583,38 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
|
|||||||
<input x-ref="searchInput" type="text" placeholder="Filter files and folders..." x-model="searchQuery" @input="doFilter" autocomplete="off">
|
<input x-ref="searchInput" type="text" placeholder="Filter files and folders..." x-model="searchQuery" @input="doFilter" autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Bulk actions bar -->
|
<!-- Bulk actions bar (Library-style) -->
|
||||||
<div class="ws-select-bar" x-show="selectedIdsCount > 0" x-transition>
|
<div class="ws-select-bar" x-show="selectedIdsCount > 0" x-transition>
|
||||||
<span class="sel-count" x-text="selectedIdsCount + ' selected'"></span>
|
<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="clearSelection()" title="Clear selection">
|
||||||
<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"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
||||||
<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>
|
</button>
|
||||||
|
<button @click="copySelectedLinks()" title="Copy links">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"/></svg>
|
||||||
|
</button>
|
||||||
|
<button @click="openMovePicker()" title="Move to">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>
|
||||||
|
</button>
|
||||||
|
<div style="position:relative;margin-left:auto;">
|
||||||
|
<button @click.stop="moreOpen=!moreOpen" title="More actions">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>
|
||||||
|
</button>
|
||||||
|
<div class="ws-select-menu" x-show="moreOpen" @click.outside="moreOpen=false" x-transition>
|
||||||
|
<div class="menu-item" @click="copySelectedLinks()">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 007.54.54l3-3a5 5 0 00-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 00-7.54-.54l-3 3a5 5 0 007.07 7.07l1.71-1.71"/></svg>
|
||||||
|
Copy links to all
|
||||||
|
</div>
|
||||||
|
<div class="menu-item" @click="openMovePicker()">
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 19a2 2 0 01-2 2H4a2 2 0 01-2-2V5a2 2 0 012-2h5l2 3h9a2 2 0 012 2z"/></svg>
|
||||||
|
Move to
|
||||||
|
</div>
|
||||||
|
<div class="more-sep"></div>
|
||||||
|
<div class="menu-item menu-danger" @click="bulkDelete()">
|
||||||
|
<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>
|
||||||
|
Move to Trash <span class="shortcut">Del</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Filter chips (visual indicator when filter is active — shown in all views) -->
|
<!-- Filter chips (visual indicator when filter is active — shown in all views) -->
|
||||||
@@ -2494,6 +2631,15 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
|
|||||||
<!-- Tree -->
|
<!-- Tree -->
|
||||||
<div x-show="viewMode==='tree'" style="display:contents;">
|
<div x-show="viewMode==='tree'" style="display:contents;">
|
||||||
|
|
||||||
|
<!-- Select all toggle -->
|
||||||
|
<div x-show="viewMode==='tree' && displayTree.length>0"
|
||||||
|
style="display:flex;align-items:center;gap:6px;padding:4px 8px;margin-bottom:2px;">
|
||||||
|
<input type="checkbox" class="item-checkbox" :checked="allSelected" @click="selectAll($event)" title="Select / unselect all">
|
||||||
|
<span style="font-size:12px;color:var(--text-secondary);cursor:pointer;user-select:none;" @click="selectAll($event)"
|
||||||
|
x-text="allSelected ? 'Clear selection' : 'Select all'"></span>
|
||||||
|
<span x-show="selectedIdsCount > 0" style="font-size:12px;color:var(--text-dim);" x-text="'(' + selectedIdsCount + ' selected)'"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ul class="ws-tree" x-show="viewMode==='tree' && (displayTree.length>0 || (currentFolder > 0))" @click="handleTreeAction($event)">
|
<ul class="ws-tree" x-show="viewMode==='tree' && (displayTree.length>0 || (currentFolder > 0))" @click="handleTreeAction($event)">
|
||||||
<!-- Parent folder ".." -->
|
<!-- Parent folder ".." -->
|
||||||
<li x-show="currentFolder > 0" style="list-style:none;">
|
<li x-show="currentFolder > 0" style="list-style:none;">
|
||||||
@@ -2615,7 +2761,7 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
|
|||||||
<!-- Filter table (shown when search/filter/tag is active) -->
|
<!-- Filter table (shown when search/filter/tag is active) -->
|
||||||
<table class="ws-table-enhanced" x-show="(searchQuery || filterType || tagFilter) && filteredTableItems.length > 0">
|
<table class="ws-table-enhanced" x-show="(searchQuery || filterType || tagFilter) && filteredTableItems.length > 0">
|
||||||
<thead><tr>
|
<thead><tr>
|
||||||
<th style="width:30px"><input type="checkbox" @click="selectAllFiltered($event)"></th>
|
<th style="width:30px"><input type="checkbox" :checked="allSelected" @click="selectAllFiltered($event)"></th>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Path</th>
|
<th>Path</th>
|
||||||
<th>Size</th>
|
<th>Size</th>
|
||||||
@@ -2820,6 +2966,25 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind
|
|||||||
<span>{{ fd_icon('alert-triangle',14) }} You are offline. Changes will sync when connection is restored.</span>
|
<span>{{ fd_icon('alert-triangle',14) }} You are offline. Changes will sync when connection is restored.</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Move-to modal -->
|
||||||
|
<div class="modal-overlay" x-show="moveModalOpen" @click.self="moveModalOpen=false" @keydown.escape="moveModalOpen=false" style="display:none">
|
||||||
|
<div class="modal-box" style="width:440px;max-width:90vw;padding:0;overflow:hidden;display:flex;flex-direction:column;max-height:60vh;">
|
||||||
|
<div style="padding:14px 16px;border-bottom:1px solid var(--border);font-size:14px;font-weight:600;color:var(--text-primary);">Move to…</div>
|
||||||
|
<div style="padding:8px 12px;border-bottom:1px solid var(--border);">
|
||||||
|
<input class="modal-input" type="text" placeholder="Search folders..." x-model="moveSearchQuery" @input="doMoveSearch()" style="padding:7px 10px;font-size:13px;">
|
||||||
|
</div>
|
||||||
|
<div style="flex:1;overflow-y:auto;padding:4px;">
|
||||||
|
<div class="move-modal-item" @click="confirmBulkMove(0)">{{ fd_icon("folder",14) }} <span>Root (no parent)</span></div>
|
||||||
|
<template x-for="it in moveCandidates" :key="'mv'+it.id">
|
||||||
|
<div class="move-modal-item" :style="{paddingLeft: (8 + (it.depth||0)*20) + 'px'}" @click="confirmBulkMove(it.id)">
|
||||||
|
{{ fd_icon("folder",14) }} <span x-text="it.name"></span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div x-show="moveCandidates.length === 0" style="padding:20px;text-align:center;color:var(--text-tertiary);font-size:13px;">No folders found</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Create Modal -->
|
<!-- Create Modal -->
|
||||||
<div class="modal-overlay" x-show="showCreate" @click.outside="showCreate=false" @keydown.escape="showCreate=false" style="display:none">
|
<div class="modal-overlay" x-show="showCreate" @click.outside="showCreate=false" @keydown.escape="showCreate=false" style="display:none">
|
||||||
<div class="modal-box">
|
<div class="modal-box">
|
||||||
|
|||||||
Reference in New Issue
Block a user