fix: hamburger menu sur toutes les pages + cookies path="/" Chrome fix
- local_workspace.html: supprimé Alpine.data dupliqué + ajout hamburger - workspaces.html, workspace.html, settings.html, page_editor.html: +hamburger - auth.py: +path="/" sur tous les set_cookie de session (Chrome compat) - csrf.py: +path="/" sur cookie CSRF Root cause des bugs: 1. Chrome: cookie sans path="/" → non envoyé sur certaines routes 2. Firefox: les templates écrasaient le block topbar → pas de hamburger → sidebar inaccessible sur mobile (overlay + slide-in ne fonctionnaient pas)
This commit is contained in:
@@ -33,6 +33,7 @@ class CSRFMiddleware(BaseHTTPMiddleware):
|
||||
httponly=False, # Must be readable by JS
|
||||
samesite="lax",
|
||||
max_age=86400,
|
||||
path="/",
|
||||
)
|
||||
return response
|
||||
|
||||
|
||||
+4
-4
@@ -100,7 +100,7 @@ async def login(request: Request, provider: str = Query("gitea")):
|
||||
user_data = dict(user)
|
||||
session = SessionManager.create_session(user_data)
|
||||
response = RedirectResponse(url="/workspaces", status_code=302)
|
||||
response.set_cookie("flowdeck_session", session, httponly=True, max_age=86400 * 7, samesite="lax")
|
||||
response.set_cookie("flowdeck_session", session, httponly=True, max_age=86400 * 7, samesite="lax", path="/")
|
||||
return response
|
||||
|
||||
state = secrets.token_hex(32)
|
||||
@@ -146,7 +146,7 @@ async def register(request: Request):
|
||||
session = SessionManager.create_session(user_data)
|
||||
from fastapi.responses import JSONResponse
|
||||
response = JSONResponse({"status": "ok", "user": {"login": email, "name": name}})
|
||||
response.set_cookie("flowdeck_session", session, httponly=True, max_age=86400 * 7, samesite="lax")
|
||||
response.set_cookie("flowdeck_session", session, httponly=True, max_age=86400 * 7, samesite="lax", path="/")
|
||||
return response
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ async def local_login(request: Request):
|
||||
conn.commit()
|
||||
session = SessionManager.create_session(ud)
|
||||
response = JSONResponse({"status": "ok", "user": {"login": ud["login"], "name": ud["full_name"]}})
|
||||
response.set_cookie("flowdeck_session", session, httponly=True, max_age=86400 * 7, samesite="lax")
|
||||
response.set_cookie("flowdeck_session", session, httponly=True, max_age=86400 * 7, samesite="lax", path="/")
|
||||
return response
|
||||
|
||||
|
||||
@@ -266,7 +266,7 @@ async def callback(
|
||||
# Create session
|
||||
session = SessionManager.create_session(user_data)
|
||||
response = RedirectResponse(url="/workspaces", status_code=302)
|
||||
response.set_cookie("flowdeck_session", session, httponly=True, max_age=86400 * 7, samesite="lax")
|
||||
response.set_cookie("flowdeck_session", session, httponly=True, max_age=86400 * 7, samesite="lax", path="/")
|
||||
return response
|
||||
|
||||
|
||||
|
||||
@@ -4,18 +4,24 @@
|
||||
|
||||
{% block topbar %}
|
||||
<header class="topbar">
|
||||
<button class="hamburger-btn" @click="mobileSidebarOpen = true; sidebarCollapsed = false;" title="Menu">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||||
</button>
|
||||
<div class="topbar-left">
|
||||
<span class="breadcrumb" style="color:rgba(255,255,255,.5)">Workspace</span>
|
||||
<span class="page-title-topbar">{{ workspace_name }}</span>
|
||||
</div>
|
||||
<div class="topbar-right"></div>
|
||||
<div class="topbar-right">
|
||||
<a href="/workspaces" class="page-action-btn" title="All workspaces">🏠</a>
|
||||
</div>
|
||||
</header>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<style>
|
||||
.ws-layout{max-width:800px;margin:0 auto;padding:40px 24px;}
|
||||
.ws-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px;}
|
||||
@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;}
|
||||
|
||||
@@ -55,79 +61,6 @@
|
||||
.delete-confirm{padding:12px;background:rgba(255,80,80,.08);border-radius:8px;margin-top:12px;font-size:13px;color:var(--text-secondary);}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener('alpine:init', function() {
|
||||
Alpine.data('wsInit', function() {
|
||||
return {
|
||||
tree:[],
|
||||
loaded:false,
|
||||
showCreate:false, showRename:false, showDelete:false,
|
||||
createType:'file', newName:'', parentFolder:null, target:null,
|
||||
|
||||
async init() {
|
||||
try {
|
||||
var r = await fetch('/api/local-workspace/tree');
|
||||
var d = await r.json();
|
||||
this.tree = flatten(d.tree||[],0);
|
||||
this.loaded = true;
|
||||
} catch(e) { this.loaded = true; }
|
||||
},
|
||||
|
||||
openModal(type, parent) {
|
||||
this.createType = type; this.parentFolder = parent||null; this.newName = '';
|
||||
this.showCreate = true;
|
||||
var self = this;
|
||||
this.$nextTick(function() {
|
||||
var el = self.$refs.cinp;
|
||||
if (el) setTimeout(function() { el.focus(); }, 100);
|
||||
});
|
||||
},
|
||||
|
||||
async doCreate() {
|
||||
var n = this.newName.trim(); if (!n) return;
|
||||
var body = { name:n, type:this.createType };
|
||||
if (this.parentFolder) body.parent_id = this.parentFolder.db_id;
|
||||
var r = await fetch('/api/local-workspace/items', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body)
|
||||
});
|
||||
if (r.ok) {
|
||||
this.showCreate = false; this.parentFolder = null; this.newName = '';
|
||||
window.location.reload();
|
||||
}
|
||||
},
|
||||
|
||||
openPage(id) { window.location = '/pages/'+id; },
|
||||
|
||||
startRename(node) {
|
||||
this.target = node; this.newName = node.name.trim(); this.showRename = true;
|
||||
var self = this;
|
||||
this.$nextTick(function() {
|
||||
var el = self.$refs.rinp;
|
||||
if (el) setTimeout(function() { el.focus(); }, 100);
|
||||
});
|
||||
},
|
||||
|
||||
async doRename() {
|
||||
var n = this.newName.trim(); if (!n||!this.target) return;
|
||||
await fetch('/api/local-workspace/items/'+this.target.db_id, {
|
||||
method:'PUT', headers:{'Content-Type':'application/json'}, body:JSON.stringify({name:n})
|
||||
});
|
||||
this.showRename = false; this.target = null; this.newName = '';
|
||||
await this.init();
|
||||
},
|
||||
|
||||
startDelete(node) { this.target = node; this.showDelete = true; },
|
||||
|
||||
async doDelete() {
|
||||
if (!this.target) return;
|
||||
await fetch('/api/local-workspace/items/'+this.target.db_id, {method:'DELETE'});
|
||||
this.showDelete = false; this.target = null;
|
||||
await this.init();
|
||||
}
|
||||
};});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="ws-layout" x-data="wsInit()">
|
||||
|
||||
<div class="ws-header">
|
||||
@@ -214,6 +147,17 @@ document.addEventListener('alpine:init', function() {
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function flatten(nodes, depth) {
|
||||
var result = [];
|
||||
for (var i=0; i<nodes.length; i++) {
|
||||
var n = nodes[i];
|
||||
var isFolder = n.type==='folder';
|
||||
result.push({db_id:n.id, name:n.name, icon:isFolder?'📁':'📄', is_folder:isFolder, depth:depth});
|
||||
if (n.children) result = result.concat(flatten(n.children, depth+1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
document.addEventListener('alpine:init', function() {
|
||||
Alpine.data('wsInit', function() {
|
||||
return {
|
||||
@@ -263,9 +207,6 @@ document.addEventListener('alpine:init', function() {
|
||||
this.parentFolder = null;
|
||||
this.newName = '';
|
||||
window.location.reload();
|
||||
} else {
|
||||
var e = await r.json();
|
||||
alert(e.error||'Failed');
|
||||
}
|
||||
},
|
||||
|
||||
@@ -307,16 +248,5 @@ document.addEventListener('alpine:init', function() {
|
||||
}
|
||||
};});
|
||||
});
|
||||
|
||||
function flatten(nodes, depth) {
|
||||
var result = [];
|
||||
for (var i=0; i<nodes.length; i++) {
|
||||
var n = nodes[i];
|
||||
var isFolder = n.type==='folder';
|
||||
result.push({db_id:n.id, name:n.name, icon:isFolder?'📁':'📄', is_folder:isFolder, depth:depth});
|
||||
if (n.children) result = result.concat(flatten(n.children, depth+1));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
{% extends "base.html" %} {% block page_icon %}📄{% endblock %} {% block
|
||||
page_title %}{{ page.title }}{% endblock %} {% block topbar %}
|
||||
<!-- Page editor uses its own topbar -->
|
||||
<header class="topbar" style="background:var(--bg-primary);">
|
||||
<button class="hamburger-btn" @click="mobileSidebarOpen = true; sidebarCollapsed = false;" title="Menu">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||||
</button>
|
||||
<div class="topbar-left">
|
||||
<span class="breadcrumb" style="color:rgba(255,255,255,.5);font-size:13px">{{ page.title }}</span>
|
||||
</div>
|
||||
</header>
|
||||
{% endblock %} {% block content %}
|
||||
<div class="page-editor-wrapper" x-data="editorState()">
|
||||
<!-- ═══════════ Top Action Bar ═══════════ -->
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
|
||||
{% block topbar %}
|
||||
<header class="topbar">
|
||||
<button class="hamburger-btn" @click="mobileSidebarOpen = true; sidebarCollapsed = false;" title="Menu">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||||
</button>
|
||||
<div class="topbar-left">
|
||||
<span class="breadcrumb" style="color:rgba(255,255,255,.5)">
|
||||
Settings
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
|
||||
{% block topbar %}
|
||||
<header class="topbar">
|
||||
<button class="hamburger-btn" @click="mobileSidebarOpen = true; sidebarCollapsed = false;" title="Menu">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||||
</button>
|
||||
<div class="topbar-left">
|
||||
<span class="breadcrumb" style="color:rgba(255,255,255,.5)">Workspace</span>
|
||||
<span class="page-title-topbar">Projects</span>
|
||||
|
||||
@@ -4,6 +4,9 @@
|
||||
|
||||
{% block topbar %}
|
||||
<header class="topbar">
|
||||
<button class="hamburger-btn" @click="mobileSidebarOpen = true; sidebarCollapsed = false;" title="Menu">
|
||||
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
||||
</button>
|
||||
<div class="topbar-left">
|
||||
<span class="breadcrumb" style="color:rgba(255,255,255,.5)">Home</span>
|
||||
<span class="page-title-topbar">Workspaces</span>
|
||||
|
||||
Reference in New Issue
Block a user