Convert all API endpoint URLs to SX expression format

Every URL at sx-web.org now uses bracketed SX expressions — pages AND
API endpoints. defhandler :path values, sx-get/sx-post/sx-delete attrs,
code examples, and Python route decorators all converted.

- Add SxAtomConverter to handlers.py for parameter matching inside
  expression URLs (e.g. /(api.(item.<sx:item_id>)))
- Convert ~50 defhandler :path values in ref-api.sx and examples.sx
- Convert ~90 sx-get/sx-post/sx-delete URLs in reference.sx, examples.sx
- Convert ~30 code example URLs in examples-content.sx
- Convert ~30 API URLs in pages.py (Python string code examples)
- Convert ~70 page navigation URLs in pages.py
- Convert 7 Python route decorators in routes.py
- Convert ~10 reactive API URLs in marshes.sx
- Add API redirect patterns to sx_router.py (301 for old paths)
- Remove /api/ skip in app.py redirects (old API paths now redirect)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 10:02:26 +00:00
parent da1ca6009a
commit feecbb66ba
11 changed files with 358 additions and 299 deletions

View File

@@ -24,11 +24,25 @@ import logging
import os import os
from typing import Any, Callable, Awaitable from typing import Any, Callable, Awaitable
from werkzeug.routing import BaseConverter
from .types import HandlerDef from .types import HandlerDef
logger = logging.getLogger("sx.handlers") logger = logging.getLogger("sx.handlers")
class SxAtomConverter(BaseConverter):
"""URL converter for SX atoms inside expression URLs.
Matches a single atom — stops at dots, parens, slashes, and query chars.
Use as ``<sx:param>`` in route patterns like::
/(geography.(hypermedia.(reference.(api.(item.<sx:item_id>)))))
"""
regex = r"[^./)(?&= ]+"
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Registry — service → handler-name → HandlerDef # Registry — service → handler-name → HandlerDef
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -229,6 +243,10 @@ def register_route_handlers(app_or_bp: Any, service_name: str) -> int:
from quart import Response, request from quart import Response, request
from shared.browser.app.csrf import csrf_exempt from shared.browser.app.csrf import csrf_exempt
# Register SX atom converter for expression URL parameters
if hasattr(app_or_bp, 'url_map'):
app_or_bp.url_map.converters.setdefault('sx', SxAtomConverter)
handlers = get_all_handlers(service_name) handlers = get_all_handlers(service_name)
count = 0 count = 0

View File

@@ -128,9 +128,6 @@ def create_app() -> "Quart":
# Skip SX expression URLs (already in new format) # Skip SX expression URLs (already in new format)
if path.startswith("/(") or path.startswith("/~"): if path.startswith("/(") or path.startswith("/~"):
return None return None
# Skip API/handler paths
if "/api/" in path:
return None
new_url = redirect_old_url(path) new_url = redirect_old_url(path)
if new_url: if new_url:
qs = request.query_string.decode() qs = request.query_string.decode()
@@ -149,7 +146,6 @@ def create_app() -> "Quart":
and not path.endswith("/") and not path.endswith("/")
and request.method == "GET" and request.method == "GET"
and not path.startswith(("/static/", "/internal/", "/auth/")) and not path.startswith(("/static/", "/internal/", "/auth/"))
and "/api/" not in path
and "." not in path.rsplit("/", 1)[-1]): and "." not in path.rsplit("/", 1)[-1]):
qs = request.query_string.decode() qs = request.query_string.decode()
target = path + "/" + ("?" + qs if qs else "") target = path + "/" + ("?" + qs if qs else "")

View File

