Add SX editor to post edit page, prevent sx_content clearing on save
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m5s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 4m5s
- 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:
169
sx/sxc/pages/__init__.py
Normal file
169
sx/sxc/pages/__init__.py
Normal file
@@ -0,0 +1,169 @@
|
||||
"""SX docs defpage setup — registers layouts and page helpers."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
|
||||
def setup_sx_pages() -> None:
|
||||
"""Register sx-specific layouts, page helpers, and load page definitions.
|
||||
|
||||
Called during app startup before mount_pages().
|
||||
"""
|
||||
_register_sx_layouts()
|
||||
_register_sx_helpers()
|
||||
_load_sx_page_files()
|
||||
|
||||
|
||||
def _load_sx_page_files() -> None:
|
||||
"""Load defpage definitions from sx/sxc/pages/*.sx."""
|
||||
import os
|
||||
from shared.sx.pages import load_page_dir
|
||||
pages_dir = os.path.dirname(__file__)
|
||||
load_page_dir(pages_dir, "sx")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Layouts
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _register_sx_layouts() -> None:
|
||||
"""Register the sx docs layout presets."""
|
||||
from shared.sx.layouts import register_custom_layout
|
||||
|
||||
register_custom_layout("sx", _sx_full_headers, _sx_oob_headers)
|
||||
register_custom_layout("sx-section", _sx_section_full_headers, _sx_section_oob_headers)
|
||||
|
||||
|
||||
def _sx_full_headers(ctx: dict, **kw: Any) -> str:
|
||||
"""Full headers for sx home page: root + sx menu row."""
|
||||
from shared.sx.helpers import root_header_sx
|
||||
from sxc.sx_components import _sx_header_sx, _main_nav_sx
|
||||
|
||||
main_nav = _main_nav_sx(kw.get("section"))
|
||||
root_hdr = root_header_sx(ctx)
|
||||
sx_row = _sx_header_sx(main_nav)
|
||||
return "(<> " + root_hdr + " " + sx_row + ")"
|
||||
|
||||
|
||||
def _sx_oob_headers(ctx: dict, **kw: Any) -> str:
|
||||
"""OOB headers for sx home page."""
|
||||
from shared.sx.helpers import root_header_sx, oob_header_sx
|
||||
from sxc.sx_components import _sx_header_sx, _main_nav_sx
|
||||
|
||||
root_hdr = root_header_sx(ctx)
|
||||
main_nav = _main_nav_sx(kw.get("section"))
|
||||
sx_row = _sx_header_sx(main_nav)
|
||||
rows = "(<> " + root_hdr + " " + sx_row + ")"
|
||||
return oob_header_sx("root-header-child", "sx-header-child", rows)
|
||||
|
||||
|
||||
def _sx_section_full_headers(ctx: dict, **kw: Any) -> str:
|
||||
"""Full headers for sx section pages: root + sx row + sub row."""
|
||||
from shared.sx.helpers import root_header_sx
|
||||
from sxc.sx_components import (
|
||||
_sx_header_sx, _main_nav_sx, _sub_row_sx,
|
||||
)
|
||||
|
||||
section = kw.get("section", "")
|
||||
sub_label = kw.get("sub_label", section)
|
||||
sub_href = kw.get("sub_href", "/")
|
||||
sub_nav = kw.get("sub_nav", "")
|
||||
selected = kw.get("selected", "")
|
||||
|
||||
root_hdr = root_header_sx(ctx)
|
||||
main_nav = _main_nav_sx(section)
|
||||
sub_row = _sub_row_sx(sub_label, sub_href, sub_nav, selected)
|
||||
sx_row = _sx_header_sx(main_nav, child=sub_row)
|
||||
return "(<> " + root_hdr + " " + sx_row + ")"
|
||||
|
||||
|
||||
def _sx_section_oob_headers(ctx: dict, **kw: Any) -> str:
|
||||
"""OOB headers for sx section pages."""
|
||||
from shared.sx.helpers import root_header_sx, oob_header_sx
|
||||
from sxc.sx_components import (
|
||||
_sx_header_sx, _main_nav_sx, _sub_row_sx,
|
||||
)
|
||||
|
||||
section = kw.get("section", "")
|
||||
sub_label = kw.get("sub_label", section)
|
||||
sub_href = kw.get("sub_href", "/")
|
||||
sub_nav = kw.get("sub_nav", "")
|
||||
selected = kw.get("selected", "")
|
||||
|
||||
root_hdr = root_header_sx(ctx)
|
||||
main_nav = _main_nav_sx(section)
|
||||
sub_row = _sub_row_sx(sub_label, sub_href, sub_nav, selected)
|
||||
sx_row = _sx_header_sx(main_nav, child=sub_row)
|
||||
rows = "(<> " + root_hdr + " " + sx_row + ")"
|
||||
return oob_header_sx("root-header-child", "sx-header-child", rows)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Page helpers — Python functions callable from defpage content expressions
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _register_sx_helpers() -> None:
|
||||
"""Register Python content builder functions as page helpers."""
|
||||
from shared.sx.pages import register_page_helpers
|
||||
from sxc.sx_components import (
|
||||
_docs_content_sx, _reference_content_sx,
|
||||
_protocol_content_sx, _examples_content_sx,
|
||||
_essay_content_sx,
|
||||
_docs_nav_sx, _reference_nav_sx,
|
||||
_protocols_nav_sx, _examples_nav_sx, _essays_nav_sx,
|
||||
)
|
||||
from content.highlight import highlight as _highlight
|
||||
from content.pages import (
|
||||
DOCS_NAV, REFERENCE_NAV, PROTOCOLS_NAV,
|
||||
EXAMPLES_NAV, ESSAYS_NAV,
|
||||
)
|
||||
|
||||
def _find_current(nav_list, slug, match_fn=None):
|
||||
"""Find the current nav label for a slug."""
|
||||
if match_fn:
|
||||
return match_fn(nav_list, slug)
|
||||
for label, href in nav_list:
|
||||
if href.endswith(slug):
|
||||
return label
|
||||
return None
|
||||
|
||||
def _home_content():
|
||||
"""Build home page content (uses highlight for hero code block)."""
|
||||
hero_code = _highlight(
|
||||
'(div :class "p-4 bg-white rounded shadow"\n'
|
||||
' (h1 :class "text-2xl font-bold" "Hello")\n'
|
||||
' (button :sx-get "/api/data"\n'
|
||||
' :sx-target "#result"\n'
|
||||
' "Load data"))', "lisp")
|
||||
return (
|
||||
f'(div :id "main-content"'
|
||||
f' (~sx-hero {hero_code})'
|
||||
f' (~sx-philosophy)'
|
||||
f' (~sx-how-it-works)'
|
||||
f' (~sx-credits))'
|
||||
)
|
||||
|
||||
register_page_helpers("sx", {
|
||||
# Content builders
|
||||
"home-content": _home_content,
|
||||
"docs-content": _docs_content_sx,
|
||||
"reference-content": _reference_content_sx,
|
||||
"protocol-content": _protocol_content_sx,
|
||||
"examples-content": _examples_content_sx,
|
||||
"essay-content": _essay_content_sx,
|
||||
"highlight": _highlight,
|
||||
# Nav builders
|
||||
"docs-nav": _docs_nav_sx,
|
||||
"reference-nav": _reference_nav_sx,
|
||||
"protocols-nav": _protocols_nav_sx,
|
||||
"examples-nav": _examples_nav_sx,
|
||||
"essays-nav": _essays_nav_sx,
|
||||
# Nav data (for current label lookup)
|
||||
"DOCS_NAV": DOCS_NAV,
|
||||
"REFERENCE_NAV": REFERENCE_NAV,
|
||||
"PROTOCOLS_NAV": PROTOCOLS_NAV,
|
||||
"EXAMPLES_NAV": EXAMPLES_NAV,
|
||||
"ESSAYS_NAV": ESSAYS_NAV,
|
||||
# Utility
|
||||
"find-current": _find_current,
|
||||
})
|
||||
98
sx/sxc/pages/docs.sx
Normal file
98
sx/sxc/pages/docs.sx
Normal file
@@ -0,0 +1,98 @@
|
||||
;; SX docs app — declarative page definitions
|
||||
;; These replace the GET route handlers in routes.py
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Home page
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage home
|
||||
:path "/"
|
||||
:auth :public
|
||||
:layout :sx
|
||||
:content (home-content))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Docs section
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage docs-page
|
||||
:path "/docs/<slug>"
|
||||
:auth :public
|
||||
:layout (:sx-section
|
||||
:section "Docs"
|
||||
:sub-label "Docs"
|
||||
:sub-href "/docs/introduction"
|
||||
:sub-nav (docs-nav (find-current DOCS_NAV slug))
|
||||
:selected (or (find-current DOCS_NAV slug) ""))
|
||||
:content (docs-content slug))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Reference section
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage reference-index
|
||||
:path "/reference/"
|
||||
:auth :public
|
||||
:layout (:sx-section
|
||||
:section "Reference"
|
||||
:sub-label "Reference"
|
||||
:sub-href "/reference/"
|
||||
:sub-nav (reference-nav "Attributes")
|
||||
:selected "Attributes")
|
||||
:content (reference-content ""))
|
||||
|
||||
(defpage reference-page
|
||||
:path "/reference/<slug>"
|
||||
:auth :public
|
||||
:layout (:sx-section
|
||||
:section "Reference"
|
||||
:sub-label "Reference"
|
||||
:sub-href "/reference/"
|
||||
:sub-nav (reference-nav (find-current REFERENCE_NAV slug))
|
||||
:selected (or (find-current REFERENCE_NAV slug) ""))
|
||||
:content (reference-content slug))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Protocols section
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage protocol-page
|
||||
:path "/protocols/<slug>"
|
||||
:auth :public
|
||||
:layout (:sx-section
|
||||
:section "Protocols"
|
||||
:sub-label "Protocols"
|
||||
:sub-href "/protocols/wire-format"
|
||||
:sub-nav (protocols-nav (find-current PROTOCOLS_NAV slug))
|
||||
:selected (or (find-current PROTOCOLS_NAV slug) ""))
|
||||
:content (protocol-content slug))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Examples section
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage examples-page
|
||||
:path "/examples/<slug>"
|
||||
:auth :public
|
||||
:layout (:sx-section
|
||||
:section "Examples"
|
||||
:sub-label "Examples"
|
||||
:sub-href "/examples/click-to-load"
|
||||
:sub-nav (examples-nav (find-current EXAMPLES_NAV slug))
|
||||
:selected (or (find-current EXAMPLES_NAV slug) ""))
|
||||
:content (examples-content slug))
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Essays section
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
(defpage essay-page
|
||||
:path "/essays/<slug>"
|
||||
:auth :public
|
||||
:layout (:sx-section
|
||||
:section "Essays"
|
||||
:sub-label "Essays"
|
||||
:sub-href "/essays/sx-sucks"
|
||||
:sub-nav (essays-nav (find-current ESSAYS_NAV slug))
|
||||
:selected (or (find-current ESSAYS_NAV slug) ""))
|
||||
:content (essay-content slug))
|
||||
Reference in New Issue
Block a user