Add nav-tree fragment: blog renders nav for all apps
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m5s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m5s
Blog now provides a nav-tree fragment at /internal/fragments/nav-tree that accepts app_name and path params for correct aria-selected highlighting. Blog itself consumes this fragment alongside cart-mini and auth-menu in a single concurrent fetch_fragments() call. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
12
app.py
12
app.py
@@ -23,20 +23,16 @@ async def blog_context() -> dict:
|
|||||||
"""
|
"""
|
||||||
Blog app context processor.
|
Blog app context processor.
|
||||||
|
|
||||||
- menu_items: via shared.services.navigation
|
|
||||||
- cart_count/cart_total: via cart service (shared DB)
|
- cart_count/cart_total: via cart service (shared DB)
|
||||||
- cart_mini_html / auth_menu_html: pre-fetched fragments
|
- cart_mini_html / auth_menu_html / nav_tree_html: pre-fetched fragments
|
||||||
"""
|
"""
|
||||||
from shared.infrastructure.context import base_context
|
from shared.infrastructure.context import base_context
|
||||||
from shared.services.navigation import get_navigation_tree
|
|
||||||
from shared.services.registry import services
|
from shared.services.registry import services
|
||||||
from shared.infrastructure.cart_identity import current_cart_identity
|
from shared.infrastructure.cart_identity import current_cart_identity
|
||||||
from shared.infrastructure.fragments import fetch_fragments
|
from shared.infrastructure.fragments import fetch_fragments
|
||||||
|
|
||||||
ctx = await base_context()
|
ctx = await base_context()
|
||||||
|
|
||||||
ctx["menu_items"] = await get_navigation_tree(g.s)
|
|
||||||
|
|
||||||
# Cart data via service (replaces cross-app HTTP API)
|
# Cart data via service (replaces cross-app HTTP API)
|
||||||
ident = current_cart_identity()
|
ident = current_cart_identity()
|
||||||
summary = await services.cart.cart_summary(
|
summary = await services.cart.cart_summary(
|
||||||
@@ -55,12 +51,16 @@ async def blog_context() -> dict:
|
|||||||
|
|
||||||
auth_params = {"email": user.email} if user else {}
|
auth_params = {"email": user.email} if user else {}
|
||||||
|
|
||||||
cart_mini_html, auth_menu_html = await fetch_fragments([
|
nav_params = {"app_name": "blog", "path": request.path}
|
||||||
|
|
||||||
|
cart_mini_html, auth_menu_html, nav_tree_html = await fetch_fragments([
|
||||||
("cart", "cart-mini", cart_params or None),
|
("cart", "cart-mini", cart_params or None),
|
||||||
("account", "auth-menu", auth_params or None),
|
("account", "auth-menu", auth_params or None),
|
||||||
|
("blog", "nav-tree", nav_params),
|
||||||
])
|
])
|
||||||
ctx["cart_mini_html"] = cart_mini_html
|
ctx["cart_mini_html"] = cart_mini_html
|
||||||
ctx["auth_menu_html"] = auth_menu_html
|
ctx["auth_menu_html"] = auth_menu_html
|
||||||
|
ctx["nav_tree_html"] = nav_tree_html
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,10 @@ by other coop apps via the fragment client.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from quart import Blueprint, Response, request
|
from quart import Blueprint, Response, g, render_template, request
|
||||||
|
|
||||||
from shared.infrastructure.fragments import FRAGMENT_HEADER
|
from shared.infrastructure.fragments import FRAGMENT_HEADER
|
||||||
|
from shared.services.navigation import get_navigation_tree
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
@@ -30,6 +31,21 @@ def register():
|
|||||||
html = await handler()
|
html = await handler()
|
||||||
return Response(html, status=200, content_type="text/html")
|
return Response(html, status=200, content_type="text/html")
|
||||||
|
|
||||||
|
# --- nav-tree fragment ---
|
||||||
|
async def _nav_tree_handler():
|
||||||
|
app_name = request.args.get("app_name", "")
|
||||||
|
path = request.args.get("path", "/")
|
||||||
|
first_seg = path.strip("/").split("/")[0]
|
||||||
|
menu_items = await get_navigation_tree(g.s)
|
||||||
|
return await render_template(
|
||||||
|
"fragments/nav_tree.html",
|
||||||
|
menu_items=menu_items,
|
||||||
|
frag_app_name=app_name,
|
||||||
|
frag_first_seg=first_seg,
|
||||||
|
)
|
||||||
|
|
||||||
|
_handlers["nav-tree"] = _nav_tree_handler
|
||||||
|
|
||||||
# Store handlers dict on blueprint so app code can register handlers
|
# Store handlers dict on blueprint so app code can register handlers
|
||||||
bp._fragment_handlers = _handlers
|
bp._fragment_handlers = _handlers
|
||||||
|
|
||||||
|
|||||||
2
shared
2
shared
Submodule shared updated: 0d40dfaeca...7e650a0ee3
@@ -24,7 +24,11 @@
|
|||||||
|
|
||||||
{# Desktop nav #}
|
{# Desktop nav #}
|
||||||
<nav class="hidden md:flex gap-4 text-sm ml-2 justify-end items-center flex-0">
|
<nav class="hidden md:flex gap-4 text-sm ml-2 justify-end items-center flex-0">
|
||||||
{% include '_types/root/_nav.html' %}
|
{% if nav_tree_html %}
|
||||||
|
{{ nav_tree_html | safe }}
|
||||||
|
{% else %}
|
||||||
|
{% include '_types/root/_nav.html' %}
|
||||||
|
{% endif %}
|
||||||
{# Auth menu — fetched from account app as fragment #}
|
{# Auth menu — fetched from account app as fragment #}
|
||||||
{% if auth_menu_html %}
|
{% if auth_menu_html %}
|
||||||
{{ auth_menu_html | safe }}
|
{{ auth_menu_html | safe }}
|
||||||
|
|||||||
26
templates/fragments/nav_tree.html
Normal file
26
templates/fragments/nav_tree.html
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{# Nav-tree fragment — rendered by blog, consumed by all apps.
|
||||||
|
Uses frag_app_name / frag_first_seg instead of request.path / app_name
|
||||||
|
so the consuming app's context is reflected correctly.
|
||||||
|
No hx-boost — cross-app nav links are full page navigations. #}
|
||||||
|
{% set _app_slugs = {'cart': cart_url('/')} %}
|
||||||
|
<div class="flex flex-col sm:flex-row sm:items-center gap-2 border-r border-stone-200 mr-2 sm:max-w-2xl"
|
||||||
|
id="menu-items-nav-wrapper">
|
||||||
|
{% from 'macros/scrolling_menu.html' import scrolling_menu with context %}
|
||||||
|
{% call(item) scrolling_menu('menu-items-container', menu_items) %}
|
||||||
|
{% set _href = _app_slugs.get(item.slug, blog_url('/' + item.slug + '/')) %}
|
||||||
|
<a
|
||||||
|
href="{{ _href }}"
|
||||||
|
aria-selected="{{ 'true' if (item.slug == frag_first_seg or item.slug == frag_app_name) else 'false' }}"
|
||||||
|
class="{{styles.nav_button_less_pad}}"
|
||||||
|
>
|
||||||
|
{% if item.feature_image %}
|
||||||
|
<img src="{{ item.feature_image }}"
|
||||||
|
alt="{{ item.label }}"
|
||||||
|
class="w-8 h-8 rounded-full object-cover flex-shrink-0" />
|
||||||
|
{% else %}
|
||||||
|
<div class="w-8 h-8 rounded-full bg-stone-200 flex-shrink-0"></div>
|
||||||
|
{% endif %}
|
||||||
|
<span>{{ item.label }}</span>
|
||||||
|
</a>
|
||||||
|
{% endcall %}
|
||||||
|
</div>
|
||||||
Reference in New Issue
Block a user