Unify post admin nav across all services
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m56s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 1m56s
Move post admin header into shared/sexp/helpers.py so blog, cart, events, and market all render the same admin row with identical nav: calendars | markets | payments | entries | data | edit | settings. All links are external (cross-service). The selected item shows highlighted on the right and as white text next to "admin" on the left. - blog: delegates to shared helper, removes blog-specific nav builder - cart: delegates to shared helper for payments admin - events: adds shared admin row (selected=calendars) to calendar admin - market: adds /<slug>/admin/ route + page_admin blueprint, delegates to shared helper (selected=markets). Fixes 404 on page-level admin. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ from shared.sexp.jinja_bridge import render, load_service_components
|
|||||||
from shared.sexp.helpers import (
|
from shared.sexp.helpers import (
|
||||||
call_url, get_asset_url, root_header_html,
|
call_url, get_asset_url, root_header_html,
|
||||||
post_header_html as _shared_post_header_html,
|
post_header_html as _shared_post_header_html,
|
||||||
|
post_admin_header_html as _shared_post_admin_header_html,
|
||||||
oob_header_html,
|
oob_header_html,
|
||||||
search_mobile_html, search_desktop_html,
|
search_mobile_html, search_desktop_html,
|
||||||
full_page, oob_page,
|
full_page, oob_page,
|
||||||
@@ -87,81 +88,9 @@ def _post_header_html(ctx: dict, *, oob: bool = False) -> str:
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _post_admin_header_html(ctx: dict, *, oob: bool = False, selected: str = "") -> str:
|
def _post_admin_header_html(ctx: dict, *, oob: bool = False, selected: str = "") -> str:
|
||||||
"""Post admin header row with admin icon and nav links."""
|
"""Post admin header row — delegates to shared helper."""
|
||||||
from quart import url_for as qurl
|
slug = (ctx.get("post") or {}).get("slug", "")
|
||||||
|
return _shared_post_admin_header_html(ctx, slug, oob=oob, selected=selected)
|
||||||
post = ctx.get("post") or {}
|
|
||||||
slug = post.get("slug", "")
|
|
||||||
hx_select = ctx.get("hx_select_search", "#main-panel")
|
|
||||||
select_colours = ctx.get("select_colours", "")
|
|
||||||
styles = ctx.get("styles") or {}
|
|
||||||
nav_btn = styles.get("nav_button", "") if isinstance(styles, dict) else getattr(styles, "nav_button", "")
|
|
||||||
|
|
||||||
admin_href = qurl("blog.post.admin.admin", slug=slug)
|
|
||||||
label_html = render("blog-admin-label")
|
|
||||||
if selected:
|
|
||||||
label_html += f' <span class="text-white">{escape(selected)}</span>'
|
|
||||||
|
|
||||||
nav_html = _post_admin_nav_html(ctx, selected=selected)
|
|
||||||
|
|
||||||
return render("menu-row",
|
|
||||||
id="post-admin-row", level=2,
|
|
||||||
link_href=admin_href, link_label_html=label_html,
|
|
||||||
nav_html=nav_html, child_id="post-admin-header-child", oob=oob,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _post_admin_nav_html(ctx: dict, *, selected: str = "") -> str:
|
|
||||||
"""Post admin desktop nav: calendars, markets, payments, entries, data, edit, settings."""
|
|
||||||
from quart import url_for as qurl
|
|
||||||
|
|
||||||
post = ctx.get("post") or {}
|
|
||||||
slug = post.get("slug", "")
|
|
||||||
hx_select = ctx.get("hx_select_search", "#main-panel")
|
|
||||||
select_colours = ctx.get("select_colours", "")
|
|
||||||
styles = ctx.get("styles") or {}
|
|
||||||
nav_btn = styles.get("nav_button", "") if isinstance(styles, dict) else getattr(styles, "nav_button", "")
|
|
||||||
|
|
||||||
# Base and selected class for nav items
|
|
||||||
base_cls = "justify-center cursor-pointer flex flex-row items-center gap-2 rounded bg-stone-200 text-black p-3"
|
|
||||||
selected_cls = "justify-center cursor-pointer flex flex-row items-center gap-2 rounded !bg-stone-500 !text-white p-3"
|
|
||||||
|
|
||||||
parts = []
|
|
||||||
|
|
||||||
# External links to events / market services
|
|
||||||
events_url_fn = ctx.get("events_url")
|
|
||||||
market_url_fn = ctx.get("market_url")
|
|
||||||
if callable(events_url_fn):
|
|
||||||
for url_fn, path, label in [
|
|
||||||
(events_url_fn, f"/{slug}/admin/", "calendars"),
|
|
||||||
(market_url_fn, f"/{slug}/admin/", "markets"),
|
|
||||||
(ctx.get("cart_url"), f"/{slug}/admin/payments/", "payments"),
|
|
||||||
]:
|
|
||||||
if not callable(url_fn):
|
|
||||||
continue
|
|
||||||
href = url_fn(path)
|
|
||||||
cls = selected_cls if label == selected else (nav_btn or base_cls)
|
|
||||||
parts.append(render("blog-admin-nav-item",
|
|
||||||
href=href, nav_btn_class=cls, label=label,
|
|
||||||
select_colours=select_colours,
|
|
||||||
))
|
|
||||||
|
|
||||||
# HTMX links
|
|
||||||
for endpoint, label in [
|
|
||||||
("blog.post.admin.entries", "entries"),
|
|
||||||
("blog.post.admin.data", "data"),
|
|
||||||
("blog.post.admin.edit", "edit"),
|
|
||||||
("blog.post.admin.settings", "settings"),
|
|
||||||
]:
|
|
||||||
href = qurl(endpoint, slug=slug)
|
|
||||||
is_sel = label == selected
|
|
||||||
parts.append(render("nav-link",
|
|
||||||
href=href, label=label, select_colours=select_colours,
|
|
||||||
is_selected=is_sel,
|
|
||||||
aclass=(selected_cls + " " + select_colours) if is_sel else None,
|
|
||||||
))
|
|
||||||
|
|
||||||
return "".join(parts)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ from markupsafe import escape
|
|||||||
|
|
||||||
from shared.sexp.jinja_bridge import render, load_service_components
|
from shared.sexp.jinja_bridge import render, load_service_components
|
||||||
from shared.sexp.helpers import (
|
from shared.sexp.helpers import (
|
||||||
call_url, root_header_html, search_desktop_html,
|
call_url, root_header_html, post_admin_header_html,
|
||||||
search_mobile_html, full_page, oob_page,
|
search_desktop_html, search_mobile_html, full_page, oob_page,
|
||||||
)
|
)
|
||||||
from shared.infrastructure.urls import market_product_url, cart_url
|
from shared.infrastructure.urls import market_product_url, cart_url
|
||||||
|
|
||||||
@@ -725,15 +725,9 @@ async def render_checkout_error_page(ctx: dict, error: str | None = None, order:
|
|||||||
|
|
||||||
def _cart_page_admin_header_html(ctx: dict, page_post: Any, *, oob: bool = False,
|
def _cart_page_admin_header_html(ctx: dict, page_post: Any, *, oob: bool = False,
|
||||||
selected: str = "") -> str:
|
selected: str = "") -> str:
|
||||||
"""Build the page-level admin header row."""
|
"""Build the page-level admin header row — delegates to shared helper."""
|
||||||
from quart import url_for
|
slug = page_post.slug if page_post else ""
|
||||||
link_href = url_for("page_admin.admin")
|
return post_admin_header_html(ctx, slug, oob=oob, selected=selected)
|
||||||
label_html = '<i class="fa fa-cog" aria-hidden="true"></i> admin'
|
|
||||||
if selected:
|
|
||||||
label_html += f' <span class="text-white">{escape(selected)}</span>'
|
|
||||||
return render("menu-row", id="page-admin-row", level=2, colour="sky",
|
|
||||||
link_href=link_href, link_label_html=label_html,
|
|
||||||
child_id="page-admin-header-child", oob=oob)
|
|
||||||
|
|
||||||
|
|
||||||
def _cart_admin_main_panel_html(ctx: dict) -> str:
|
def _cart_admin_main_panel_html(ctx: dict) -> str:
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from shared.sexp.jinja_bridge import render, load_service_components
|
|||||||
from shared.sexp.helpers import (
|
from shared.sexp.helpers import (
|
||||||
call_url, get_asset_url, root_header_html,
|
call_url, get_asset_url, root_header_html,
|
||||||
post_header_html as _shared_post_header_html,
|
post_header_html as _shared_post_header_html,
|
||||||
|
post_admin_header_html,
|
||||||
oob_header_html,
|
oob_header_html,
|
||||||
search_mobile_html, search_desktop_html,
|
search_mobile_html, search_desktop_html,
|
||||||
full_page, oob_page,
|
full_page, oob_page,
|
||||||
@@ -1352,11 +1353,19 @@ async def render_day_admin_oob(ctx: dict) -> str:
|
|||||||
# Calendar admin
|
# Calendar admin
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _events_post_admin_header_html(ctx: dict, *, oob: bool = False,
|
||||||
|
selected: str = "") -> str:
|
||||||
|
"""Post-level admin row for events — delegates to shared helper."""
|
||||||
|
slug = (ctx.get("post") or {}).get("slug", "")
|
||||||
|
return post_admin_header_html(ctx, slug, oob=oob, selected=selected)
|
||||||
|
|
||||||
|
|
||||||
async def render_calendar_admin_page(ctx: dict) -> str:
|
async def render_calendar_admin_page(ctx: dict) -> str:
|
||||||
"""Full page: calendar admin."""
|
"""Full page: calendar admin."""
|
||||||
content = _calendar_admin_main_panel_html(ctx)
|
content = _calendar_admin_main_panel_html(ctx)
|
||||||
hdr = root_header_html(ctx)
|
hdr = root_header_html(ctx)
|
||||||
child = (_post_header_html(ctx)
|
child = (_post_header_html(ctx)
|
||||||
|
+ _events_post_admin_header_html(ctx, selected="calendars")
|
||||||
+ _calendar_header_html(ctx) + _calendar_admin_header_html(ctx))
|
+ _calendar_header_html(ctx) + _calendar_admin_header_html(ctx))
|
||||||
hdr += render("header-child", inner_html=child)
|
hdr += render("header-child", inner_html=child)
|
||||||
return full_page(ctx, header_rows_html=hdr, content_html=content)
|
return full_page(ctx, header_rows_html=hdr, content_html=content)
|
||||||
@@ -1365,7 +1374,8 @@ async def render_calendar_admin_page(ctx: dict) -> str:
|
|||||||
async def render_calendar_admin_oob(ctx: dict) -> str:
|
async def render_calendar_admin_oob(ctx: dict) -> str:
|
||||||
"""OOB response: calendar admin."""
|
"""OOB response: calendar admin."""
|
||||||
content = _calendar_admin_main_panel_html(ctx)
|
content = _calendar_admin_main_panel_html(ctx)
|
||||||
oobs = _calendar_header_html(ctx, oob=True)
|
oobs = (_events_post_admin_header_html(ctx, oob=True, selected="calendars")
|
||||||
|
+ _calendar_header_html(ctx, oob=True))
|
||||||
oobs += _oob_header_html("calendar-header-child", "calendar-admin-header-child",
|
oobs += _oob_header_html("calendar-header-child", "calendar-admin-header-child",
|
||||||
_calendar_admin_header_html(ctx))
|
_calendar_admin_header_html(ctx))
|
||||||
return oob_page(ctx, oobs_html=oobs, content_html=content)
|
return oob_page(ctx, oobs_html=oobs, content_html=content)
|
||||||
@@ -1383,6 +1393,7 @@ async def render_slots_page(ctx: dict) -> str:
|
|||||||
content = render_slots_table(slots, calendar)
|
content = render_slots_table(slots, calendar)
|
||||||
hdr = root_header_html(ctx)
|
hdr = root_header_html(ctx)
|
||||||
child = (_post_header_html(ctx)
|
child = (_post_header_html(ctx)
|
||||||
|
+ _events_post_admin_header_html(ctx, selected="calendars")
|
||||||
+ _calendar_header_html(ctx) + _calendar_admin_header_html(ctx))
|
+ _calendar_header_html(ctx) + _calendar_admin_header_html(ctx))
|
||||||
hdr += render("header-child", inner_html=child)
|
hdr += render("header-child", inner_html=child)
|
||||||
return full_page(ctx, header_rows_html=hdr, content_html=content)
|
return full_page(ctx, header_rows_html=hdr, content_html=content)
|
||||||
@@ -1393,7 +1404,8 @@ async def render_slots_oob(ctx: dict) -> str:
|
|||||||
slots = ctx.get("slots") or []
|
slots = ctx.get("slots") or []
|
||||||
calendar = ctx.get("calendar")
|
calendar = ctx.get("calendar")
|
||||||
content = render_slots_table(slots, calendar)
|
content = render_slots_table(slots, calendar)
|
||||||
oobs = _calendar_admin_header_html(ctx, oob=True)
|
oobs = (_events_post_admin_header_html(ctx, oob=True, selected="calendars")
|
||||||
|
+ _calendar_admin_header_html(ctx, oob=True))
|
||||||
return oob_page(ctx, oobs_html=oobs, content_html=content)
|
return oob_page(ctx, oobs_html=oobs, content_html=content)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from sqlalchemy import select
|
|||||||
from shared.infrastructure.factory import create_base_app
|
from shared.infrastructure.factory import create_base_app
|
||||||
from shared.config import config
|
from shared.config import config
|
||||||
|
|
||||||
from bp import register_market_bp, register_all_markets, register_page_markets, register_fragments, register_actions, register_data
|
from bp import register_market_bp, register_all_markets, register_page_markets, register_page_admin, register_fragments, register_actions, register_data
|
||||||
|
|
||||||
|
|
||||||
async def market_context() -> dict:
|
async def market_context() -> dict:
|
||||||
@@ -111,6 +111,12 @@ def create_app() -> "Quart":
|
|||||||
url_prefix="/<slug>",
|
url_prefix="/<slug>",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Page admin: /<slug>/admin/ — post-level admin for markets
|
||||||
|
app.register_blueprint(
|
||||||
|
register_page_admin(),
|
||||||
|
url_prefix="/<slug>/admin",
|
||||||
|
)
|
||||||
|
|
||||||
# Market blueprint nested under post slug: /<page_slug>/<market_slug>/
|
# Market blueprint nested under post slug: /<page_slug>/<market_slug>/
|
||||||
app.register_blueprint(
|
app.register_blueprint(
|
||||||
register_market_bp(
|
register_market_bp(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from .market.routes import register as register_market_bp
|
|||||||
from .product.routes import register as register_product
|
from .product.routes import register as register_product
|
||||||
from .all_markets.routes import register as register_all_markets
|
from .all_markets.routes import register as register_all_markets
|
||||||
from .page_markets.routes import register as register_page_markets
|
from .page_markets.routes import register as register_page_markets
|
||||||
|
from .page_admin.routes import register as register_page_admin
|
||||||
from .fragments import register_fragments
|
from .fragments import register_fragments
|
||||||
from .actions import register_actions
|
from .actions import register_actions
|
||||||
from .data import register_data
|
from .data import register_data
|
||||||
|
|||||||
0
market/bp/page_admin/__init__.py
Normal file
0
market/bp/page_admin/__init__.py
Normal file
25
market/bp/page_admin/routes.py
Normal file
25
market/bp/page_admin/routes.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from quart import make_response, Blueprint
|
||||||
|
|
||||||
|
from shared.browser.app.authz import require_admin
|
||||||
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
|
|
||||||
|
|
||||||
|
def register():
|
||||||
|
bp = Blueprint("page_admin", __name__)
|
||||||
|
|
||||||
|
@bp.get("/")
|
||||||
|
@require_admin
|
||||||
|
async def admin(**kwargs):
|
||||||
|
from shared.sexp.page import get_template_context
|
||||||
|
from sexp.sexp_components import render_page_admin_page, render_page_admin_oob
|
||||||
|
|
||||||
|
tctx = await get_template_context()
|
||||||
|
if not is_htmx_request():
|
||||||
|
html = await render_page_admin_page(tctx)
|
||||||
|
else:
|
||||||
|
html = await render_page_admin_oob(tctx)
|
||||||
|
return await make_response(html)
|
||||||
|
|
||||||
|
return bp
|
||||||
@@ -15,6 +15,7 @@ from shared.sexp.jinja_bridge import render, load_service_components
|
|||||||
from shared.sexp.helpers import (
|
from shared.sexp.helpers import (
|
||||||
call_url, get_asset_url, root_header_html,
|
call_url, get_asset_url, root_header_html,
|
||||||
post_header_html as _post_header_html,
|
post_header_html as _post_header_html,
|
||||||
|
post_admin_header_html,
|
||||||
oob_header_html as _oob_header_html,
|
oob_header_html as _oob_header_html,
|
||||||
search_mobile_html, search_desktop_html,
|
search_mobile_html, search_desktop_html,
|
||||||
full_page, oob_page,
|
full_page, oob_page,
|
||||||
@@ -1420,7 +1421,7 @@ async def render_market_admin_page(ctx: dict) -> str:
|
|||||||
content = "market admin"
|
content = "market admin"
|
||||||
|
|
||||||
hdr = root_header_html(ctx)
|
hdr = root_header_html(ctx)
|
||||||
child = _post_header_html(ctx) + _market_header_html(ctx) + _market_admin_header_html(ctx)
|
child = _post_header_html(ctx) + _market_header_html(ctx) + _market_admin_header_html(ctx, selected="markets")
|
||||||
hdr += render("header-child", inner_html=child)
|
hdr += render("header-child", inner_html=child)
|
||||||
return full_page(ctx, header_rows_html=hdr, content_html=content)
|
return full_page(ctx, header_rows_html=hdr, content_html=content)
|
||||||
|
|
||||||
@@ -1431,21 +1432,37 @@ async def render_market_admin_oob(ctx: dict) -> str:
|
|||||||
|
|
||||||
oobs = _market_header_html(ctx, oob=True)
|
oobs = _market_header_html(ctx, oob=True)
|
||||||
oobs += _oob_header_html("market-header-child", "market-admin-header-child",
|
oobs += _oob_header_html("market-header-child", "market-admin-header-child",
|
||||||
_market_admin_header_html(ctx))
|
_market_admin_header_html(ctx, selected="markets"))
|
||||||
return oob_page(ctx, oobs_html=oobs, content_html=content)
|
return oob_page(ctx, oobs_html=oobs, content_html=content)
|
||||||
|
|
||||||
|
|
||||||
def _market_admin_header_html(ctx: dict, *, oob: bool = False) -> str:
|
def _market_admin_header_html(ctx: dict, *, oob: bool = False, selected: str = "") -> str:
|
||||||
"""Build market admin header row."""
|
"""Build market admin header row — delegates to shared helper."""
|
||||||
from quart import url_for
|
slug = (ctx.get("post") or {}).get("slug", "")
|
||||||
|
return post_admin_header_html(ctx, slug, oob=oob, selected=selected)
|
||||||
|
|
||||||
link_href = url_for("market.admin.admin")
|
|
||||||
return render(
|
# ---------------------------------------------------------------------------
|
||||||
"menu-row",
|
# Page admin (/<slug>/admin/) — post-level admin for markets
|
||||||
id="market-admin-row", level=3,
|
# ---------------------------------------------------------------------------
|
||||||
link_href=link_href, link_label="admin", icon="fa fa-cog",
|
|
||||||
child_id="market-admin-header-child", oob=oob,
|
async def render_page_admin_page(ctx: dict) -> str:
|
||||||
)
|
"""Full page: page-level market admin."""
|
||||||
|
slug = (ctx.get("post") or {}).get("slug", "")
|
||||||
|
admin_hdr = post_admin_header_html(ctx, slug, selected="markets")
|
||||||
|
hdr = root_header_html(ctx)
|
||||||
|
child = _post_header_html(ctx) + admin_hdr
|
||||||
|
hdr += render("header-child", inner_html=child)
|
||||||
|
content = '<div id="main-panel"><div class="p-4 text-stone-500">Market admin</div></div>'
|
||||||
|
return full_page(ctx, header_rows_html=hdr, content_html=content)
|
||||||
|
|
||||||
|
|
||||||
|
async def render_page_admin_oob(ctx: dict) -> str:
|
||||||
|
"""OOB response: page-level market admin."""
|
||||||
|
slug = (ctx.get("post") or {}).get("slug", "")
|
||||||
|
oobs = post_admin_header_html(ctx, slug, oob=True, selected="markets")
|
||||||
|
content = '<div id="main-panel"><div class="p-4 text-stone-500">Market admin</div></div>'
|
||||||
|
return oob_page(ctx, oobs_html=oobs, content_html=content)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ from __future__ import annotations
|
|||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from markupsafe import escape
|
||||||
|
|
||||||
from .jinja_bridge import render
|
from .jinja_bridge import render
|
||||||
from .page import SEARCH_HEADERS_MOBILE, SEARCH_HEADERS_DESKTOP
|
from .page import SEARCH_HEADERS_MOBILE, SEARCH_HEADERS_DESKTOP
|
||||||
|
|
||||||
@@ -104,6 +106,62 @@ def post_header_html(ctx: dict, *, oob: bool = False) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def post_admin_header_html(ctx: dict, slug: str, *, oob: bool = False,
|
||||||
|
selected: str = "", admin_href: str = "") -> str:
|
||||||
|
"""Shared post admin header row with unified nav across all services.
|
||||||
|
|
||||||
|
Shows: calendars | markets | payments | entries | data | edit | settings
|
||||||
|
All links are external (cross-service). The *selected* item is
|
||||||
|
highlighted on the nav and shown in white next to the admin label.
|
||||||
|
"""
|
||||||
|
# Label: shield icon + "admin" + optional selected sub-page in white
|
||||||
|
label_html = '<i class="fa fa-shield-halved" aria-hidden="true"></i> admin'
|
||||||
|
if selected:
|
||||||
|
label_html += f' <span class="text-white">{escape(selected)}</span>'
|
||||||
|
|
||||||
|
# Nav items — all external links to the appropriate service
|
||||||
|
select_colours = ctx.get("select_colours", "")
|
||||||
|
base_cls = ("justify-center cursor-pointer flex flex-row items-center"
|
||||||
|
" gap-2 rounded bg-stone-200 text-black p-3")
|
||||||
|
selected_cls = ("justify-center cursor-pointer flex flex-row items-center"
|
||||||
|
" gap-2 rounded !bg-stone-500 !text-white p-3")
|
||||||
|
nav_parts: list[str] = []
|
||||||
|
items = [
|
||||||
|
("events_url", f"/{slug}/admin/", "calendars"),
|
||||||
|
("market_url", f"/{slug}/admin/", "markets"),
|
||||||
|
("cart_url", f"/{slug}/admin/payments/", "payments"),
|
||||||
|
("blog_url", f"/{slug}/admin/entries/", "entries"),
|
||||||
|
("blog_url", f"/{slug}/admin/data/", "data"),
|
||||||
|
("blog_url", f"/{slug}/admin/edit/", "edit"),
|
||||||
|
("blog_url", f"/{slug}/admin/settings/", "settings"),
|
||||||
|
]
|
||||||
|
for url_key, path, label in items:
|
||||||
|
url_fn = ctx.get(url_key)
|
||||||
|
if not callable(url_fn):
|
||||||
|
continue
|
||||||
|
href = url_fn(path)
|
||||||
|
is_sel = label == selected
|
||||||
|
cls = selected_cls if is_sel else base_cls
|
||||||
|
aria = ' aria-selected="true"' if is_sel else ""
|
||||||
|
nav_parts.append(
|
||||||
|
f'<div class="relative nav-group">'
|
||||||
|
f'<a href="{escape(href)}"{aria}'
|
||||||
|
f' class="{cls} {escape(select_colours)}">'
|
||||||
|
f'{escape(label)}</a></div>'
|
||||||
|
)
|
||||||
|
nav_html = "".join(nav_parts)
|
||||||
|
|
||||||
|
if not admin_href:
|
||||||
|
blog_fn = ctx.get("blog_url")
|
||||||
|
admin_href = blog_fn(f"/{slug}/admin/") if callable(blog_fn) else f"/{slug}/admin/"
|
||||||
|
|
||||||
|
return render("menu-row",
|
||||||
|
id="post-admin-row", level=2,
|
||||||
|
link_href=admin_href, link_label_html=label_html,
|
||||||
|
nav_html=nav_html, child_id="post-admin-header-child", oob=oob,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def oob_header_html(parent_id: str, child_id: str, row_html: str) -> str:
|
def oob_header_html(parent_id: str, child_id: str, row_html: str) -> str:
|
||||||
"""Wrap a header row in an OOB swap div with child placeholder."""
|
"""Wrap a header row in an OOB swap div with child placeholder."""
|
||||||
return render("oob-header",
|
return render("oob-header",
|
||||||
|
|||||||
Reference in New Issue
Block a user