fix(ctxmenu): repair library rendering and complete unified row menu

- library.html: remove leftover syntax error + orphan </template></div> that
  broke libraryPage() (page showed 'undefined' and no files), call the correct
  fdCtx.openMenu and use self instead of a throwaway libraryPage() instance
- _ctx_menu.html: full conventional menu with shortcuts, tags + colour picker,
  edit-icon picker; closes on outside click and Escape
- local_workspace.html: migrate onContextMenu to the shared fdCtx store with
  complete handlers; drop legacy window._ctxMenuData DOM hacks
- dashboard.py: expose page_icon + favorited in the workspace tree
This commit is contained in:
2026-09-11 22:00:54 -04:00
parent fbc8d657e7
commit c36082be6a
4 changed files with 359 additions and 253 deletions
+16 -6
View File
@@ -992,16 +992,17 @@ async def local_workspace_tree(request: Request, folder: int = None):
ws_id = ws["id"] if ws else None
if not ws_id:
return {"tree": [], "breadcrumb": []}
uid = _get_user_id(request)
with get_conn() as conn:
if folder:
# Show only this folder's children + build breadcrumb
children = _build_tree_children(conn, folder, ws_id)
children = _build_tree_children(conn, folder, ws_id, uid)
breadcrumb = _build_breadcrumb(conn, folder)
return {"tree": children, "breadcrumb": breadcrumb, "current_folder": folder}
else:
# Full tree from root
roots = _build_tree_children(conn, None, ws_id)
roots = _build_tree_children(conn, None, ws_id, uid)
return {"tree": roots, "breadcrumb": [], "current_folder": None}
@@ -1028,11 +1029,11 @@ async def local_workspace_breadcrumb(request: Request, folder: int):
return {"breadcrumb": breadcrumb}
def _build_tree_children(conn, parent_id: int | None, ws_id: int) -> list:
def _build_tree_children(conn, parent_id: int | None, ws_id: int, uid: int | None = None) -> list:
"""Recursively build the tree of children for a node."""
if parent_id is None:
rows = conn.execute(
"SELECT id, title, parent_section, content_format, content, "
"SELECT id, title, parent_section, content_format, content, page_icon, "
"(is_shared OR share_mode != 'private' OR COALESCE(published,0)) as is_shared, "
"created_at, updated_at FROM pages "
"WHERE parent_id IS NULL AND workspace_id=? AND deleted_at IS NULL "
@@ -1041,7 +1042,7 @@ def _build_tree_children(conn, parent_id: int | None, ws_id: int) -> list:
).fetchall()
else:
rows = conn.execute(
"SELECT id, title, parent_section, content_format, content, "
"SELECT id, title, parent_section, content_format, content, page_icon, "
"(is_shared OR share_mode != 'private' OR COALESCE(published,0)) as is_shared, "
"created_at, updated_at FROM pages "
"WHERE parent_id=? AND workspace_id=? AND deleted_at IS NULL "
@@ -1059,6 +1060,7 @@ def _build_tree_children(conn, parent_id: int | None, ws_id: int) -> list:
# Collect all page IDs to fetch tags in one query
all_ids = [r["id"] for r in rows]
tags_map = {}
favorited_ids = set()
if all_ids:
placeholders = ",".join("?" for _ in all_ids)
tag_rows = conn.execute(
@@ -1070,11 +1072,17 @@ def _build_tree_children(conn, parent_id: int | None, ws_id: int) -> list:
tags_map.setdefault(tr["page_id"], []).append({
"id": tr["id"], "name": tr["name"], "color": tr["color"],
})
if uid is not None:
fav_rows = conn.execute(
f"SELECT page_id FROM favorites WHERE user_id=? AND page_id IN ({placeholders})",
[uid, *all_ids],
).fetchall()
favorited_ids = {fr["page_id"] for fr in fav_rows}
tree = []
for r in rows:
is_folder = r["parent_section"] == "Workspace"
children = _build_tree_children(conn, r["id"], ws_id)
children = _build_tree_children(conn, r["id"], ws_id, uid)
# Compute size
size = 0
@@ -1094,6 +1102,7 @@ def _build_tree_children(conn, parent_id: int | None, ws_id: int) -> list:
"type": "folder" if is_folder else "page",
"is_folder": is_folder,
"content_format": r["content_format"] if not is_folder else None,
"page_icon": r["page_icon"] or "",
"children": children,
"has_children": len(children) > 0,
"child_count": len(children),
@@ -1104,6 +1113,7 @@ def _build_tree_children(conn, parent_id: int | None, ws_id: int) -> list:
"author": author,
"tags": tags_map.get(r["id"], []),
"is_shared": bool(r["is_shared"]),
"favorited": r["id"] in favorited_ids,
})
return tree