From 881c3e3e0a8378344c8aa7bfd111e107f51e3887 Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Fri, 11 Sep 2026 22:33:37 -0400 Subject: [PATCH] feat(library): tags + tag filtering, monochrome icons; fix live tag updates - library: expose page tags (batch), render tag chips per row and add a tag filter bar; register tag create/associate/remove in the shared context menu exactly like local-workspace - library: restore monochrome SVG icons (folder/edit/image/file) matching local-workspace, with optional custom page_icon - workspace: refresh tag chips/filter live on tag add/remove (reassign arrays + tagsVersion) instead of requiring a page reload - ctxmenu: measure the rendered menu and clamp it to the viewport on open --- app/routers/library.py | 33 ++++++- app/templates/_ctx_menu.html | 22 ++++- app/templates/library.html | 148 +++++++++++++++++++++++++---- app/templates/local_workspace.html | 27 ++++-- 4 files changed, 198 insertions(+), 32 deletions(-) diff --git a/app/routers/library.py b/app/routers/library.py index 1be596c..1313f07 100644 --- a/app/routers/library.py +++ b/app/routers/library.py @@ -69,6 +69,7 @@ def _build_item(db_row: dict, uid: int = 1) -> dict: "id": page_id, "title": title, "icon": icon, + "page_icon": db_row.get("page_icon") or "", "is_folder": bool(db_row.get("is_folder", 0)), "source_type": source_type, "source_label": _source_label(source_type, workspace), @@ -84,6 +85,7 @@ def _build_item(db_row: dict, uid: int = 1) -> dict: "content_format": content_format, "favorited": bool(db_row.get("favorited", 0)), "share_mode": db_row.get("share_mode", "private"), + "tags": [], } @@ -98,7 +100,30 @@ def _apply_source_filter(query: str, params: list, source_type: str) -> tuple[st def _rows_to_items(rows, uid: int = 1) -> list: - return [_build_item(dict(r), uid) for r in rows] + items = [_build_item(dict(r), uid) for r in rows] + return _attach_tags(items) + + +def _attach_tags(items: list) -> list: + """Batch-load page tags for library items.""" + if not items: + return items + ids = [it["id"] for it in items] + placeholders = ",".join("?" for _ in ids) + with get_conn() as conn: + rows = conn.execute( + f"SELECT pt.page_id, t.id, t.name, t.color FROM page_tags pt " + f"JOIN tags t ON t.id = pt.tag_id WHERE pt.page_id IN ({placeholders})", + ids, + ).fetchall() + tag_map: dict = {} + for r in rows: + tag_map.setdefault(r["page_id"], []).append({ + "id": r["id"], "name": r["name"], "color": r["color"], + }) + for it in items: + it["tags"] = tag_map.get(it["id"], []) + return items def _enrich_children(items: list) -> list: @@ -124,7 +149,7 @@ def _enrich_children(items: list) -> list: BASE_SELECT = ( "SELECT p.id, p.title, p.workspace, p.updated_at, p.content_format, " - "p.parent_id, p.share_mode, p.parent_section" + "p.parent_id, p.share_mode, p.parent_section, p.page_icon" ) @@ -361,6 +386,8 @@ async def library_local_workspace_children(item_id: int, request: Request): "url": f"/local-workspace?folder={r['id']}" if is_folder else f"/pages/{r['id']}", "content_format": r["content_format"] or "file", "favorited": False, + "page_icon": "", + "tags": [], "size_display": _format_size(r["size"]) if r["size"] else "", }) @@ -469,6 +496,8 @@ async def library_local_workspace( "url": f"/local-workspace?folder={r['id']}" if is_folder else f"/pages/{r['id']}", "content_format": r["content_format"] or "file", "favorited": False, + "page_icon": "", + "tags": [], "size_display": _format_size(r["size"]) if r["size"] else "", }) diff --git a/app/templates/_ctx_menu.html b/app/templates/_ctx_menu.html index d5fe7a9..d66cfcd 100644 --- a/app/templates/_ctx_menu.html +++ b/app/templates/_ctx_menu.html @@ -54,9 +54,27 @@ this.tagExistingOpen = false; this.iconOpen = false; this.newTagColor = (node && node.tags && node.tags[0] && node.tags[0].color) || '#787774'; - this.x = Math.max(4, Math.min((ev && ev.clientX) || 0, window.innerWidth - 240)); - this.y = Math.max(4, Math.min((ev && ev.clientY) || 0, window.innerHeight - 48)); + var cx = (ev && ev.clientX) || 0; + var cy = (ev && ev.clientY) || 0; + this.x = Math.max(8, Math.min(cx, window.innerWidth - 240)); + this.y = Math.max(8, Math.min(cy, window.innerHeight - 48)); this.open = true; + var self = this; + /* Mesure la taille réelle du menu (une fois rendu) puis le replace + pour qu'il reste TOUJOURS entièrement visible dans le viewport. */ + var place = function () { + var el = document.querySelector('.fd-ctx-menu'); + var w = el ? el.offsetWidth : 240; + var h = el ? el.offsetHeight : 320; + var vw = window.innerWidth, vh = window.innerHeight; + var nx = cx, ny = cy; + if (nx + w > vw - 8) nx = vw - w - 8; + if (ny + h > vh - 8) ny = vh - h - 8; + self.x = Math.max(8, nx); + self.y = Math.max(8, ny); + }; + if (window.Alpine && window.Alpine.nextTick) window.Alpine.nextTick(place); + else setTimeout(place, 0); }, close() { this.open = false; diff --git a/app/templates/library.html b/app/templates/library.html index e8cba14..3628d14 100644 --- a/app/templates/library.html +++ b/app/templates/library.html @@ -88,6 +88,13 @@ .row-title:hover{text-decoration:underline;} .row-title input{width:100%;font-size:13px;padding:2px 4px;background:var(--bg-primary);border:1px solid var(--accent);border-radius:4px;color:var(--text-primary);outline:none;} +/* ── Tag chips (représentation + filtre) ── */ +.row-tags{display:flex;align-items:center;gap:3px;flex-shrink:0;overflow:hidden;max-width:45%;} +.tag-chip{display:inline-flex;align-items:center;gap:3px;font-size:10px;line-height:1;padding:2px 6px;border-radius:999px;color:#fff;white-space:nowrap;border:none;cursor:pointer;font-family:inherit;} +.tag-chip.active{outline:2px solid var(--text-primary);outline-offset:1px;} +.lib-tag-filter{display:flex;align-items:center;gap:6px;flex-wrap:wrap;padding:4px 16px 2px;} +.lib-tag-filter .tf-label{font-size:11px;color:var(--text-dim);margin-right:2px;} + /* ── Shared direction sub-filter pils ── */ .lib-share-directory{display:flex;align-items:center;gap:6px;padding:4px 16px;} .lib-dir-pill{font-size:12px;padding:3px 10px;border-radius:999px;border:1px solid var(--border);background:transparent;color:var(--text-secondary);cursor:pointer;transition:background 0.15s,color 0.15s,border-color 0.15s;} @@ -271,6 +278,20 @@ + +
+ Tags: + + +
+ @@ -393,7 +414,7 @@
- +