diff --git a/app/templates/local_workspace.html b/app/templates/local_workspace.html index 2fe8429..ed5211a 100644 --- a/app/templates/local_workspace.html +++ b/app/templates/local_workspace.html @@ -142,6 +142,15 @@ /* Smooth expand animation */ .ws-tree[x-show] { transition: max-height 0.25s ease, opacity 0.2s ease; overflow:hidden; } +/* Path bar */ +.path-bar{display:flex;align-items:center;gap:6px;margin-bottom:12px;padding:6px 10px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:8px;} +.path-bar:focus-within{border-color:var(--accent);} +.path-icon{font-size:14px;flex-shrink:0;} +.path-input{flex:1;background:none;border:none;color:var(--text);font-size:13px;outline:none;font-family:'Fira Code',monospace;} +.path-input::placeholder{color:var(--text-tertiary);} +.path-go{background:var(--accent);color:#fff;border:none;padding:3px 8px;border-radius:4px;cursor:pointer;font-size:12px;} +.path-go:hover{opacity:.85;} + /* 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);} @@ -199,6 +208,16 @@ + +
+ 📂 + + +
+

📁 {{ workspace_name }}

@@ -408,6 +427,15 @@ document.addEventListener('alpine:init', function() { uploadSpeed: '', uploadStartTime: 0, + // Path bar + get pathValue() { + return '/workspace/' + this.breadcrumb.map(function(b){return b.name}).join('/'); + }, + + // Drag indicators + dragCount: 0, + dragOverParentId: null, + // Preview popup previewVisible: false, previewContent: '', @@ -703,6 +731,106 @@ document.addEventListener('alpine:init', function() { }, 500); }, + // ── Path bar navigation ── + navigateToPath() { + var path = (this.$el.querySelector('.path-input') || {}).value || ''; + var parts = path.replace(/^\/workspace\/?/, '').split('/').filter(Boolean); + if (parts.length === 0) { window.location = '/local-workspace'; return; } + // Walk tree to find matching folder + var targetId = this._findFolderByPath(this.tree, parts, 0); + if (targetId) { + window.location = '/local-workspace?folder=' + targetId; + } else { + alert('Path not found: ' + parts.join('/')); + } + }, + _findFolderByPath(nodes, parts, idx) { + if (idx >= parts.length) return null; + for (var i=0; i'; + this.previewContent = html; this.previewCache[cacheKey] = html; + } else if (isPdf) { + var html = '
📕
PDF Document
Open viewer →
'; + this.previewContent = html; this.previewCache[cacheKey] = html; + } else { + this.previewContent = 'Loading...'; + this._fetchRetry(url).then(function(t) { + var content = t.substring(0, 500); + self.previewContent = content; self.previewCache[cacheKey] = content; + }).catch(function() { self.previewContent = '(preview not available)'; }); + } + } else { + this.previewContent = 'Loading...'; + this._fetchRetry('/api/local-workspace/page-content/' + node.id, true) + .then(function(d) { + var content = self._extractPreview(d.content || '', d.format); + self.previewContent = content; self.previewCache[cacheKey] = content; + }).catch(function() { self.previewContent = '(preview not available)'; }); + } + }, + + // ── Fetch with retry ── + _fetchRetry(url, json) { + var maxRetries = 2; + var delay = 300; + var self = this; + return new Promise(function(resolve, reject) { + function attempt(n) { + fetch(url).then(function(r) { + if (!r.ok) throw new Error('HTTP ' + r.status); + return json ? r.json() : r.text(); + }).then(resolve).catch(function(e) { + if (n < maxRetries) { + setTimeout(function() { attempt(n+1); }, delay * (n+1)); + } else { reject(e); } + }); + } + attempt(0); + }); + }, + + // ── Touch haptic feedback ── + _haptic() { + if (navigator.vibrate) navigator.vibrate(10); + }, + // ── Expand / Collapse all ── expandAll() { var self = this; @@ -801,47 +929,6 @@ document.addEventListener('alpine:init', function() { }, // ── Preview on hover ── - showPreview(ev, node) { - if (node.is_folder) return; - this.previewName = node.name; - this.previewX = ev.clientX + 12; - this.previewY = ev.clientY + 12; - this.previewVisible = true; - var self = this; - var wsId = {{ workspace_id }} || 1; - - // Image preview - var imgExts = ['.png','.jpg','.jpeg','.gif','.webp','.svg','.bmp','.ico']; - var isImg = false; - for (var i=0; i'; - } else if ((node.name||'').toLowerCase().endsWith('.pdf')) { - this.previewContent = '
📕
PDF Document
Open viewer →
'; - } else { - this.previewContent = 'Loading...'; - fetch(url) - .then(function(r) { return r.text(); }) - .then(function(t) { self.previewContent = t.substring(0, 500); }) - .catch(function() { self.previewContent = '(preview not available)'; }); - } - } else { - // FlowDeck page: fetch content via API - this.previewContent = 'Loading...'; - fetch('/api/local-workspace/page-content/' + node.id) - .then(function(r) { return r.json(); }) - .then(function(d) { - self.previewContent = self._extractPreview(d.content || '', d.format); - }) - .catch(function() { self.previewContent = '(preview not available)'; }); - } - }, _extractPreview(content, format) { if (!content) return '(empty)'; // Try to extract text from Notion block JSON