diff --git a/blog/sx/sx_components.py b/blog/sx/sx_components.py index 160a476..6f096d0 100644 --- a/blog/sx/sx_components.py +++ b/blog/sx/sx_components.py @@ -32,6 +32,12 @@ from shared.sx.helpers import ( load_service_components(os.path.dirname(os.path.dirname(__file__))) +def _ctx_csrf(ctx: dict) -> str: + """Get CSRF token from context, handling Jinja callable globals.""" + val = ctx.get("csrf_token", "") + return val() if callable(val) else val + + # --------------------------------------------------------------------------- # OOB header helper — delegates to shared # --------------------------------------------------------------------------- @@ -269,7 +275,7 @@ def _blog_card_sx(post: dict, ctx: dict) -> str: if user: kwargs["liked"] = post.get("is_liked", False) kwargs["like_url"] = call_url(ctx, "blog_url", f"/{slug}/like/toggle/") - kwargs["csrf_token"] = ctx.get("csrf_token", "") + kwargs["csrf_token"] = _ctx_csrf(ctx) if tags: kwargs["tags"] = tags @@ -692,7 +698,7 @@ def _post_main_panel_sx(ctx: dict) -> str: like_url = call_url(ctx, "blog_url", f"/{slug}/like/toggle/") like_sx = sx_call("blog-detail-like", like_url=like_url, - hx_headers=f'{{"X-CSRFToken": "{ctx.get("csrf_token", "")}"}}', + hx_headers=f'{{"X-CSRFToken": "{_ctx_csrf(ctx)}"}}', heart="\u2764\ufe0f" if liked else "\U0001f90d", ) @@ -790,7 +796,7 @@ def _settings_main_panel_sx(ctx: dict) -> str: def _cache_main_panel_sx(ctx: dict) -> str: from quart import url_for as qurl - csrf = ctx.get("csrf_token", "") + csrf = _ctx_csrf(ctx) clear_url = qurl("settings.cache_clear") return sx_call("blog-cache-panel", clear_url=clear_url, csrf=csrf) @@ -810,7 +816,7 @@ def _snippets_list_sx(ctx: dict) -> str: snippets = ctx.get("snippets") or [] is_admin = ctx.get("is_admin", False) - csrf = ctx.get("csrf_token", "") + csrf = _ctx_csrf(ctx) user = getattr(g, "user", None) user_id = getattr(user, "id", None) @@ -881,7 +887,7 @@ def _menu_items_list_sx(ctx: dict) -> str: from quart import url_for as qurl menu_items = ctx.get("menu_items") or [] - csrf = ctx.get("csrf_token", "") + csrf = _ctx_csrf(ctx) if not menu_items: return sx_call("blog-menu-items-empty") @@ -919,7 +925,7 @@ def _tag_groups_main_panel_sx(ctx: dict) -> str: groups = ctx.get("groups") or [] unassigned_tags = ctx.get("unassigned_tags") or [] - csrf = ctx.get("csrf_token", "") + csrf = _ctx_csrf(ctx) create_url = qurl("blog.tag_groups_admin.create") form_sx = sx_call("blog-tag-groups-create-form", @@ -979,7 +985,7 @@ def _tag_groups_edit_main_panel_sx(ctx: dict) -> str: group = ctx.get("group") all_tags = ctx.get("all_tags") or [] assigned_tag_ids = ctx.get("assigned_tag_ids") or set() - csrf = ctx.get("csrf_token", "") + csrf = _ctx_csrf(ctx) g_id = getattr(group, "id", None) or group.get("id") if group else None g_name = getattr(group, "name", "") if hasattr(group, "name") else (group.get("name", "") if group else "") @@ -1046,12 +1052,12 @@ async def render_home_page(ctx: dict) -> str: async def render_home_oob(ctx: dict) -> str: - root_hdr = root_header_sx(ctx, oob=True) - post_oob = _oob_header_sx("root-header-child", "post-header-child", - _post_header_sx(ctx)) + root_hdr = root_header_sx(ctx) + post_hdr = _post_header_sx(ctx) + rows = "(<> " + root_hdr + " " + post_hdr + ")" + header_oob = _oob_header_sx("root-header-child", "post-header-child", rows) content = _home_main_panel_sx(ctx) - oobs = "(<> " + root_hdr + " " + post_oob + ")" - return oob_page_sx(oobs=oobs, content=content) + return oob_page_sx(oobs=header_oob, content=content) # ---- Blog index ---- @@ -1068,15 +1074,15 @@ async def render_blog_page(ctx: dict) -> str: async def render_blog_oob(ctx: dict) -> str: - root_hdr = root_header_sx(ctx, oob=True) - blog_oob = _oob_header_sx("root-header-child", "blog-header-child", - _blog_header_sx(ctx)) + root_hdr = root_header_sx(ctx) + blog_hdr = _blog_header_sx(ctx) + rows = "(<> " + root_hdr + " " + blog_hdr + ")" + header_oob = _oob_header_sx("root-header-child", "blog-header-child", rows) content = _blog_main_panel_sx(ctx) aside = _blog_aside_sx(ctx) filter_sx = _blog_filter_sx(ctx) nav = ctx.get("nav_sx", "") or "" - oobs = "(<> " + root_hdr + " " + blog_oob + ")" - return oob_page_sx(oobs=oobs, content=content, aside=aside, + return oob_page_sx(oobs=header_oob, content=content, aside=aside, filter=filter_sx, menu=nav) @@ -1136,7 +1142,9 @@ def render_editor_panel(save_error: str | None = None, is_page: bool = False) -> # Editor JS + init script init_js = ( + "console.log('[EDITOR-DEBUG] init script running');\n" "(function() {\n" + " console.log('[EDITOR-DEBUG] IIFE entered, mountEditor=', typeof window.mountEditor);\n" " function applyEditorFontSize() {\n" " document.documentElement.style.fontSize = '62.5%';\n" " document.body.style.fontSize = '1.6rem';\n" @@ -1282,11 +1290,12 @@ async def render_new_post_page(ctx: dict) -> str: async def render_new_post_oob(ctx: dict) -> str: - root_hdr = root_header_sx(ctx, oob=True) - blog_oob = _blog_header_sx(ctx, oob=True) + root_hdr = root_header_sx(ctx) + blog_hdr = _blog_header_sx(ctx) + rows = "(<> " + root_hdr + " " + blog_hdr + ")" + header_oob = _oob_header_sx("root-header-child", "blog-header-child", rows) content = ctx.get("editor_html", "") - oobs = "(<> " + root_hdr + " " + blog_oob + ")" - return oob_page_sx(oobs=oobs, content=content) + return oob_page_sx(oobs=header_oob, content=content) # ---- Post detail ---- @@ -1303,12 +1312,13 @@ async def render_post_page(ctx: dict) -> str: async def render_post_oob(ctx: dict) -> str: - root_hdr = root_header_sx(ctx, oob=True) - post_oob = _oob_header_sx("root-header-child", "post-header-child", - _post_header_sx(ctx)) + root_hdr = root_header_sx(ctx) # non-OOB (nested inside root-header-child) + post_hdr = _post_header_sx(ctx) + rows = "(<> " + root_hdr + " " + post_hdr + ")" + post_oob = _oob_header_sx("root-header-child", "post-header-child", rows) content = _post_main_panel_sx(ctx) menu = ctx.get("nav_sx", "") or "" - oobs = "(<> " + root_hdr + " " + post_oob + ")" + oobs = post_oob return oob_page_sx(oobs=oobs, content=content, menu=menu) @@ -1318,7 +1328,7 @@ async def render_post_admin_page(ctx: dict) -> str: root_hdr = root_header_sx(ctx) post_hdr = _post_header_sx(ctx) admin_hdr = _post_admin_header_sx(ctx) - header_rows = "(<> " + root_hdr + " " + post_hdr + ")" + admin_hdr + header_rows = "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")" content = _post_admin_main_panel_sx(ctx) menu = ctx.get("nav_sx", "") or "" return full_page_sx(ctx, header_rows=header_rows, content=content, @@ -1341,7 +1351,7 @@ async def render_post_data_page(ctx: dict) -> str: root_hdr = root_header_sx(ctx) post_hdr = _post_header_sx(ctx) admin_hdr = _post_admin_header_sx(ctx, selected="data") - header_rows = "(<> " + root_hdr + " " + post_hdr + ")" + admin_hdr + header_rows = "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")" content = _raw_html_sx(ctx.get("data_html", "")) return full_page_sx(ctx, header_rows=header_rows, content=content) @@ -1358,7 +1368,7 @@ async def render_post_entries_page(ctx: dict) -> str: root_hdr = root_header_sx(ctx) post_hdr = _post_header_sx(ctx) admin_hdr = _post_admin_header_sx(ctx, selected="entries") - header_rows = "(<> " + root_hdr + " " + post_hdr + ")" + admin_hdr + header_rows = "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")" content = _raw_html_sx(ctx.get("entries_html", "")) return full_page_sx(ctx, header_rows=header_rows, content=content) @@ -1382,7 +1392,7 @@ async def render_post_edit_page(ctx: dict) -> str: root_hdr = root_header_sx(ctx) post_hdr = _post_header_sx(ctx) admin_hdr = _post_admin_header_sx(ctx, selected="edit") - header_rows = "(<> " + root_hdr + " " + post_hdr + ")" + admin_hdr + header_rows = "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")" content = _raw_html_sx(ctx.get("edit_html", "")) return full_page_sx(ctx, header_rows=header_rows, content=content) @@ -1399,7 +1409,7 @@ async def render_post_settings_page(ctx: dict) -> str: root_hdr = root_header_sx(ctx) post_hdr = _post_header_sx(ctx) admin_hdr = _post_admin_header_sx(ctx, selected="settings") - header_rows = "(<> " + root_hdr + " " + post_hdr + ")" + admin_hdr + header_rows = "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")" content = _raw_html_sx(ctx.get("settings_html", "")) return full_page_sx(ctx, header_rows=header_rows, content=content) @@ -1423,13 +1433,13 @@ async def render_settings_page(ctx: dict) -> str: async def render_settings_oob(ctx: dict) -> str: - root_hdr = root_header_sx(ctx, oob=True) - settings_oob = _oob_header_sx("root-header-child", "root-settings-header-child", - _settings_header_sx(ctx)) + root_hdr = root_header_sx(ctx) + settings_hdr = _settings_header_sx(ctx) + rows = "(<> " + root_hdr + " " + settings_hdr + ")" + header_oob = _oob_header_sx("root-header-child", "root-settings-header-child", rows) content = _settings_main_panel_sx(ctx) menu = _settings_nav_sx(ctx) - oobs = "(<> " + root_hdr + " " + settings_oob + ")" - return oob_page_sx(oobs=oobs, content=content, menu=menu) + return oob_page_sx(oobs=header_oob, content=content, menu=menu) # ---- Cache ---- @@ -1442,7 +1452,7 @@ async def render_cache_page(ctx: dict) -> str: "cache-row", "cache-header-child", qurl("settings.cache"), "refresh", "Cache", ctx, ) - header_rows = "(<> " + root_hdr + " " + settings_hdr + ")" + cache_hdr + header_rows = "(<> " + root_hdr + " " + settings_hdr + " " + cache_hdr + ")" content = _cache_main_panel_sx(ctx) return full_page_sx(ctx, header_rows=header_rows, content=content) @@ -1471,7 +1481,7 @@ async def render_snippets_page(ctx: dict) -> str: "snippets-row", "snippets-header-child", qurl("snippets.list_snippets"), "puzzle-piece", "Snippets", ctx, ) - header_rows = "(<> " + root_hdr + " " + settings_hdr + ")" + snippets_hdr + header_rows = "(<> " + root_hdr + " " + settings_hdr + " " + snippets_hdr + ")" content = _snippets_main_panel_sx(ctx) return full_page_sx(ctx, header_rows=header_rows, content=content) @@ -1500,7 +1510,7 @@ async def render_menu_items_page(ctx: dict) -> str: "menu_items-row", "menu_items-header-child", qurl("menu_items.list_menu_items"), "bars", "Menu Items", ctx, ) - header_rows = "(<> " + root_hdr + " " + settings_hdr + ")" + mi_hdr + header_rows = "(<> " + root_hdr + " " + settings_hdr + " " + mi_hdr + ")" content = _menu_items_main_panel_sx(ctx) return full_page_sx(ctx, header_rows=header_rows, content=content) @@ -1529,7 +1539,7 @@ async def render_tag_groups_page(ctx: dict) -> str: "tag-groups-row", "tag-groups-header-child", qurl("blog.tag_groups_admin.index"), "tags", "Tag Groups", ctx, ) - header_rows = "(<> " + root_hdr + " " + settings_hdr + ")" + tg_hdr + header_rows = "(<> " + root_hdr + " " + settings_hdr + " " + tg_hdr + ")" content = _tag_groups_main_panel_sx(ctx) return full_page_sx(ctx, header_rows=header_rows, content=content) @@ -1559,7 +1569,7 @@ async def render_tag_group_edit_page(ctx: dict) -> str: "tag-groups-row", "tag-groups-header-child", qurl("blog.tag_groups_admin.edit", id=g_id), "tags", "Tag Groups", ctx, ) - header_rows = "(<> " + root_hdr + " " + settings_hdr + ")" + tg_hdr + header_rows = "(<> " + root_hdr + " " + settings_hdr + " " + tg_hdr + ")" content = _tag_groups_edit_main_panel_sx(ctx) return full_page_sx(ctx, header_rows=header_rows, content=content) diff --git a/shared/static/scripts/sx.js b/shared/static/scripts/sx.js index 669f773..5844725 100644 --- a/shared/static/scripts/sx.js +++ b/shared/static/scripts/sx.js @@ -122,7 +122,7 @@ this._advance(m[0].length); var raw = m[0].slice(1, -1); return raw.replace(/\\n/g, "\n").replace(/\\t/g, "\t") - .replace(/\\"/g, '"').replace(/\\\\/g, "\\"); + .replace(/\\"/g, '"').replace(/\\[/]/g, "/").replace(/\\\\/g, "\\"); } // Keyword @@ -1115,8 +1115,14 @@ } var open = "<" + tag + attrs.join("") + ">"; if (VOID_ELEMENTS[tag]) return open; + var isRawText = (tag === "script" || tag === "style"); var inner = []; - for (var ci = 0; ci < children.length; ci++) inner.push(renderStr(children[ci], env)); + for (var ci = 0; ci < children.length; ci++) { + var child = children[ci]; + if (isRawText && typeof child === "string") inner.push(child); + else if (isRawText && isSym(child)) inner.push(String(sxEval(child, env))); + else inner.push(renderStr(child, env)); + } return open + inner.join("") + ""; } @@ -1413,7 +1419,7 @@ var PROCESSED = "_sxBound"; var VERBS = ["get", "post", "put", "delete", "patch"]; var DEFAULT_SWAP = "outerHTML"; - var HISTORY_MAX = 20; + function dispatch(el, name, detail) { var evt = new CustomEvent(name, { bubbles: true, cancelable: true, detail: detail || {} }); @@ -1627,7 +1633,12 @@ // Check for text/sx content type var ct = resp.headers.get("Content-Type") || ""; if (ct.indexOf("text/sx") >= 0) { - try { text = Sx.renderToString(text); } + try { + // Strip and load any