Add SX editor to post edit page, prevent sx_content clearing on save
- Add sx_content to _post_to_edit_dict so edit page receives existing content - Add SX/Koenig editor tabs, sx-editor mount point, and SxEditor.mount init - Only pass sx_content to writer_update when form field is present (prevents accidental clearing when editing via Koenig-only path) - Add csrf_exempt to example API POST/DELETE/PUT demo endpoints - Add defpage infrastructure (pages.py, layouts.py) and sx docs page definitions - Add defhandler definitions for example API handlers (examples.sx) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
139
shared/sx/layouts.py
Normal file
139
shared/sx/layouts.py
Normal file
@@ -0,0 +1,139 @@
|
||||
"""
|
||||
Named layout presets for defpage.
|
||||
|
||||
Each layout generates header rows for full-page and OOB rendering.
|
||||
Layouts wrap existing helper functions from ``shared.sx.helpers`` so
|
||||
defpage can reference them by name (e.g. ``:layout :root``).
|
||||
|
||||
Layouts are registered in ``_LAYOUT_REGISTRY`` and looked up by
|
||||
``get_layout()`` at request time.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable, Awaitable
|
||||
|
||||
from .helpers import (
|
||||
root_header_sx, post_header_sx, post_admin_header_sx,
|
||||
oob_header_sx, header_child_sx,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Layout protocol
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class Layout:
|
||||
"""A named layout that generates header rows for full and OOB rendering."""
|
||||
|
||||
__slots__ = ("name", "_full_fn", "_oob_fn")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
full_fn: Callable[..., str | Awaitable[str]],
|
||||
oob_fn: Callable[..., str | Awaitable[str]],
|
||||
):
|
||||
self.name = name
|
||||
self._full_fn = full_fn
|
||||
self._oob_fn = oob_fn
|
||||
|
||||
async def full_headers(self, ctx: dict, **kwargs: Any) -> str:
|
||||
result = self._full_fn(ctx, **kwargs)
|
||||
if hasattr(result, "__await__"):
|
||||
result = await result
|
||||
return result
|
||||
|
||||
async def oob_headers(self, ctx: dict, **kwargs: Any) -> str:
|
||||
result = self._oob_fn(ctx, **kwargs)
|
||||
if hasattr(result, "__await__"):
|
||||
result = await result
|
||||
return result
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Layout:{self.name}>"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Registry
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_LAYOUT_REGISTRY: dict[str, Layout] = {}
|
||||
|
||||
|
||||
def register_layout(layout: Layout) -> None:
|
||||
"""Register a layout preset."""
|
||||
_LAYOUT_REGISTRY[layout.name] = layout
|
||||
|
||||
|
||||
def get_layout(name: str) -> Layout | None:
|
||||
"""Look up a layout by name."""
|
||||
return _LAYOUT_REGISTRY.get(name)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Built-in layouts
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _root_full(ctx: dict, **kw: Any) -> str:
|
||||
return root_header_sx(ctx)
|
||||
|
||||
|
||||
def _root_oob(ctx: dict, **kw: Any) -> str:
|
||||
root_hdr = root_header_sx(ctx)
|
||||
return oob_header_sx("root-header-child", "root-header-child", root_hdr)
|
||||
|
||||
|
||||
def _post_full(ctx: dict, **kw: Any) -> str:
|
||||
root_hdr = root_header_sx(ctx)
|
||||
post_hdr = post_header_sx(ctx)
|
||||
return "(<> " + root_hdr + " " + post_hdr + ")"
|
||||
|
||||
|
||||
def _post_oob(ctx: dict, **kw: Any) -> str:
|
||||
post_hdr = post_header_sx(ctx, oob=True)
|
||||
return post_hdr
|
||||
|
||||
|
||||
def _post_admin_full(ctx: dict, **kw: Any) -> str:
|
||||
slug = ctx.get("post", {}).get("slug", "")
|
||||
selected = kw.get("selected", "")
|
||||
root_hdr = root_header_sx(ctx)
|
||||
post_hdr = post_header_sx(ctx)
|
||||
admin_hdr = post_admin_header_sx(ctx, slug, selected=selected)
|
||||
return "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")"
|
||||
|
||||
|
||||
def _post_admin_oob(ctx: dict, **kw: Any) -> str:
|
||||
slug = ctx.get("post", {}).get("slug", "")
|
||||
selected = kw.get("selected", "")
|
||||
post_hdr = post_header_sx(ctx, oob=True)
|
||||
admin_hdr = post_admin_header_sx(ctx, slug, selected=selected)
|
||||
admin_oob = oob_header_sx("post-header-child", "post-admin-header-child", admin_hdr)
|
||||
return "(<> " + post_hdr + " " + admin_oob + ")"
|
||||
|
||||
|
||||
register_layout(Layout("root", _root_full, _root_oob))
|
||||
register_layout(Layout("post", _post_full, _post_oob))
|
||||
register_layout(Layout("post-admin", _post_admin_full, _post_admin_oob))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Callable layout — services register custom Python layout functions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_CUSTOM_LAYOUTS: dict[str, tuple] = {} # name → (full_fn, oob_fn)
|
||||
|
||||
|
||||
def register_custom_layout(name: str,
|
||||
full_fn: Callable[..., str | Awaitable[str]],
|
||||
oob_fn: Callable[..., str | Awaitable[str]]) -> None:
|
||||
"""Register a custom layout function.
|
||||
|
||||
Used by services with non-standard header patterns::
|
||||
|
||||
register_custom_layout("sx-section",
|
||||
full_fn=my_full_headers,
|
||||
oob_fn=my_oob_headers)
|
||||
"""
|
||||
register_layout(Layout(name, full_fn, oob_fn))
|
||||
Reference in New Issue
Block a user