feat(nav): partial HTMX navigation — swap main content without full reload
FlowDeck CI / lint (push) Successful in 45s
FlowDeck CI / test (push) Successful in 4m17s
FlowDeck CI / docker (push) Successful in 41s

Intercept internal links and load only .main-wrapper via htmx.ajax, keeping the sidebar/header shell in place. Adds history pushState/popstate handling, sidebar active-state resync and document title update. Moves page script blocks inside #main-content so they run on swap, and makes editor/board scripts idempotent. Routes navigateTo/breadcrumb/library/local-workspace through window.fdNavigate and fixes the duplicated navigateTo definition.
This commit is contained in:
2026-09-12 21:26:56 -04:00
parent da3e09c215
commit e707becbf8
9 changed files with 197 additions and 27 deletions
+13 -13
View File
@@ -211,7 +211,7 @@
<div class="um-sep"></div>
<div class="um-version">FlowDeck v{{ app_version }}</div>
<div class="um-sep"></div>
<a href="/auth/logout" class="um-item um-danger" @click="workspaceMenuOpen=false">{{ fd_icon("log-out", 16) }} Log out</a>
<a href="/auth/logout" class="um-item um-danger" hx-boost="false" @click="workspaceMenuOpen=false">{{ fd_icon("log-out", 16) }} Log out</a>
</div>
</div>
@@ -265,7 +265,7 @@
{% else %}
<li class="sidebar-item empty-hint"><span class="page-icon page-icon-svg">{{ fd_icon("folder",16) }}</span><span class="page-name text-dim">Open a workspace to see your files</span></li>
{% endif %}
<li class="sidebar-item" style="margin-top:4px;border-top:1px solid var(--border);padding-top:8px;" onclick="window.location='/workspaces'">
<li class="sidebar-item" style="margin-top:4px;border-top:1px solid var(--border);padding-top:8px;" onclick="window.fdNavigate ? window.fdNavigate('/workspaces') : window.location='/workspaces'">
<span class="page-icon page-icon-svg">{{ fd_icon("settings",16) }}</span><span class="page-name text-dim">Manage Workspaces</span>
</li>
</ul>
@@ -598,6 +598,7 @@
<!-- Content -->
<div class="content-area" id="main-content">
{% block content %}{% endblock %}
{% block scripts %}{% endblock %}
</div>
</div>
@@ -1255,11 +1256,6 @@
toggleWorkspaceMenu() {
this.workspaceMenuOpen = !this.workspaceMenuOpen;
},
navigateTo(url) {
this.mobileSidebarOpen = false;
this.sidebarCollapsed = true;
window.location.href = url;
},
openQuickFind() { if (window.FlowDeckPalette) window.FlowDeckPalette.open(); },
// ── Tree state ──
@@ -1328,12 +1324,15 @@
},
navigateTo(url) {
this.mobileSidebarOpen = false;
this.sidebarCollapsed = true;
// Preserve workspace context when navigating to library/trash
if ((url === '/library' || url === '/trash') && this.workspaceKey && this.workspaceKey.includes('/')) {
const parts = this.workspaceKey.split('/');
url = `${url}?owner=${parts[0]}&repo=${parts.slice(1).join('/')}`;
}
window.location.href = url;
if (window.fdNavigate) window.fdNavigate(url);
else window.location.href = url;
},
showContextMenu(event, pageId, pageName) {
@@ -1669,7 +1668,8 @@
.catch(err => { this.toast('Failed: ' + err.message, 'error'); });
},
openWorkspacePage(pageId) {
window.location = `/pages/${pageId}`;
if (window.fdNavigate) window.fdNavigate('/pages/' + pageId);
else window.location = `/pages/${pageId}`;
},
// ── Tree expand/collapse + folder navigation ──
@@ -1689,7 +1689,8 @@
localStorage.setItem('fd_expanded_folders', JSON.stringify(this.expandedFolders));
localStorage.setItem('fd_sections', JSON.stringify(this.sectionsOpen));
} catch(e) {}
window.location = `/local-workspace?folder=${id}`;
if (window.fdNavigate) window.fdNavigate(`/local-workspace?folder=${id}`);
else window.location = `/local-workspace?folder=${id}`;
},
createEmptyPage() {
@@ -1841,8 +1842,7 @@
}
});
</script>
<script src="/static/js/app.js?v=2.4.6" defer></script>
{% block scripts %}{% endblock %}
<script src="/static/js/app.js?v=2.4.7" defer></script>
<style>
.flowdeck-modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.6);z-index:2000;display:flex;align-items:center;justify-content:center;}
@@ -2034,7 +2034,7 @@
function choose(i){
var it = state.items[i]; if(!it) return;
if(it.run){ var ok = it.run(); if(ok) close(); return; }
if(it.url){ window.location.href=it.url; close(); return; }
if(it.url){ if(window.fdNavigate) window.fdNavigate(it.url); else window.location.href=it.url; close(); return; }
}
function onKey(e){