@@ -23,7 +23,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# SSE stays in Python — fundamentally different paradigm (async generator). # SSE stays in Python — fundamentally different paradigm (async generator).
# ------------------------------------------------------------------ # ------------------------------------------------------------------
@bp.get("/geography/hypermedia/reference/api/sse-time") @bp.get("/(geography.(hypermedia.(reference.(api.sse-time))))")
async def ref_sse_time(): async def ref_sse_time():
async def generate(): async def generate():
for _ in range(30): # stream for 60 seconds max for _ in range(30): # stream for 60 seconds max
@@ -38,7 +38,7 @@ def register(url_prefix: str = "/") -> Blueprint:
_marsh_sale_idx = {"n": 0} _marsh_sale_idx = {"n": 0}
@bp.get("/geography/reactive/api/flash-sale") @bp.get("/(geography.(reactive.(api.flash-sale)))")
async def api_marsh_flash_sale(): async def api_marsh_flash_sale():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
prices = [14.99, 9.99, 24.99, 12.49, 7.99, 29.99, 4.99, 16.50] prices = [14.99, 9.99, 24.99, 12.49, 7.99, 29.99, 4.99, 16.50]
@@ -60,7 +60,7 @@ def register(url_prefix: str = "/") -> Blueprint:
_settle_counter = {"n": 0} _settle_counter = {"n": 0}
@bp.get("/geography/reactive/api/settle-data") @bp.get("/(geography.(reactive.(api.settle-data)))")
async def api_settle_data(): async def api_settle_data():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
_settle_counter["n"] += 1 _settle_counter["n"] += 1
@@ -76,7 +76,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Demo 4: signal-bound URL endpoints --- # --- Demo 4: signal-bound URL endpoints ---
@bp.get("/geography/reactive/api/search/products") @bp.get("/(geography.(reactive.(api.search-products)))")
async def api_search_products(): async def api_search_products():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
q = request.args.get("q", "") q = request.args.get("q", "")
@@ -95,7 +95,7 @@ def register(url_prefix: str = "/") -> Blueprint:
) )
return sx_response(sx_src) return sx_response(sx_src)
@bp.get("/geography/reactive/api/search/events") @bp.get("/(geography.(reactive.(api.search-events)))")
async def api_search_events(): async def api_search_events():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
q = request.args.get("q", "") q = request.args.get("q", "")
@@ -114,7 +114,7 @@ def register(url_prefix: str = "/") -> Blueprint:
) )
return sx_response(sx_src) return sx_response(sx_src)
@bp.get("/geography/reactive/api/search/posts") @bp.get("/(geography.(reactive.(api.search-posts)))")
async def api_search_posts(): async def api_search_posts():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
q = request.args.get("q", "") q = request.args.get("q", "")
@@ -135,7 +135,7 @@ def register(url_prefix: str = "/") -> Blueprint:
# --- Demo 5: marsh transform endpoint --- # --- Demo 5: marsh transform endpoint ---
@bp.get("/geography/reactive/api/catalog") @bp.get("/(geography.(reactive.(api.catalog)))")
async def api_catalog(): async def api_catalog():
from shared.sx.helpers import sx_response from shared.sx.helpers import sx_response
items = [ items = [

View File

@@ -10,79 +10,79 @@ from __future__ import annotations
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
DOCS_NAV = [ DOCS_NAV = [
("Introduction", "/language/docs/introduction"), ("Introduction", "/(language.(doc.introduction))"),
("Getting Started", "/language/docs/getting-started"), ("Getting Started", "/(language.(doc.getting-started))"),
("Components", "/language/docs/components"), ("Components", "/(language.(doc.components))"),
("Evaluator", "/language/docs/evaluator"), ("Evaluator", "/(language.(doc.evaluator))"),
("Primitives", "/language/docs/primitives"), ("Primitives", "/(language.(doc.primitives))"),
("CSS", "/language/docs/css"), ("CSS", "/(language.(doc.css))"),
("Server Rendering", "/language/docs/server-rendering"), ("Server Rendering", "/(language.(doc.server-rendering))"),
] ]
REFERENCE_NAV = [ REFERENCE_NAV = [
("Attributes", "/geography/hypermedia/reference/attributes"), ("Attributes", "/(geography.(hypermedia.(reference.attributes)))"),
("Headers", "/geography/hypermedia/reference/headers"), ("Headers", "/(geography.(hypermedia.(reference.headers)))"),
("Events", "/geography/hypermedia/reference/events"), ("Events", "/(geography.(hypermedia.(reference.events)))"),
("JS API", "/geography/hypermedia/reference/js-api"), ("JS API", "/(geography.(hypermedia.(reference.js-api)))"),
] ]
PROTOCOLS_NAV = [ PROTOCOLS_NAV = [
("Wire Format", "/applications/protocols/wire-format"), ("Wire Format", "/(applications.(protocol.wire-format))"),
("Fragments", "/applications/protocols/fragments"), ("Fragments", "/(applications.(protocol.fragments))"),
("Resolver I/O", "/applications/protocols/resolver-io"), ("Resolver I/O", "/(applications.(protocol.resolver-io))"),
("Internal Services", "/applications/protocols/internal-services"), ("Internal Services", "/(applications.(protocol.internal-services))"),
("ActivityPub", "/applications/protocols/activitypub"), ("ActivityPub", "/(applications.(protocol.activitypub))"),
("Future", "/applications/protocols/future"), ("Future", "/(applications.(protocol.future))"),
] ]
EXAMPLES_NAV = [ EXAMPLES_NAV = [
("Click to Load", "/geography/hypermedia/examples/click-to-load"), ("Click to Load", "/(geography.(hypermedia.(example.click-to-load)))"),
("Form Submission", "/geography/hypermedia/examples/form-submission"), ("Form Submission", "/(geography.(hypermedia.(example.form-submission)))"),
("Polling", "/geography/hypermedia/examples/polling"), ("Polling", "/(geography.(hypermedia.(example.polling)))"),
("Delete Row", "/geography/hypermedia/examples/delete-row"), ("Delete Row", "/(geography.(hypermedia.(example.delete-row)))"),
("Inline Edit", "/geography/hypermedia/examples/inline-edit"), ("Inline Edit", "/(geography.(hypermedia.(example.inline-edit)))"),
("OOB Swaps", "/geography/hypermedia/examples/oob-swaps"), ("OOB Swaps", "/(geography.(hypermedia.(example.oob-swaps)))"),
("Lazy Loading", "/geography/hypermedia/examples/lazy-loading"), ("Lazy Loading", "/(geography.(hypermedia.(example.lazy-loading)))"),
("Infinite Scroll", "/geography/hypermedia/examples/infinite-scroll"), ("Infinite Scroll", "/(geography.(hypermedia.(example.infinite-scroll)))"),
("Progress Bar", "/geography/hypermedia/examples/progress-bar"), ("Progress Bar", "/(geography.(hypermedia.(example.progress-bar)))"),
("Active Search", "/geography/hypermedia/examples/active-search"), ("Active Search", "/(geography.(hypermedia.(example.active-search)))"),
("Inline Validation", "/geography/hypermedia/examples/inline-validation"), ("Inline Validation", "/(geography.(hypermedia.(example.inline-validation)))"),
("Value Select", "/geography/hypermedia/examples/value-select"), ("Value Select", "/(geography.(hypermedia.(example.value-select)))"),
("Reset on Submit", "/geography/hypermedia/examples/reset-on-submit"), ("Reset on Submit", "/(geography.(hypermedia.(example.reset-on-submit)))"),
("Edit Row", "/geography/hypermedia/examples/edit-row"), ("Edit Row", "/(geography.(hypermedia.(example.edit-row)))"),
("Bulk Update", "/geography/hypermedia/examples/bulk-update"), ("Bulk Update", "/(geography.(hypermedia.(example.bulk-update)))"),
("Swap Positions", "/geography/hypermedia/examples/swap-positions"), ("Swap Positions", "/(geography.(hypermedia.(example.swap-positions)))"),
("Select Filter", "/geography/hypermedia/examples/select-filter"), ("Select Filter", "/(geography.(hypermedia.(example.select-filter)))"),
("Tabs", "/geography/hypermedia/examples/tabs"), ("Tabs", "/(geography.(hypermedia.(example.tabs)))"),
("Animations", "/geography/hypermedia/examples/animations"), ("Animations", "/(geography.(hypermedia.(example.animations)))"),
("Dialogs", "/geography/hypermedia/examples/dialogs"), ("Dialogs", "/(geography.(hypermedia.(example.dialogs)))"),
("Keyboard Shortcuts", "/geography/hypermedia/examples/keyboard-shortcuts"), ("Keyboard Shortcuts", "/(geography.(hypermedia.(example.keyboard-shortcuts)))"),
("PUT / PATCH", "/geography/hypermedia/examples/put-patch"), ("PUT / PATCH", "/(geography.(hypermedia.(example.put-patch)))"),
("JSON Encoding", "/geography/hypermedia/examples/json-encoding"), ("JSON Encoding", "/(geography.(hypermedia.(example.json-encoding)))"),
("Vals & Headers", "/geography/hypermedia/examples/vals-and-headers"), ("Vals & Headers", "/(geography.(hypermedia.(example.vals-and-headers)))"),
("Loading States", "/geography/hypermedia/examples/loading-states"), ("Loading States", "/(geography.(hypermedia.(example.loading-states)))"),
("Request Abort", "/geography/hypermedia/examples/sync-replace"), ("Request Abort", "/(geography.(hypermedia.(example.sync-replace)))"),
("Retry", "/geography/hypermedia/examples/retry"), ("Retry", "/(geography.(hypermedia.(example.retry)))"),
] ]
ESSAYS_NAV = [ ESSAYS_NAV = [
("sx sucks", "/etc/essays/sx-sucks"), ("sx sucks", "/(etc.(essay.sx-sucks))"),
("Why S-Expressions", "/etc/essays/why-sexps"), ("Why S-Expressions", "/(etc.(essay.why-sexps))"),
("The htmx/React Hybrid", "/etc/essays/htmx-react-hybrid"), ("The htmx/React Hybrid", "/(etc.(essay.htmx-react-hybrid))"),
("On-Demand CSS", "/etc/essays/on-demand-css"), ("On-Demand CSS", "/(etc.(essay.on-demand-css))"),
("Client Reactivity", "/etc/essays/client-reactivity"), ("Client Reactivity", "/(etc.(essay.client-reactivity))"),
("SX Native", "/etc/essays/sx-native"), ("SX Native", "/(etc.(essay.sx-native))"),
("The SX Manifesto", "/etc/philosophy/sx-manifesto"), ("The SX Manifesto", "/(etc.(philosophy.sx-manifesto))"),
("Tail-Call Optimization", "/etc/essays/tail-call-optimization"), ("Tail-Call Optimization", "/(etc.(essay.tail-call-optimization))"),
("Continuations", "/etc/essays/continuations"), ("Continuations", "/(etc.(essay.continuations))"),
] ]
MAIN_NAV = [ MAIN_NAV = [
("Docs", "/language/docs/introduction"), ("Docs", "/(language.(doc.introduction))"),
("Reference", "/geography/hypermedia/reference/"), ("Reference", "/(geography.(hypermedia.(reference)))"),
("Protocols", "/applications/protocols/wire-format"), ("Protocols", "/(applications.(protocol.wire-format))"),
("Examples", "/geography/hypermedia/examples/click-to-load"), ("Examples", "/(geography.(hypermedia.(example.click-to-load)))"),
("Essays", "/etc/essays/sx-sucks"), ("Essays", "/(etc.(essay.sx-sucks))"),
] ]
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -744,7 +744,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-get-demo", "demo": "ref-get-demo",
"example": ( "example": (
'(button :sx-get "/geography/hypermedia/reference/api/time"\n' '(button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"\n'
' :sx-target "#ref-get-result"\n' ' :sx-target "#ref-get-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' "Load server time")' ' "Load server time")'
@@ -764,7 +764,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-post-demo", "demo": "ref-post-demo",
"example": ( "example": (
'(form :sx-post "/geography/hypermedia/reference/api/greet"\n' '(form :sx-post "/(geography.(hypermedia.(reference.(api.greet))))"\n'
' :sx-target "#ref-post-result"\n' ' :sx-target "#ref-post-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' (input :type "text" :name "name"\n' ' (input :type "text" :name "name"\n'
@@ -786,7 +786,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-put-demo", "demo": "ref-put-demo",
"example": ( "example": (
'(button :sx-put "/geography/hypermedia/reference/api/status"\n' '(button :sx-put "/(geography.(hypermedia.(reference.(api.status))))"\n'
' :sx-target "#ref-put-view"\n' ' :sx-target "#ref-put-view"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-vals "{\\"status\\": \\"published\\"}"\n' ' :sx-vals "{\\"status\\": \\"published\\"}"\n'
@@ -807,7 +807,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-delete-demo", "demo": "ref-delete-demo",
"example": ( "example": (
'(button :sx-delete "/geography/hypermedia/reference/api/item/1"\n' '(button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.1)))))"\n'
' :sx-target "#ref-del-1"\n' ' :sx-target "#ref-del-1"\n'
' :sx-swap "delete"\n' ' :sx-swap "delete"\n'
' "Remove")' ' "Remove")'
@@ -826,7 +826,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-patch-demo", "demo": "ref-patch-demo",
"example": ( "example": (
'(button :sx-patch "/geography/hypermedia/reference/api/theme"\n' '(button :sx-patch "/(geography.(hypermedia.(reference.(api.theme))))"\n'
' :sx-vals "{\\"theme\\": \\"dark\\"}"\n' ' :sx-vals "{\\"theme\\": \\"dark\\"}"\n'
' :sx-target "#ref-patch-val"\n' ' :sx-target "#ref-patch-val"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
@@ -852,7 +852,7 @@ ATTR_DETAILS: dict[str, dict] = {
"example": ( "example": (
'(input :type "text" :name "q"\n' '(input :type "text" :name "q"\n'
' :placeholder "Type to search..."\n' ' :placeholder "Type to search..."\n'
' :sx-get "/geography/hypermedia/reference/api/trigger-search"\n' ' :sx-get "/(geography.(hypermedia.(reference.(api.trigger-search))))"\n'
' :sx-trigger "input changed delay:300ms"\n' ' :sx-trigger "input changed delay:300ms"\n'
' :sx-target "#ref-trigger-result"\n' ' :sx-target "#ref-trigger-result"\n'
' :sx-swap "innerHTML")' ' :sx-swap "innerHTML")'
@@ -874,12 +874,12 @@ ATTR_DETAILS: dict[str, dict] = {
"demo": "ref-target-demo", "demo": "ref-target-demo",
"example": ( "example": (
';; Two buttons targeting different elements\n' ';; Two buttons targeting different elements\n'
'(button :sx-get "/geography/hypermedia/reference/api/time"\n' '(button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"\n'
' :sx-target "#ref-target-a"\n' ' :sx-target "#ref-target-a"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' "Update Box A")\n' ' "Update Box A")\n'
'\n' '\n'
'(button :sx-get "/geography/hypermedia/reference/api/time"\n' '(button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"\n'
' :sx-target "#ref-target-b"\n' ' :sx-target "#ref-target-b"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' "Update Box B")' ' "Update Box B")'
@@ -894,13 +894,13 @@ ATTR_DETAILS: dict[str, dict] = {
"demo": "ref-swap-demo", "demo": "ref-swap-demo",
"example": ( "example": (
';; Append to the end of a list\n' ';; Append to the end of a list\n'
'(button :sx-get "/geography/hypermedia/reference/api/swap-item"\n' '(button :sx-get "/(geography.(hypermedia.(reference.(api.swap-item))))"\n'
' :sx-target "#ref-swap-list"\n' ' :sx-target "#ref-swap-list"\n'
' :sx-swap "beforeend"\n' ' :sx-swap "beforeend"\n'
' "beforeend")\n' ' "beforeend")\n'
'\n' '\n'
';; Prepend to the start\n' ';; Prepend to the start\n'
'(button :sx-get "/geography/hypermedia/reference/api/swap-item"\n' '(button :sx-get "/(geography.(hypermedia.(reference.(api.swap-item))))"\n'
' :sx-target "#ref-swap-list"\n' ' :sx-target "#ref-swap-list"\n'
' :sx-swap "afterbegin"\n' ' :sx-swap "afterbegin"\n'
' "afterbegin")' ' "afterbegin")'
@@ -921,7 +921,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-oob-demo", "demo": "ref-oob-demo",
"example": ( "example": (
'(button :sx-get "/geography/hypermedia/reference/api/oob"\n' '(button :sx-get "/(geography.(hypermedia.(reference.(api.oob))))"\n'
' :sx-target "#ref-oob-main"\n' ' :sx-target "#ref-oob-main"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' "Update both boxes")' ' "Update both boxes")'
@@ -944,7 +944,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-select-demo", "demo": "ref-select-demo",
"example": ( "example": (
'(button :sx-get "/geography/hypermedia/reference/api/select-page"\n' '(button :sx-get "/(geography.(hypermedia.(reference.(api.select-page))))"\n'
' :sx-target "#ref-select-result"\n' ' :sx-target "#ref-select-result"\n'
' :sx-select "#the-content"\n' ' :sx-select "#the-content"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
@@ -968,7 +968,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-confirm-demo", "demo": "ref-confirm-demo",
"example": ( "example": (
'(button :sx-delete "/geography/hypermedia/reference/api/item/confirm"\n' '(button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.confirm)))))"\n'
' :sx-target "#ref-confirm-item"\n' ' :sx-target "#ref-confirm-item"\n'
' :sx-swap "delete"\n' ' :sx-swap "delete"\n'
' :sx-confirm "Are you sure you want to delete this file?"\n' ' :sx-confirm "Are you sure you want to delete this file?"\n'
@@ -1003,7 +1003,7 @@ ATTR_DETAILS: dict[str, dict] = {
"example": ( "example": (
'(input :type "text" :name "q"\n' '(input :type "text" :name "q"\n'
' :placeholder "Type quickly..."\n' ' :placeholder "Type quickly..."\n'
' :sx-get "/geography/hypermedia/reference/api/slow-echo"\n' ' :sx-get "/(geography.(hypermedia.(reference.(api.slow-echo))))"\n'
' :sx-trigger "input changed delay:100ms"\n' ' :sx-trigger "input changed delay:100ms"\n'
' :sx-sync "replace"\n' ' :sx-sync "replace"\n'
' :sx-target "#ref-sync-result"\n' ' :sx-target "#ref-sync-result"\n'
@@ -1024,7 +1024,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-encoding-demo", "demo": "ref-encoding-demo",
"example": ( "example": (
'(form :sx-post "/geography/hypermedia/reference/api/upload-name"\n' '(form :sx-post "/(geography.(hypermedia.(reference.(api.upload-name))))"\n'
' :sx-encoding "multipart/form-data"\n' ' :sx-encoding "multipart/form-data"\n'
' :sx-target "#ref-encoding-result"\n' ' :sx-target "#ref-encoding-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
@@ -1044,7 +1044,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-headers-demo", "demo": "ref-headers-demo",
"example": ( "example": (
'(button :sx-get "/geography/hypermedia/reference/api/echo-headers"\n' '(button :sx-get "/(geography.(hypermedia.(reference.(api.echo-headers))))"\n'
' :sx-headers \'{"X-Custom-Token": "abc123", "X-Request-Source": "demo"}\'\n' ' :sx-headers \'{"X-Custom-Token": "abc123", "X-Request-Source": "demo"}\'\n'
' :sx-target "#ref-headers-result"\n' ' :sx-target "#ref-headers-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
@@ -1073,7 +1073,7 @@ ATTR_DETAILS: dict[str, dict] = {
' (option :value "books" "Books")\n' ' (option :value "books" "Books")\n'
' (option :value "tools" "Tools"))\n' ' (option :value "tools" "Tools"))\n'
'\n' '\n'
'(button :sx-get "/geography/hypermedia/reference/api/echo-vals"\n' '(button :sx-get "/(geography.(hypermedia.(reference.(api.echo-vals))))"\n'
' :sx-include "#ref-inc-cat"\n' ' :sx-include "#ref-inc-cat"\n'
' :sx-target "#ref-include-result"\n' ' :sx-target "#ref-include-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
@@ -1097,7 +1097,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-vals-demo", "demo": "ref-vals-demo",
"example": ( "example": (
'(button :sx-post "/geography/hypermedia/reference/api/echo-vals"\n' '(button :sx-post "/(geography.(hypermedia.(reference.(api.echo-vals))))"\n'
' :sx-vals \'{"source": "demo", "page": "3"}\'\n' ' :sx-vals \'{"source": "demo", "page": "3"}\'\n'
' :sx-target "#ref-vals-result"\n' ' :sx-target "#ref-vals-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
@@ -1133,7 +1133,7 @@ ATTR_DETAILS: dict[str, dict] = {
';; Left box: sx works normally\n' ';; Left box: sx works normally\n'
';; Right box: sx-disable prevents any sx behavior\n' ';; Right box: sx-disable prevents any sx behavior\n'
'(div :sx-disable "true"\n' '(div :sx-disable "true"\n'
' (button :sx-get "/geography/hypermedia/reference/api/time"\n' ' (button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"\n'
' :sx-target "#ref-dis-b"\n' ' :sx-target "#ref-dis-b"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' "Load")\n' ' "Load")\n'
@@ -1166,7 +1166,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-retry-demo", "demo": "ref-retry-demo",
"example": ( "example": (
'(button :sx-get "/geography/hypermedia/reference/api/flaky"\n' '(button :sx-get "/(geography.(hypermedia.(reference.(api.flaky))))"\n'
' :sx-target "#ref-retry-result"\n' ' :sx-target "#ref-retry-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-retry "true"\n' ' :sx-retry "true"\n'
@@ -1239,7 +1239,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-preload-demo", "demo": "ref-preload-demo",
"example": ( "example": (
'(button :sx-get "/geography/hypermedia/reference/api/time"\n' '(button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"\n'
' :sx-target "#ref-preload-result"\n' ' :sx-target "#ref-preload-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-preload "mouseover"\n' ' :sx-preload "mouseover"\n'
@@ -1275,7 +1275,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-indicator-demo", "demo": "ref-indicator-demo",
"example": ( "example": (
'(button :sx-get "/geography/hypermedia/reference/api/slow-echo"\n' '(button :sx-get "/(geography.(hypermedia.(reference.(api.slow-echo))))"\n'
' :sx-target "#ref-indicator-result"\n' ' :sx-target "#ref-indicator-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-indicator "#ref-spinner"\n' ' :sx-indicator "#ref-spinner"\n'
@@ -1302,7 +1302,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-validate-demo", "demo": "ref-validate-demo",
"example": ( "example": (
'(form :sx-post "/geography/hypermedia/reference/api/greet"\n' '(form :sx-post "/(geography.(hypermedia.(reference.(api.greet))))"\n'
' :sx-target "#ref-validate-result"\n' ' :sx-target "#ref-validate-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-validate "true"\n' ' :sx-validate "true"\n'
@@ -1340,7 +1340,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-optimistic-demo", "demo": "ref-optimistic-demo",
"example": ( "example": (
'(button :sx-delete "/geography/hypermedia/reference/api/item/opt1"\n' '(button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.opt1)))))"\n'
' :sx-target "#ref-opt-item"\n' ' :sx-target "#ref-opt-item"\n'
' :sx-swap "delete"\n' ' :sx-swap "delete"\n'
' :sx-optimistic "remove"\n' ' :sx-optimistic "remove"\n'
@@ -1362,7 +1362,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-replace-url-demo", "demo": "ref-replace-url-demo",
"example": ( "example": (
'(button :sx-get "/geography/hypermedia/reference/api/time"\n' '(button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"\n'
' :sx-target "#ref-replurl-result"\n' ' :sx-target "#ref-replurl-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-replace-url "true"\n' ' :sx-replace-url "true"\n'
@@ -1378,7 +1378,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-disabled-elt-demo", "demo": "ref-disabled-elt-demo",
"example": ( "example": (
'(button :sx-get "/geography/hypermedia/reference/api/slow-echo"\n' '(button :sx-get "/(geography.(hypermedia.(reference.(api.slow-echo))))"\n'
' :sx-target "#ref-diselt-result"\n' ' :sx-target "#ref-diselt-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-disabled-elt "this"\n' ' :sx-disabled-elt "this"\n'
@@ -1394,7 +1394,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-prompt-demo", "demo": "ref-prompt-demo",
"example": ( "example": (
'(button :sx-get "/geography/hypermedia/reference/api/prompt-echo"\n' '(button :sx-get "/(geography.(hypermedia.(reference.(api.prompt-echo))))"\n'
' :sx-target "#ref-prompt-result"\n' ' :sx-target "#ref-prompt-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-prompt "Enter your name:"\n' ' :sx-prompt "Enter your name:"\n'
@@ -1414,7 +1414,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-params-demo", "demo": "ref-params-demo",
"example": ( "example": (
'(form :sx-post "/geography/hypermedia/reference/api/echo-vals"\n' '(form :sx-post "/(geography.(hypermedia.(reference.(api.echo-vals))))"\n'
' :sx-target "#ref-params-result"\n' ' :sx-target "#ref-params-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'
' :sx-params "name"\n' ' :sx-params "name"\n'
@@ -1433,7 +1433,7 @@ ATTR_DETAILS: dict[str, dict] = {
), ),
"demo": "ref-sse-demo", "demo": "ref-sse-demo",
"example": ( "example": (
'(div :sx-sse "/geography/hypermedia/reference/api/sse-time"\n' '(div :sx-sse "/(geography.(hypermedia.(reference.(api.sse-time))))"\n'
' :sx-sse-swap "time"\n' ' :sx-sse-swap "time"\n'
' :sx-target "#ref-sse-result"\n' ' :sx-target "#ref-sse-result"\n'
' :sx-swap "innerHTML"\n' ' :sx-swap "innerHTML"\n'

View File

@@ -8,7 +8,7 @@
:description "The simplest sx interaction: click a button, fetch content from the server, swap it in." :description "The simplest sx interaction: click a button, fetch content from the server, swap it in."
:demo-description "Click the button to load server-rendered content." :demo-description "Click the button to load server-rendered content."
:demo (~click-to-load-demo) :demo (~click-to-load-demo)
:sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/click\"\n :sx-target \"#click-result\"\n :sx-swap \"innerHTML\"\n \"Load content\")" :sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.click))))\"\n :sx-target \"#click-result\"\n :sx-swap \"innerHTML\"\n \"Load content\")"
:handler-code (handler-source "ex-click") :handler-code (handler-source "ex-click")
:comp-placeholder-id "click-comp" :comp-placeholder-id "click-comp"
:wire-placeholder-id "click-wire" :wire-placeholder-id "click-wire"
@@ -20,7 +20,7 @@
:description "Forms with sx-post submit via AJAX and swap the response into a target." :description "Forms with sx-post submit via AJAX and swap the response into a target."
:demo-description "Enter a name and submit." :demo-description "Enter a name and submit."
:demo (~form-demo) :demo (~form-demo)
:sx-code "(form\n :sx-post \"/geography/hypermedia/examples/api/form\"\n :sx-target \"#form-result\"\n :sx-swap \"innerHTML\"\n (input :type \"text\" :name \"name\")\n (button :type \"submit\" \"Submit\"))" :sx-code "(form\n :sx-post \"/(geography.(hypermedia.(example.(api.form))))\"\n :sx-target \"#form-result\"\n :sx-swap \"innerHTML\"\n (input :type \"text\" :name \"name\")\n (button :type \"submit\" \"Submit\"))"
:handler-code (handler-source "ex-form") :handler-code (handler-source "ex-form")
:comp-placeholder-id "form-comp" :comp-placeholder-id "form-comp"
:wire-placeholder-id "form-wire")) :wire-placeholder-id "form-wire"))
@@ -31,7 +31,7 @@
:description "Use sx-trigger with \"every\" to poll the server at regular intervals." :description "Use sx-trigger with \"every\" to poll the server at regular intervals."
:demo-description "This div polls the server every 2 seconds." :demo-description "This div polls the server every 2 seconds."
:demo (~polling-demo) :demo (~polling-demo)
:sx-code "(div\n :sx-get \"/geography/hypermedia/examples/api/poll\"\n :sx-trigger \"load, every 2s\"\n :sx-swap \"innerHTML\"\n \"Loading...\")" :sx-code "(div\n :sx-get \"/(geography.(hypermedia.(example.(api.poll))))\"\n :sx-trigger \"load, every 2s\"\n :sx-swap \"innerHTML\"\n \"Loading...\")"
:handler-code (handler-source "ex-poll") :handler-code (handler-source "ex-poll")
:comp-placeholder-id "poll-comp" :comp-placeholder-id "poll-comp"
:wire-placeholder-id "poll-wire" :wire-placeholder-id "poll-wire"
@@ -48,7 +48,7 @@
(list "3" "Write documentation") (list "3" "Write documentation")
(list "4" "Deploy to production") (list "4" "Deploy to production")
(list "5" "Add unit tests"))) (list "5" "Add unit tests")))
:sx-code "(button\n :sx-delete \"/api/delete/1\"\n :sx-target \"#row-1\"\n :sx-swap \"outerHTML\"\n :sx-confirm \"Delete this item?\"\n \"delete\")" :sx-code "(button\n :sx-delete \"/(geography.(hypermedia.(example.(api.(delete.1)))))\"\n :sx-target \"#row-1\"\n :sx-swap \"outerHTML\"\n :sx-confirm \"Delete this item?\"\n \"delete\")"
:handler-code (handler-source "ex-delete") :handler-code (handler-source "ex-delete")
:comp-placeholder-id "delete-comp" :comp-placeholder-id "delete-comp"
:wire-placeholder-id "delete-wire" :wire-placeholder-id "delete-wire"
@@ -73,7 +73,7 @@
:description "sx-swap-oob lets a single response update multiple elements anywhere in the DOM." :description "sx-swap-oob lets a single response update multiple elements anywhere in the DOM."
:demo-description "One request updates both Box A (via sx-target) and Box B (via sx-swap-oob)." :demo-description "One request updates both Box A (via sx-target) and Box B (via sx-swap-oob)."
:demo (~oob-demo) :demo (~oob-demo)
:sx-code ";; Button targets Box A\n(button\n :sx-get \"/geography/hypermedia/examples/api/oob\"\n :sx-target \"#oob-box-a\"\n :sx-swap \"innerHTML\"\n \"Update both boxes\")" :sx-code ";; Button targets Box A\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.oob))))\"\n :sx-target \"#oob-box-a\"\n :sx-swap \"innerHTML\"\n \"Update both boxes\")"
:handler-code (handler-source "ex-oob") :handler-code (handler-source "ex-oob")
:wire-placeholder-id "oob-wire" :wire-placeholder-id "oob-wire"
:wire-note "The fragment contains both the main content and an OOB element. sx.js splits them: main content goes to sx-target, OOB elements find their targets by ID.")) :wire-note "The fragment contains both the main content and an OOB element. sx.js splits them: main content goes to sx-target, OOB elements find their targets by ID."))
@@ -84,7 +84,7 @@
:description "Use sx-trigger=\"load\" to fetch content as soon as the element enters the DOM. Great for deferring expensive content below the fold." :description "Use sx-trigger=\"load\" to fetch content as soon as the element enters the DOM. Great for deferring expensive content below the fold."
:demo-description "Content loads automatically when the page renders." :demo-description "Content loads automatically when the page renders."
:demo (~lazy-loading-demo) :demo (~lazy-loading-demo)
:sx-code "(div\n :sx-get \"/geography/hypermedia/examples/api/lazy\"\n :sx-trigger \"load\"\n :sx-swap \"innerHTML\"\n (div :class \"animate-pulse\" \"Loading...\"))" :sx-code "(div\n :sx-get \"/(geography.(hypermedia.(example.(api.lazy))))\"\n :sx-trigger \"load\"\n :sx-swap \"innerHTML\"\n (div :class \"animate-pulse\" \"Loading...\"))"
:handler-code (handler-source "ex-lazy") :handler-code (handler-source "ex-lazy")
:comp-placeholder-id "lazy-comp" :comp-placeholder-id "lazy-comp"
:wire-placeholder-id "lazy-wire")) :wire-placeholder-id "lazy-wire"))
@@ -95,7 +95,7 @@
:description "A sentinel element at the bottom uses sx-trigger=\"intersect once\" to load the next page when scrolled into view. Each response appends items and a new sentinel." :description "A sentinel element at the bottom uses sx-trigger=\"intersect once\" to load the next page when scrolled into view. Each response appends items and a new sentinel."
:demo-description "Scroll down in the container to load more items (5 pages total)." :demo-description "Scroll down in the container to load more items (5 pages total)."
:demo (~infinite-scroll-demo) :demo (~infinite-scroll-demo)
:sx-code "(div :id \"scroll-sentinel\"\n :sx-get \"/geography/hypermedia/examples/api/scroll?page=2\"\n :sx-trigger \"intersect once\"\n :sx-target \"#scroll-items\"\n :sx-swap \"beforeend\"\n \"Loading more...\")" :sx-code "(div :id \"scroll-sentinel\"\n :sx-get \"/(geography.(hypermedia.(example.(api.scroll))))?page=2\"\n :sx-trigger \"intersect once\"\n :sx-target \"#scroll-items\"\n :sx-swap \"beforeend\"\n \"Loading more...\")"
:handler-code (handler-source "ex-scroll") :handler-code (handler-source "ex-scroll")
:comp-placeholder-id "scroll-comp" :comp-placeholder-id "scroll-comp"
:wire-placeholder-id "scroll-wire")) :wire-placeholder-id "scroll-wire"))
@@ -106,7 +106,7 @@
:description "Start a server-side job, then poll for progress using sx-trigger=\"load delay:500ms\" on each response. The bar fills up and stops when complete." :description "Start a server-side job, then poll for progress using sx-trigger=\"load delay:500ms\" on each response. The bar fills up and stops when complete."
:demo-description "Click start to begin a simulated job." :demo-description "Click start to begin a simulated job."
:demo (~progress-bar-demo) :demo (~progress-bar-demo)
:sx-code ";; Start the job\n(button\n :sx-post \"/geography/hypermedia/examples/api/progress/start\"\n :sx-target \"#progress-target\"\n :sx-swap \"innerHTML\")\n\n;; Each response re-polls via sx-trigger=\"load\"\n(div :sx-get \"/api/progress/status?job=ID\"\n :sx-trigger \"load delay:500ms\"\n :sx-target \"#progress-target\"\n :sx-swap \"innerHTML\")" :sx-code ";; Start the job\n(button\n :sx-post \"/(geography.(hypermedia.(example.(api.progress-start))))\"\n :sx-target \"#progress-target\"\n :sx-swap \"innerHTML\")\n\n;; Each response re-polls via sx-trigger=\"load\"\n(div :sx-get \"/(geography.(hypermedia.(example.(api.progress-status))))?job=ID\"\n :sx-trigger \"load delay:500ms\"\n :sx-target \"#progress-target\"\n :sx-swap \"innerHTML\")"
:handler-code (str (handler-source "ex-progress-start") "\n\n" (handler-source "ex-progress-status")) :handler-code (str (handler-source "ex-progress-start") "\n\n" (handler-source "ex-progress-status"))
:comp-placeholder-id "progress-comp" :comp-placeholder-id "progress-comp"
:wire-placeholder-id "progress-wire")) :wire-placeholder-id "progress-wire"))
@@ -117,7 +117,7 @@
:description "An input with sx-trigger=\"keyup delay:300ms changed\" debounces keystrokes and only fires when the value changes. The server filters a list of programming languages." :description "An input with sx-trigger=\"keyup delay:300ms changed\" debounces keystrokes and only fires when the value changes. The server filters a list of programming languages."
:demo-description "Type to search through 20 programming languages." :demo-description "Type to search through 20 programming languages."
:demo (~active-search-demo) :demo (~active-search-demo)
:sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/geography/hypermedia/examples/api/search\"\n :sx-trigger \"keyup delay:300ms changed\"\n :sx-target \"#search-results\"\n :sx-swap \"innerHTML\"\n :placeholder \"Search...\")" :sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/(geography.(hypermedia.(example.(api.search))))\"\n :sx-trigger \"keyup delay:300ms changed\"\n :sx-target \"#search-results\"\n :sx-swap \"innerHTML\"\n :placeholder \"Search...\")"
:handler-code (handler-source "ex-search") :handler-code (handler-source "ex-search")
:comp-placeholder-id "search-comp" :comp-placeholder-id "search-comp"
:wire-placeholder-id "search-wire")) :wire-placeholder-id "search-wire"))
@@ -128,7 +128,7 @@
:description "Validate an email field on blur. The server checks format and whether it is taken, returning green or red feedback inline." :description "Validate an email field on blur. The server checks format and whether it is taken, returning green or red feedback inline."
:demo-description "Enter an email and click away (blur) to validate." :demo-description "Enter an email and click away (blur) to validate."
:demo (~inline-validation-demo) :demo (~inline-validation-demo)
:sx-code "(input :type \"text\" :name \"email\"\n :sx-get \"/geography/hypermedia/examples/api/validate\"\n :sx-trigger \"blur\"\n :sx-target \"#email-feedback\"\n :sx-swap \"innerHTML\"\n :placeholder \"user@example.com\")" :sx-code "(input :type \"text\" :name \"email\"\n :sx-get \"/(geography.(hypermedia.(example.(api.validate))))\"\n :sx-trigger \"blur\"\n :sx-target \"#email-feedback\"\n :sx-swap \"innerHTML\"\n :placeholder \"user@example.com\")"
:handler-code (handler-source "ex-validate") :handler-code (handler-source "ex-validate")
:comp-placeholder-id "validate-comp" :comp-placeholder-id "validate-comp"
:wire-placeholder-id "validate-wire")) :wire-placeholder-id "validate-wire"))
@@ -139,7 +139,7 @@
:description "Two linked selects: pick a category and the second select updates with matching items via sx-get." :description "Two linked selects: pick a category and the second select updates with matching items via sx-get."
:demo-description "Select a category to populate the item dropdown." :demo-description "Select a category to populate the item dropdown."
:demo (~value-select-demo) :demo (~value-select-demo)
:sx-code "(select :name \"category\"\n :sx-get \"/geography/hypermedia/examples/api/values\"\n :sx-trigger \"change\"\n :sx-target \"#value-items\"\n :sx-swap \"innerHTML\"\n (option \"Languages\")\n (option \"Frameworks\")\n (option \"Databases\"))" :sx-code "(select :name \"category\"\n :sx-get \"/(geography.(hypermedia.(example.(api.values))))\"\n :sx-trigger \"change\"\n :sx-target \"#value-items\"\n :sx-swap \"innerHTML\"\n (option \"Languages\")\n (option \"Frameworks\")\n (option \"Databases\"))"
:handler-code (handler-source "ex-values") :handler-code (handler-source "ex-values")
:comp-placeholder-id "values-comp" :comp-placeholder-id "values-comp"
:wire-placeholder-id "values-wire")) :wire-placeholder-id "values-wire"))
@@ -150,7 +150,7 @@
:description "Use sx-on:afterSwap=\"this.reset()\" to clear form inputs after a successful submission." :description "Use sx-on:afterSwap=\"this.reset()\" to clear form inputs after a successful submission."
:demo-description "Submit a message — the input resets after each send." :demo-description "Submit a message — the input resets after each send."
:demo (~reset-on-submit-demo) :demo (~reset-on-submit-demo)
:sx-code "(form :id \"reset-form\"\n :sx-post \"/geography/hypermedia/examples/api/reset-submit\"\n :sx-target \"#reset-result\"\n :sx-swap \"innerHTML\"\n :sx-on:afterSwap \"this.reset()\"\n (input :type \"text\" :name \"message\")\n (button :type \"submit\" \"Send\"))" :sx-code "(form :id \"reset-form\"\n :sx-post \"/(geography.(hypermedia.(example.(api.reset-submit))))\"\n :sx-target \"#reset-result\"\n :sx-swap \"innerHTML\"\n :sx-on:afterSwap \"this.reset()\"\n (input :type \"text\" :name \"message\")\n (button :type \"submit\" \"Send\"))"
:handler-code (handler-source "ex-reset-submit") :handler-code (handler-source "ex-reset-submit")
:comp-placeholder-id "reset-comp" :comp-placeholder-id "reset-comp"
:wire-placeholder-id "reset-wire")) :wire-placeholder-id "reset-wire"))
@@ -165,7 +165,7 @@
(list "2" "Widget B" "24.50" "89") (list "2" "Widget B" "24.50" "89")
(list "3" "Widget C" "12.00" "305") (list "3" "Widget C" "12.00" "305")
(list "4" "Widget D" "45.00" "67"))) (list "4" "Widget D" "45.00" "67")))
:sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/editrow/1\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n \"edit\")\n\n;; Save sends form data via POST\n(button\n :sx-post \"/geography/hypermedia/examples/api/editrow/1\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n :sx-include \"#erow-1\"\n \"save\")" :sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.(editrow.1)))))\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n \"edit\")\n\n;; Save sends form data via POST\n(button\n :sx-post \"/(geography.(hypermedia.(example.(api.(editrow.1)))))\"\n :sx-target \"#erow-1\"\n :sx-swap \"outerHTML\"\n :sx-include \"#erow-1\"\n \"save\")"
:handler-code (str (handler-source "ex-editrow-form") "\n\n" (handler-source "ex-editrow-save")) :handler-code (str (handler-source "ex-editrow-form") "\n\n" (handler-source "ex-editrow-save"))
:comp-placeholder-id "editrow-comp" :comp-placeholder-id "editrow-comp"
:wire-placeholder-id "editrow-wire")) :wire-placeholder-id "editrow-wire"))
@@ -181,7 +181,7 @@
(list "3" "Carol Zhang" "carol@example.com" "active") (list "3" "Carol Zhang" "carol@example.com" "active")
(list "4" "Dan Okafor" "dan@example.com" "inactive") (list "4" "Dan Okafor" "dan@example.com" "inactive")
(list "5" "Eve Larsson" "eve@example.com" "active"))) (list "5" "Eve Larsson" "eve@example.com" "active")))
:sx-code "(button\n :sx-post \"/geography/hypermedia/examples/api/bulk?action=activate\"\n :sx-target \"#bulk-table\"\n :sx-swap \"innerHTML\"\n :sx-include \"#bulk-form\"\n \"Activate\")" :sx-code "(button\n :sx-post \"/(geography.(hypermedia.(example.(api.bulk))))?action=activate\"\n :sx-target \"#bulk-table\"\n :sx-swap \"innerHTML\"\n :sx-include \"#bulk-form\"\n \"Activate\")"
:handler-code (handler-source "ex-bulk") :handler-code (handler-source "ex-bulk")
:comp-placeholder-id "bulk-comp" :comp-placeholder-id "bulk-comp"
:wire-placeholder-id "bulk-wire")) :wire-placeholder-id "bulk-wire"))
@@ -192,7 +192,7 @@
:description "Demonstrates different swap modes: beforeend appends, afterbegin prepends, and none skips the main swap while still processing OOB updates." :description "Demonstrates different swap modes: beforeend appends, afterbegin prepends, and none skips the main swap while still processing OOB updates."
:demo-description "Try each button to see different swap behaviours." :demo-description "Try each button to see different swap behaviours."
:demo (~swap-positions-demo) :demo (~swap-positions-demo)
:sx-code ";; Append to end\n(button :sx-post \"/api/swap-log?mode=beforeend\"\n :sx-target \"#swap-log\" :sx-swap \"beforeend\"\n \"Add to End\")\n\n;; Prepend to start\n(button :sx-post \"/api/swap-log?mode=afterbegin\"\n :sx-target \"#swap-log\" :sx-swap \"afterbegin\"\n \"Add to Start\")\n\n;; No swap — OOB counter update only\n(button :sx-post \"/api/swap-log?mode=none\"\n :sx-target \"#swap-log\" :sx-swap \"none\"\n \"Silent Ping\")" :sx-code ";; Append to end\n(button :sx-post \"/(geography.(hypermedia.(example.(api.swap-log))))?mode=beforeend\"\n :sx-target \"#swap-log\" :sx-swap \"beforeend\"\n \"Add to End\")\n\n;; Prepend to start\n(button :sx-post \"/(geography.(hypermedia.(example.(api.swap-log))))?mode=afterbegin\"\n :sx-target \"#swap-log\" :sx-swap \"afterbegin\"\n \"Add to Start\")\n\n;; No swap — OOB counter update only\n(button :sx-post \"/(geography.(hypermedia.(example.(api.swap-log))))?mode=none\"\n :sx-target \"#swap-log\" :sx-swap \"none\"\n \"Silent Ping\")"
:handler-code (handler-source "ex-swap-log") :handler-code (handler-source "ex-swap-log")
:wire-placeholder-id "swap-wire")) :wire-placeholder-id "swap-wire"))
@@ -202,7 +202,7 @@
:description "sx-select lets the client pick a specific section from the server response by CSS selector. The server always returns the full dashboard — the client filters." :description "sx-select lets the client pick a specific section from the server response by CSS selector. The server always returns the full dashboard — the client filters."
:demo-description "Different buttons select different parts of the same server response." :demo-description "Different buttons select different parts of the same server response."
:demo (~select-filter-demo) :demo (~select-filter-demo)
:sx-code ";; Pick just the stats section from the response\n(button\n :sx-get \"/geography/hypermedia/examples/api/dashboard\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n :sx-select \"#dash-stats\"\n \"Stats Only\")\n\n;; No sx-select — get the full response\n(button\n :sx-get \"/geography/hypermedia/examples/api/dashboard\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n \"Full Dashboard\")" :sx-code ";; Pick just the stats section from the response\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.dashboard))))\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n :sx-select \"#dash-stats\"\n \"Stats Only\")\n\n;; No sx-select — get the full response\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.dashboard))))\"\n :sx-target \"#filter-target\"\n :sx-swap \"innerHTML\"\n \"Full Dashboard\")"
:handler-code (handler-source "ex-dashboard") :handler-code (handler-source "ex-dashboard")
:wire-placeholder-id "filter-wire")) :wire-placeholder-id "filter-wire"))
@@ -212,7 +212,7 @@
:description "Tab navigation using sx-push-url to update the browser URL. Back/forward buttons navigate between previously visited tabs." :description "Tab navigation using sx-push-url to update the browser URL. Back/forward buttons navigate between previously visited tabs."
:demo-description "Click tabs to switch content. Watch the browser URL change." :demo-description "Click tabs to switch content. Watch the browser URL change."
:demo (~tabs-demo) :demo (~tabs-demo)
:sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/tabs/tab1\"\n :sx-target \"#tab-content\"\n :sx-swap \"innerHTML\"\n :sx-push-url \"/geography/hypermedia/examples/tabs?tab=tab1\"\n \"Overview\")" :sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.(tabs.tab1)))))\"\n :sx-target \"#tab-content\"\n :sx-swap \"innerHTML\"\n :sx-push-url \"/(geography.(hypermedia.(example.tabs)))?tab=tab1\"\n \"Overview\")"
:handler-code (handler-source "ex-tabs") :handler-code (handler-source "ex-tabs")
:wire-placeholder-id "tabs-wire")) :wire-placeholder-id "tabs-wire"))
@@ -222,7 +222,7 @@
:description "CSS animations play on swap. The component injects a style tag with a keyframe animation and applies the class. Each click picks a random background colour." :description "CSS animations play on swap. The component injects a style tag with a keyframe animation and applies the class. Each click picks a random background colour."
:demo-description "Click to swap in content with a fade-in animation." :demo-description "Click to swap in content with a fade-in animation."
:demo (~animations-demo) :demo (~animations-demo)
:sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/animate\"\n :sx-target \"#anim-target\"\n :sx-swap \"innerHTML\"\n \"Load with animation\")\n\n;; Component uses CSS animation class\n(defcomp ~anim-result (&key color time)\n (div :class \"sx-fade-in ...\"\n (style \".sx-fade-in { animation: sxFadeIn 0.5s }\")\n (p \"Faded in!\")))" :sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.animate))))\"\n :sx-target \"#anim-target\"\n :sx-swap \"innerHTML\"\n \"Load with animation\")\n\n;; Component uses CSS animation class\n(defcomp ~anim-result (&key color time)\n (div :class \"sx-fade-in ...\"\n (style \".sx-fade-in { animation: sxFadeIn 0.5s }\")\n (p \"Faded in!\")))"
:handler-code (handler-source "ex-animate") :handler-code (handler-source "ex-animate")
:comp-placeholder-id "anim-comp" :comp-placeholder-id "anim-comp"
:wire-placeholder-id "anim-wire")) :wire-placeholder-id "anim-wire"))
@@ -233,7 +233,7 @@
:description "Open a modal dialog by swapping in the dialog component. Close by swapping in empty content. Pure sx — no JavaScript library needed." :description "Open a modal dialog by swapping in the dialog component. Close by swapping in empty content. Pure sx — no JavaScript library needed."
:demo-description "Click to open a modal dialog." :demo-description "Click to open a modal dialog."
:demo (~dialogs-demo) :demo (~dialogs-demo)
:sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/dialog\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Open Dialog\")\n\n;; Dialog closes by swapping empty content\n(button\n :sx-get \"/geography/hypermedia/examples/api/dialog/close\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Close\")" :sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.dialog))))\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Open Dialog\")\n\n;; Dialog closes by swapping empty content\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.dialog-close))))\"\n :sx-target \"#dialog-container\"\n :sx-swap \"innerHTML\"\n \"Close\")"
:handler-code (str (handler-source "ex-dialog") "\n\n" (handler-source "ex-dialog-close")) :handler-code (str (handler-source "ex-dialog") "\n\n" (handler-source "ex-dialog-close"))
:comp-placeholder-id "dialog-comp" :comp-placeholder-id "dialog-comp"
:wire-placeholder-id "dialog-wire")) :wire-placeholder-id "dialog-wire"))
@@ -244,7 +244,7 @@
:description "Use sx-trigger with keyup event filters and from:body to listen for global keyboard shortcuts. The filter prevents firing when typing in inputs." :description "Use sx-trigger with keyup event filters and from:body to listen for global keyboard shortcuts. The filter prevents firing when typing in inputs."
:demo-description "Press s, n, or h on your keyboard." :demo-description "Press s, n, or h on your keyboard."
:demo (~keyboard-shortcuts-demo) :demo (~keyboard-shortcuts-demo)
:sx-code "(div :id \"kbd-target\"\n :sx-get \"/geography/hypermedia/examples/api/keyboard?key=s\"\n :sx-trigger \"keyup[key=='s'&&!event.target.matches('input,textarea')] from:body\"\n :sx-swap \"innerHTML\"\n \"Press a shortcut key...\")" :sx-code "(div :id \"kbd-target\"\n :sx-get \"/(geography.(hypermedia.(example.(api.keyboard))))?key=s\"\n :sx-trigger \"keyup[key=='s'&&!event.target.matches('input,textarea')] from:body\"\n :sx-swap \"innerHTML\"\n \"Press a shortcut key...\")"
:handler-code (handler-source "ex-keyboard") :handler-code (handler-source "ex-keyboard")
:comp-placeholder-id "kbd-comp" :comp-placeholder-id "kbd-comp"
:wire-placeholder-id "kbd-wire")) :wire-placeholder-id "kbd-wire"))
@@ -255,7 +255,7 @@
:description "sx-put replaces the entire resource. This example shows a profile card with an Edit All button that sends a PUT with all fields." :description "sx-put replaces the entire resource. This example shows a profile card with an Edit All button that sends a PUT with all fields."
:demo-description "Click Edit All to replace the full profile via PUT." :demo-description "Click Edit All to replace the full profile via PUT."
:demo (~put-patch-demo :name "Ada Lovelace" :email "ada@example.com" :role "Engineer") :demo (~put-patch-demo :name "Ada Lovelace" :email "ada@example.com" :role "Engineer")
:sx-code ";; Replace entire resource\n(form :sx-put \"/geography/hypermedia/examples/api/putpatch\"\n :sx-target \"#pp-target\" :sx-swap \"innerHTML\"\n (input :name \"name\") (input :name \"email\")\n (button \"Save All (PUT)\"))" :sx-code ";; Replace entire resource\n(form :sx-put \"/(geography.(hypermedia.(example.(api.putpatch))))\"\n :sx-target \"#pp-target\" :sx-swap \"innerHTML\"\n (input :name \"name\") (input :name \"email\")\n (button \"Save All (PUT)\"))"
:handler-code (str (handler-source "ex-pp-edit-all") "\n\n" (handler-source "ex-pp-put")) :handler-code (str (handler-source "ex-pp-edit-all") "\n\n" (handler-source "ex-pp-put"))
:comp-placeholder-id "pp-comp" :comp-placeholder-id "pp-comp"
:wire-placeholder-id "pp-wire")) :wire-placeholder-id "pp-wire"))
@@ -266,7 +266,7 @@
:description "Use sx-encoding=\"json\" to send form data as a JSON body instead of URL-encoded form data. The server echoes back what it received." :description "Use sx-encoding=\"json\" to send form data as a JSON body instead of URL-encoded form data. The server echoes back what it received."
:demo-description "Submit the form and see the JSON body the server received." :demo-description "Submit the form and see the JSON body the server received."
:demo (~json-encoding-demo) :demo (~json-encoding-demo)
:sx-code "(form\n :sx-post \"/geography/hypermedia/examples/api/json-echo\"\n :sx-target \"#json-result\"\n :sx-swap \"innerHTML\"\n :sx-encoding \"json\"\n (input :name \"name\" :value \"Ada\")\n (input :type \"number\" :name \"age\" :value \"36\")\n (button \"Submit as JSON\"))" :sx-code "(form\n :sx-post \"/(geography.(hypermedia.(example.(api.json-echo))))\"\n :sx-target \"#json-result\"\n :sx-swap \"innerHTML\"\n :sx-encoding \"json\"\n (input :name \"name\" :value \"Ada\")\n (input :type \"number\" :name \"age\" :value \"36\")\n (button \"Submit as JSON\"))"
:handler-code (handler-source "ex-json-echo") :handler-code (handler-source "ex-json-echo")
:comp-placeholder-id "json-comp" :comp-placeholder-id "json-comp"
:wire-placeholder-id "json-wire")) :wire-placeholder-id "json-wire"))
@@ -277,7 +277,7 @@
:description "sx-vals adds extra key/value pairs to the request parameters. sx-headers adds custom HTTP headers. The server echoes back what it received." :description "sx-vals adds extra key/value pairs to the request parameters. sx-headers adds custom HTTP headers. The server echoes back what it received."
:demo-description "Click each button to see what the server receives." :demo-description "Click each button to see what the server receives."
:demo (~vals-headers-demo) :demo (~vals-headers-demo)
:sx-code ";; Send extra values with the request\n(button\n :sx-get \"/geography/hypermedia/examples/api/echo-vals\"\n :sx-vals \"{\\\"source\\\": \\\"button\\\"}\"\n \"Send with vals\")\n\n;; Send custom headers\n(button\n :sx-get \"/geography/hypermedia/examples/api/echo-headers\"\n :sx-headers {:X-Custom-Token \"abc123\"}\n \"Send with headers\")" :sx-code ";; Send extra values with the request\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.echo-vals))))\"\n :sx-vals \"{\\\"source\\\": \\\"button\\\"}\"\n \"Send with vals\")\n\n;; Send custom headers\n(button\n :sx-get \"/(geography.(hypermedia.(example.(api.echo-headers))))\"\n :sx-headers {:X-Custom-Token \"abc123\"}\n \"Send with headers\")"
:handler-code (str (handler-source "ex-echo-vals") "\n\n" (handler-source "ex-echo-headers")) :handler-code (str (handler-source "ex-echo-vals") "\n\n" (handler-source "ex-echo-headers"))
:comp-placeholder-id "vals-comp" :comp-placeholder-id "vals-comp"
:wire-placeholder-id "vals-wire")) :wire-placeholder-id "vals-wire"))
@@ -288,7 +288,7 @@
:description "sx.js adds the .sx-request CSS class to any element that has an active request. Use pure CSS to show spinners, disable buttons, or change opacity during loading." :description "sx.js adds the .sx-request CSS class to any element that has an active request. Use pure CSS to show spinners, disable buttons, or change opacity during loading."
:demo-description "Click the button — it shows a spinner during the 2-second request." :demo-description "Click the button — it shows a spinner during the 2-second request."
:demo (~loading-states-demo) :demo (~loading-states-demo)
:sx-code ";; .sx-request class added during request\n(style \".sx-loading-btn.sx-request {\n opacity: 0.7; pointer-events: none; }\n.sx-loading-btn.sx-request .sx-spinner {\n display: inline-block; }\n.sx-loading-btn .sx-spinner {\n display: none; }\")\n\n(button :class \"sx-loading-btn\"\n :sx-get \"/geography/hypermedia/examples/api/slow\"\n :sx-target \"#loading-result\"\n (span :class \"sx-spinner animate-spin\" \"...\")\n \"Load slow endpoint\")" :sx-code ";; .sx-request class added during request\n(style \".sx-loading-btn.sx-request {\n opacity: 0.7; pointer-events: none; }\n.sx-loading-btn.sx-request .sx-spinner {\n display: inline-block; }\n.sx-loading-btn .sx-spinner {\n display: none; }\")\n\n(button :class \"sx-loading-btn\"\n :sx-get \"/(geography.(hypermedia.(example.(api.slow))))\"\n :sx-target \"#loading-result\"\n (span :class \"sx-spinner animate-spin\" \"...\")\n \"Load slow endpoint\")"
:handler-code (handler-source "ex-slow") :handler-code (handler-source "ex-slow")
:comp-placeholder-id "loading-comp" :comp-placeholder-id "loading-comp"
:wire-placeholder-id "loading-wire")) :wire-placeholder-id "loading-wire"))
@@ -299,7 +299,7 @@
:description "sx-sync=\"replace\" aborts any in-flight request before sending a new one. This prevents stale responses from overwriting newer ones, even with random server delays." :description "sx-sync=\"replace\" aborts any in-flight request before sending a new one. This prevents stale responses from overwriting newer ones, even with random server delays."
:demo-description "Type quickly — only the latest result appears despite random 0.5-2s server delays." :demo-description "Type quickly — only the latest result appears despite random 0.5-2s server delays."
:demo (~sync-replace-demo) :demo (~sync-replace-demo)
:sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/geography/hypermedia/examples/api/slow-search\"\n :sx-trigger \"keyup delay:200ms changed\"\n :sx-target \"#sync-result\"\n :sx-swap \"innerHTML\"\n :sx-sync \"replace\"\n \"Type to search...\")" :sx-code "(input :type \"text\" :name \"q\"\n :sx-get \"/(geography.(hypermedia.(example.(api.slow-search))))\"\n :sx-trigger \"keyup delay:200ms changed\"\n :sx-target \"#sync-result\"\n :sx-swap \"innerHTML\"\n :sx-sync \"replace\"\n \"Type to search...\")"
:handler-code (handler-source "ex-slow-search") :handler-code (handler-source "ex-slow-search")
:comp-placeholder-id "sync-comp" :comp-placeholder-id "sync-comp"
:wire-placeholder-id "sync-wire")) :wire-placeholder-id "sync-wire"))
@@ -310,7 +310,7 @@
:description "sx-retry=\"exponential:1000:8000\" retries failed requests with exponential backoff starting at 1s up to 8s. The endpoint fails the first 2 attempts and succeeds on the 3rd." :description "sx-retry=\"exponential:1000:8000\" retries failed requests with exponential backoff starting at 1s up to 8s. The endpoint fails the first 2 attempts and succeeds on the 3rd."
:demo-description "Click the button — watch it retry automatically after failures." :demo-description "Click the button — watch it retry automatically after failures."
:demo (~retry-demo) :demo (~retry-demo)
:sx-code "(button\n :sx-get \"/geography/hypermedia/examples/api/flaky\"\n :sx-target \"#retry-result\"\n :sx-swap \"innerHTML\"\n :sx-retry \"exponential:1000:8000\"\n \"Call flaky endpoint\")" :sx-code "(button\n :sx-get \"/(geography.(hypermedia.(example.(api.flaky))))\"\n :sx-target \"#retry-result\"\n :sx-swap \"innerHTML\"\n :sx-retry \"exponential:1000:8000\"\n \"Call flaky endpoint\")"
:handler-code (handler-source "ex-flaky") :handler-code (handler-source "ex-flaky")
:comp-placeholder-id "retry-comp" :comp-placeholder-id "retry-comp"
:wire-placeholder-id "retry-wire")) :wire-placeholder-id "retry-wire"))

