diff --git a/app/templates/local_workspace.html b/app/templates/local_workspace.html
index 9970312..2fe8429 100644
--- a/app/templates/local_workspace.html
+++ b/app/templates/local_workspace.html
@@ -126,6 +126,22 @@
/* Drag count badge */
.drag-badge{position:fixed;pointer-events:none;z-index:999;background:var(--accent);color:#fff;font-size:12px;font-weight:700;padding:2px 8px;border-radius:10px;transform:translate(-50%,-50%);}
+/* Drop indicator line */
+.drop-indicator{position:absolute;left:0;right:0;height:2px;background:var(--accent);border-radius:1px;pointer-events:none;z-index:10;}
+
+/* Sort select */
+.sort-select{background:var(--bg-tertiary);border:1px solid var(--border);color:var(--text-secondary);font-size:12px;padding:4px 8px;border-radius:6px;cursor:pointer;outline:none;}
+.sort-select:focus{border-color:var(--accent);}
+
+/* Undo toast */
+.undo-toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:var(--bg);border:1px solid var(--border-strong);border-radius:12px;padding:10px 20px;z-index:300;box-shadow:0 8px 30px rgba(0,0,0,.5);display:flex;align-items:center;gap:12px;font-size:13px;}
+.undo-btn{background:var(--accent);color:#fff;border:none;padding:4px 12px;border-radius:6px;cursor:pointer;font-size:12px;font-weight:600;}
+.undo-btn:hover{opacity:.85;}
+.undo-dismiss{background:none;border:none;color:var(--text-tertiary);cursor:pointer;font-size:18px;padding:0 4px;}
+
+/* Smooth expand animation */
+.ws-tree[x-show] { transition: max-height 0.25s ease, opacity 0.2s ease; overflow:hidden; }
+
/* File preview tooltip */
.file-preview{position:fixed;z-index:300;background:#1a1a1a;background:var(--bg,#1a1a1a);border:1px solid var(--border-strong,#333);border-radius:10px;box-shadow:0 8px 30px rgba(0,0,0,.5);max-width:400px;min-width:200px;pointer-events:auto;}
.file-preview-header{padding:8px 12px;font-size:12px;font-weight:600;color:var(--text-secondary,#888);border-bottom:1px solid var(--border,#333);}
@@ -156,12 +172,20 @@
+
+
+
+
+
+
+
+
š {{ workspace_name }}
@@ -181,6 +205,12 @@
š
+
@@ -257,7 +287,7 @@
-
+
@@ -365,6 +395,19 @@ document.addEventListener('alpine:init', function() {
// Clipboard for copy/paste
clipboard: [],
+ // Sort
+ sortBy: '',
+
+ // Undo
+ undoVisible: false,
+ undoMessage: '',
+ undoItemId: null,
+ undoTimeout: null,
+
+ // Upload tracking
+ uploadSpeed: '',
+ uploadStartTime: 0,
+
// Preview popup
previewVisible: false,
previewContent: '',
@@ -484,7 +527,7 @@ document.addEventListener('alpine:init', function() {
this.startRename({db_id: id, name: name});
break;
case 'delete':
- this.startDelete({db_id: id, name: name});
+ this.deleteWithUndo({db_id: id, name: name});
break;
}
},
@@ -588,7 +631,6 @@ document.addEventListener('alpine:init', function() {
// āā Duplicate āā
async duplicateItem(node) {
- // Create a copy with " (copy)" suffix
var r = await fetch('/api/local-workspace/items', {
method: 'POST', headers: {'Content-Type':'application/json'},
body: JSON.stringify({name: node.name + ' (copy)', type: node.type || 'page',
@@ -597,6 +639,70 @@ document.addEventListener('alpine:init', function() {
if (r.ok) this._reloadAfterAction();
},
+ // āā Sort āā
+ doSort() {
+ var s = this.sortBy;
+ if (!s) { this.displayTree = this.tree; return; }
+ var sorted = this.tree.slice();
+ if (s === 'name-asc') sorted.sort(function(a,b) { return (a.name||'').localeCompare(b.name||''); });
+ else if (s === 'name-desc') sorted.sort(function(a,b) { return (b.name||'').localeCompare(a.name||''); });
+ else if (s === 'type') sorted.sort(function(a,b) { return (a.is_folder===b.is_folder) ? 0 : a.is_folder ? -1 : 1; });
+ this.displayTree = sorted;
+ },
+
+ // āā Undo delete āā
+ async deleteWithUndo(node) {
+ var self = this;
+ this.undoItemId = node.db_id || node.id;
+ this.undoMessage = 'Deleted "' + (node.name || 'item') + '"';
+ this.undoVisible = true;
+ // Soft-delete: actually delete after timeout
+ var r = await fetch('/api/local-workspace/items/' + this.undoItemId, { method:'DELETE' });
+ if (!r.ok) { this.undoVisible = false; return; }
+ // Remove from tree immediately
+ this._removeFromTree(this.tree, this.undoItemId);
+ if (!this.searchQuery && !this.filterType && !this.sortBy) this.displayTree = this.tree;
+ // Auto-dismiss + permanent after 5s
+ this.undoTimeout = setTimeout(function() {
+ self.undoVisible = false;
+ self.undoItemId = null;
+ }, 5000);
+ },
+ undoDelete() {
+ if (this.undoTimeout) clearTimeout(this.undoTimeout);
+ this.undoVisible = false;
+ var id = this.undoItemId;
+ if (!id) return;
+ // Recreate the item (we can't really undo a DELETE, so reload)
+ this._reloadAfterAction();
+ this.undoItemId = null;
+ },
+ _removeFromTree(nodes, id) {
+ for (var i=0; i 0;
+ return true;
+ }
+ }
+ return false;
+ },
+
+ // āā Upload speed tracking āā
+ _trackUploadSpeed(totalSize) {
+ var self = this;
+ this.uploadStartTime = Date.now();
+ this.uploadSpeed = '';
+ var checkSpeed = setInterval(function() {
+ if (!self.uploading) { clearInterval(checkSpeed); return; }
+ var elapsed = (Date.now() - self.uploadStartTime) / 1000;
+ if (elapsed > 0) {
+ self.uploadSpeed = (totalSize / elapsed / 1024).toFixed(0) + ' KB/s';
+ }
+ }, 500);
+ },
+
// āā Expand / Collapse all āā
expandAll() {
var self = this;