from __future__ import annotations from quart import Blueprint, render_template, make_response, request, jsonify, g from shared.browser.app.authz import require_admin from .services.menu_items import ( get_all_menu_items, get_menu_item_by_id, create_menu_item, update_menu_item, delete_menu_item, search_pages, MenuItemError, ) from shared.browser.app.utils.htmx import is_htmx_request def register(): bp = Blueprint("menu_items", __name__, url_prefix='/settings/menu_items') async def get_menu_items_nav_oob(): """Helper to generate OOB update for root nav menu items""" menu_items = await get_all_menu_items(g.s) nav_oob = await render_template( "_types/menu_items/_nav_oob.html", menu_items=menu_items, ) return nav_oob @bp.get("/") @require_admin async def list_menu_items(): """List all menu items""" menu_items = await get_all_menu_items(g.s) if not is_htmx_request(): # Normal browser request: full page with layout html = await render_template( "_types/menu_items/index.html", menu_items=menu_items, ) else: html = await render_template( "_types/menu_items/_oob_elements.html", menu_items=menu_items, ) #html = await render_template("_types/root/settings/_oob_elements.html") return await make_response(html) @bp.get("/new/") @require_admin async def new_menu_item(): """Show form to create new menu item""" html = await render_template( "_types/menu_items/_form.html", menu_item=None, ) return await make_response(html) @bp.post("/") @require_admin async def create_menu_item_route(): """Create a new menu item""" form = await request.form post_id = form.get("post_id") if not post_id: return jsonify({"message": "Page is required", "errors": {"post_id": ["Please select a page"]}}), 422 try: post_id = int(post_id) except ValueError: return jsonify({"message": "Invalid page ID", "errors": {"post_id": ["Invalid page"]}}), 422 try: menu_item = await create_menu_item(g.s, post_id) await g.s.flush() # Get updated list and nav OOB menu_items = await get_all_menu_items(g.s) nav_oob = await get_menu_items_nav_oob() html = await render_template( "_types/menu_items/_list.html", menu_items=menu_items, ) return await make_response(html + nav_oob, 200) except MenuItemError as e: return jsonify({"message": str(e), "errors": {}}), 400 @bp.get("//edit/") @require_admin async def edit_menu_item(item_id: int): """Show form to edit menu item""" menu_item = await get_menu_item_by_id(g.s, item_id) if not menu_item: return await make_response("Menu item not found", 404) html = await render_template( "_types/menu_items/_form.html", menu_item=menu_item, ) return await make_response(html) @bp.put("//") @require_admin async def update_menu_item_route(item_id: int): """Update a menu item""" form = await request.form post_id = form.get("post_id") if not post_id: return jsonify({"message": "Page is required", "errors": {"post_id": ["Please select a page"]}}), 422 try: post_id = int(post_id) except ValueError: return jsonify({"message": "Invalid page ID", "errors": {"post_id": ["Invalid page"]}}), 422 try: menu_item = await update_menu_item(g.s, item_id, post_id=post_id) await g.s.flush() # Get updated list and nav OOB menu_items = await get_all_menu_items(g.s) nav_oob = await get_menu_items_nav_oob() html = await render_template( "_types/menu_items/_list.html", menu_items=menu_items, ) return await make_response(html + nav_oob, 200) except MenuItemError as e: return jsonify({"message": str(e), "errors": {}}), 400 @bp.delete("//") @require_admin async def delete_menu_item_route(item_id: int): """Delete a menu item""" success = await delete_menu_item(g.s, item_id) if not success: return await make_response("Menu item not found", 404) await g.s.flush() # Get updated list and nav OOB menu_items = await get_all_menu_items(g.s) nav_oob = await get_menu_items_nav_oob() html = await render_template( "_types/menu_items/_list.html", menu_items=menu_items, ) return await make_response(html + nav_oob, 200) @bp.get("/pages/search/") @require_admin async def search_pages_route(): """Search for pages to add as menu items""" query = request.args.get("q", "").strip() page = int(request.args.get("page", 1)) per_page = 10 pages, total = await search_pages(g.s, query, page, per_page) has_more = (page * per_page) < total html = await render_template( "_types/menu_items/_page_search_results.html", pages=pages, query=query, page=page, has_more=has_more, ) return await make_response(html) @bp.post("/reorder/") @require_admin async def reorder_menu_items_route(): """Reorder menu items""" from .services.menu_items import reorder_menu_items form = await request.form item_ids_str = form.get("item_ids", "") if not item_ids_str: return jsonify({"message": "No items to reorder", "errors": {}}), 400 try: item_ids = [int(id.strip()) for id in item_ids_str.split(",") if id.strip()] except ValueError: return jsonify({"message": "Invalid item IDs", "errors": {}}), 400 await reorder_menu_items(g.s, item_ids) await g.s.flush() # Get updated list and nav OOB menu_items = await get_all_menu_items(g.s) nav_oob = await get_menu_items_nav_oob() html = await render_template( "_types/menu_items/_list.html", menu_items=menu_items, ) return await make_response(html + nav_oob, 200) return bp