feat: complete favorites system — sidebar, library, context menu, API
6 files changed:
- db.py: migrate favorites table FK from collection_pages(id) to pages(id)
- board.py: add favorites API (POST/DELETE /board/api/favorites/{id}, GET list)
- board.py: _sidebar_data() now loads favorite_pages from DB via JOIN
- dashboard.py: library_page loads lib_favorites from DB (not parent_section)
- csrf.py: exclude /board/api/favorites from CSRF checks
- base.html: context menu toggles Add/Remove Favorites based on state
- base.html: favoriteIds Alpine set initialized from server-rendered favorites
- test_app.py: test_favorites_crud rewritten for new page-based favorites API
Favorites now work end-to-end:
- Right-click → Add to Favorites (or Remove if already favorited)
- Sidebar Favorites section shows favorited pages
- Library Favorites tab shows the same pages
- API: POST/DELETE /board/api/favorites/{page_id}, GET /board/api/favorites
This commit is contained in:
@@ -109,13 +109,36 @@ async def library_page(request: Request, owner: str = Query(default=""), repo: s
|
||||
|
||||
all_pages = [to_page(r) for r in rows]
|
||||
|
||||
# Load favorites from DB for Library
|
||||
user = SessionManager.decode_session(request.cookies.get("flowdeck_session", ""))
|
||||
uid = user["id"] if user and user.get("id") else 1
|
||||
with get_conn() as conn:
|
||||
fav_rows = conn.execute(
|
||||
"SELECT p.id, p.title, p.workspace, p.updated_at FROM favorites f "
|
||||
"JOIN pages p ON p.id = f.page_id "
|
||||
"WHERE f.user_id=? ORDER BY f.position", (uid,)
|
||||
).fetchall()
|
||||
lib_favs = []
|
||||
for r in fav_rows:
|
||||
r = dict(r)
|
||||
lib_favs.append({
|
||||
"id": f"page/{r['id']}",
|
||||
"name": r["title"] or "Untitled",
|
||||
"icon": "📄",
|
||||
"url": f"/pages/{r['id']}",
|
||||
"created_by": "You",
|
||||
"source": r.get("workspace") or "🔒 Private",
|
||||
"last_edited": r.get("updated_at", "now"),
|
||||
"last_visited": r.get("updated_at", "now"),
|
||||
})
|
||||
|
||||
# Catégoriser par parent_section — sidebar data intacte
|
||||
sidebar["lib_recent"] = all_pages
|
||||
sidebar["lib_favorites"] = [p for p, r in zip(all_pages, rows) if (r["parent_section"] or "") == "Favorites"]
|
||||
sidebar["lib_shared"] = [p for p, r in zip(all_pages, rows) if (r["parent_section"] or "") == "Shared"]
|
||||
sidebar["lib_private"] = [p for p, r in zip(all_pages, rows) if (r["parent_section"] or "Private") == "Private"]
|
||||
sidebar["lib_has_favorites"] = len(sidebar["lib_favorites"]) > 0
|
||||
sidebar["lib_has_shared"] = len(sidebar["lib_shared"]) > 0
|
||||
sidebar["lib_favorites"] = lib_favs
|
||||
sidebar["lib_shared"] = []
|
||||
sidebar["lib_private"] = [p for p in all_pages if p.get("source") == "🔒 Private"]
|
||||
sidebar["lib_has_favorites"] = len(lib_favs) > 0
|
||||
sidebar["lib_has_shared"] = False
|
||||
|
||||
template = env.get_template("library.html")
|
||||
return template.render(**sidebar)
|
||||
|
||||
Reference in New Issue
Block a user