View File

@@ -55,7 +55,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-click (defhandler ex-click
:path "/geography/hypermedia/examples/api/click" :path "/(geography.(hypermedia.(example.(api.click))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -73,7 +73,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-form (defhandler ex-form
:path "/geography/hypermedia/examples/api/form" :path "/(geography.(hypermedia.(example.(api.form))))"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
@@ -92,7 +92,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-poll (defhandler ex-poll
:path "/geography/hypermedia/examples/api/poll" :path "/(geography.(hypermedia.(example.(api.poll))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -113,7 +113,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-delete (defhandler ex-delete
:path "/geography/hypermedia/examples/api/delete/<item_id>" :path "/(geography.(hypermedia.(example.(api.(delete.<sx:item_id>)))))"
:method :delete :method :delete
:csrf false :csrf false
:returns "element" :returns "element"
@@ -130,7 +130,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-edit-form (defhandler ex-edit-form
:path "/geography/hypermedia/examples/api/edit" :path "/(geography.(hypermedia.(example.(api.edit))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -143,7 +143,7 @@
:text (str "(~inline-edit-form :value \"" value "\")"))))) :text (str "(~inline-edit-form :value \"" value "\")")))))
(defhandler ex-edit-save (defhandler ex-edit-save
:path "/geography/hypermedia/examples/api/edit" :path "/(geography.(hypermedia.(example.(api.edit))))"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
@@ -157,7 +157,7 @@
:text (str "(~inline-view :value \"" value "\")"))))) :text (str "(~inline-view :value \"" value "\")")))))
(defhandler ex-edit-cancel (defhandler ex-edit-cancel
:path "/geography/hypermedia/examples/api/edit/cancel" :path "/(geography.(hypermedia.(example.(api.edit-cancel))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -175,7 +175,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-oob (defhandler ex-oob
:path "/geography/hypermedia/examples/api/oob" :path "/(geography.(hypermedia.(example.(api.oob))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -195,7 +195,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-lazy (defhandler ex-lazy
:path "/geography/hypermedia/examples/api/lazy" :path "/(geography.(hypermedia.(example.(api.lazy))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -213,7 +213,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-scroll (defhandler ex-scroll
:path "/geography/hypermedia/examples/api/scroll" :path "/(geography.(hypermedia.(example.(api.scroll))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -227,7 +227,7 @@
(range start (+ start 5))) (range start (+ start 5)))
(if (<= (+ pg 1) 6) (if (<= (+ pg 1) 6)
(div :id "scroll-sentinel" (div :id "scroll-sentinel"
:sx-get (str "/geography/hypermedia/examples/api/scroll?page=" (+ pg 1)) :sx-get (str "/(geography.(hypermedia.(example.(api.scroll))))?page=" (+ pg 1))
:sx-trigger "intersect once" :sx-trigger "intersect once"
:sx-target "#scroll-items" :sx-target "#scroll-items"
:sx-swap "beforeend" :sx-swap "beforeend"
@@ -244,7 +244,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-progress-start (defhandler ex-progress-start
:path "/geography/hypermedia/examples/api/progress/start" :path "/(geography.(hypermedia.(example.(api.progress-start))))"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
@@ -261,7 +261,7 @@
:text (str "(~progress-status :percent 0 :job-id \"" job-id "\")")))))) :text (str "(~progress-status :percent 0 :job-id \"" job-id "\")"))))))
(defhandler ex-progress-status (defhandler ex-progress-status
:path "/geography/hypermedia/examples/api/progress/status" :path "/(geography.(hypermedia.(example.(api.progress-status))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -282,7 +282,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-search (defhandler ex-search
:path "/geography/hypermedia/examples/api/search" :path "/(geography.(hypermedia.(example.(api.search))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -304,7 +304,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-validate (defhandler ex-validate
:path "/geography/hypermedia/examples/api/validate" :path "/(geography.(hypermedia.(example.(api.validate))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -331,7 +331,7 @@
:text (nth result 1)))))) :text (nth result 1))))))
(defhandler ex-validate-submit (defhandler ex-validate-submit
:path "/geography/hypermedia/examples/api/validate/submit" :path "/(geography.(hypermedia.(example.(api.validate-submit))))"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
@@ -347,7 +347,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-values (defhandler ex-values
:path "/geography/hypermedia/examples/api/values" :path "/(geography.(hypermedia.(example.(api.values))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -367,7 +367,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-reset-submit (defhandler ex-reset-submit
:path "/geography/hypermedia/examples/api/reset-submit" :path "/(geography.(hypermedia.(example.(api.reset-submit))))"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
@@ -387,7 +387,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-editrow-form (defhandler ex-editrow-form
:path "/geography/hypermedia/examples/api/editrow/<row_id>" :path "/(geography.(hypermedia.(example.(api.(editrow.<sx:row_id>)))))"
:method :get :method :get
:returns "element" :returns "element"
(&key row-id) (&key row-id)
@@ -402,7 +402,7 @@
:text (str "(~edit-row-form :id \"" (get row "id") "\" ...)")))))) :text (str "(~edit-row-form :id \"" (get row "id") "\" ...)"))))))
(defhandler ex-editrow-save (defhandler ex-editrow-save
:path "/geography/hypermedia/examples/api/editrow/<row_id>" :path "/(geography.(hypermedia.(example.(api.(editrow.<sx:row_id>)))))"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
@@ -420,7 +420,7 @@
:text (str "(~edit-row-view :id \"" row-id "\" ...)"))))) :text (str "(~edit-row-view :id \"" row-id "\" ...)")))))
(defhandler ex-editrow-cancel (defhandler ex-editrow-cancel
:path "/geography/hypermedia/examples/api/editrow/<row_id>/cancel" :path "/(geography.(hypermedia.(example.(api.(editrow-cancel.<sx:row_id>)))))"
:method :get :method :get
:returns "element" :returns "element"
(&key row-id) (&key row-id)
@@ -439,7 +439,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-bulk (defhandler ex-bulk
:path "/geography/hypermedia/examples/api/bulk" :path "/(geography.(hypermedia.(example.(api.bulk))))"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
@@ -476,7 +476,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-swap-log (defhandler ex-swap-log
:path "/geography/hypermedia/examples/api/swap-log" :path "/(geography.(hypermedia.(example.(api.swap-log))))"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
@@ -500,7 +500,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-dashboard (defhandler ex-dashboard
:path "/geography/hypermedia/examples/api/dashboard" :path "/(geography.(hypermedia.(example.(api.dashboard))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -530,7 +530,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-tabs (defhandler ex-tabs
:path "/geography/hypermedia/examples/api/tabs/<tab>" :path "/(geography.(hypermedia.(example.(api.(tabs.<sx:tab>)))))"
:method :get :method :get
:returns "element" :returns "element"
(&key tab) (&key tab)
@@ -551,7 +551,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-animate (defhandler ex-animate
:path "/geography/hypermedia/examples/api/animate" :path "/(geography.(hypermedia.(example.(api.animate))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -571,7 +571,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-dialog (defhandler ex-dialog
:path "/geography/hypermedia/examples/api/dialog" :path "/(geography.(hypermedia.(example.(api.dialog))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -584,7 +584,7 @@
:text "(~dialog-modal :title \"Confirm Action\" :message \"...\")"))) :text "(~dialog-modal :title \"Confirm Action\" :message \"...\")")))
(defhandler ex-dialog-close (defhandler ex-dialog-close
:path "/geography/hypermedia/examples/api/dialog/close" :path "/(geography.(hypermedia.(example.(api.dialog-close))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -598,7 +598,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-keyboard (defhandler ex-keyboard
:path "/geography/hypermedia/examples/api/keyboard" :path "/(geography.(hypermedia.(example.(api.keyboard))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -617,7 +617,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-pp-edit-all (defhandler ex-pp-edit-all
:path "/geography/hypermedia/examples/api/putpatch/edit-all" :path "/(geography.(hypermedia.(example.(api.putpatch-edit-all))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -631,7 +631,7 @@
:text (str "(~pp-form-full :name \"" (get p "name") "\" ...)"))))) :text (str "(~pp-form-full :name \"" (get p "name") "\" ...)")))))
(defhandler ex-pp-put (defhandler ex-pp-put
:path "/geography/hypermedia/examples/api/putpatch" :path "/(geography.(hypermedia.(example.(api.putpatch))))"
:method :put :method :put
:csrf false :csrf false
:returns "element" :returns "element"
@@ -648,7 +648,7 @@
:text (str "(~pp-view :name \"" name "\" ...)"))))) :text (str "(~pp-view :name \"" name "\" ...)")))))
(defhandler ex-pp-cancel (defhandler ex-pp-cancel
:path "/geography/hypermedia/examples/api/putpatch/cancel" :path "/(geography.(hypermedia.(example.(api.putpatch-cancel))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -667,7 +667,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-json-echo (defhandler ex-json-echo
:path "/geography/hypermedia/examples/api/json-echo" :path "/(geography.(hypermedia.(example.(api.json-echo))))"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
@@ -688,7 +688,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-echo-vals (defhandler ex-echo-vals
:path "/geography/hypermedia/examples/api/echo-vals" :path "/(geography.(hypermedia.(example.(api.echo-vals))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -705,7 +705,7 @@
:text (str "(~echo-result :label \"values\" :items (list ...))"))))))) :text (str "(~echo-result :label \"values\" :items (list ...))")))))))
(defhandler ex-echo-headers (defhandler ex-echo-headers
:path "/geography/hypermedia/examples/api/echo-headers" :path "/(geography.(hypermedia.(example.(api.echo-headers))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -725,7 +725,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-slow (defhandler ex-slow
:path "/geography/hypermedia/examples/api/slow" :path "/(geography.(hypermedia.(example.(api.slow))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -744,7 +744,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-slow-search (defhandler ex-slow-search
:path "/geography/hypermedia/examples/api/slow-search" :path "/(geography.(hypermedia.(example.(api.slow-search))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -764,7 +764,7 @@
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
(defhandler ex-flaky (defhandler ex-flaky
:path "/geography/hypermedia/examples/api/flaky" :path "/(geography.(hypermedia.(example.(api.flaky))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)

View File

@@ -6,7 +6,7 @@
;; --- sx-get demo: server time --- ;; --- sx-get demo: server time ---
(defhandler ref-time (defhandler ref-time
:path "/geography/hypermedia/reference/api/time" :path "/(geography.(hypermedia.(reference.(api.time))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -19,7 +19,7 @@
;; --- sx-post demo: greet --- ;; --- sx-post demo: greet ---
(defhandler ref-greet (defhandler ref-greet
:path "/geography/hypermedia/reference/api/greet" :path "/(geography.(hypermedia.(reference.(api.greet))))"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
@@ -33,7 +33,7 @@
;; --- sx-put demo: status update --- ;; --- sx-put demo: status update ---
(defhandler ref-status (defhandler ref-status
:path "/geography/hypermedia/reference/api/status" :path "/(geography.(hypermedia.(reference.(api.status))))"
:method :put :method :put
:csrf false :csrf false
:returns "element" :returns "element"
@@ -47,7 +47,7 @@
;; --- sx-patch demo: theme --- ;; --- sx-patch demo: theme ---
(defhandler ref-theme (defhandler ref-theme
:path "/geography/hypermedia/reference/api/theme" :path "/(geography.(hypermedia.(reference.(api.theme))))"
:method :patch :method :patch
:csrf false :csrf false
:returns "element" :returns "element"
@@ -61,7 +61,7 @@
;; --- sx-delete demo --- ;; --- sx-delete demo ---
(defhandler ref-delete-item (defhandler ref-delete-item
:path "/geography/hypermedia/reference/api/item/<item_id>" :path "/(geography.(hypermedia.(reference.(api.(item.<sx:item_id>)))))"
:method :delete :method :delete
:csrf false :csrf false
:returns "element" :returns "element"
@@ -72,7 +72,7 @@
;; --- sx-trigger demo: search --- ;; --- sx-trigger demo: search ---
(defhandler ref-trigger-search (defhandler ref-trigger-search
:path "/geography/hypermedia/reference/api/trigger-search" :path "/(geography.(hypermedia.(reference.(api.trigger-search))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -89,7 +89,7 @@
;; --- sx-swap demo --- ;; --- sx-swap demo ---
(defhandler ref-swap-item (defhandler ref-swap-item
:path "/geography/hypermedia/reference/api/swap-item" :path "/(geography.(hypermedia.(reference.(api.swap-item))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -102,7 +102,7 @@
;; --- sx-swap-oob demo --- ;; --- sx-swap-oob demo ---
(defhandler ref-oob (defhandler ref-oob
:path "/geography/hypermedia/reference/api/oob" :path "/(geography.(hypermedia.(reference.(api.oob))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -117,7 +117,7 @@
;; --- sx-select demo --- ;; --- sx-select demo ---
(defhandler ref-select-page (defhandler ref-select-page
:path "/geography/hypermedia/reference/api/select-page" :path "/(geography.(hypermedia.(reference.(api.select-page))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -134,7 +134,7 @@
;; --- sx-sync demo: slow echo --- ;; --- sx-sync demo: slow echo ---
(defhandler ref-slow-echo (defhandler ref-slow-echo
:path "/geography/hypermedia/reference/api/slow-echo" :path "/(geography.(hypermedia.(reference.(api.slow-echo))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -148,7 +148,7 @@
;; --- sx-prompt demo --- ;; --- sx-prompt demo ---
(defhandler ref-prompt-echo (defhandler ref-prompt-echo
:path "/geography/hypermedia/reference/api/prompt-echo" :path "/(geography.(hypermedia.(reference.(api.prompt-echo))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -161,7 +161,7 @@
;; --- Error demo --- ;; --- Error demo ---
(defhandler ref-error-500 (defhandler ref-error-500
:path "/geography/hypermedia/reference/api/error-500" :path "/(geography.(hypermedia.(reference.(api.error-500))))"
:method :get :method :get
:returns "nil" :returns "nil"
(&key) (&key)
@@ -175,7 +175,7 @@
;; --- sx-encoding demo: file upload name --- ;; --- sx-encoding demo: file upload name ---
(defhandler ref-upload-name (defhandler ref-upload-name
:path "/geography/hypermedia/reference/api/upload-name" :path "/(geography.(hypermedia.(reference.(api.upload-name))))"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
@@ -190,7 +190,7 @@
;; --- sx-headers demo: echo custom headers --- ;; --- sx-headers demo: echo custom headers ---
(defhandler ref-echo-headers (defhandler ref-echo-headers
:path "/geography/hypermedia/reference/api/echo-headers" :path "/(geography.(hypermedia.(reference.(api.echo-headers))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -214,7 +214,7 @@
;; --- sx-include demo: echo GET query params --- ;; --- sx-include demo: echo GET query params ---
(defhandler ref-echo-vals-get (defhandler ref-echo-vals-get
:path "/geography/hypermedia/reference/api/echo-vals" :path "/(geography.(hypermedia.(reference.(api.echo-vals))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -235,7 +235,7 @@
;; --- sx-vals demo: echo POST form values --- ;; --- sx-vals demo: echo POST form values ---
(defhandler ref-echo-vals-post (defhandler ref-echo-vals-post
:path "/geography/hypermedia/reference/api/echo-vals" :path "/(geography.(hypermedia.(reference.(api.echo-vals))))"
:method :post :method :post
:csrf false :csrf false
:returns "element" :returns "element"
@@ -257,7 +257,7 @@
;; --- sx-retry demo: flaky endpoint (fails 2/3 times) --- ;; --- sx-retry demo: flaky endpoint (fails 2/3 times) ---
(defhandler ref-flaky (defhandler ref-flaky
:path "/geography/hypermedia/reference/api/flaky" :path "/(geography.(hypermedia.(reference.(api.flaky))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -275,7 +275,7 @@
;; --- sx-trigger-event demo: response header triggers --- ;; --- sx-trigger-event demo: response header triggers ---
(defhandler ref-trigger-event (defhandler ref-trigger-event
:path "/geography/hypermedia/reference/api/trigger-event" :path "/(geography.(hypermedia.(reference.(api.trigger-event))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)
@@ -287,7 +287,7 @@
;; --- sx-retarget demo: response header retargets --- ;; --- sx-retarget demo: response header retargets ---
(defhandler ref-retarget (defhandler ref-retarget
:path "/geography/hypermedia/reference/api/retarget" :path "/(geography.(hypermedia.(reference.(api.retarget))))"
:method :get :method :get
:returns "element" :returns "element"
(&key) (&key)

View File

@@ -291,7 +291,7 @@
(~demo-marsh-product) (~demo-marsh-product)
(~doc-code :code (highlight ";; Island with a store-backed price signal\n(defisland ~demo-marsh-product ()\n (let ((price (def-store \"demo-price\" (fn () (signal 19.99))))\n (qty (signal 1))\n (total (computed (fn () (* (deref price) (deref qty))))))\n (div\n ;; Reactive price display — updates when store changes\n (span \"$\" (deref price))\n (span \"Qty:\") (button \"-\") (span (deref qty)) (button \"+\")\n (span \"Total: $\" (deref total))\n\n ;; Fetch from server — response arrives as hypermedia\n (button :sx-get \"/geography/reactive/api/flash-sale\"\n :sx-target \"#marsh-server-msg\"\n :sx-swap \"innerHTML\"\n \"Fetch Price\")\n ;; Server response lands here:\n (div :id \"marsh-server-msg\"))))" "lisp")) (~doc-code :code (highlight ";; Island with a store-backed price signal\n(defisland ~demo-marsh-product ()\n (let ((price (def-store \"demo-price\" (fn () (signal 19.99))))\n (qty (signal 1))\n (total (computed (fn () (* (deref price) (deref qty))))))\n (div\n ;; Reactive price display — updates when store changes\n (span \"$\" (deref price))\n (span \"Qty:\") (button \"-\") (span (deref qty)) (button \"+\")\n (span \"Total: $\" (deref total))\n\n ;; Fetch from server — response arrives as hypermedia\n (button :sx-get \"/(geography.(reactive.(api.flash-sale)))\"\n :sx-target \"#marsh-server-msg\"\n :sx-swap \"innerHTML\"\n \"Fetch Price\")\n ;; Server response lands here:\n (div :id \"marsh-server-msg\"))))" "lisp"))
(~doc-code :code (highlight ";; Server returns SX content + a data-init script:\n;;\n;; (<>\n;; (p \"Flash sale! Price: $14.99\")\n;; (script :type \"text/sx\" :data-init\n;; \"(reset! (use-store \\\"demo-price\\\") 14.99)\"))\n;;\n;; The <p> is swapped in as normal hypermedia content.\n;; The script writes to the store signal.\n;; The island's (deref price), total, and savings\n;; all update reactively — no re-render, no diffing." "lisp")) (~doc-code :code (highlight ";; Server returns SX content + a data-init script:\n;;\n;; (<>\n;; (p \"Flash sale! Price: $14.99\")\n;; (script :type \"text/sx\" :data-init\n;; \"(reset! (use-store \\\"demo-price\\\") 14.99)\"))\n;;\n;; The <p> is swapped in as normal hypermedia content.\n;; The script writes to the store signal.\n;; The island's (deref price), total, and savings\n;; all update reactively — no re-render, no diffing." "lisp"))
@@ -315,7 +315,7 @@
(div :id "marsh-flash-target" (div :id "marsh-flash-target"
:class "min-h-[2rem]") :class "min-h-[2rem]")
(button :class "mt-2 px-3 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700" (button :class "mt-2 px-3 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
:sx-get "/geography/reactive/api/flash-sale" :sx-get "/(geography.(reactive.(api.flash-sale)))"
:sx-target "#marsh-flash-target" :sx-target "#marsh-flash-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
"Fetch from server")) "Fetch from server"))
@@ -334,7 +334,7 @@
(~demo-marsh-settle) (~demo-marsh-settle)
(~doc-code :code (highlight ";; sx-on-settle runs SX after the swap settles\n(defisland ~demo-marsh-settle ()\n (let ((count (def-store \"settle-count\" (fn () (signal 0)))))\n (div\n ;; Reactive counter — updates from sx-on-settle\n (span \"Fetched: \" (deref count) \" times\")\n\n ;; Button with sx-on-settle hook\n (button :sx-get \"/geography/reactive/api/settle-data\"\n :sx-target \"#settle-result\"\n :sx-swap \"innerHTML\"\n :sx-on-settle \"(swap! (use-store \\\"settle-count\\\") inc)\"\n \"Fetch Item\")\n\n ;; Server content lands here (pure hypermedia)\n (div :id \"settle-result\"))))" "lisp")) (~doc-code :code (highlight ";; sx-on-settle runs SX after the swap settles\n(defisland ~demo-marsh-settle ()\n (let ((count (def-store \"settle-count\" (fn () (signal 0)))))\n (div\n ;; Reactive counter — updates from sx-on-settle\n (span \"Fetched: \" (deref count) \" times\")\n\n ;; Button with sx-on-settle hook\n (button :sx-get \"/(geography.(reactive.(api.settle-data)))\"\n :sx-target \"#settle-result\"\n :sx-swap \"innerHTML\"\n :sx-on-settle \"(swap! (use-store \\\"settle-count\\\") inc)\"\n \"Fetch Item\")\n\n ;; Server content lands here (pure hypermedia)\n (div :id \"settle-result\"))))" "lisp"))
(p "The server knows nothing about signals or counters. It returns plain content. The " (code "sx-on-settle") " hook is a client-side concern — it runs in the global SX environment with access to all primitives.")) (p "The server knows nothing about signals or counters. It returns plain content. The " (code "sx-on-settle") " hook is a client-side concern — it runs in the global SX environment with access to all primitives."))
@@ -348,7 +348,7 @@
(~demo-marsh-signal-url) (~demo-marsh-signal-url)
(~doc-code :code (highlight ";; sx-get URL computed from a signal\n(defisland ~demo-marsh-signal-url ()\n (let ((mode (signal \"products\"))\n (query (signal \"\")))\n (div\n ;; Mode selector — changes what we're searching\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! mode \"products\"))\n :class (computed (fn () ...active-class...))\n \"Products\")\n (button :on-click (fn (e) (reset! mode \"events\")) \"Events\")\n (button :on-click (fn (e) (reset! mode \"posts\")) \"Posts\"))\n\n ;; Search button — URL is a computed expression\n (button :sx-get (computed (fn ()\n (str \"/geography/reactive/api/search/\"\n (deref mode) \"?q=\" (deref query))))\n :sx-target \"#signal-results\"\n :sx-swap \"innerHTML\"\n \"Search\")\n\n (div :id \"signal-results\"))))" "lisp")) (~doc-code :code (highlight ";; sx-get URL computed from a signal\n(defisland ~demo-marsh-signal-url ()\n (let ((mode (signal \"products\"))\n (query (signal \"\")))\n (div\n ;; Mode selector — changes what we're searching\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! mode \"products\"))\n :class (computed (fn () ...active-class...))\n \"Products\")\n (button :on-click (fn (e) (reset! mode \"events\")) \"Events\")\n (button :on-click (fn (e) (reset! mode \"posts\")) \"Posts\"))\n\n ;; Search button — URL is a computed expression\n (button :sx-get (computed (fn ()\n (str \"/(geography.(reactive.(api.search-\"\n (deref mode) \")))\" \"?q=\" (deref query))))\n :sx-target \"#signal-results\"\n :sx-swap \"innerHTML\"\n \"Search\")\n\n (div :id \"signal-results\"))))" "lisp"))
(p "No custom plumbing. The same " (code "reactive-attr") " mechanism that makes " (code ":class") " reactive also makes " (code ":sx-get") " reactive. " (code "get-verb-info") " reads " (code "dom-get-attr") " at trigger time — it sees the current URL because the effect already updated the DOM attribute.")) (p "No custom plumbing. The same " (code "reactive-attr") " mechanism that makes " (code ":class") " reactive also makes " (code ":sx-get") " reactive. " (code "get-verb-info") " reads " (code "dom-get-attr") " at trigger time — it sees the current URL because the effect already updated the DOM attribute."))
@@ -361,7 +361,7 @@
(~demo-marsh-view-transform) (~demo-marsh-view-transform)
(~doc-code :code (highlight ";; View mode transforms display without refetch\n(defisland ~demo-marsh-view-transform ()\n (let ((view (signal \"list\"))\n (items (signal nil)))\n (div\n ;; View toggle\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! view \"list\")) \"List\")\n (button :on-click (fn (e) (reset! view \"grid\")) \"Grid\")\n (button :on-click (fn (e) (reset! view \"compact\")) \"Compact\"))\n\n ;; Fetch from server — stores raw data in signal\n (button :sx-get \"/geography/reactive/api/catalog\"\n :sx-target \"#catalog-raw\"\n :sx-swap \"innerHTML\"\n \"Fetch Catalog\")\n\n ;; Raw server content (hidden, used as data source)\n (div :id \"catalog-raw\" :class \"hidden\")\n\n ;; Reactive display — re-renders when view changes\n (div (computed (fn () (render-view (deref view) (deref items))))))))" "lisp")) (~doc-code :code (highlight ";; View mode transforms display without refetch\n(defisland ~demo-marsh-view-transform ()\n (let ((view (signal \"list\"))\n (items (signal nil)))\n (div\n ;; View toggle\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! view \"list\")) \"List\")\n (button :on-click (fn (e) (reset! view \"grid\")) \"Grid\")\n (button :on-click (fn (e) (reset! view \"compact\")) \"Compact\"))\n\n ;; Fetch from server — stores raw data in signal\n (button :sx-get \"/(geography.(reactive.(api.catalog)))\"\n :sx-target \"#catalog-raw\"\n :sx-swap \"innerHTML\"\n \"Fetch Catalog\")\n\n ;; Raw server content (hidden, used as data source)\n (div :id \"catalog-raw\" :class \"hidden\")\n\n ;; Reactive display — re-renders when view changes\n (div (computed (fn () (render-view (deref view) (deref items))))))))" "lisp"))
(p "The view signal doesn't just toggle CSS classes — it fundamentally reshapes the DOM. List view shows description. Grid view arranges in columns. Compact view shows names only. All from the same server data, transformed by client state.")) (p "The view signal doesn't just toggle CSS classes — it fundamentally reshapes the DOM. List view shows description. Grid view arranges in columns. Compact view shows names only. All from the same server data, transformed by client state."))
@@ -408,7 +408,7 @@
(div :class "border-t border-stone-200 pt-3" (div :class "border-t border-stone-200 pt-3"
(div :class "flex items-center gap-3" (div :class "flex items-center gap-3"
(button :class "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700" (button :class "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
:sx-get "/geography/reactive/api/flash-sale" :sx-get "/(geography.(reactive.(api.flash-sale)))"
:sx-target "#marsh-server-msg" :sx-target "#marsh-server-msg"
:sx-swap "innerHTML" :sx-swap "innerHTML"
"Fetch Price from Server") "Fetch Price from Server")
@@ -471,7 +471,7 @@
(span :class "text-sm text-stone-600" " times"))) (span :class "text-sm text-stone-600" " times")))
(div :class "flex items-center gap-3" (div :class "flex items-center gap-3"
(button :class "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700" (button :class "px-4 py-2 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
:sx-get "/geography/reactive/api/settle-data" :sx-get "/(geography.(reactive.(api.settle-data)))"
:sx-target "#settle-result" :sx-target "#settle-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-on-settle "(swap! (use-store \"settle-count\") inc)" :sx-on-settle "(swap! (use-store \"settle-count\") inc)"
@@ -516,15 +516,15 @@
:class "flex-1 px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400") :class "flex-1 px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400")
(button :class "px-4 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700" (button :class "px-4 py-1.5 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700"
:sx-get (computed (fn () :sx-get (computed (fn ()
(str "/geography/reactive/api/search/" (deref mode) (str "/(geography.(reactive.(api.search-" (deref mode)
"?q=" (deref query)))) ")))""?q=" (deref query))))
:sx-target "#signal-results" :sx-target "#signal-results"
:sx-swap "innerHTML" :sx-swap "innerHTML"
"Search")) "Search"))
;; Current URL display ;; Current URL display
(p :class "text-xs text-stone-400 font-mono" (p :class "text-xs text-stone-400 font-mono"
"URL: " (computed (fn () "URL: " (computed (fn ()
(str "/geography/reactive/api/search/" (deref mode) "?q=" (deref query))))) (str "/(geography.(reactive.(api.search-" (deref mode) ")))" "?q=" (deref query)))))
;; Results ;; Results
(div :id "signal-results" :class "min-h-[3rem] rounded bg-stone-50 p-2" (div :id "signal-results" :class "min-h-[3rem] rounded bg-stone-50 p-2"
(p :class "text-sm text-stone-400 italic" "Select a category and search."))))) (p :class "text-sm text-stone-400 italic" "Select a category and search.")))))
@@ -561,7 +561,7 @@
;; Fetch button — response writes structured data to store via data-init ;; Fetch button — response writes structured data to store via data-init
(div :class "flex items-center gap-3" (div :class "flex items-center gap-3"
(button :class "px-4 py-2 rounded bg-emerald-600 text-white text-sm font-medium hover:bg-emerald-700" (button :class "px-4 py-2 rounded bg-emerald-600 text-white text-sm font-medium hover:bg-emerald-700"
:sx-get "/geography/reactive/api/catalog" :sx-get "/(geography.(reactive.(api.catalog)))"
:sx-target "#catalog-msg" :sx-target "#catalog-msg"
:sx-swap "innerHTML" :sx-swap "innerHTML"
"Fetch Catalog") "Fetch Catalog")

View File

@@ -22,7 +22,7 @@
(div :id "click-result" :class "p-4 rounded bg-stone-100 text-stone-500 text-center" (div :id "click-result" :class "p-4 rounded bg-stone-100 text-stone-500 text-center"
"Click the button to load content.") "Click the button to load content.")
(button (button
:sx-get "/geography/hypermedia/examples/api/click" :sx-get "/(geography.(hypermedia.(example.(api.click))))"
:sx-target "#click-result" :sx-target "#click-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors"
@@ -39,7 +39,7 @@
(defcomp ~form-demo () (defcomp ~form-demo ()
(div :class "space-y-4" (div :class "space-y-4"
(form (form
:sx-post "/geography/hypermedia/examples/api/form" :sx-post "/(geography.(hypermedia.(example.(api.form))))"
:sx-target "#form-result" :sx-target "#form-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "space-y-3" :class "space-y-3"
@@ -63,7 +63,7 @@
(defcomp ~polling-demo () (defcomp ~polling-demo ()
(div :class "space-y-4" (div :class "space-y-4"
(div :id "poll-target" (div :id "poll-target"
:sx-get "/geography/hypermedia/examples/api/poll" :sx-get "/(geography.(hypermedia.(example.(api.poll))))"
:sx-trigger "load, every 2s" :sx-trigger "load, every 2s"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "p-4 rounded border border-stone-200 bg-stone-100 text-center font-mono" :class "p-4 rounded border border-stone-200 bg-stone-100 text-center font-mono"
@@ -99,7 +99,7 @@
(td :class "px-3 py-2 text-stone-700" name) (td :class "px-3 py-2 text-stone-700" name)
(td :class "px-3 py-2" (td :class "px-3 py-2"
(button (button
:sx-delete (str "/geography/hypermedia/examples/api/delete/" id) :sx-delete (str "/(geography.(hypermedia.(example.(api.(delete." id ")))))")
:sx-target (str "#row-" id) :sx-target (str "#row-" id)
:sx-swap "outerHTML" :sx-swap "outerHTML"
:sx-confirm "Delete this item?" :sx-confirm "Delete this item?"
@@ -116,7 +116,7 @@
(div :class "flex items-center justify-between p-3 rounded border border-stone-200" (div :class "flex items-center justify-between p-3 rounded border border-stone-200"
(span :class "text-stone-800" value) (span :class "text-stone-800" value)
(button (button
:sx-get (str "/geography/hypermedia/examples/api/edit?value=" value) :sx-get (str "/(geography.(hypermedia.(example.(api.edit))))?value=" value)
:sx-target "#edit-target" :sx-target "#edit-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "text-sm text-violet-600 hover:text-violet-800" :class "text-sm text-violet-600 hover:text-violet-800"
@@ -124,7 +124,7 @@
(defcomp ~inline-edit-form (&key value) (defcomp ~inline-edit-form (&key value)
(form (form
:sx-post "/geography/hypermedia/examples/api/edit" :sx-post "/(geography.(hypermedia.(example.(api.edit))))"
:sx-target "#edit-target" :sx-target "#edit-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "flex items-center gap-2 p-3 rounded border border-violet-300 bg-violet-50" :class "flex items-center gap-2 p-3 rounded border border-violet-300 bg-violet-50"
@@ -134,7 +134,7 @@
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"save") "save")
(button :type "button" (button :type "button"
:sx-get (str "/geography/hypermedia/examples/api/edit/cancel?value=" value) :sx-get (str "/(geography.(hypermedia.(example.(api.edit-cancel))))?value=" value)
:sx-target "#edit-target" :sx-target "#edit-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-3 py-1.5 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300" :class "px-3 py-1.5 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300"
@@ -152,7 +152,7 @@
(p :class "text-stone-500" "Box B") (p :class "text-stone-500" "Box B")
(p :class "text-sm text-stone-400" "Waiting..."))) (p :class "text-sm text-stone-400" "Waiting...")))
(button (button
:sx-get "/geography/hypermedia/examples/api/oob" :sx-get "/(geography.(hypermedia.(example.(api.oob))))"
:sx-target "#oob-box-a" :sx-target "#oob-box-a"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -164,7 +164,7 @@
(div :class "space-y-4" (div :class "space-y-4"
(p :class "text-sm text-stone-500" "The content below loads automatically when the page renders.") (p :class "text-sm text-stone-500" "The content below loads automatically when the page renders.")
(div :id "lazy-target" (div :id "lazy-target"
:sx-get "/geography/hypermedia/examples/api/lazy" :sx-get "/(geography.(hypermedia.(example.(api.lazy))))"
:sx-trigger "load" :sx-trigger "load"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "p-6 rounded border border-stone-200 bg-stone-100 text-center" :class "p-6 rounded border border-stone-200 bg-stone-100 text-center"
@@ -187,7 +187,7 @@
(str "Item " (+ i 1) " — loaded with the page"))) (str "Item " (+ i 1) " — loaded with the page")))
(list 1 2 3 4 5)) (list 1 2 3 4 5))
(div :id "scroll-sentinel" (div :id "scroll-sentinel"
:sx-get "/geography/hypermedia/examples/api/scroll?page=2" :sx-get "/(geography.(hypermedia.(example.(api.scroll))))?page=2"
:sx-trigger "intersect once" :sx-trigger "intersect once"
:sx-target "#scroll-items" :sx-target "#scroll-items"
:sx-swap "beforeend" :sx-swap "beforeend"
@@ -201,7 +201,7 @@
items) items)
(when (<= page 5) (when (<= page 5)
(div :id "scroll-sentinel" (div :id "scroll-sentinel"
:sx-get (str "/geography/hypermedia/examples/api/scroll?page=" page) :sx-get (str "/(geography.(hypermedia.(example.(api.scroll))))?page=" page)
:sx-trigger "intersect once" :sx-trigger "intersect once"
:sx-target "#scroll-items" :sx-target "#scroll-items"
:sx-swap "beforeend" :sx-swap "beforeend"
@@ -217,7 +217,7 @@
(div :class "bg-violet-600 h-4 rounded-full transition-all" :style "width: 0%")) (div :class "bg-violet-600 h-4 rounded-full transition-all" :style "width: 0%"))
(p :class "text-sm text-stone-500 text-center" "Click start to begin.")) (p :class "text-sm text-stone-500 text-center" "Click start to begin."))
(button (button
:sx-post "/geography/hypermedia/examples/api/progress/start" :sx-post "/(geography.(hypermedia.(example.(api.progress-start))))"
:sx-target "#progress-target" :sx-target "#progress-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -230,7 +230,7 @@
:style (str "width: " percent "%"))) :style (str "width: " percent "%")))
(p :class "text-sm text-stone-500 text-center" (str percent "% complete")) (p :class "text-sm text-stone-500 text-center" (str percent "% complete"))
(when (< percent 100) (when (< percent 100)
(div :sx-get (str "/geography/hypermedia/examples/api/progress/status?job=" job-id) (div :sx-get (str "/(geography.(hypermedia.(example.(api.progress-status))))?job=" job-id)
:sx-trigger "load delay:500ms" :sx-trigger "load delay:500ms"
:sx-target "#progress-target" :sx-target "#progress-target"
:sx-swap "innerHTML")) :sx-swap "innerHTML"))
@@ -242,7 +242,7 @@
(defcomp ~active-search-demo () (defcomp ~active-search-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(input :type "text" :name "q" (input :type "text" :name "q"
:sx-get "/geography/hypermedia/examples/api/search" :sx-get "/(geography.(hypermedia.(example.(api.search))))"
:sx-trigger "keyup delay:300ms changed" :sx-trigger "keyup delay:300ms changed"
:sx-target "#search-results" :sx-target "#search-results"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -262,11 +262,11 @@
;; --- Inline validation demo --- ;; --- Inline validation demo ---
(defcomp ~inline-validation-demo () (defcomp ~inline-validation-demo ()
(form :class "space-y-4" :sx-post "/geography/hypermedia/examples/api/validate/submit" :sx-target "#validation-result" :sx-swap "innerHTML" (form :class "space-y-4" :sx-post "/(geography.(hypermedia.(example.(api.validate-submit))))" :sx-target "#validation-result" :sx-swap "innerHTML"
(div (div
(label :class "block text-sm font-medium text-stone-700 mb-1" "Email") (label :class "block text-sm font-medium text-stone-700 mb-1" "Email")
(input :type "text" :name "email" :placeholder "user@example.com" (input :type "text" :name "email" :placeholder "user@example.com"
:sx-get "/geography/hypermedia/examples/api/validate" :sx-get "/(geography.(hypermedia.(example.(api.validate))))"
:sx-trigger "blur" :sx-trigger "blur"
:sx-target "#email-feedback" :sx-target "#email-feedback"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -290,7 +290,7 @@
(div (div
(label :class "block text-sm font-medium text-stone-700 mb-1" "Category") (label :class "block text-sm font-medium text-stone-700 mb-1" "Category")
(select :name "category" (select :name "category"
:sx-get "/geography/hypermedia/examples/api/values" :sx-get "/(geography.(hypermedia.(example.(api.values))))"
:sx-trigger "change" :sx-trigger "change"
:sx-target "#value-items" :sx-target "#value-items"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -314,7 +314,7 @@
(defcomp ~reset-on-submit-demo () (defcomp ~reset-on-submit-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(form :id "reset-form" (form :id "reset-form"
:sx-post "/geography/hypermedia/examples/api/reset-submit" :sx-post "/(geography.(hypermedia.(example.(api.reset-submit))))"
:sx-target "#reset-result" :sx-target "#reset-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-on:afterSwap "this.reset()" :sx-on:afterSwap "this.reset()"
@@ -354,7 +354,7 @@
(td :class "px-3 py-2 text-stone-700" stock) (td :class "px-3 py-2 text-stone-700" stock)
(td :class "px-3 py-2" (td :class "px-3 py-2"
(button (button
:sx-get (str "/geography/hypermedia/examples/api/editrow/" id) :sx-get (str "/(geography.(hypermedia.(example.(api.(editrow." id ")))))")
:sx-target (str "#erow-" id) :sx-target (str "#erow-" id)
:sx-swap "outerHTML" :sx-swap "outerHTML"
:class "text-sm text-violet-600 hover:text-violet-800" :class "text-sm text-violet-600 hover:text-violet-800"
@@ -373,14 +373,14 @@
:class "w-20 px-2 py-1 border border-stone-300 rounded text-sm")) :class "w-20 px-2 py-1 border border-stone-300 rounded text-sm"))
(td :class "px-3 py-2 space-x-1" (td :class "px-3 py-2 space-x-1"
(button (button
:sx-post (str "/geography/hypermedia/examples/api/editrow/" id) :sx-post (str "/(geography.(hypermedia.(example.(api.(editrow." id ")))))")
:sx-target (str "#erow-" id) :sx-target (str "#erow-" id)
:sx-swap "outerHTML" :sx-swap "outerHTML"
:sx-include (str "#erow-" id) :sx-include (str "#erow-" id)
:class "text-sm text-emerald-600 hover:text-emerald-800" :class "text-sm text-emerald-600 hover:text-emerald-800"
"save") "save")
(button (button
:sx-get (str "/geography/hypermedia/examples/api/editrow/" id "/cancel") :sx-get (str "/(geography.(hypermedia.(example.(api.(editrow-cancel." id ")))))")
:sx-target (str "#erow-" id) :sx-target (str "#erow-" id)
:sx-swap "outerHTML" :sx-swap "outerHTML"
:class "text-sm text-stone-500 hover:text-stone-700" :class "text-sm text-stone-500 hover:text-stone-700"
@@ -393,14 +393,14 @@
(form :id "bulk-form" (form :id "bulk-form"
(div :class "flex gap-2 mb-3" (div :class "flex gap-2 mb-3"
(button :type "button" (button :type "button"
:sx-post "/geography/hypermedia/examples/api/bulk?action=activate" :sx-post "/(geography.(hypermedia.(example.(api.bulk))))?action=activate"
:sx-target "#bulk-table" :sx-target "#bulk-table"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-include "#bulk-form" :sx-include "#bulk-form"
:class "px-3 py-1.5 bg-emerald-600 text-white rounded text-sm hover:bg-emerald-700" :class "px-3 py-1.5 bg-emerald-600 text-white rounded text-sm hover:bg-emerald-700"
"Activate") "Activate")
(button :type "button" (button :type "button"
:sx-post "/geography/hypermedia/examples/api/bulk?action=deactivate" :sx-post "/(geography.(hypermedia.(example.(api.bulk))))?action=deactivate"
:sx-target "#bulk-table" :sx-target "#bulk-table"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-include "#bulk-form" :sx-include "#bulk-form"
@@ -437,19 +437,19 @@
(div :class "space-y-3" (div :class "space-y-3"
(div :class "flex gap-2" (div :class "flex gap-2"
(button (button
:sx-post "/geography/hypermedia/examples/api/swap-log?mode=beforeend" :sx-post "/(geography.(hypermedia.(example.(api.swap-log))))?mode=beforeend"
:sx-target "#swap-log" :sx-target "#swap-log"
:sx-swap "beforeend" :sx-swap "beforeend"
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Add to End") "Add to End")
(button (button
:sx-post "/geography/hypermedia/examples/api/swap-log?mode=afterbegin" :sx-post "/(geography.(hypermedia.(example.(api.swap-log))))?mode=afterbegin"
:sx-target "#swap-log" :sx-target "#swap-log"
:sx-swap "afterbegin" :sx-swap "afterbegin"
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Add to Start") "Add to Start")
(button (button
:sx-post "/geography/hypermedia/examples/api/swap-log?mode=none" :sx-post "/(geography.(hypermedia.(example.(api.swap-log))))?mode=none"
:sx-target "#swap-log" :sx-target "#swap-log"
:sx-swap "none" :sx-swap "none"
:class "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700" :class "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700"
@@ -469,21 +469,21 @@
(div :class "space-y-3" (div :class "space-y-3"
(div :class "flex gap-2" (div :class "flex gap-2"
(button (button
:sx-get "/geography/hypermedia/examples/api/dashboard" :sx-get "/(geography.(hypermedia.(example.(api.dashboard))))"
:sx-target "#filter-target" :sx-target "#filter-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-select "#dash-stats" :sx-select "#dash-stats"
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Stats Only") "Stats Only")
(button (button
:sx-get "/geography/hypermedia/examples/api/dashboard" :sx-get "/(geography.(hypermedia.(example.(api.dashboard))))"
:sx-target "#filter-target" :sx-target "#filter-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-select "#dash-header" :sx-select "#dash-header"
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Header Only") "Header Only")
(button (button
:sx-get "/geography/hypermedia/examples/api/dashboard" :sx-get "/(geography.(hypermedia.(example.(api.dashboard))))"
:sx-target "#filter-target" :sx-target "#filter-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700" :class "px-3 py-1.5 bg-stone-600 text-white rounded text-sm hover:bg-stone-700"
@@ -505,7 +505,7 @@
(defcomp ~tab-btn (&key tab label active) (defcomp ~tab-btn (&key tab label active)
(button (button
:sx-get (str "/geography/hypermedia/examples/api/tabs/" tab) :sx-get (str "/(geography.(hypermedia.(example.(api.(tabs." tab ")))))")
:sx-target "#tab-content" :sx-target "#tab-content"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-push-url (str "/geography/hypermedia/examples/tabs?tab=" tab) :sx-push-url (str "/geography/hypermedia/examples/tabs?tab=" tab)
@@ -520,7 +520,7 @@
(defcomp ~animations-demo () (defcomp ~animations-demo ()
(div :class "space-y-4" (div :class "space-y-4"
(button (button
:sx-get "/geography/hypermedia/examples/api/animate" :sx-get "/(geography.(hypermedia.(example.(api.animate))))"
:sx-target "#anim-target" :sx-target "#anim-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -539,7 +539,7 @@
(defcomp ~dialogs-demo () (defcomp ~dialogs-demo ()
(div (div
(button (button
:sx-get "/geography/hypermedia/examples/api/dialog" :sx-get "/(geography.(hypermedia.(example.(api.dialog))))"
:sx-target "#dialog-container" :sx-target "#dialog-container"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -549,7 +549,7 @@
(defcomp ~dialog-modal (&key title message) (defcomp ~dialog-modal (&key title message)
(div :class "fixed inset-0 z-50 flex items-center justify-center" (div :class "fixed inset-0 z-50 flex items-center justify-center"
(div :class "absolute inset-0 bg-black/50" (div :class "absolute inset-0 bg-black/50"
:sx-get "/geography/hypermedia/examples/api/dialog/close" :sx-get "/(geography.(hypermedia.(example.(api.dialog-close))))"
:sx-target "#dialog-container" :sx-target "#dialog-container"
:sx-swap "innerHTML") :sx-swap "innerHTML")
(div :class "relative bg-stone-100 rounded-lg shadow-xl p-6 max-w-md w-full mx-4 space-y-4" (div :class "relative bg-stone-100 rounded-lg shadow-xl p-6 max-w-md w-full mx-4 space-y-4"
@@ -557,13 +557,13 @@
(p :class "text-stone-600" message) (p :class "text-stone-600" message)
(div :class "flex justify-end gap-2" (div :class "flex justify-end gap-2"
(button (button
:sx-get "/geography/hypermedia/examples/api/dialog/close" :sx-get "/(geography.(hypermedia.(example.(api.dialog-close))))"
:sx-target "#dialog-container" :sx-target "#dialog-container"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300" :class "px-4 py-2 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300"
"Cancel") "Cancel")
(button (button
:sx-get "/geography/hypermedia/examples/api/dialog/close" :sx-get "/(geography.(hypermedia.(example.(api.dialog-close))))"
:sx-target "#dialog-container" :sx-target "#dialog-container"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
@@ -586,16 +586,16 @@
(kbd :class "px-2 py-0.5 bg-stone-100 border border-stone-300 rounded text-xs font-mono" "h") (kbd :class "px-2 py-0.5 bg-stone-100 border border-stone-300 rounded text-xs font-mono" "h")
(span :class "text-sm text-stone-500" "Help")))) (span :class "text-sm text-stone-500" "Help"))))
(div :id "kbd-target" (div :id "kbd-target"
:sx-get "/geography/hypermedia/examples/api/keyboard?key=s" :sx-get "/(geography.(hypermedia.(example.(api.keyboard))))?key=s"
:sx-trigger "keyup[key=='s'&&!event.target.matches('input,textarea')] from:body" :sx-trigger "keyup[key=='s'&&!event.target.matches('input,textarea')] from:body"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "p-4 rounded border border-stone-200 bg-stone-100 text-center" :class "p-4 rounded border border-stone-200 bg-stone-100 text-center"
(p :class "text-stone-400 text-sm" "Press a shortcut key...")) (p :class "text-stone-400 text-sm" "Press a shortcut key..."))
(div :sx-get "/geography/hypermedia/examples/api/keyboard?key=n" (div :sx-get "/(geography.(hypermedia.(example.(api.keyboard))))?key=n"
:sx-trigger "keyup[key=='n'&&!event.target.matches('input,textarea')] from:body" :sx-trigger "keyup[key=='n'&&!event.target.matches('input,textarea')] from:body"
:sx-target "#kbd-target" :sx-target "#kbd-target"
:sx-swap "innerHTML") :sx-swap "innerHTML")
(div :sx-get "/geography/hypermedia/examples/api/keyboard?key=h" (div :sx-get "/(geography.(hypermedia.(example.(api.keyboard))))?key=h"
:sx-trigger "keyup[key=='h'&&!event.target.matches('input,textarea')] from:body" :sx-trigger "keyup[key=='h'&&!event.target.matches('input,textarea')] from:body"
:sx-target "#kbd-target" :sx-target "#kbd-target"
:sx-swap "innerHTML"))) :sx-swap "innerHTML")))
@@ -619,7 +619,7 @@
(p :class "text-sm text-stone-500" email) (p :class "text-sm text-stone-500" email)
(p :class "text-sm text-stone-500" role)) (p :class "text-sm text-stone-500" role))
(button (button
:sx-get "/geography/hypermedia/examples/api/putpatch/edit-all" :sx-get "/(geography.(hypermedia.(example.(api.putpatch-edit-all))))"
:sx-target "#pp-target" :sx-target "#pp-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "text-sm text-violet-600 hover:text-violet-800" :class "text-sm text-violet-600 hover:text-violet-800"
@@ -627,7 +627,7 @@
(defcomp ~pp-form-full (&key name email role) (defcomp ~pp-form-full (&key name email role)
(form (form
:sx-put "/geography/hypermedia/examples/api/putpatch" :sx-put "/(geography.(hypermedia.(example.(api.putpatch))))"
:sx-target "#pp-target" :sx-target "#pp-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "space-y-3" :class "space-y-3"
@@ -648,7 +648,7 @@
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Save All (PUT)") "Save All (PUT)")
(button :type "button" (button :type "button"
:sx-get "/geography/hypermedia/examples/api/putpatch/cancel" :sx-get "/(geography.(hypermedia.(example.(api.putpatch-cancel))))"
:sx-target "#pp-target" :sx-target "#pp-target"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300" :class "px-4 py-2 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300"
@@ -659,7 +659,7 @@
(defcomp ~json-encoding-demo () (defcomp ~json-encoding-demo ()
(div :class "space-y-4" (div :class "space-y-4"
(form (form
:sx-post "/geography/hypermedia/examples/api/json-echo" :sx-post "/(geography.(hypermedia.(example.(api.json-echo))))"
:sx-target "#json-result" :sx-target "#json-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-encoding "json" :sx-encoding "json"
@@ -691,7 +691,7 @@
(div :class "space-y-2" (div :class "space-y-2"
(h4 :class "text-sm font-semibold text-stone-700" "sx-vals — send extra values") (h4 :class "text-sm font-semibold text-stone-700" "sx-vals — send extra values")
(button (button
:sx-get "/geography/hypermedia/examples/api/echo-vals" :sx-get "/(geography.(hypermedia.(example.(api.echo-vals))))"
:sx-target "#vals-result" :sx-target "#vals-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-vals "{\"source\": \"button\", \"version\": \"2.0\"}" :sx-vals "{\"source\": \"button\", \"version\": \"2.0\"}"
@@ -702,7 +702,7 @@
(div :class "space-y-2" (div :class "space-y-2"
(h4 :class "text-sm font-semibold text-stone-700" "sx-headers — send custom headers") (h4 :class "text-sm font-semibold text-stone-700" "sx-headers — send custom headers")
(button (button
:sx-get "/geography/hypermedia/examples/api/echo-headers" :sx-get "/(geography.(hypermedia.(example.(api.echo-headers))))"
:sx-target "#headers-result" :sx-target "#headers-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-headers {:X-Custom-Token "abc123" :X-Request-Source "demo"} :sx-headers {:X-Custom-Token "abc123" :X-Request-Source "demo"}
@@ -723,7 +723,7 @@
(defcomp ~loading-states-demo () (defcomp ~loading-states-demo ()
(div :class "space-y-4" (div :class "space-y-4"
(button (button
:sx-get "/geography/hypermedia/examples/api/slow" :sx-get "/(geography.(hypermedia.(example.(api.slow))))"
:sx-target "#loading-result" :sx-target "#loading-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "sx-loading-btn px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm flex items-center gap-2" :class "sx-loading-btn px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm flex items-center gap-2"
@@ -742,7 +742,7 @@
(defcomp ~sync-replace-demo () (defcomp ~sync-replace-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(input :type "text" :name "q" (input :type "text" :name "q"
:sx-get "/geography/hypermedia/examples/api/slow-search" :sx-get "/(geography.(hypermedia.(example.(api.slow-search))))"
:sx-trigger "keyup delay:200ms changed" :sx-trigger "keyup delay:200ms changed"
:sx-target "#sync-result" :sx-target "#sync-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -762,7 +762,7 @@
(defcomp ~retry-demo () (defcomp ~retry-demo ()
(div :class "space-y-4" (div :class "space-y-4"
(button (button
:sx-get "/geography/hypermedia/examples/api/flaky" :sx-get "/(geography.(hypermedia.(example.(api.flaky))))"
:sx-target "#retry-result" :sx-target "#retry-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-retry "exponential:1000:8000" :sx-retry "exponential:1000:8000"

View File

@@ -280,6 +280,51 @@ async def eval_sx_url(raw_path: str) -> Any:
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
_REDIRECT_PATTERNS = [ _REDIRECT_PATTERNS = [
# --- API endpoint redirects (most specific first) ---
# Reference API: /geography/hypermedia/reference/api/item/<id>
(re.compile(r"^/geography/hypermedia/reference/api/item/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(reference.(api.(item.{m.group(1)})))))"),
# Reference API: /geography/hypermedia/reference/api/<name>
(re.compile(r"^/geography/hypermedia/reference/api/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(reference.(api.{m.group(1)}))))"),
# Example API: sub-paths with params
(re.compile(r"^/geography/hypermedia/examples/api/editrow/(.+?)/cancel/?$"),
lambda m: f"/(geography.(hypermedia.(example.(api.(editrow-cancel.{m.group(1)})))))"),
(re.compile(r"^/geography/hypermedia/examples/api/editrow/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(example.(api.(editrow.{m.group(1)})))))"),
(re.compile(r"^/geography/hypermedia/examples/api/delete/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(example.(api.(delete.{m.group(1)})))))"),
(re.compile(r"^/geography/hypermedia/examples/api/tabs/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(example.(api.(tabs.{m.group(1)})))))"),
# Example API: sub-paths collapsed to hyphens
(re.compile(r"^/geography/hypermedia/examples/api/edit/cancel/?$"),
"/(geography.(hypermedia.(example.(api.edit-cancel))))"),
(re.compile(r"^/geography/hypermedia/examples/api/progress/start/?$"),
"/(geography.(hypermedia.(example.(api.progress-start))))"),
(re.compile(r"^/geography/hypermedia/examples/api/progress/status/?$"),
"/(geography.(hypermedia.(example.(api.progress-status))))"),
(re.compile(r"^/geography/hypermedia/examples/api/validate/submit/?$"),
"/(geography.(hypermedia.(example.(api.validate-submit))))"),
(re.compile(r"^/geography/hypermedia/examples/api/dialog/close/?$"),
"/(geography.(hypermedia.(example.(api.dialog-close))))"),
(re.compile(r"^/geography/hypermedia/examples/api/putpatch/edit-all/?$"),
"/(geography.(hypermedia.(example.(api.putpatch-edit-all))))"),
(re.compile(r"^/geography/hypermedia/examples/api/putpatch/cancel/?$"),
"/(geography.(hypermedia.(example.(api.putpatch-cancel))))"),
# Example API: simple names
(re.compile(r"^/geography/hypermedia/examples/api/(.+?)/?$"),
lambda m: f"/(geography.(hypermedia.(example.(api.{m.group(1)}))))"),
# Reactive API: sub-paths collapsed
(re.compile(r"^/geography/reactive/api/search/(.+?)/?$"),
lambda m: f"/(geography.(reactive.(api.search-{m.group(1)})))"),
(re.compile(r"^/geography/reactive/api/(.+?)/?$"),
lambda m: f"/(geography.(reactive.(api.{m.group(1)})))"),
# --- Page redirects ---
# More specific first # More specific first
(re.compile(r"^/language/specs/explore/(.+?)/?$"), (re.compile(r"^/language/specs/explore/(.+?)/?$"),
lambda m: f"/(language.(spec.(explore.{m.group(1)})))"), lambda m: f"/(language.(spec.(explore.{m.group(1)})))"),

View File

@@ -10,7 +10,7 @@
(defcomp ~ref-get-demo () (defcomp ~ref-get-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/geography/hypermedia/reference/api/time" :sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-get-result" :sx-target "#ref-get-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -26,7 +26,7 @@
(defcomp ~ref-post-demo () (defcomp ~ref-post-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(form (form
:sx-post "/geography/hypermedia/reference/api/greet" :sx-post "/(geography.(hypermedia.(reference.(api.greet))))"
:sx-target "#ref-post-result" :sx-target "#ref-post-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "flex gap-2" :class "flex gap-2"
@@ -48,7 +48,7 @@
(div :class "flex items-center justify-between p-3 bg-stone-100 rounded" (div :class "flex items-center justify-between p-3 bg-stone-100 rounded"
(span :class "text-stone-700 text-sm" "Status: " (strong "draft")) (span :class "text-stone-700 text-sm" "Status: " (strong "draft"))
(button (button
:sx-put "/geography/hypermedia/reference/api/status" :sx-put "/(geography.(hypermedia.(reference.(api.status))))"
:sx-target "#ref-put-view" :sx-target "#ref-put-view"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-vals "{\"status\": \"published\"}" :sx-vals "{\"status\": \"published\"}"
@@ -63,17 +63,17 @@
(div :class "space-y-2" (div :class "space-y-2"
(div :id "ref-del-1" :class "flex items-center justify-between p-2 border border-stone-200 rounded" (div :id "ref-del-1" :class "flex items-center justify-between p-2 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Item A") (span :class "text-sm text-stone-700" "Item A")
(button :sx-delete "/geography/hypermedia/reference/api/item/1" (button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.1)))))"
:sx-target "#ref-del-1" :sx-swap "delete" :sx-target "#ref-del-1" :sx-swap "delete"
:class "text-red-500 text-sm hover:text-red-700" "Remove")) :class "text-red-500 text-sm hover:text-red-700" "Remove"))
(div :id "ref-del-2" :class "flex items-center justify-between p-2 border border-stone-200 rounded" (div :id "ref-del-2" :class "flex items-center justify-between p-2 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Item B") (span :class "text-sm text-stone-700" "Item B")
(button :sx-delete "/geography/hypermedia/reference/api/item/2" (button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.2)))))"
:sx-target "#ref-del-2" :sx-swap "delete" :sx-target "#ref-del-2" :sx-swap "delete"
:class "text-red-500 text-sm hover:text-red-700" "Remove")) :class "text-red-500 text-sm hover:text-red-700" "Remove"))
(div :id "ref-del-3" :class "flex items-center justify-between p-2 border border-stone-200 rounded" (div :id "ref-del-3" :class "flex items-center justify-between p-2 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Item C") (span :class "text-sm text-stone-700" "Item C")
(button :sx-delete "/geography/hypermedia/reference/api/item/3" (button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.3)))))"
:sx-target "#ref-del-3" :sx-swap "delete" :sx-target "#ref-del-3" :sx-swap "delete"
:class "text-red-500 text-sm hover:text-red-700" "Remove")))) :class "text-red-500 text-sm hover:text-red-700" "Remove"))))
@@ -86,11 +86,11 @@
(div :class "p-3 bg-stone-100 rounded" (div :class "p-3 bg-stone-100 rounded"
(span :class "text-stone-700 text-sm" "Theme: " (strong :id "ref-patch-val" "light"))) (span :class "text-stone-700 text-sm" "Theme: " (strong :id "ref-patch-val" "light")))
(div :class "flex gap-2" (div :class "flex gap-2"
(button :sx-patch "/geography/hypermedia/reference/api/theme" (button :sx-patch "/(geography.(hypermedia.(reference.(api.theme))))"
:sx-vals "{\"theme\": \"dark\"}" :sx-vals "{\"theme\": \"dark\"}"
:sx-target "#ref-patch-val" :sx-swap "innerHTML" :sx-target "#ref-patch-val" :sx-swap "innerHTML"
:class "px-3 py-1 bg-stone-800 text-white rounded text-sm" "Dark") :class "px-3 py-1 bg-stone-800 text-white rounded text-sm" "Dark")
(button :sx-patch "/geography/hypermedia/reference/api/theme" (button :sx-patch "/(geography.(hypermedia.(reference.(api.theme))))"
:sx-vals "{\"theme\": \"light\"}" :sx-vals "{\"theme\": \"light\"}"
:sx-target "#ref-patch-val" :sx-swap "innerHTML" :sx-target "#ref-patch-val" :sx-swap "innerHTML"
:class "px-3 py-1 bg-stone-100 border border-stone-300 text-stone-700 rounded text-sm" "Light")))) :class "px-3 py-1 bg-stone-100 border border-stone-300 text-stone-700 rounded text-sm" "Light"))))
@@ -102,7 +102,7 @@
(defcomp ~ref-trigger-demo () (defcomp ~ref-trigger-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(input :type "text" :name "q" :placeholder "Type to search..." (input :type "text" :name "q" :placeholder "Type to search..."
:sx-get "/geography/hypermedia/reference/api/trigger-search" :sx-get "/(geography.(hypermedia.(reference.(api.trigger-search))))"
:sx-trigger "input changed delay:300ms" :sx-trigger "input changed delay:300ms"
:sx-target "#ref-trigger-result" :sx-target "#ref-trigger-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -118,12 +118,12 @@
(defcomp ~ref-target-demo () (defcomp ~ref-target-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(div :class "flex gap-2" (div :class "flex gap-2"
(button :sx-get "/geography/hypermedia/reference/api/time" (button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-target-a" :sx-target "#ref-target-a"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-3 py-1 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-3 py-1 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
"Update Box A") "Update Box A")
(button :sx-get "/geography/hypermedia/reference/api/time" (button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-target-b" :sx-target "#ref-target-b"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-3 py-1 bg-emerald-600 text-white rounded text-sm hover:bg-emerald-700" :class "px-3 py-1 bg-emerald-600 text-white rounded text-sm hover:bg-emerald-700"
@@ -141,13 +141,13 @@
(defcomp ~ref-swap-demo () (defcomp ~ref-swap-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(div :class "flex gap-2 flex-wrap" (div :class "flex gap-2 flex-wrap"
(button :sx-get "/geography/hypermedia/reference/api/swap-item" (button :sx-get "/(geography.(hypermedia.(reference.(api.swap-item))))"
:sx-target "#ref-swap-list" :sx-swap "beforeend" :sx-target "#ref-swap-list" :sx-swap "beforeend"
:class "px-3 py-1 bg-violet-600 text-white rounded text-sm" "beforeend") :class "px-3 py-1 bg-violet-600 text-white rounded text-sm" "beforeend")
(button :sx-get "/geography/hypermedia/reference/api/swap-item" (button :sx-get "/(geography.(hypermedia.(reference.(api.swap-item))))"
:sx-target "#ref-swap-list" :sx-swap "afterbegin" :sx-target "#ref-swap-list" :sx-swap "afterbegin"
:class "px-3 py-1 bg-emerald-600 text-white rounded text-sm" "afterbegin") :class "px-3 py-1 bg-emerald-600 text-white rounded text-sm" "afterbegin")
(button :sx-get "/geography/hypermedia/reference/api/swap-item" (button :sx-get "/(geography.(hypermedia.(reference.(api.swap-item))))"
:sx-target "#ref-swap-list" :sx-swap "innerHTML" :sx-target "#ref-swap-list" :sx-swap "innerHTML"
:class "px-3 py-1 bg-blue-600 text-white rounded text-sm" "innerHTML")) :class "px-3 py-1 bg-blue-600 text-white rounded text-sm" "innerHTML"))
(div :id "ref-swap-list" (div :id "ref-swap-list"
@@ -160,7 +160,7 @@
(defcomp ~ref-oob-demo () (defcomp ~ref-oob-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button :sx-get "/geography/hypermedia/reference/api/oob" (button :sx-get "/(geography.(hypermedia.(reference.(api.oob))))"
:sx-target "#ref-oob-main" :sx-target "#ref-oob-main"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700" :class "px-4 py-2 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
@@ -179,7 +179,7 @@
(defcomp ~ref-select-demo () (defcomp ~ref-select-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button :sx-get "/geography/hypermedia/reference/api/select-page" (button :sx-get "/(geography.(hypermedia.(reference.(api.select-page))))"
:sx-target "#ref-select-result" :sx-target "#ref-select-result"
:sx-select "#the-content" :sx-select "#the-content"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -198,7 +198,7 @@
(div :id "ref-confirm-item" (div :id "ref-confirm-item"
:class "flex items-center justify-between p-3 border border-stone-200 rounded" :class "flex items-center justify-between p-3 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Important file.txt") (span :class "text-sm text-stone-700" "Important file.txt")
(button :sx-delete "/geography/hypermedia/reference/api/item/confirm" (button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.confirm)))))"
:sx-target "#ref-confirm-item" :sx-swap "delete" :sx-target "#ref-confirm-item" :sx-swap "delete"
:sx-confirm "Are you sure you want to delete this file?" :sx-confirm "Are you sure you want to delete this file?"
:class "px-3 py-1 text-red-500 text-sm border border-red-200 rounded hover:bg-red-50" :class "px-3 py-1 text-red-500 text-sm border border-red-200 rounded hover:bg-red-50"
@@ -211,14 +211,14 @@
(defcomp ~ref-pushurl-demo () (defcomp ~ref-pushurl-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(div :class "flex gap-2" (div :class "flex gap-2"
(a :href "/geography/hypermedia/reference/attributes/sx-get" (a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
:sx-get "/geography/hypermedia/reference/attributes/sx-get" :sx-get "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
:sx-target "#main-panel" :sx-select "#main-panel" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
:class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200" :class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200"
"sx-get page") "sx-get page")
(a :href "/geography/hypermedia/reference/attributes/sx-post" (a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-post)))"
:sx-get "/geography/hypermedia/reference/attributes/sx-post" :sx-get "/(geography.(hypermedia.(reference-detail.attributes.sx-post)))"
:sx-target "#main-panel" :sx-select "#main-panel" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
:class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200" :class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200"
@@ -233,7 +233,7 @@
(defcomp ~ref-sync-demo () (defcomp ~ref-sync-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(input :type "text" :name "q" :placeholder "Type quickly..." (input :type "text" :name "q" :placeholder "Type quickly..."
:sx-get "/geography/hypermedia/reference/api/slow-echo" :sx-get "/(geography.(hypermedia.(reference.(api.slow-echo))))"
:sx-trigger "input changed delay:100ms" :sx-trigger "input changed delay:100ms"
:sx-sync "replace" :sx-sync "replace"
:sx-target "#ref-sync-result" :sx-target "#ref-sync-result"
@@ -251,7 +251,7 @@
(defcomp ~ref-encoding-demo () (defcomp ~ref-encoding-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(form :sx-post "/geography/hypermedia/reference/api/upload-name" (form :sx-post "/(geography.(hypermedia.(reference.(api.upload-name))))"
:sx-encoding "multipart/form-data" :sx-encoding "multipart/form-data"
:sx-target "#ref-encoding-result" :sx-target "#ref-encoding-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -271,7 +271,7 @@
(defcomp ~ref-headers-demo () (defcomp ~ref-headers-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button :sx-get "/geography/hypermedia/reference/api/echo-headers" (button :sx-get "/(geography.(hypermedia.(reference.(api.echo-headers))))"
:sx-headers {:X-Custom-Token "abc123" :X-Request-Source "demo"} :sx-headers {:X-Custom-Token "abc123" :X-Request-Source "demo"}
:sx-target "#ref-headers-result" :sx-target "#ref-headers-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -295,7 +295,7 @@
(option :value "all" "All") (option :value "all" "All")
(option :value "books" "Books") (option :value "books" "Books")
(option :value "tools" "Tools"))) (option :value "tools" "Tools")))
(button :sx-get "/geography/hypermedia/reference/api/echo-vals" (button :sx-get "/(geography.(hypermedia.(reference.(api.echo-vals))))"
:sx-include "#ref-inc-cat" :sx-include "#ref-inc-cat"
:sx-target "#ref-include-result" :sx-target "#ref-include-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -311,7 +311,7 @@
(defcomp ~ref-vals-demo () (defcomp ~ref-vals-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button :sx-post "/geography/hypermedia/reference/api/echo-vals" (button :sx-post "/(geography.(hypermedia.(reference.(api.echo-vals))))"
:sx-vals "{\"source\": \"demo\", \"page\": \"3\"}" :sx-vals "{\"source\": \"demo\", \"page\": \"3\"}"
:sx-target "#ref-vals-result" :sx-target "#ref-vals-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
@@ -327,8 +327,8 @@
(defcomp ~ref-media-demo () (defcomp ~ref-media-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(a :href "/geography/hypermedia/reference/attributes/sx-get" (a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
:sx-get "/geography/hypermedia/reference/attributes/sx-get" :sx-get "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
:sx-target "#main-panel" :sx-select "#main-panel" :sx-target "#main-panel" :sx-select "#main-panel"
:sx-swap "outerHTML" :sx-push-url "true" :sx-swap "outerHTML" :sx-push-url "true"
:sx-media "(min-width: 768px)" :sx-media "(min-width: 768px)"
@@ -346,13 +346,13 @@
(div :class "grid grid-cols-2 gap-3" (div :class "grid grid-cols-2 gap-3"
(div :class "p-3 border border-stone-200 rounded" (div :class "p-3 border border-stone-200 rounded"
(p :class "text-xs text-stone-400 mb-2" "sx enabled") (p :class "text-xs text-stone-400 mb-2" "sx enabled")
(button :sx-get "/geography/hypermedia/reference/api/time" (button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-dis-a" :sx-swap "innerHTML" :sx-target "#ref-dis-a" :sx-swap "innerHTML"
:class "px-3 py-1 bg-violet-600 text-white rounded text-sm" "Load") :class "px-3 py-1 bg-violet-600 text-white rounded text-sm" "Load")
(div :id "ref-dis-a" :class "mt-2 text-sm text-stone-500" "—")) (div :id "ref-dis-a" :class "mt-2 text-sm text-stone-500" "—"))
(div :sx-disable "true" :class "p-3 border border-stone-200 rounded" (div :sx-disable "true" :class "p-3 border border-stone-200 rounded"
(p :class "text-xs text-stone-400 mb-2" "sx disabled") (p :class "text-xs text-stone-400 mb-2" "sx disabled")
(button :sx-get "/geography/hypermedia/reference/api/time" (button :sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-dis-b" :sx-swap "innerHTML" :sx-target "#ref-dis-b" :sx-swap "innerHTML"
:class "px-3 py-1 bg-stone-400 text-white rounded text-sm" "Load") :class "px-3 py-1 bg-stone-400 text-white rounded text-sm" "Load")
(div :id "ref-dis-b" :class "mt-2 text-sm text-stone-500" (div :id "ref-dis-b" :class "mt-2 text-sm text-stone-500"
@@ -378,7 +378,7 @@
(defcomp ~ref-retry-demo () (defcomp ~ref-retry-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button :sx-get "/geography/hypermedia/reference/api/flaky" (button :sx-get "/(geography.(hypermedia.(reference.(api.flaky))))"
:sx-target "#ref-retry-result" :sx-target "#ref-retry-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-retry "true" :sx-retry "true"
@@ -414,13 +414,13 @@
(defcomp ~ref-boost-demo () (defcomp ~ref-boost-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(nav :sx-boost "true" :class "flex gap-3" (nav :sx-boost "true" :class "flex gap-3"
(a :href "/geography/hypermedia/reference/attributes/sx-get" (a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
:class "text-violet-600 hover:text-violet-800 underline text-sm" :class "text-violet-600 hover:text-violet-800 underline text-sm"
"sx-get") "sx-get")
(a :href "/geography/hypermedia/reference/attributes/sx-post" (a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-post)))"
:class "text-violet-600 hover:text-violet-800 underline text-sm" :class "text-violet-600 hover:text-violet-800 underline text-sm"
"sx-post") "sx-post")
(a :href "/geography/hypermedia/reference/attributes/sx-target" (a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-target)))"
:class "text-violet-600 hover:text-violet-800 underline text-sm" :class "text-violet-600 hover:text-violet-800 underline text-sm"
"sx-target")) "sx-target"))
(p :class "text-xs text-stone-400" (p :class "text-xs text-stone-400"
@@ -434,7 +434,7 @@
(defcomp ~ref-preload-demo () (defcomp ~ref-preload-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/geography/hypermedia/reference/api/time" :sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-preload-result" :sx-target "#ref-preload-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-preload "mouseover" :sx-preload "mouseover"
@@ -452,7 +452,7 @@
(div :class "space-y-3" (div :class "space-y-3"
(div :class "flex gap-2 items-center" (div :class "flex gap-2 items-center"
(button (button
:sx-get "/geography/hypermedia/reference/api/time" :sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-preserve-container" :sx-target "#ref-preserve-container"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -473,7 +473,7 @@
(div :class "space-y-3" (div :class "space-y-3"
(div :class "flex gap-3 items-center" (div :class "flex gap-3 items-center"
(button (button
:sx-get "/geography/hypermedia/reference/api/slow-echo" :sx-get "/(geography.(hypermedia.(reference.(api.slow-echo))))"
:sx-target "#ref-indicator-result" :sx-target "#ref-indicator-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-indicator "#ref-spinner" :sx-indicator "#ref-spinner"
@@ -495,7 +495,7 @@
(defcomp ~ref-validate-demo () (defcomp ~ref-validate-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(form (form
:sx-post "/geography/hypermedia/reference/api/greet" :sx-post "/(geography.(hypermedia.(reference.(api.greet))))"
:sx-target "#ref-validate-result" :sx-target "#ref-validate-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-validate "true" :sx-validate "true"
@@ -517,7 +517,7 @@
(defcomp ~ref-ignore-demo () (defcomp ~ref-ignore-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/geography/hypermedia/reference/api/time" :sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-ignore-container" :sx-target "#ref-ignore-container"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -539,14 +539,14 @@
(div :id "ref-opt-item-1" (div :id "ref-opt-item-1"
:class "flex items-center justify-between p-2 border border-stone-200 rounded" :class "flex items-center justify-between p-2 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Optimistic item A") (span :class "text-sm text-stone-700" "Optimistic item A")
(button :sx-delete "/geography/hypermedia/reference/api/item/opt1" (button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.opt1)))))"
:sx-target "#ref-opt-item-1" :sx-swap "delete" :sx-target "#ref-opt-item-1" :sx-swap "delete"
:sx-optimistic "remove" :sx-optimistic "remove"
:class "text-red-500 text-sm hover:text-red-700" "Remove")) :class "text-red-500 text-sm hover:text-red-700" "Remove"))
(div :id "ref-opt-item-2" (div :id "ref-opt-item-2"
:class "flex items-center justify-between p-2 border border-stone-200 rounded" :class "flex items-center justify-between p-2 border border-stone-200 rounded"
(span :class "text-sm text-stone-700" "Optimistic item B") (span :class "text-sm text-stone-700" "Optimistic item B")
(button :sx-delete "/geography/hypermedia/reference/api/item/opt2" (button :sx-delete "/(geography.(hypermedia.(reference.(api.(item.opt2)))))"
:sx-target "#ref-opt-item-2" :sx-swap "delete" :sx-target "#ref-opt-item-2" :sx-swap "delete"
:sx-optimistic "remove" :sx-optimistic "remove"
:class "text-red-500 text-sm hover:text-red-700" "Remove")) :class "text-red-500 text-sm hover:text-red-700" "Remove"))
@@ -560,7 +560,7 @@
(defcomp ~ref-replace-url-demo () (defcomp ~ref-replace-url-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/geography/hypermedia/reference/api/time" :sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-replurl-result" :sx-target "#ref-replurl-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-replace-url "true" :sx-replace-url "true"
@@ -578,7 +578,7 @@
(div :class "space-y-3" (div :class "space-y-3"
(div :class "flex gap-3 items-center" (div :class "flex gap-3 items-center"
(button :id "ref-diselt-btn" (button :id "ref-diselt-btn"
:sx-get "/geography/hypermedia/reference/api/slow-echo" :sx-get "/(geography.(hypermedia.(reference.(api.slow-echo))))"
:sx-target "#ref-diselt-result" :sx-target "#ref-diselt-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-disabled-elt "#ref-diselt-btn" :sx-disabled-elt "#ref-diselt-btn"
@@ -597,7 +597,7 @@
(defcomp ~ref-prompt-demo () (defcomp ~ref-prompt-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/geography/hypermedia/reference/api/prompt-echo" :sx-get "/(geography.(hypermedia.(reference.(api.prompt-echo))))"
:sx-target "#ref-prompt-result" :sx-target "#ref-prompt-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-prompt "Enter your name:" :sx-prompt "Enter your name:"
@@ -614,7 +614,7 @@
(defcomp ~ref-params-demo () (defcomp ~ref-params-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(form (form
:sx-post "/geography/hypermedia/reference/api/echo-vals" :sx-post "/(geography.(hypermedia.(reference.(api.echo-vals))))"
:sx-target "#ref-params-result" :sx-target "#ref-params-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-params "name" :sx-params "name"
@@ -636,7 +636,7 @@
(defcomp ~ref-sse-demo () (defcomp ~ref-sse-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(div :sx-sse "/geography/hypermedia/reference/api/sse-time" (div :sx-sse "/(geography.(hypermedia.(reference.(api.sse-time))))"
:sx-sse-swap "time" :sx-sse-swap "time"
:sx-swap "innerHTML" :sx-swap "innerHTML"
(div :id "ref-sse-result" (div :id "ref-sse-result"
@@ -656,7 +656,7 @@
(defcomp ~ref-header-prompt-demo () (defcomp ~ref-header-prompt-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/geography/hypermedia/reference/api/prompt-echo" :sx-get "/(geography.(hypermedia.(reference.(api.prompt-echo))))"
:sx-target "#ref-hdr-prompt-result" :sx-target "#ref-hdr-prompt-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-prompt "Enter your name:" :sx-prompt "Enter your name:"
@@ -673,7 +673,7 @@
(defcomp ~ref-header-trigger-demo () (defcomp ~ref-header-trigger-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/geography/hypermedia/reference/api/trigger-event" :sx-get "/(geography.(hypermedia.(reference.(api.trigger-event))))"
:sx-target "#ref-hdr-trigger-result" :sx-target "#ref-hdr-trigger-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -690,7 +690,7 @@
(defcomp ~ref-header-retarget-demo () (defcomp ~ref-header-retarget-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/geography/hypermedia/reference/api/retarget" :sx-get "/(geography.(hypermedia.(reference.(api.retarget))))"
:sx-target "#ref-hdr-retarget-main" :sx-target "#ref-hdr-retarget-main"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm" :class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors text-sm"
@@ -717,7 +717,7 @@
(input :id "ref-evt-br-input" :type "text" :placeholder "Type something first..." (input :id "ref-evt-br-input" :type "text" :placeholder "Type something first..."
:class "flex-1 px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500") :class "flex-1 px-3 py-2 border border-stone-300 rounded text-sm focus:outline-none focus:ring-2 focus:ring-violet-500")
(button (button
:sx-get "/geography/hypermedia/reference/api/time" :sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-evt-br-result" :sx-target "#ref-evt-br-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-on:sx:beforeRequest "if (!document.getElementById('ref-evt-br-input').value) { event.preventDefault(); document.getElementById('ref-evt-br-result').textContent = 'Cancelled — input is empty!'; }" :sx-on:sx:beforeRequest "if (!document.getElementById('ref-evt-br-input').value) { event.preventDefault(); document.getElementById('ref-evt-br-result').textContent = 'Cancelled — input is empty!'; }"
@@ -734,7 +734,7 @@
(defcomp ~ref-event-after-request-demo () (defcomp ~ref-event-after-request-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/geography/hypermedia/reference/api/time" :sx-get "/(geography.(hypermedia.(reference.(api.time))))"
:sx-target "#ref-evt-ar-result" :sx-target "#ref-evt-ar-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-on:sx:afterRequest "document.getElementById('ref-evt-ar-log').textContent = 'Response status: ' + (event.detail ? event.detail.status : '?')" :sx-on:sx:afterRequest "document.getElementById('ref-evt-ar-log').textContent = 'Response status: ' + (event.detail ? event.detail.status : '?')"
@@ -754,7 +754,7 @@
(defcomp ~ref-event-after-swap-demo () (defcomp ~ref-event-after-swap-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/geography/hypermedia/reference/api/swap-item" :sx-get "/(geography.(hypermedia.(reference.(api.swap-item))))"
:sx-target "#ref-evt-as-list" :sx-target "#ref-evt-as-list"
:sx-swap "beforeend" :sx-swap "beforeend"
:sx-on:sx:afterSwap "var items = document.querySelectorAll('#ref-evt-as-list > div'); if (items.length) items[items.length-1].scrollIntoView({behavior:'smooth'}); document.getElementById('ref-evt-as-count').textContent = items.length + ' items'" :sx-on:sx:afterSwap "var items = document.querySelectorAll('#ref-evt-as-list > div'); if (items.length) items[items.length-1].scrollIntoView({behavior:'smooth'}); document.getElementById('ref-evt-as-count').textContent = items.length + ' items'"
@@ -774,7 +774,7 @@
(defcomp ~ref-event-response-error-demo () (defcomp ~ref-event-response-error-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(button (button
:sx-get "/geography/hypermedia/reference/api/error-500" :sx-get "/(geography.(hypermedia.(reference.(api.error-500))))"
:sx-target "#ref-evt-err-result" :sx-target "#ref-evt-err-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-on:sx:responseError "var s=document.getElementById('ref-evt-err-status'); s.style.display='block'; s.textContent='Error ' + (event.detail ? event.detail.status || '?' : '?') + ' received'" :sx-on:sx:responseError "var s=document.getElementById('ref-evt-err-status'); s.style.display='block'; s.textContent='Error ' + (event.detail ? event.detail.status || '?' : '?') + ' received'"
@@ -796,7 +796,7 @@
(defcomp ~ref-event-validation-failed-demo () (defcomp ~ref-event-validation-failed-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(form (form
:sx-post "/geography/hypermedia/reference/api/greet" :sx-post "/(geography.(hypermedia.(reference.(api.greet))))"
:sx-target "#ref-evt-vf-result" :sx-target "#ref-evt-vf-result"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-validate "true" :sx-validate "true"
@@ -847,13 +847,13 @@
"Open DevTools console, then navigate to a pure page (no :data expression). " "Open DevTools console, then navigate to a pure page (no :data expression). "
"You'll see \"sx:route client /path\" in the console — no network request is made.") "You'll see \"sx:route client /path\" in the console — no network request is made.")
(div :class "flex gap-2 flex-wrap" (div :class "flex gap-2 flex-wrap"
(a :href "/etc/essays/" (a :href "/(etc.(essay))"
:class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200" :class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200"
"Essays") "Essays")
(a :href "/etc/plans/" (a :href "/(etc.(plan))"
:class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200" :class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200"
"Plans") "Plans")
(a :href "/applications/protocols/" (a :href "/(applications.(protocol))"
:class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200" :class "px-3 py-1 bg-violet-100 text-violet-700 rounded text-sm no-underline hover:bg-violet-200"
"Protocols")) "Protocols"))
(p :class "text-xs text-stone-400" (p :class "text-xs text-stone-400"
@@ -866,7 +866,7 @@
(defcomp ~ref-event-sse-open-demo () (defcomp ~ref-event-sse-open-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(div :sx-sse "/geography/hypermedia/reference/api/sse-time" (div :sx-sse "/(geography.(hypermedia.(reference.(api.sse-time))))"
:sx-sse-swap "time" :sx-sse-swap "time"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-on:sx:sseOpen "document.getElementById('ref-evt-sseopen-status').textContent = 'Connected'; document.getElementById('ref-evt-sseopen-status').className = 'inline-block px-2 py-0.5 rounded text-xs bg-emerald-100 text-emerald-700'" :sx-on:sx:sseOpen "document.getElementById('ref-evt-sseopen-status').textContent = 'Connected'; document.getElementById('ref-evt-sseopen-status').className = 'inline-block px-2 py-0.5 rounded text-xs bg-emerald-100 text-emerald-700'"
@@ -884,7 +884,7 @@
(defcomp ~ref-event-sse-message-demo () (defcomp ~ref-event-sse-message-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(div :sx-sse "/geography/hypermedia/reference/api/sse-time" (div :sx-sse "/(geography.(hypermedia.(reference.(api.sse-time))))"
:sx-sse-swap "time" :sx-sse-swap "time"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-on:sx:sseMessage "var c = parseInt(document.getElementById('ref-evt-ssemsg-count').dataset.count || '0') + 1; document.getElementById('ref-evt-ssemsg-count').dataset.count = c; document.getElementById('ref-evt-ssemsg-count').textContent = c + ' messages received'" :sx-on:sx:sseMessage "var c = parseInt(document.getElementById('ref-evt-ssemsg-count').dataset.count || '0') + 1; document.getElementById('ref-evt-ssemsg-count').dataset.count = c; document.getElementById('ref-evt-ssemsg-count').textContent = c + ' messages received'"
@@ -902,7 +902,7 @@
(defcomp ~ref-event-sse-error-demo () (defcomp ~ref-event-sse-error-demo ()
(div :class "space-y-3" (div :class "space-y-3"
(div :sx-sse "/geography/hypermedia/reference/api/sse-time" (div :sx-sse "/(geography.(hypermedia.(reference.(api.sse-time))))"
:sx-sse-swap "time" :sx-sse-swap "time"
:sx-swap "innerHTML" :sx-swap "innerHTML"
:sx-on:sx:sseError "document.getElementById('ref-evt-sseerr-status').textContent = 'Disconnected'; document.getElementById('ref-evt-sseerr-status').className = 'inline-block px-2 py-0.5 rounded text-xs bg-red-100 text-red-700'" :sx-on:sx:sseError "document.getElementById('ref-evt-sseerr-status').textContent = 'Disconnected'; document.getElementById('ref-evt-sseerr-status').className = 'inline-block px-2 py-0.5 rounded text-xs bg-red-100 text-red-700'"