Files
flowdeck/app/templates/workspace.html
T
bruno c5398757ca
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
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)
2026-07-11 00:30:59 -04:00

199 lines
8.8 KiB
HTML

{% extends "base.html" %}
{% block page_title %}Workspace{% endblock %}
{% block page_icon %}🏠{% endblock %}
{% 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>
</div>
<div class="topbar-right">
<button class="page-action-btn" onclick="window.location='/auth/login?provider=local'" title="Login">🔑</button>
<button class="page-action-btn" onclick="window.location='/accounts/settings'" title="Settings">⚙</button>
</div>
</header>
{% endblock %}
{% block content %}
<style>
.workspace-layout{max-width:900px;margin:0 auto;padding:32px 24px;}
.workspace-header{margin-bottom:24px;}
.workspace-header h1{font-size:28px;font-weight:600;margin-bottom:8px;}
.workspace-header p{color:rgba(255,255,255,.5);font-size:14px;}
.forge-section{margin-bottom:32px;}
.forge-section h2{font-size:14px;color:rgba(255,255,255,.45);text-transform:uppercase;letter-spacing:.5px;margin-bottom:12px;font-weight:500;display:flex;align-items:center;gap:8px;}
.forge-badge{display:inline-flex;align-items:center;gap:4px;padding:2px 8px;border-radius:4px;font-size:11px;font-weight:500;}
.forge-badge.gitea{background:rgba(96,155,80,.15);color:#609b50;}
.forge-badge.github{background:rgba(110,84,148,.15);color:#6e5494;}
.forge-badge.builtin{background:rgba(35,131,226,.15);color:#2383E2;}
.project-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;}
.project-card{background:#222;border:1px solid rgba(255,255,255,.06);border-radius:12px;padding:16px;cursor:pointer;transition:background 150ms ease;}
.project-card:hover{background:#2A2A2A;border-color:rgba(255,255,255,.12);}
.project-card-top{display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:8px;}
.project-card-icon{font-size:20px;}
.project-card-name{font-size:14px;font-weight:500;color:#fff;margin-bottom:4px;}
.project-card-desc{font-size:12px;color:rgba(255,255,255,.4);margin-bottom:8px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
.project-card-meta{display:flex;align-items:center;gap:8px;font-size:11px;color:rgba(255,255,255,.3);}
.empty-state{text-align:center;padding:40px;color:rgba(255,255,255,.3);font-size:14px;}
.empty-state .btn{margin-top:12px;display:inline-block;padding:8px 16px;background:#2383E2;color:#fff;border-radius:8px;text-decoration:none;font-size:13px;}
.new-project-btn{display:inline-flex;align-items:center;gap:6px;padding:8px 16px;background:rgba(35,131,226,.15);color:#2383E2;border:1px solid rgba(35,131,226,.3);border-radius:8px;cursor:pointer;font-size:13px;}
.new-project-btn:hover{background:rgba(35,131,226,.25);}
/* Modal */
.modal-overlay{position:fixed;inset:0;background:rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center;z-index:500;}
.modal-box{background:#1F1F1F;border:1px solid rgba(255,255,255,.08);border-radius:18px;padding:32px;width:400px;max-width:90vw;}
.modal-box h3{font-size:16px;margin-bottom:16px;}
.modal-input{width:100%;padding:10px 12px;background:#2A2A2A;border:1px solid rgba(255,255,255,.1);border-radius:8px;color:#fff;font-size:14px;margin-bottom:12px;outline:none;}
.modal-input:focus{border-color:#2383E2;}
.modal-actions{display:flex;gap:8px;justify-content:flex-end;}
</style>
<div class="workspace-layout" x-data="workspacePage()">
<div class="workspace-header">
<h1>🏠 Workspace</h1>
<p>All your projects in one place — built-in, Gitea, and GitHub</p>
</div>
<!-- Built-in Projects -->
<div class="forge-section">
<h2><span class="forge-badge builtin">Built-in</span> My Projects</h2>
<div class="project-grid">
<template x-for="p in builtinProjects" :key="p.id">
<div class="project-card" @click="openProject(p)">
<div class="project-card-top">
<span class="project-card-icon" x-text="p.icon || '📁'"></span>
<span class="forge-badge builtin">Built-in</span>
</div>
<div class="project-card-name" x-text="p.name"></div>
<div class="project-card-desc" x-text="p.description || 'No description'"></div>
<div class="project-card-meta">
<span x-text="p.pageCount + ' pages'"></span>
</div>
</div>
</template>
<div class="project-card" style="border:1px dashed rgba(255,255,255,.1);display:flex;align-items:center;justify-content:center;min-height:100px" @click="showCreateModal = true">
<span style="color:rgba(255,255,255,.3);font-size:24px">+</span>
</div>
</div>
</div>
<!-- Gitea Projects -->
<div class="forge-section" x-show="giteaProjects.length">
<h2><span class="forge-badge gitea">🔗 Gitea</span> Repositories</h2>
<div class="project-grid">
<template x-for="p in giteaProjects" :key="p.id">
<div class="project-card" @click="openProject(p)">
<div class="project-card-top">
<span class="project-card-icon">📂</span>
<span class="forge-badge gitea">Gitea</span>
</div>
<div class="project-card-name" x-text="p.name"></div>
<div class="project-card-desc" x-text="p.description || 'No description'"></div>
<div class="project-card-meta">
<span x-text="p.full_name"></span>
<span x-show="p.language" x-text="'· ' + p.language"></span>
</div>
</div>
</template>
</div>
</div>
<!-- GitHub Projects -->
<div class="forge-section" x-show="githubProjects.length">
<h2><span class="forge-badge github">🐙 GitHub</span> Repositories</h2>
<div class="project-grid">
<template x-for="p in githubProjects" :key="p.id">
<div class="project-card" @click="openProject(p)">
<div class="project-card-top">
<span class="project-card-icon">📂</span>
<span class="forge-badge github">GitHub</span>
</div>
<div class="project-card-name" x-text="p.name"></div>
<div class="project-card-desc" x-text="p.description || 'No description'"></div>
<div class="project-card-meta">
<span x-text="p.full_name"></span>
<span x-show="p.language" x-text="'· ' + p.language"></span>
</div>
</div>
</template>
</div>
</div>
<!-- Empty state -->
<div class="empty-state" x-show="!builtinProjects.length && !giteaProjects.length && !githubProjects.length">
<p>No projects yet. Create a new project or connect a forge.</p>
<a href="/auth/login?provider=local" class="btn">Get Started</a>
</div>
<!-- Create Modal -->
<div class="modal-overlay" x-show="showCreateModal" @click.away="showCreateModal=false" @keydown.escape="showCreateModal=false">
<div class="modal-box">
<h3>Create New Project</h3>
<input class="modal-input" x-model="newProjectName" placeholder="Project name" @keydown.enter="createProject">
<div class="modal-actions">
<button class="settings-btn settings-btn-secondary" @click="showCreateModal=false">Cancel</button>
<button class="settings-btn settings-btn-primary" @click="createProject">Create</button>
</div>
</div>
</div>
</div>
<script>
function workspacePage() {
return {
builtinProjects: [],
giteaProjects: [],
githubProjects: [],
showCreateModal: false,
newProjectName: '',
async init() {
try {
const r = await fetch('/api/workspace/projects');
const data = await r.json();
this.builtinProjects = data.builtin || [];
this.giteaProjects = data.gitea || [];
this.githubProjects = data.github || [];
} catch(e) { console.error('Failed to load projects', e); }
},
openProject(p) {
if (p.forge === 'gitea') {
const parts = p.full_name.split('/');
window.location = `/board/${parts[0]}/${parts[1]}`;
} else if (p.forge === 'github') {
window.open(p.html_url, '_blank');
} else {
window.location = `/pages/${p.id}`;
}
},
async createProject() {
if (!this.newProjectName.trim()) return;
const r = await fetch('/api/workspace/projects', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name: this.newProjectName.trim()})
});
if (r.ok) {
const p = await r.json();
this.builtinProjects.unshift({...p, icon: '📁', pageCount: 0});
this.newProjectName = '';
this.showCreateModal = false;
}
}
};
}
</script>
{% endblock %}