feat: add Notion-style breadcrumb navigation with dropdown menus
FlowDeck CI / test (push) Failing after 5s
FlowDeck CI / docker (push) Has been skipped

Backend:
- Added _nav_breadcrumb() to build page hierarchy chain (root → current)
- New /api/nav/menu endpoint returns workspace/folder children with has_children flag
- view_page() and view_page_root() now pass breadcrumb_items, nav_workspace_id, nav_page_id to template

Header template (_header.html):
- Replaced static breadcrumb with Alpine.js fdBreadcrumb() component
- Breadcrumb items now clickable with dropdown menus showing
This commit is contained in:
2026-07-17 23:30:59 -04:00
parent 6f1eabf91d
commit d751415308
14 changed files with 471 additions and 27 deletions
+67
View File
@@ -1847,3 +1847,70 @@ def test_gitea_private_pages_create_no_auth(client):
"""POST private-pages — 401 without session."""
resp = client.post("/api/gitea/projects/owner/repo/private-pages", json={"title": "Test"})
assert resp.status_code == 401
# ── v2.6.0: Notion-style breadcrumb navigation ──
def test_nav_menu_endpoint_responds(client):
"""GET /api/nav/menu — always returns an items list (200)."""
resp = client.get("/api/nav/menu")
assert resp.status_code == 200
assert "items" in resp.json()
def test_nav_menu_workspace_pages(client):
"""/api/nav/menu returns root pages and nested children for a workspace."""
from app.db import get_conn
with get_conn() as conn:
u = conn.execute("INSERT OR IGNORE INTO users (login) VALUES ('navuser')")
uid = conn.execute("SELECT id FROM users WHERE login='navuser'").fetchone()["id"]
cur = conn.execute("INSERT INTO workspaces (name, owner_id) VALUES ('NavWS', ?)", (uid,))
ws_id = cur.lastrowid
p = conn.execute(
"INSERT INTO pages (workspace, workspace_id, title, content, content_format, "
"parent_section, parent_id) VALUES ('NavWS', ?, 'Parent Page', '', 'blocks', 'Private', NULL)",
(ws_id,),
)
parent_id = p.lastrowid
conn.execute(
"INSERT INTO pages (workspace, workspace_id, title, content, content_format, "
"parent_section, parent_id) VALUES ('NavWS', ?, 'Child Page', '', 'blocks', 'Private', ?)",
(ws_id, parent_id),
)
conn.commit()
# Root level should list the parent and flag it as having children
resp = client.get(f"/api/nav/menu?workspace_id={ws_id}")
assert resp.status_code == 200
items = resp.json()["items"]
names = {i["name"]: i for i in items}
assert "Parent Page" in names
assert names["Parent Page"]["has_children"] is True
# Children of the parent should include the child page
resp = client.get(f"/api/nav/menu?workspace_id={ws_id}&parent_id={parent_id}")
child_names = {i["name"] for i in resp.json()["items"]}
assert "Child Page" in child_names
def test_page_renders_breadcrumb_data(client):
"""Rendered page includes the breadcrumb JSON payload with a Home crumb."""
from app.db import get_conn
with get_conn() as conn:
conn.execute("INSERT OR IGNORE INTO users (login) VALUES ('crumbuser')")
uid = conn.execute("SELECT id FROM users WHERE login='crumbuser'").fetchone()["id"]
cur = conn.execute("INSERT INTO workspaces (name, owner_id) VALUES ('CrumbWS', ?)", (uid,))
ws_id = cur.lastrowid
p = conn.execute(
"INSERT INTO pages (workspace, workspace_id, title, content, content_format, "
"parent_section, parent_id) VALUES ('CrumbWS', ?, 'Crumb Page', '', 'blocks', 'Private', NULL)",
(ws_id,),
)
page_id = p.lastrowid
conn.commit()
resp = client.get(f"/pages/{page_id}")
assert resp.status_code == 200
assert 'id="fd-breadcrumb-data"' in resp.text
assert '"Home"' in resp.text
assert "fdBreadcrumb" in resp.text