From 3b27c572302ff0172ca0b067cbc450380be12997 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Tue, 8 Sep 2026 13:57:35 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20badge=20emoji=20partag=C3=A9=20(noir=20?= =?UTF-8?q?&=20blanc)=20dans=20l'arborescence=20+=20sidebar=20=C2=ABPar=20?= =?UTF-8?q?moi=C2=BB=20inclut=20is=5Fshared?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - board/dashboard: helper _load_shared_sidebar_pages intégrant les pages marquées is_shared directement - _workspace_tree_macro: badge 👥 N&B sur les fichiers partagés du tree du sidebar - local_workspace: badge 👥 en noir & blanc - static/css/app.css: style .page-shared-badge --- app/routers/board.py | 117 +++++++++++++---------- app/routers/dashboard.py | 17 +++- app/templates/_workspace_tree_macro.html | 1 + app/templates/local_workspace.html | 17 +++- static/css/app.css | 9 ++ 5 files changed, 103 insertions(+), 58 deletions(-) diff --git a/app/routers/board.py b/app/routers/board.py index f2c58a6..c761313 100644 --- a/app/routers/board.py +++ b/app/routers/board.py @@ -175,7 +175,9 @@ def _load_workspace_pages(ws_cookie: str) -> list: ws_id = int(ws_cookie) with get_conn() as conn: rows = conn.execute( - "SELECT id, title, parent_section, content_format FROM pages WHERE workspace_id=? AND parent_id IS NULL AND deleted_at IS NULL ORDER BY created_at DESC", + "SELECT id, title, parent_section, content_format, " + "is_shared, share_mode, COALESCE(published,0) AS published " + "FROM pages WHERE workspace_id=? AND parent_id IS NULL AND deleted_at IS NULL ORDER BY created_at DESC", (ws_id,), ).fetchall() items = [] @@ -183,11 +185,13 @@ def _load_workspace_pages(ws_cookie: str) -> list: is_folder = r["parent_section"] == "Workspace" title = r["title"] or "Untitled" sub_children = _load_children(r["id"]) + is_shared = bool(r["is_shared"] or r["share_mode"] != "private" or r["published"]) items.append({ "db_id": r["id"], "name": title, "id": f"page/{r['id']}", "icon": "📁" if is_folder else _file_icon(title, r["content_format"]), "is_folder": is_folder, + "is_shared": is_shared, "child_count": len(sub_children), "children": sub_children, }) @@ -200,7 +204,9 @@ def _load_children(parent_id: int) -> list: """Recursively load children of a page.""" with get_conn() as conn: rows = conn.execute( - "SELECT id, title, parent_section, content_format FROM pages WHERE parent_id=? AND deleted_at IS NULL ORDER BY created_at", + "SELECT id, title, parent_section, content_format, " + "is_shared, share_mode, COALESCE(published,0) AS published " + "FROM pages WHERE parent_id=? AND deleted_at IS NULL ORDER BY created_at", (parent_id,), ).fetchall() children = [] @@ -208,11 +214,13 @@ def _load_children(parent_id: int) -> list: is_folder = r["parent_section"] == "Workspace" title = r["title"] or "Untitled" sub_children = _load_children(r["id"]) + is_shared = bool(r["is_shared"] or r["share_mode"] != "private" or r["published"]) children.append({ "db_id": r["id"], "name": title, "id": f"page/{r['id']}", "icon": "📁" if is_folder else _file_icon(title, r["content_format"]), "is_folder": is_folder, + "is_shared": is_shared, "child_count": len(sub_children), "children": sub_children, }) @@ -235,6 +243,61 @@ def _local_workspaces_for_user(user: dict | None, workspace_id: int = 0) -> list return [] +def _load_shared_sidebar_pages(user_id: int) -> tuple[list, list, list, list]: + """Shared / received / published pages for the sidebar (reused by dashboard).""" + with get_conn() as conn: + made_nominal = conn.execute( + "SELECT DISTINCT p.id, p.title, p.workspace, p.updated_at FROM page_shares s " + "JOIN pages p ON p.id=s.page_id " + "WHERE s.created_by=? AND p.deleted_at IS NULL", + (user_id,), + ).fetchall() + made_link = conn.execute( + "SELECT id, title, workspace, updated_at FROM pages " + "WHERE share_mode='anyone' AND published=0 AND deleted_at IS NULL" + ).fetchall() + made_flag = conn.execute( + "SELECT id, title, workspace, updated_at FROM pages " + "WHERE (is_shared=1 OR share_mode != 'private') AND COALESCE(published,0)=0 AND deleted_at IS NULL" + ).fetchall() + published_rows = conn.execute( + "SELECT id, title, workspace, updated_at FROM pages " + "WHERE published=1 AND deleted_at IS NULL ORDER BY updated_at DESC LIMIT 20" + ).fetchall() + received_rows = conn.execute( + "SELECT DISTINCT p.id, p.title, p.workspace, p.updated_at FROM page_shares s " + "JOIN pages p ON p.id=s.page_id " + "WHERE s.shared_with_user_id=? AND p.deleted_at IS NULL", + (user_id,), + ).fetchall() + + def _entry(r, icon): + return { + "id": f"page/{r['id']}", + "db_id": r["id"], + "name": r["title"] or "New page", + "icon": icon, + "url": f"/pages/{r['id']}", + "active": False, + "indent": 0, + "depth": 0, + "has_children": False, + "children": [], + } + + made_map = {} + for r in (*made_nominal, *made_link, *made_flag): + made_map.setdefault(r["id"], r) + made_sorted = sorted(made_map.values(), key=lambda r: r["updated_at"] or "", reverse=True)[:20] + shared_made = [_entry(r, "link") for r in made_sorted] + received_sorted = [r for r in received_rows if r["id"] not in made_map] + received_sorted = sorted(received_sorted, key=lambda r: r["updated_at"] or "", reverse=True)[:20] + shared_received = [_entry(r, "users") for r in received_sorted] + published = [_entry(r, "globe") for r in published_rows] + shared_all = [_entry(r, "link") for r in made_sorted] + return shared_made, shared_received, published, shared_all + + def _sidebar_data(request: Request, owner: str = "", repo: str = "") -> dict: user = SessionManager.decode_session(request.cookies.get("flowdeck_session", "")) ws_name = user.get("login", "Bruno") if user else "Bruno" @@ -338,54 +401,8 @@ def _sidebar_data(request: Request, owner: str = "", repo: str = "") -> dict: "children": [], }) - # Load shared pages: made (page_shares created by me + link-mode pages) and - # received (page_shares toward me), plus published pages. - with get_conn() as conn: - made_nominal = conn.execute( - "SELECT DISTINCT p.id, p.title, p.workspace, p.updated_at FROM page_shares s " - "JOIN pages p ON p.id=s.page_id " - "WHERE s.created_by=? AND p.deleted_at IS NULL", - (uid,), - ).fetchall() - made_link = conn.execute( - "SELECT id, title, workspace, updated_at FROM pages " - "WHERE share_mode='anyone' AND published=0 AND deleted_at IS NULL" - ).fetchall() - published_rows = conn.execute( - "SELECT id, title, workspace, updated_at FROM pages " - "WHERE published=1 AND deleted_at IS NULL ORDER BY updated_at DESC LIMIT 20" - ).fetchall() - received_rows = conn.execute( - "SELECT DISTINCT p.id, p.title, p.workspace, p.updated_at FROM page_shares s " - "JOIN pages p ON p.id=s.page_id " - "WHERE s.shared_with_user_id=? AND p.deleted_at IS NULL", - (uid,), - ).fetchall() - - def _shared_entry(r, icon): - return { - "id": f"page/{r['id']}", - "db_id": r["id"], - "name": r["title"] or "New page", - "icon": icon, - "url": f"/pages/{r['id']}", - "active": False, - "indent": 0, - "depth": 0, - "has_children": False, - "children": [], - } - - made_map = {} - for r in (*made_nominal, *made_link): - made_map.setdefault(r["id"], r) - made_sorted = sorted(made_map.values(), key=lambda r: r["updated_at"] or "", reverse=True)[:20] - shared_made_pages = [_shared_entry(r, "link") for r in made_sorted] - received_sorted = [r for r in received_rows if r["id"] not in made_map] - received_sorted = sorted(received_sorted, key=lambda r: r["updated_at"] or "", reverse=True)[:20] - shared_received_pages = [_shared_entry(r, "users") for r in received_sorted] - published_pages = [_shared_entry(r, "globe") for r in published_rows] - shared_pages = [_shared_entry(r, "link") for r in made_sorted] + # Load shared pages + shared_made_pages, shared_received_pages, published_pages, shared_pages = _load_shared_sidebar_pages(uid) # Auth method & OAuth badge data auth_method = "local" diff --git a/app/routers/dashboard.py b/app/routers/dashboard.py index e121db1..1fa0ca9 100644 --- a/app/routers/dashboard.py +++ b/app/routers/dashboard.py @@ -208,6 +208,15 @@ def _sidebar_data(request: Request, repos: list[dict], include_workspace: bool = except Exception: pass + # Shared / received / published pages for the sidebar (Par moi / Avec moi) + shared_made_pages = [] + shared_received_pages = [] + published_pages = [] + shared_pages = [] + if user and user.get("id"): + from app.routers.board import _load_shared_sidebar_pages + shared_made_pages, shared_received_pages, published_pages, shared_pages = _load_shared_sidebar_pages(user["id"]) + return { "workspace_name": ws, "workspace_initial": initial, "active_ws_name": active_ws_name, @@ -221,10 +230,10 @@ def _sidebar_data(request: Request, repos: list[dict], include_workspace: bool = "recent_pages": recent_pages, "private_pages": private_pages, "favorite_pages": [], - "shared_pages": [], - "shared_made_pages": [], - "shared_received_pages": [], - "published_pages": [], + "shared_pages": shared_pages, + "shared_made_pages": shared_made_pages, + "shared_received_pages": shared_received_pages, + "published_pages": published_pages, "user": user, "avatar_url": avatar_url, "avatar_color": avatar_color, diff --git a/app/templates/_workspace_tree_macro.html b/app/templates/_workspace_tree_macro.html index 9722809..9cc88df 100644 --- a/app/templates/_workspace_tree_macro.html +++ b/app/templates/_workspace_tree_macro.html @@ -27,6 +27,7 @@ {% endif %} {% set valid_icons = ["folder","file","star","link","trash","book","home","settings","lock","globe","image","edit","calendar","users","user","search","tag","bar-chart","grid","list","align-left","zap"] %}{% if page.icon in valid_icons %}{{ fd_icon(page.icon,14) }}{% elif page.is_folder %}{{ fd_icon("folder",14) }}{% else %}{{ fd_icon("file",14) }}{% endif %} + {% if page.is_shared %}👥{% endif %} {% if page.is_folder %} + @@ -2550,6 +2551,7 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind @mouseenter="showPreview($event, node)" @mouseleave="hidePreview()" title="Click to rename, double-click to open"> + + @@ -2684,7 +2687,8 @@ console.log('FLOWDECK v2.4.6 — IIFE done, _wsInitData keys:', Object.keys(wind