Fix duplicate headers on HTMX nav, editor content loading, and double mount
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m14s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m14s
- Nest admin header inside post-header-child (layouts.py/helpers.py) so full-page DOM matches OOB swap structure, eliminating duplicate headers - Clear post-header-child on post layout OOB to remove stale admin rows - Read SX initial content from #sx-content-input instead of window.__SX_INITIAL__ to avoid escaping issues through SX pipeline - Fix client-side SX parser RE_STRING to handle escaped newlines - Clear root element in SxEditor.mount() to prevent double content on HTMX re-mount - Remove unused ~blog-editor-sx-initial component Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -153,10 +153,6 @@
|
|||||||
" sync();"
|
" sync();"
|
||||||
"})();"))
|
"})();"))
|
||||||
|
|
||||||
;; SX initial content script for edit form
|
|
||||||
(defcomp ~blog-editor-sx-initial (&key sx-content)
|
|
||||||
(script (str "window.__SX_INITIAL__ = '" sx-content "' || null;")))
|
|
||||||
|
|
||||||
(defcomp ~blog-editor-styles (&key css-href)
|
(defcomp ~blog-editor-styles (&key css-href)
|
||||||
(<> (link :rel "stylesheet" :href css-href)
|
(<> (link :rel "stylesheet" :href css-href)
|
||||||
(style
|
(style
|
||||||
|
|||||||
@@ -1271,7 +1271,7 @@ def render_editor_panel(save_error: str | None = None, is_page: bool = False) ->
|
|||||||
"\n"
|
"\n"
|
||||||
" if (typeof SxEditor !== 'undefined') {\n"
|
" if (typeof SxEditor !== 'undefined') {\n"
|
||||||
" SxEditor.mount('sx-editor', {\n"
|
" SxEditor.mount('sx-editor', {\n"
|
||||||
" initialSx: window.__SX_INITIAL__ || null,\n"
|
" initialSx: (document.getElementById('sx-content-input') || {}).value || null,\n"
|
||||||
" csrfToken: csrfToken,\n"
|
" csrfToken: csrfToken,\n"
|
||||||
" uploadUrls: uploadUrls,\n"
|
" uploadUrls: uploadUrls,\n"
|
||||||
f" oembedUrl: '{oembed_url}',\n"
|
f" oembedUrl: '{oembed_url}',\n"
|
||||||
@@ -1748,16 +1748,13 @@ def _post_edit_content_sx(ctx: dict) -> str:
|
|||||||
badge_parts.append(f'(span :class "inline-block px-2 py-0.5 rounded-full text-xs font-semibold bg-green-100 text-green-800" "Emailed{suffix}")')
|
badge_parts.append(f'(span :class "inline-block px-2 py-0.5 rounded-full text-xs font-semibold bg-green-100 text-green-800" "Emailed{suffix}")')
|
||||||
footer_extra_sx = SxExpr("(<> " + " ".join(badge_parts) + ")") if badge_parts else None
|
footer_extra_sx = SxExpr("(<> " + " ".join(badge_parts) + ")") if badge_parts else None
|
||||||
|
|
||||||
# Escape sx_content for JS string literal
|
|
||||||
sx_initial_escaped = sx_content.replace('\\', '\\\\').replace("'", "\\'").replace('\n', '\\n').replace('\r', '')
|
|
||||||
|
|
||||||
parts: list[str] = []
|
parts: list[str] = []
|
||||||
|
|
||||||
# Error banner
|
# Error banner
|
||||||
if save_error:
|
if save_error:
|
||||||
parts.append(sx_call("blog-editor-error", error=save_error))
|
parts.append(sx_call("blog-editor-error", error=save_error))
|
||||||
|
|
||||||
# Form
|
# Form (sx_content_val populates #sx-content-input; JS reads from there)
|
||||||
parts.append(sx_call("blog-editor-edit-form",
|
parts.append(sx_call("blog-editor-edit-form",
|
||||||
csrf=csrf,
|
csrf=csrf,
|
||||||
updated_at=str(updated_at),
|
updated_at=str(updated_at),
|
||||||
@@ -1778,9 +1775,6 @@ def _post_edit_content_sx(ctx: dict) -> str:
|
|||||||
# Publish-mode JS
|
# Publish-mode JS
|
||||||
parts.append(sx_call("blog-editor-publish-js", already_emailed=already_emailed))
|
parts.append(sx_call("blog-editor-publish-js", already_emailed=already_emailed))
|
||||||
|
|
||||||
# SX initial content
|
|
||||||
parts.append(sx_call("blog-editor-sx-initial", sx_content=sx_initial_escaped))
|
|
||||||
|
|
||||||
# Editor CSS + styles
|
# Editor CSS + styles
|
||||||
parts.append(sx_call("blog-editor-styles", css_href=editor_css))
|
parts.append(sx_call("blog-editor-styles", css_href=editor_css))
|
||||||
parts.append(sx_call("sx-editor-styles"))
|
parts.append(sx_call("sx-editor-styles"))
|
||||||
@@ -1863,7 +1857,7 @@ def _post_edit_content_sx(ctx: dict) -> str:
|
|||||||
' });'
|
' });'
|
||||||
" if (typeof SxEditor !== 'undefined') {"
|
" if (typeof SxEditor !== 'undefined') {"
|
||||||
" SxEditor.mount('sx-editor', {"
|
" SxEditor.mount('sx-editor', {"
|
||||||
" initialSx: window.__SX_INITIAL__ || null,"
|
" initialSx: (document.getElementById('sx-content-input') || {}).value || null,"
|
||||||
' csrfToken: csrfToken,'
|
' csrfToken: csrfToken,'
|
||||||
' uploadUrls: uploadUrls,'
|
' uploadUrls: uploadUrls,'
|
||||||
f" oembedUrl: '{oembed_url}',"
|
f" oembedUrl: '{oembed_url}',"
|
||||||
|
|||||||
@@ -2249,7 +2249,9 @@
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
root.className = (root.className || "") + " sx-editor";
|
// Clear any previous mount
|
||||||
|
root.innerHTML = "";
|
||||||
|
root.className = (root.className || "").replace(/\bsx-editor\b/g, "").trim() + " sx-editor";
|
||||||
|
|
||||||
var container = el("div", { className: "sx-blocks-container" });
|
var container = el("div", { className: "sx-blocks-container" });
|
||||||
root.appendChild(container);
|
root.appendChild(container);
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
|
|
||||||
var RE_WS = /\s+/y;
|
var RE_WS = /\s+/y;
|
||||||
var RE_COMMENT = /;[^\n]*/y;
|
var RE_COMMENT = /;[^\n]*/y;
|
||||||
var RE_STRING = /"(?:[^"\\]|\\.)*"/y;
|
var RE_STRING = /"(?:[^"\\]|\\[\s\S])*"/y;
|
||||||
var RE_NUMBER = /-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/y;
|
var RE_NUMBER = /-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/y;
|
||||||
var RE_KEYWORD = /:[a-zA-Z_][a-zA-Z0-9_>:\-]*/y;
|
var RE_KEYWORD = /:[a-zA-Z_][a-zA-Z0-9_>:\-]*/y;
|
||||||
var RE_SYMBOL = /[a-zA-Z_~*+\-><=/!?&][a-zA-Z0-9_~*+\-><=/!?.:&]*/y;
|
var RE_SYMBOL = /[a-zA-Z_~*+\-><=/!?&][a-zA-Z0-9_~*+\-><=/!?.:&]*/y;
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ def search_desktop_sx(ctx: dict) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def post_header_sx(ctx: dict, *, oob: bool = False) -> str:
|
def post_header_sx(ctx: dict, *, oob: bool = False, child: str = "") -> str:
|
||||||
"""Build the post-level header row as sx call string."""
|
"""Build the post-level header row as sx call string."""
|
||||||
post = ctx.get("post") or {}
|
post = ctx.get("post") or {}
|
||||||
slug = post.get("slug", "")
|
slug = post.get("slug", "")
|
||||||
@@ -273,6 +273,7 @@ def post_header_sx(ctx: dict, *, oob: bool = False) -> str:
|
|||||||
link_label_content=SxExpr(label_sx),
|
link_label_content=SxExpr(label_sx),
|
||||||
nav=SxExpr(nav_sx) if nav_sx else None,
|
nav=SxExpr(nav_sx) if nav_sx else None,
|
||||||
child_id="post-header-child",
|
child_id="post-header-child",
|
||||||
|
child=SxExpr(child) if child else None,
|
||||||
oob=oob, external=True,
|
oob=oob, external=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -104,16 +104,18 @@ def _post_full(ctx: dict, **kw: Any) -> str:
|
|||||||
|
|
||||||
def _post_oob(ctx: dict, **kw: Any) -> str:
|
def _post_oob(ctx: dict, **kw: Any) -> str:
|
||||||
post_hdr = post_header_sx(ctx, oob=True)
|
post_hdr = post_header_sx(ctx, oob=True)
|
||||||
return post_hdr
|
# Also replace #post-header-child (empty — clears any nested admin rows)
|
||||||
|
child_oob = oob_header_sx("post-header-child", "", "")
|
||||||
|
return "(<> " + post_hdr + " " + child_oob + ")"
|
||||||
|
|
||||||
|
|
||||||
def _post_admin_full(ctx: dict, **kw: Any) -> str:
|
def _post_admin_full(ctx: dict, **kw: Any) -> str:
|
||||||
slug = ctx.get("post", {}).get("slug", "")
|
slug = ctx.get("post", {}).get("slug", "")
|
||||||
selected = kw.get("selected", "")
|
selected = kw.get("selected", "")
|
||||||
root_hdr = root_header_sx(ctx)
|
root_hdr = root_header_sx(ctx)
|
||||||
post_hdr = post_header_sx(ctx)
|
|
||||||
admin_hdr = post_admin_header_sx(ctx, slug, selected=selected)
|
admin_hdr = post_admin_header_sx(ctx, slug, selected=selected)
|
||||||
return "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")"
|
post_hdr = post_header_sx(ctx, child=admin_hdr)
|
||||||
|
return "(<> " + root_hdr + " " + post_hdr + ")"
|
||||||
|
|
||||||
|
|
||||||
def _post_admin_oob(ctx: dict, **kw: Any) -> str:
|
def _post_admin_oob(ctx: dict, **kw: Any) -> str:
|
||||||
|
|||||||
Reference in New Issue
Block a user