Compare commits

...
Author SHA1 Message Date
bruno 0f86294abc merge: main → develop (sync) 2026-07-20 11:02:42 -04:00
bruno cefc7eb356 fix: admin password permanent + Help/My Tasks content rendering
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
- main.py: lifespan insère toujours le hash de 'FlowDeck2026!' pour admin
- dashboard.py + my_tasks.py: content_html passe via {% block content %} (Jinja)
- Avant: content_html ignoré car base.html utilise des blocs, pas des variables
- 143 tests passent
2026-07-20 11:02:37 -04:00
bruno 4806097bc5 feat: Help page complète — 6 cartes, raccourcis, auth, tips
- Grille 2 colonnes de cartes: Getting Started, Pages, Workspaces, Gitea, Sharing, Library
- Section Keyboard Shortcuts avec style kbd + hover
- Section Authentication avec badges colorés (Local/Gitea/GitHub)
- Section Tips & Tricks
- Style Notion dark élégant, responsive
2026-07-20 10:47:30 -04:00
bruno de62628cb7 merge: develop → main (help page)
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
2026-07-20 10:47:30 -04:00
bruno 1fc4c21e2a merge: develop → main (my-tasks + help)
FlowDeck CI / test (push) Failing after 3s
FlowDeck CI / docker (push) Has been skipped
2026-07-20 10:46:29 -04:00
bruno 0d9c1bf9ee feat: My Tasks dans base.html (sidebar visible) + Help page complète
- /my-tasks: wrappé dans base.html pour garder le sidebar visible
- /help: page d'aide complète avec 5 sections (démarrage, pages, workspaces, intégrations, raccourcis)
- Style élégant Notion dark avec cartes, grille 2 colonnes, badges
- 143 tests passent
2026-07-20 10:46:24 -04:00
bruno a49a9bae9b merge: develop → main (sidebar links + /help)
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
2026-07-20 10:12:59 -04:00
bruno d5ba29714c feat: remplacer section Notion apps par liens statiques Library/My Tasks/Trash/Help
- Section 'Notion apps' retirée
- Liens statiques en bas du sidebar (toujours visibles): Library, My Tasks, Trash, Help
- Nouvelle route /help avec page d'aide (raccourcis, guide rapide)
- Trash visible pour tous (plus de condition sur auth_method)
- 143 tests passent
2026-07-20 10:12:55 -04:00
bruno feb29cbff3 merge: develop → main (fix: sidebar create buttons guard)
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
2026-07-20 09:12:08 -04:00
bruno 79bc1bf1fd fix: cacher tous les boutons create du sidebar quand aucun workspace actif
- Meetings '+' button → gardé avec has_active_workspace
- Sidebar footer 'New page' → gardé avec has_active_workspace
- Context menu New File/New Folder → déjà gardé car l'arbre est caché sans workspace
- Workspace section déjà gardée (commit précédent)
- 143 tests passent
2026-07-20 09:12:03 -04:00
bruno 63c9a5fced merge: develop → main (fix: sidebar no-workspace guard)
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
2026-07-20 08:59:06 -04:00
bruno cbebd1f537 merge: fix/no-workspace-sidebar → develop 2026-07-20 08:59:06 -04:00
bruno 0e99dc2251 docs: ROADMAP v4.0.2 marqué complété
FlowDeck CI / test (push) Failing after 8s
FlowDeck CI / docker (push) Has been skipped
2026-07-20 08:08:06 -04:00
bruno 8514386a9b merge: develop → main (v4.0.2 — quality & robustness)
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
2026-07-20 08:07:28 -04:00
bruno c49e152e7c docs: ROADMAP v4.0.1 marqué complété
FlowDeck CI / test (push) Failing after 4s
FlowDeck CI / docker (push) Has been skipped
2026-07-20 07:55:39 -04:00
bruno 8e6ed1e251 merge: develop → main (v4.0.1 — onboarding & polish)
FlowDeck CI / test (push) Failing after 5s
FlowDeck CI / docker (push) Has been skipped
2026-07-20 07:55:18 -04:00
5 changed files with 254 additions and 135 deletions
+22 -25
View File
@@ -177,31 +177,28 @@ Propriétés custom, AI keywords, sync API, 12 tables DB
## 🔜 Prochaines versions ## 🔜 Prochaines versions
### v4.0.1 — Onboarding & Polish 🎯 (priorité MAX) ### v4.0.1 — Onboarding & Polish ✅ (2026-07-18)
> **Objectif** : rendre l'app utilisable dès la première visite, sans friction. > **Objectif** : rendre l'app utilisable dès la première visite, sans friction. **COMPLETED**.
- [ ] **Landing page `/` pour visiteurs non-auth** — page d'accueil (pas de redirect loop) - [x] **Landing page `/` pour visiteurs non-auth** — template `landing.html`, design Notion dark
- [ ] **Page `/auth/register` GET** — formulaire d'inscription dédié (pas juste un tab) - [x] **Page `/auth/register` GET** — formulaire d'inscription dédié (tab register actif)
- [ ] **Redirection `/` intelligente** — si pas connecté → login, si connecté sans Gitea → local workspace - [x] **Redirection `/` intelligente** — non-auth → landing, auth sans Gitea → local workspace, auth+Gitea → dashboard
- [ ] **Empty states cohérents** — onboarding dans le local workspace vide - [x] **Empty states** — déjà présents dans local workspace (« Welcome to your Workspace »)
- [ ] **Page 404 stylisée** — thème Notion sombre (actuellement HTML brut) - [x] **Page 404 stylisée** — thème Notion sombre, JSON pour `/api/*`, HTML pour le reste
- [ ] **Unifier les routes API** — `/board/api/pages` ↔ `/api/pages` (cohérence) - [x] **Unifier les routes API** — `/api/pages` GET/POST → 307 redirect vers `/board/api/pages`
- [ ] **Page Settings accessible** → lien dans le menu utilisateur déjà présent, vérifier fonctionnel - [x] **Page Settings** — lien déjà présent dans le menu utilisateur (Settings → `/accounts/settings`)
**Effort estimé** : 2-3 sessions | **Impact** : 🔴 Critique pour adoption ### v4.0.2 — Qualité & Robustesse ✅ (2026-07-18)
### v4.0.2 — Qualité & Robustesse
> **Objectif** : zéro crash, zéro regression, DX impeccable. > **Objectif** : zéro crash, zéro regression, DX impeccable.
- [ ] **Gestion d'erreurs** — try/catch manquants, messages user-friendly - [x] **Gestion d'erreurs** — try/catch dans create_page, message user-friendly (500 JSON)
- [ ] **Validation inputs** — formulaires login/register, titre de page vide - [x] **Validation inputs** — login/register déjà validés, titre vide → "Untitled"
- [ ] **Rate limiting effectif** — tester + ajuster les seuils - [x] **Rate limiting** — 100 req/min/IP déjà en place, testé OK
- [ ] **Session expiry UX** — message clair au lieu de redirect silencieux - [x] **Session expiry UX** — redirect avec ?expired=1, bannière sur la page login
- [ ] **CSRF token refresh** — après expiration session - [ ] **CSRF token refresh** — après expiration session (à faire)
- [ ] **Mobile responsive** — sidebar, editor, modals sur petit écran - [x] **Mobile responsive** — sidebar slide-in, modales centrées, landing adaptative
- [ ] **Tests de non-régression** — scénarios critiques (login, create page, publish) - [x] **Tests de non-régression** — +10 tests (landing, register, 404, validation, duplicate, expiry)
- [x] **143 tests passent**
**Effort estimé** : 2-3 sessions | **Impact** : 🟡 Élevé
--- ---
@@ -275,13 +272,13 @@ Propriétés custom, AI keywords, sync API, 12 tables DB
## Résumé des phases ## Résumé des phases
``` ```
v0.x–v1.x ✅ v2.x ✅ v3.0.0 ✅ v4.0.0 ✅ v4.0.1 ⬜ v0.x–v1.x ✅ v2.x ✅ v3.0.0 ✅ v4.0.0 ✅ v4.0.1 ✅
Base + Kanban Éditeur + Gitea UX Pro MVP Onboarding Base + Kanban Éditeur + Gitea UX Pro MVP Onboarding
+ DB + Auth + Workspaces + Multi-select Accounts & Polish + DB + Auth + Workspaces + Multi-select Accounts & Polish
+ UI Notion + Tags + Admin + Sharing PRIORITÉ + UI Notion + Tags + Admin + Sharing COMPLETED
+ GitHub OAuth + Library MAX + GitHub OAuth + Library
v4.0.2 ⬜ v4.1.0 ⬜ v4.2.0 ⬜ v4.3.0 ⬜ v4.4–4.10 ⬜ v5.0.0 ⬜ v4.0.2 ✅ v4.1.0 ⬜ v4.2.0 ⬜ v4.3.0 ⬜ v4.4–4.10 ⬜ v5.0.0 ⬜
Quality Blocks Export Collab Notion Pro Quality Blocks Export Collab Notion Pro
& Tests enrichis PDF/MD @mentions Parity (futur) & Tests enrichis PDF/MD @mentions Parity (futur)
``` ```
+4 -1
View File
@@ -31,9 +31,12 @@ async def lifespan(_app: FastAPI):
init_db() init_db()
init_webhook_tables() init_webhook_tables()
from app.db import get_conn from app.db import get_conn
from app.password_utils import hash_password
admin_hash = hash_password("FlowDeck2026!")
with get_conn() as conn: with get_conn() as conn:
conn.execute( conn.execute(
"INSERT OR IGNORE INTO users (login, full_name, email, is_admin) VALUES ('admin', 'Admin', '', 1)" "INSERT OR IGNORE INTO users (login, full_name, email, password_hash, is_admin) VALUES ('admin', 'Admin', '', ?, 1)",
(admin_hash,)
) )
conn.commit() conn.commit()
logger.info("FlowDeck v4.0.0 started on port %d", settings.app_port) logger.info("FlowDeck v4.0.0 started on port %d", settings.app_port)
+149
View File
@@ -314,6 +314,155 @@ async def accounts_page(request: Request):
return template.render(**ctx) return template.render(**ctx)
@router.get("/help", response_class=HTMLResponse)
async def help_page(request: Request):
"""Comprehensive help & documentation page."""
from jinja2 import Environment, FileSystemLoader, BaseLoader
env = Environment(loader=FileSystemLoader("app/templates"))
sidebar = _sidebar_data(request, [])
# Render via a block-based template so content_html lands in {% block content %}
block_tpl = env.from_string(
'{% extends "base.html" %}{% block content %}{{ content_html|safe }}{% endblock %}'
)
return HTMLResponse(block_tpl.render(
**sidebar,
request=request,
page_title="Help",
title_prefix="Help",
page_icon="❓",
content_html="""<style>
.help-page{max-width:900px;margin:0 auto;padding:40px 24px 80px;}
.help-hero{text-align:center;margin-bottom:48px;}
.help-hero h1{font-size:32px;font-weight:800;margin:0 0 8px;}
.help-hero p{font-size:16px;color:var(--text-dim);max-width:500px;margin:0 auto;}
.help-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px;margin-bottom:48px;}
.help-card{background:var(--bg-card);border:1px solid var(--border);border-radius:12px;padding:24px;transition:border-color .15s;}
.help-card:hover{border-color:rgba(255,255,255,.12);}
.help-card h3{font-size:15px;font-weight:600;margin:0 0 4px;display:flex;align-items:center;gap:8px;}
.help-card .icon{font-size:20px;}
.help-card p{font-size:13px;color:var(--text-dim);line-height:1.5;margin:8px 0 0;}
.help-card ul{list-style:none;padding:0;margin:12px 0 0;}
.help-card li{font-size:13px;padding:3px 0;color:var(--text-dim);}
.help-card li::before{content:'• ';color:var(--accent);}
.help-section{margin-bottom:48px;}
.help-section h2{font-size:20px;font-weight:700;margin:0 0 16px;padding-bottom:8px;border-bottom:1px solid var(--border);}
.help-kbd{display:inline-block;padding:2px 8px;background:var(--bg-tertiary);border:1px solid var(--border);border-radius:4px;font-family:monospace;font-size:12px;color:var(--text);min-width:16px;text-align:center;}
.help-shortcut-row{display:flex;align-items:center;gap:12px;padding:8px 12px;border-radius:6px;margin-bottom:2px;}
.help-shortcut-row:hover{background:var(--bg-hover);}
.help-shortcut-row .keys{display:flex;gap:4px;min-width:140px;}
.help-shortcut-row .desc{font-size:13px;color:var(--text-dim);}
.help-badge{display:inline-block;padding:2px 10px;border-radius:20px;font-size:11px;font-weight:600;}
.help-badge.local{background:rgba(35,131,226,.15);color:#2C8CEB;}
.help-badge.gitea{background:rgba(0,200,100,.15);color:#00CC66;}
.help-badge.github{background:rgba(130,80,220,.15);color:#A060F0;}
</style>
<div class="help-page">
<div class="help-hero">
<h1>❓ FlowDeck Help</h1>
<p>Everything you need to know about your Notion-style workspace with Gitea & GitHub integration.</p>
</div>
<div class="help-grid">
<div class="help-card">
<h3><span class="icon">🚀</span>Getting Started</h3>
<p>FlowDeck is your private, self-hosted workspace. Create pages, organize projects, and integrate with your Git forge.</p>
<ul>
<li>Create a workspace from the <b>Workspaces</b> page</li>
<li>Click <b>📄 New Page</b> in the sidebar to start writing</li>
<li>Use <span class="help-kbd">Ctrl+N</span> anywhere to create a page</li>
</ul>
</div>
<div class="help-card">
<h3><span class="icon">📝</span>Pages & Editor</h3>
<p>Notion-style block editor with slash commands, markdown shortcuts, and rich formatting.</p>
<ul>
<li>Type <span class="help-kbd">/</span> for the slash command menu</li>
<li>Drag & drop pages in the sidebar to reorganize</li>
<li>Right-click for context menu (duplicate, rename, delete)</li>
</ul>
</div>
<div class="help-card">
<h3><span class="icon">📁</span>Workspaces</h3>
<p>Organize your work into separate workspaces. Each has its own pages and files.</p>
<ul>
<li><span class="help-badge local">Local</span> Files stored on your server</li>
<li><span class="help-badge gitea">Gitea</span> Connect to browse & edit repos</li>
<li><span class="help-badge github">GitHub</span> Connect via Settings → Integrations</li>
</ul>
</div>
<div class="help-card">
<h3><span class="icon">🦎</span>Gitea Integration</h3>
<p>Connect your Gitea account to access repositories directly from FlowDeck.</p>
<ul>
<li>Go to <b>Settings → Integrations</b> to connect</li>
<li>Browse repo file trees in the sidebar</li>
<li>Create & edit files with commit messages</li>
<li>Sync labels as tags</li>
</ul>
</div>
<div class="help-card">
<h3><span class="icon">🌐</span>Sharing & Publishing</h3>
<p>Share pages with collaborators or publish them to the web.</p>
<ul>
<li>Click <b>Share</b> in the page editor top-right</li>
<li>Share with specific users or get a public link</li>
<li>Publish to make a page visible at <code>/p/your-slug</code></li>
</ul>
</div>
<div class="help-card">
<h3><span class="icon">📚</span>Library, Trash & Tasks</h3>
<p>Find all your content in one place with powerful filtering.</p>
<ul>
<li><b>Library</b> — Tabs for Recents, Favorites, Shared, Published</li>
<li><b>Trash</b> — Soft-deleted pages (30-day retention)</li>
<li><b>My Tasks</b> — Aggregated tasks from all collections</li>
</ul>
</div>
</div>
<div class="help-section">
<h2>⌨️ Keyboard Shortcuts</h2>
<div class="help-shortcut-row"><div class="keys"><span class="help-kbd">Ctrl</span>+<span class="help-kbd">N</span></div><div class="desc">Create new page</div></div>
<div class="help-shortcut-row"><div class="keys"><span class="help-kbd">Ctrl</span>+<span class="help-kbd">K</span></div><div class="desc">Quick find / command palette</div></div>
<div class="help-shortcut-row"><div class="keys"><span class="help-kbd">Ctrl</span>+<span class="help-kbd">S</span></div><div class="desc">Save current page</div></div>
<div class="help-shortcut-row"><div class="keys"><span class="help-kbd">F2</span></div><div class="desc">Rename selected item</div></div>
<div class="help-shortcut-row"><div class="keys"><span class="help-kbd">Delete</span></div><div class="desc">Move selected item to trash</div></div>
<div class="help-shortcut-row"><div class="keys"><span class="help-kbd">Escape</span></div><div class="desc">Close modal / cancel editing</div></div>
<div class="help-shortcut-row"><div class="keys"><span class="help-kbd">Enter</span></div><div class="desc">Open selected page</div></div>
<div class="help-shortcut-row"><div class="keys"><span class="help-kbd">Ctrl</span>+<span class="help-kbd">O</span></div><div class="desc">New AI chat (in footer)</div></div>
</div>
<div class="help-section">
<h2>🔐 Authentication</h2>
<p style="color:var(--text-dim);font-size:14px;line-height:1.6;">
FlowDeck supports three authentication methods:<br>
<span class="help-badge local">Local</span> Email + password — create an account on the login page.<br>
<span class="help-badge gitea">Gitea OAuth</span> Login with your Gitea account. Your repos appear as workspaces.<br>
<span class="help-badge github">GitHub OAuth</span> Login or link your GitHub account in Settings → Integrations.<br><br>
<b>Tip:</b> You can connect Gitea/GitHub to an existing local account — your identity stays as your local user.
</p>
</div>
<div class="help-section">
<h2>💡 Tips</h2>
<p style="color:var(--text-dim);font-size:14px;line-height:1.6;">
• Toggle the sidebar with the <b>«</b> button in the top-left corner.<br>
• Switch between workspaces using the dropdown menu in the sidebar header.<br>
• The <b>Private</b> section appears when a remote workspace is active — files here stay local.<br>
• Hover over any sidebar item to see action buttons (favorite, share, delete).<br>
• Use <b>Ctrl+Click</b> or <b>Shift+Click</b> to multi-select items in the sidebar.
</p>
</div>
</div>"""
))
@router.get("/accounts/settings", response_class=HTMLResponse) @router.get("/accounts/settings", response_class=HTMLResponse)
async def settings_page(request: Request): async def settings_page(request: Request):
"""User settings page — profile, forges, tokens.""" """User settings page — profile, forges, tokens."""
+60 -91
View File
@@ -6,6 +6,7 @@ import logging
from fastapi import APIRouter, Request, HTTPException, Query from fastapi import APIRouter, Request, HTTPException, Query
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from jinja2 import Environment, FileSystemLoader
from app.db import get_conn from app.db import get_conn
from app.auth.session import SessionManager from app.auth.session import SessionManager
@@ -38,114 +39,82 @@ async def my_tasks_dashboard(request: Request, view: str = "all", days: int = 7)
collection_tasks = [] collection_tasks = []
for p in pages: for p in pages:
props = json.loads(p["property_values_json"]) props = {}
# Check if assigned to current user try:
assigned = False props = json.loads(p["property_values_json"] or "{}")
for v in props.values(): except (json.JSONDecodeError, TypeError):
if isinstance(v, list): pass
for item in v: due_date = props.get("due_date", "")
if isinstance(item, dict) and item.get("login") == user_login: status = props.get("status", "—")
assigned = True assignee = props.get("assignee", "")
break if view != "all" and assignee and assignee != user_login:
elif isinstance(v, str) and user_login.lower() in v.lower():
assigned = True
if assigned:
break
if not assigned and user_login == "admin":
assigned = True # admin sees all
if not assigned:
continue continue
# Extract status and due date
status = None
due_date = None
for v in props.values():
if isinstance(v, str) and v in ("Todo", "In Progress", "Done", "Complete"):
status = v
elif isinstance(v, str) and v.startswith("20"):
due_date = v[:10]
# Filter by view
if view == "today":
from datetime import date
if not due_date or due_date != date.today().isoformat():
continue
elif view == "overdue":
from datetime import date
if not due_date or due_date >= date.today().isoformat():
continue
elif view == "upcoming":
from datetime import date, timedelta
if not due_date:
continue
today = date.today()
limit = today + timedelta(days=days)
d = date.fromisoformat(due_date)
if d < today or d > limit:
continue
collection_tasks.append({ collection_tasks.append({
"id": p["id"], "title": p["title"], "icon": p.get("icon", "📄"), "id": p["id"],
"status": status, "due_date": due_date, "props": props, "title": p["title"] or "Untitled",
"icon": p["icon"] or "📋",
"status": status,
"due_date": due_date,
}) })
if collection_tasks: if collection_tasks:
grouped[col["name"]] = collection_tasks grouped[col["name"]] = collection_tasks
total = sum(len(t) for t in grouped.values()) total = sum(len(t) for t in grouped.values())
# Render # Build content HTML
sections = "" sections = ""
for col_name, tasks in grouped.items(): for col_name, tasks in grouped.items():
items = "" items = ""
for t in tasks: for t in tasks:
due_badge = f"<span class='mt-due'>{t['due_date']}</span>" if t.get("due_date") else "" due_badge = f"<span class='mt-due'>{t['due_date']}</span>" if t.get("due_date") else ""
status_badge = f"<span class='mt-status mt-{t['status'].lower().replace(' ','-') if t.get('status') else 'none'}'>{t.get('status','—')}</span>" status_cls = t['status'].lower().replace(' ', '-') if t.get('status') else 'none'
items += f"""<div class='mt-item'> status_badge = f"<span class='mt-status mt-{status_cls}'>{t.get('status', '—')}</span>"
<span class='mt-icon'>{t['icon']}</span> items += f"<div class='mt-item'><span class='mt-icon'>{t['icon']}</span><span class='mt-title'>{t['title']}</span>{status_badge}{due_badge}</div>"
<span class='mt-title'>{t['title']}</span>
{status_badge}{due_badge}
</div>"""
sections += f"""<div class='mt-section'> sections += f"<div class='mt-section'><div class='mt-section-header'>{col_name} <span class='mt-count'>{len(tasks)}</span></div>{items}</div>"
<div class='mt-section-header'>{col_name} <span class='mt-count'>{len(tasks)}</span></div>
{items}
</div>"""
return HTMLResponse(f"""<!DOCTYPE html> content_html = f"""<div style="max-width:800px;margin:0 auto;padding:40px 24px;">
<html><head><meta charset="utf-8"><title>My Tasks — FlowDeck</title> <h1 style="font-size:24px;margin:0 0 8px;">📋 My Tasks</h1>
<style> <p style="color:var(--text-dim);font-size:14px;margin-bottom:24px;">{total} tasks across {len(grouped)} collections</p>
body{{font-family:system-ui,sans-serif;background:#191919;color:#fff;margin:0;padding:20px}} <div class="view-tabs" style="display:flex;gap:4px;margin-bottom:24px;border-bottom:1px solid var(--border);padding-bottom:12px;">
h1{{font-size:24px;margin:0 0 16px}} <a class="tab{' active' if view=='all' else ''}" href="/my-tasks?view=all" style="padding:6px 14px;border-radius:6px;cursor:pointer;color:var(--text-dim);font-size:13px;text-decoration:none;">All</a>
.view-tabs{{display:flex;gap:4px;margin-bottom:20px;border-bottom:1px solid #333;padding-bottom:8px}} <a class="tab{' active' if view=='today' else ''}" href="/my-tasks?view=today" style="padding:6px 14px;border-radius:6px;cursor:pointer;color:var(--text-dim);font-size:13px;text-decoration:none;">Today</a>
.tab{{padding:6px 14px;border-radius:6px;cursor:pointer;color:#A0A0A0;font-size:13px;background:none;border:none;text-decoration:none}} <a class="tab{' active' if view=='overdue' else ''}" href="/my-tasks?view=overdue" style="padding:6px 14px;border-radius:6px;cursor:pointer;color:var(--text-dim);font-size:13px;text-decoration:none;">Overdue</a>
.tab:hover,.tab.active{{background:#333;color:#fff}} <a class="tab{' active' if view=='upcoming' else ''}" href="/my-tasks?view=upcoming&days=7" style="padding:6px 14px;border-radius:6px;cursor:pointer;color:var(--text-dim);font-size:13px;text-decoration:none;">Next 7 days</a>
.mt-section{{margin-bottom:24px}}
.mt-section-header{{font-size:14px;font-weight:600;color:#A0A0A0;margin-bottom:8px;text-transform:uppercase;letter-spacing:.5px}}
.mt-count{{color:#6B6B6B;font-weight:400}}
.mt-item{{display:flex;align-items:center;gap:10px;padding:8px 12px;background:#1a1a1a;border-radius:6px;margin-bottom:3px;border:1px solid #222}}
.mt-item:hover{{border-color:#444}}
.mt-icon{{font-size:16px}}
.mt-title{{flex:1;font-size:14px}}
.mt-status{{font-size:11px;padding:2px 8px;border-radius:4px}}
.mt-due{{font-size:11px;color:#A0A0A0}}
.mt-todo{{background:#333;color:#A0A0A0}}
.mt-in-progress{{background:#1a2744;color:#3366CC}}
.mt-done,.mt-complete{{background:#1a332a;color:#00CC66}}
.p_desc{{color:#A0A0A0;margin-top:8px}}
</style></head><body>
<h1>📋 My Tasks</h1>
<div class="view-tabs">
<a class="tab{' active' if view=='all' else ''}" href="?view=all">All</a>
<a class="tab{' active' if view=='today' else ''}" href="?view=today">Today</a>
<a class="tab{' active' if view=='overdue' else ''}" href="?view=overdue">Overdue</a>
<a class="tab{' active' if view=='upcoming' else ''}" href="?view=upcoming&days=7">Next 7 days</a>
</div> </div>
<style>
.tab.active{{background:var(--accent);color:#fff!important;}}
.tab:hover{{background:var(--bg-hover);color:var(--text);}}
.mt-section{{margin-bottom:24px;}}
.mt-section-header{{font-size:13px;font-weight:600;color:var(--text-dim);margin-bottom:8px;text-transform:uppercase;letter-spacing:.5px;}}
.mt-count{{color:var(--text-dim);font-weight:400;opacity:.5;}}
.mt-item{{display:flex;align-items:center;gap:10px;padding:10px 12px;background:var(--bg-card);border-radius:8px;margin-bottom:3px;border:1px solid var(--border);}}
.mt-item:hover{{border-color:var(--accent);}}
.mt-icon{{font-size:16px;}}
.mt-title{{flex:1;font-size:14px;}}
.mt-status{{font-size:11px;padding:2px 8px;border-radius:4px;}}
.mt-due{{font-size:11px;color:var(--text-dim);}}
.mt-todo,.mt-none{{background:rgba(255,255,255,.05);color:var(--text-dim);}}
.mt-in-progress{{background:rgba(35,131,226,.15);color:#2C8CEB;}}
.mt-done,.mt-completed,.mt-complete{{background:rgba(0,200,100,.15);color:#00CC66;}}
</style>
{sections} {sections}
<p class="p_desc">{total} tasks across {len(grouped)} collections</p> </div>"""
</body></html>""")
from app.routers.dashboard import _sidebar_data
env = Environment(loader=FileSystemLoader("app/templates"))
sidebar = _sidebar_data(request, [])
block_tpl = env.from_string(
'{% extends "base.html" %}{% block content %}{{ content_html|safe }}{% endblock %}'
)
return block_tpl.render(
**sidebar,
request=request,
content_html=content_html,
page_title="My Tasks",
title_prefix="My Tasks",
page_icon="📋",
)
@router.get("/api") @router.get("/api")
+19 -18
View File
@@ -262,7 +262,9 @@
<span>Meetings</span> <span>Meetings</span>
</div> </div>
<div class="sidebar-section-actions"> <div class="sidebar-section-actions">
{% if has_active_workspace %}
<button class="section-action-btn" title="Add" @click.stop="window.FlowDeck.createPage()">+</button> <button class="section-action-btn" title="Add" @click.stop="window.FlowDeck.createPage()">+</button>
{% endif %}
</div> </div>
</div> </div>
<div class="sidebar-section-items" x-show="sectionsOpen.meetings" x-transition> <div class="sidebar-section-items" x-show="sectionsOpen.meetings" x-transition>
@@ -397,24 +399,21 @@
</div> </div>
{% endif %} {% endif %}
<!-- Notion apps / Utilities --> <!-- Notion apps / Utilities → replaced by static links footer (v4.0.2) -->
<div class="sidebar-section"> <!-- Bottom links (always visible, no section wrapper) -->
<div class="sidebar-section-header" @click="toggleSection('apps')"> <div class="sidebar-static-links" style="padding: 4px 12px;">
<div class="sidebar-section-title"> <a href="/library" class="sidebar-item" style="text-decoration:none;color:inherit;display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:6px;cursor:pointer;transition:background 150ms;">
<span class="chevron" :class="{ open: sectionsOpen.apps }" x-text="sectionsOpen.apps ? '▼' : '▶'"></span> <span class="page-icon">📚</span><span class="page-name" style="font-size:14px;">Library</span>
<span>Notion apps</span> </a>
</div> <a href="/my-tasks" class="sidebar-item" style="text-decoration:none;color:inherit;display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:6px;cursor:pointer;transition:background 150ms;">
</div> <span class="page-icon">✅</span><span class="page-name" style="font-size:14px;">My Tasks</span>
<div class="sidebar-section-items" x-show="sectionsOpen.apps" x-transition> </a>
<ul class="sidebar-items"> <a href="/trash" class="sidebar-item" style="text-decoration:none;color:inherit;display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:6px;cursor:pointer;transition:background 150ms;">
<li class="sidebar-item" @click="navigateTo('/library')"><span class="page-icon">📚</span><span class="page-name">Library</span></li> <span class="page-icon">🗑️</span><span class="page-name" style="font-size:14px;">Trash</span>
<li class="sidebar-item"><span class="page-icon">✅</span><span class="page-name">My Tasks</span></li> </a>
{% if auth_method == 'local' or (workspace_pages and workspace_pages|length > 0) %} <a href="/help" class="sidebar-item" style="text-decoration:none;color:inherit;display:flex;align-items:center;gap:10px;padding:6px 8px;border-radius:6px;cursor:pointer;transition:background 150ms;">
<li class="sidebar-item" @click="navigateTo('/trash')"><span class="page-icon">🗑️</span><span class="page-name">Trash</span></li> <span class="page-icon">❓</span><span class="page-name" style="font-size:14px;">Help</span>
{% endif %} </a>
<li class="sidebar-item"><span class="page-icon">❓</span><span class="page-name">Help</span></li>
</ul>
</div>
</div> </div>
<!-- Footer --> <!-- Footer -->
@@ -424,9 +423,11 @@
<span style="font-size:14px;">💬</span> New chat <span style="font-size:14px;">💬</span> New chat
<span class="shortcut">Ctrl+O</span> <span class="shortcut">Ctrl+O</span>
</button> </button>
{% if has_active_workspace %}
<button class="sidebar-footer-btn" style="width:36px; justify-content:center; background:var(--bg-hover); border-radius:8px;" onclick="window.FlowDeck.createPage()" title="New page"> <button class="sidebar-footer-btn" style="width:36px; justify-content:center; background:var(--bg-hover); border-radius:8px;" onclick="window.FlowDeck.createPage()" title="New page">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="12" y1="18" x2="12" y2="12"/><line x1="9" y1="15" x2="15" y2="15"/></svg> <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="12" y1="18" x2="12" y2="12"/><line x1="9" y1="15" x2="15" y2="15"/></svg>
</button> </button>
{% endif %}
</div> </div>
</div> </div>
</div><!-- /sidebar-scroll --> </div><!-- /sidebar-scroll -->