Add admin preview views + fix markdown converter
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m31s

- Fix _markdown() in lexical_to_sx.py: render markdown to HTML with
  mistune.html() before storing in ~kg-html
- Add shared/sx/prettify.py: sx_to_pretty_sx and json_to_pretty_sx
  produce sx AST for syntax-highlighted DOM (uses canonical serialize())
- Add preview tab to admin header nav
- Add GET /preview/ route with 4 views: prettified sx, prettified
  lexical JSON, sx rendered HTML, lexical rendered HTML
- Add ~blog-preview-panel and ~blog-preview-section components
- Add syntax highlight CSS for sx/JSON tokens

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 00:50:57 +00:00
parent a8e06e87fb
commit 4ede0368dc
6 changed files with 300 additions and 1 deletions

View File

@@ -1377,6 +1377,68 @@ async def render_post_data_oob(ctx: dict) -> str:
return oob_page_sx(oobs=admin_hdr_oob, content=content)
# ---- Post preview ----
def _preview_main_panel_sx(ctx: dict) -> str:
"""Build the preview panel with 4 expandable sections."""
sections: list[str] = []
# 1. Prettified SX source
sx_pretty = ctx.get("sx_pretty", "")
if sx_pretty:
sections.append(sx_call("blog-preview-section",
title="S-Expression Source",
content=SxExpr(sx_pretty),
))
# 2. Prettified Lexical JSON
json_pretty = ctx.get("json_pretty", "")
if json_pretty:
sections.append(sx_call("blog-preview-section",
title="Lexical JSON",
content=SxExpr(json_pretty),
))
# 3. SX rendered preview
sx_rendered = ctx.get("sx_rendered", "")
if sx_rendered:
rendered_sx = f'(div :class "blog-content prose max-w-none" (raw! {sx_serialize(sx_rendered)}))'
sections.append(sx_call("blog-preview-section",
title="SX Rendered",
content=SxExpr(rendered_sx),
))
# 4. Lexical rendered preview
lex_rendered = ctx.get("lex_rendered", "")
if lex_rendered:
rendered_sx = f'(div :class "blog-content prose max-w-none" (raw! {sx_serialize(lex_rendered)}))'
sections.append(sx_call("blog-preview-section",
title="Lexical Rendered",
content=SxExpr(rendered_sx),
))
if not sections:
return '(div :class "p-8 text-stone-500" "No content to preview.")'
inner = " ".join(sections)
return sx_call("blog-preview-panel", sections=SxExpr(f"(<> {inner})"))
async def render_post_preview_page(ctx: dict) -> str:
root_hdr = root_header_sx(ctx)
post_hdr = _post_header_sx(ctx)
admin_hdr = _post_admin_header_sx(ctx, selected="preview")
header_rows = "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")"
content = _preview_main_panel_sx(ctx)
return full_page_sx(ctx, header_rows=header_rows, content=content)
async def render_post_preview_oob(ctx: dict) -> str:
admin_hdr_oob = _post_admin_header_sx(ctx, oob=True, selected="preview")
content = _preview_main_panel_sx(ctx)
return oob_page_sx(oobs=admin_hdr_oob, content=content)
# ---- Post entries ----
async def render_post_entries_page(ctx: dict) -> str: