from __future__ import annotations from quart import Blueprint, 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 from shared.sx.helpers import sx_response def register(): bp = Blueprint("menu_items", __name__, url_prefix='/settings/menu_items') def get_menu_items_nav_oob_sync(menu_items): """Helper to generate OOB update for root nav menu items""" from sx.sx_components import render_menu_items_nav_oob return render_menu_items_nav_oob(menu_items) @bp.before_request async def _prepare_page_data(): if "defpage_" not in (request.endpoint or ""): return menu_items = await get_all_menu_items(g.s) from shared.sx.page import get_template_context from sx.sx_components import _menu_items_main_panel_sx tctx = await get_template_context() tctx["menu_items"] = menu_items g.menu_items_content = _menu_items_main_panel_sx(tctx) from shared.sx.pages import mount_pages mount_pages(bp, "blog", names=["menu-items-page"]) @bp.get("/new/") @require_admin async def new_menu_item(): """Show form to create new menu item""" from sx.sx_components import render_menu_item_form return sx_response(render_menu_item_form()) @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) from sx.sx_components import render_menu_items_list html = render_menu_items_list(menu_items) nav_oob = get_menu_items_nav_oob_sync(menu_items) return sx_response(html + nav_oob) 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) from sx.sx_components import render_menu_item_form return sx_response(render_menu_item_form(menu_item=menu_item)) @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) from sx.sx_components import render_menu_items_list html = render_menu_items_list(menu_items) nav_oob = get_menu_items_nav_oob_sync(menu_items) return sx_response(html + nav_oob) 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) from sx.sx_components import render_menu_items_list html = render_menu_items_list(menu_items) nav_oob = get_menu_items_nav_oob_sync(menu_items) return sx_response(html + nav_oob) @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 from sx.sx_components import render_page_search_results return sx_response(render_page_search_results(pages, query, page, has_more)) @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) from sx.sx_components import render_menu_items_list html = render_menu_items_list(menu_items) nav_oob = get_menu_items_nav_oob_sync(menu_items) return sx_response(html + nav_oob) return bp