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:
@@ -24,11 +24,25 @@ import logging
|
||||
import os
|
||||
from typing import Any, Callable, Awaitable
|
||||
|
||||
from werkzeug.routing import BaseConverter
|
||||
|
||||
from .types import HandlerDef
|
||||
|
||||
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
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -229,6 +243,10 @@ def register_route_handlers(app_or_bp: Any, service_name: str) -> int:
|
||||
from quart import Response, request
|
||||
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)
|
||||
count = 0
|
||||
|
||||
|
||||
@@ -128,9 +128,6 @@ def create_app() -> "Quart":
|
||||
# Skip SX expression URLs (already in new format)
|
||||
if path.startswith("/(") or path.startswith("/~"):
|
||||
return None
|
||||
# Skip API/handler paths
|
||||
if "/api/" in path:
|
||||
return None
|
||||
new_url = redirect_old_url(path)
|
||||
if new_url:
|
||||
qs = request.query_string.decode()
|
||||
@@ -149,7 +146,6 @@ def create_app() -> "Quart":
|
||||
and not path.endswith("/")
|
||||
and request.method == "GET"
|
||||
and not path.startswith(("/static/", "/internal/", "/auth/"))
|
||||
and "/api/" not in path
|
||||
and "." not in path.rsplit("/", 1)[-1]):
|
||||
qs = request.query_string.decode()
|
||||
target = path + "/" + ("?" + qs if qs else "")
|
||||
|
||||
@@ -23,7 +23,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
# 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 generate():
|
||||
for _ in range(30): # stream for 60 seconds max
|
||||
@@ -38,7 +38,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
|
||||
_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():
|
||||
from shared.sx.helpers import sx_response
|
||||
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}
|
||||
|
||||
@bp.get("/geography/reactive/api/settle-data")
|
||||
@bp.get("/(geography.(reactive.(api.settle-data)))")
|
||||
async def api_settle_data():
|
||||
from shared.sx.helpers import sx_response
|
||||
_settle_counter["n"] += 1
|
||||
@@ -76,7 +76,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
|
||||
# --- 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():
|
||||
from shared.sx.helpers import sx_response
|
||||
q = request.args.get("q", "")
|
||||
@@ -95,7 +95,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
)
|
||||
return sx_response(sx_src)
|
||||
|
||||
@bp.get("/geography/reactive/api/search/events")
|
||||
@bp.get("/(geography.(reactive.(api.search-events)))")
|
||||
async def api_search_events():
|
||||
from shared.sx.helpers import sx_response
|
||||
q = request.args.get("q", "")
|
||||
@@ -114,7 +114,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
)
|
||||
return sx_response(sx_src)
|
||||
|
||||
@bp.get("/geography/reactive/api/search/posts")
|
||||
@bp.get("/(geography.(reactive.(api.search-posts)))")
|
||||
async def api_search_posts():
|
||||
from shared.sx.helpers import sx_response
|
||||
q = request.args.get("q", "")
|
||||
@@ -135,7 +135,7 @@ def register(url_prefix: str = "/") -> Blueprint:
|
||||
|
||||
# --- Demo 5: marsh transform endpoint ---
|
||||
|
||||
@bp.get("/geography/reactive/api/catalog")
|
||||
@bp.get("/(geography.(reactive.(api.catalog)))")
|
||||
async def api_catalog():
|
||||
from shared.sx.helpers import sx_response
|
||||
items = [
|
||||
|
||||
@@ -10,79 +10,79 @@ from __future__ import annotations
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
DOCS_NAV = [
|
||||
("Introduction", "/language/docs/introduction"),
|
||||
("Getting Started", "/language/docs/getting-started"),
|
||||
("Components", "/language/docs/components"),
|
||||
("Evaluator", "/language/docs/evaluator"),
|
||||
("Primitives", "/language/docs/primitives"),
|
||||
("CSS", "/language/docs/css"),
|
||||
("Server Rendering", "/language/docs/server-rendering"),
|
||||
("Introduction", "/(language.(doc.introduction))"),
|
||||
("Getting Started", "/(language.(doc.getting-started))"),
|
||||
("Components", "/(language.(doc.components))"),
|
||||
("Evaluator", "/(language.(doc.evaluator))"),
|
||||
("Primitives", "/(language.(doc.primitives))"),
|
||||
("CSS", "/(language.(doc.css))"),
|
||||
("Server Rendering", "/(language.(doc.server-rendering))"),
|
||||
]
|
||||
|
||||
REFERENCE_NAV = [
|
||||
("Attributes", "/geography/hypermedia/reference/attributes"),
|
||||
("Headers", "/geography/hypermedia/reference/headers"),
|
||||
("Events", "/geography/hypermedia/reference/events"),
|
||||
("JS API", "/geography/hypermedia/reference/js-api"),
|
||||
("Attributes", "/(geography.(hypermedia.(reference.attributes)))"),
|
||||
("Headers", "/(geography.(hypermedia.(reference.headers)))"),
|
||||
("Events", "/(geography.(hypermedia.(reference.events)))"),
|
||||
("JS API", "/(geography.(hypermedia.(reference.js-api)))"),
|
||||
]
|
||||
|
||||
PROTOCOLS_NAV = [
|
||||
("Wire Format", "/applications/protocols/wire-format"),
|
||||
("Fragments", "/applications/protocols/fragments"),
|
||||
("Resolver I/O", "/applications/protocols/resolver-io"),
|
||||
("Internal Services", "/applications/protocols/internal-services"),
|
||||
("ActivityPub", "/applications/protocols/activitypub"),
|
||||
("Future", "/applications/protocols/future"),
|
||||
("Wire Format", "/(applications.(protocol.wire-format))"),
|
||||
("Fragments", "/(applications.(protocol.fragments))"),
|
||||
("Resolver I/O", "/(applications.(protocol.resolver-io))"),
|
||||
("Internal Services", "/(applications.(protocol.internal-services))"),
|
||||
("ActivityPub", "/(applications.(protocol.activitypub))"),
|
||||
("Future", "/(applications.(protocol.future))"),
|
||||
]
|
||||
|
||||
EXAMPLES_NAV = [
|
||||
("Click to Load", "/geography/hypermedia/examples/click-to-load"),
|
||||
("Form Submission", "/geography/hypermedia/examples/form-submission"),
|
||||
("Polling", "/geography/hypermedia/examples/polling"),
|
||||
("Delete Row", "/geography/hypermedia/examples/delete-row"),
|
||||
("Inline Edit", "/geography/hypermedia/examples/inline-edit"),
|
||||
("OOB Swaps", "/geography/hypermedia/examples/oob-swaps"),
|
||||
("Lazy Loading", "/geography/hypermedia/examples/lazy-loading"),
|
||||
("Infinite Scroll", "/geography/hypermedia/examples/infinite-scroll"),
|
||||
("Progress Bar", "/geography/hypermedia/examples/progress-bar"),
|
||||
("Active Search", "/geography/hypermedia/examples/active-search"),
|
||||
("Inline Validation", "/geography/hypermedia/examples/inline-validation"),
|
||||
("Value Select", "/geography/hypermedia/examples/value-select"),
|
||||
("Reset on Submit", "/geography/hypermedia/examples/reset-on-submit"),
|
||||
("Edit Row", "/geography/hypermedia/examples/edit-row"),
|
||||
("Bulk Update", "/geography/hypermedia/examples/bulk-update"),
|
||||
("Swap Positions", "/geography/hypermedia/examples/swap-positions"),
|
||||
("Select Filter", "/geography/hypermedia/examples/select-filter"),
|
||||
("Tabs", "/geography/hypermedia/examples/tabs"),
|
||||
("Animations", "/geography/hypermedia/examples/animations"),
|
||||
("Dialogs", "/geography/hypermedia/examples/dialogs"),
|
||||
("Keyboard Shortcuts", "/geography/hypermedia/examples/keyboard-shortcuts"),
|
||||
("PUT / PATCH", "/geography/hypermedia/examples/put-patch"),
|
||||
("JSON Encoding", "/geography/hypermedia/examples/json-encoding"),
|
||||
("Vals & Headers", "/geography/hypermedia/examples/vals-and-headers"),
|
||||
("Loading States", "/geography/hypermedia/examples/loading-states"),
|
||||
("Request Abort", "/geography/hypermedia/examples/sync-replace"),
|
||||
("Retry", "/geography/hypermedia/examples/retry"),
|
||||
("Click to Load", "/(geography.(hypermedia.(example.click-to-load)))"),
|
||||
("Form Submission", "/(geography.(hypermedia.(example.form-submission)))"),
|
||||
("Polling", "/(geography.(hypermedia.(example.polling)))"),
|
||||
("Delete Row", "/(geography.(hypermedia.(example.delete-row)))"),
|
||||
("Inline Edit", "/(geography.(hypermedia.(example.inline-edit)))"),
|
||||
("OOB Swaps", "/(geography.(hypermedia.(example.oob-swaps)))"),
|
||||
("Lazy Loading", "/(geography.(hypermedia.(example.lazy-loading)))"),
|
||||
("Infinite Scroll", "/(geography.(hypermedia.(example.infinite-scroll)))"),
|
||||
("Progress Bar", "/(geography.(hypermedia.(example.progress-bar)))"),
|
||||
("Active Search", "/(geography.(hypermedia.(example.active-search)))"),
|
||||
("Inline Validation", "/(geography.(hypermedia.(example.inline-validation)))"),
|
||||
("Value Select", "/(geography.(hypermedia.(example.value-select)))"),
|
||||
("Reset on Submit", "/(geography.(hypermedia.(example.reset-on-submit)))"),
|
||||
("Edit Row", "/(geography.(hypermedia.(example.edit-row)))"),
|
||||
("Bulk Update", "/(geography.(hypermedia.(example.bulk-update)))"),
|
||||
("Swap Positions", "/(geography.(hypermedia.(example.swap-positions)))"),
|
||||
("Select Filter", "/(geography.(hypermedia.(example.select-filter)))"),
|
||||
("Tabs", "/(geography.(hypermedia.(example.tabs)))"),
|
||||
("Animations", "/(geography.(hypermedia.(example.animations)))"),
|
||||
("Dialogs", "/(geography.(hypermedia.(example.dialogs)))"),
|
||||
("Keyboard Shortcuts", "/(geography.(hypermedia.(example.keyboard-shortcuts)))"),
|
||||
("PUT / PATCH", "/(geography.(hypermedia.(example.put-patch)))"),
|
||||
("JSON Encoding", "/(geography.(hypermedia.(example.json-encoding)))"),
|
||||
("Vals & Headers", "/(geography.(hypermedia.(example.vals-and-headers)))"),
|
||||
("Loading States", "/(geography.(hypermedia.(example.loading-states)))"),
|
||||
("Request Abort", "/(geography.(hypermedia.(example.sync-replace)))"),
|
||||
("Retry", "/(geography.(hypermedia.(example.retry)))"),
|
||||
]
|
||||
|
||||
ESSAYS_NAV = [
|
||||
("sx sucks", "/etc/essays/sx-sucks"),
|
||||
("Why S-Expressions", "/etc/essays/why-sexps"),
|
||||
("The htmx/React Hybrid", "/etc/essays/htmx-react-hybrid"),
|
||||
("On-Demand CSS", "/etc/essays/on-demand-css"),
|
||||
("Client Reactivity", "/etc/essays/client-reactivity"),
|
||||
("SX Native", "/etc/essays/sx-native"),
|
||||
("The SX Manifesto", "/etc/philosophy/sx-manifesto"),
|
||||
("Tail-Call Optimization", "/etc/essays/tail-call-optimization"),
|
||||
("Continuations", "/etc/essays/continuations"),
|
||||
("sx sucks", "/(etc.(essay.sx-sucks))"),
|
||||
("Why S-Expressions", "/(etc.(essay.why-sexps))"),
|
||||
("The htmx/React Hybrid", "/(etc.(essay.htmx-react-hybrid))"),
|
||||
("On-Demand CSS", "/(etc.(essay.on-demand-css))"),
|
||||
("Client Reactivity", "/(etc.(essay.client-reactivity))"),
|
||||
("SX Native", "/(etc.(essay.sx-native))"),
|
||||
("The SX Manifesto", "/(etc.(philosophy.sx-manifesto))"),
|
||||
("Tail-Call Optimization", "/(etc.(essay.tail-call-optimization))"),
|
||||
("Continuations", "/(etc.(essay.continuations))"),
|
||||
]
|
||||
|
||||
MAIN_NAV = [
|
||||
("Docs", "/language/docs/introduction"),
|
||||
("Reference", "/geography/hypermedia/reference/"),
|
||||
("Protocols", "/applications/protocols/wire-format"),
|
||||
("Examples", "/geography/hypermedia/examples/click-to-load"),
|
||||
("Essays", "/etc/essays/sx-sucks"),
|
||||
("Docs", "/(language.(doc.introduction))"),
|
||||
("Reference", "/(geography.(hypermedia.(reference)))"),
|
||||
("Protocols", "/(applications.(protocol.wire-format))"),
|
||||
("Examples", "/(geography.(hypermedia.(example.click-to-load)))"),
|
||||
("Essays", "/(etc.(essay.sx-sucks))"),
|
||||
]
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -744,7 +744,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-get-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' "Load server time")'
|
||||
@@ -764,7 +764,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-post-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' (input :type "text" :name "name"\n'
|
||||
@@ -786,7 +786,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-put-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-vals "{\\"status\\": \\"published\\"}"\n'
|
||||
@@ -807,7 +807,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-delete-demo",
|
||||
"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-swap "delete"\n'
|
||||
' "Remove")'
|
||||
@@ -826,7 +826,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-patch-demo",
|
||||
"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-target "#ref-patch-val"\n'
|
||||
' :sx-swap "innerHTML"\n'
|
||||
@@ -852,7 +852,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
"example": (
|
||||
'(input :type "text" :name "q"\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-target "#ref-trigger-result"\n'
|
||||
' :sx-swap "innerHTML")'
|
||||
@@ -874,12 +874,12 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
"demo": "ref-target-demo",
|
||||
"example": (
|
||||
';; 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-swap "innerHTML"\n'
|
||||
' "Update Box A")\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-swap "innerHTML"\n'
|
||||
' "Update Box B")'
|
||||
@@ -894,13 +894,13 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
"demo": "ref-swap-demo",
|
||||
"example": (
|
||||
';; 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-swap "beforeend"\n'
|
||||
' "beforeend")\n'
|
||||
'\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-swap "afterbegin"\n'
|
||||
' "afterbegin")'
|
||||
@@ -921,7 +921,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-oob-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' "Update both boxes")'
|
||||
@@ -944,7 +944,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-select-demo",
|
||||
"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-select "#the-content"\n'
|
||||
' :sx-swap "innerHTML"\n'
|
||||
@@ -968,7 +968,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-confirm-demo",
|
||||
"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-swap "delete"\n'
|
||||
' :sx-confirm "Are you sure you want to delete this file?"\n'
|
||||
@@ -1003,7 +1003,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
"example": (
|
||||
'(input :type "text" :name "q"\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-sync "replace"\n'
|
||||
' :sx-target "#ref-sync-result"\n'
|
||||
@@ -1024,7 +1024,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-encoding-demo",
|
||||
"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-target "#ref-encoding-result"\n'
|
||||
' :sx-swap "innerHTML"\n'
|
||||
@@ -1044,7 +1044,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-headers-demo",
|
||||
"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-target "#ref-headers-result"\n'
|
||||
' :sx-swap "innerHTML"\n'
|
||||
@@ -1073,7 +1073,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
' (option :value "books" "Books")\n'
|
||||
' (option :value "tools" "Tools"))\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-target "#ref-include-result"\n'
|
||||
' :sx-swap "innerHTML"\n'
|
||||
@@ -1097,7 +1097,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-vals-demo",
|
||||
"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-target "#ref-vals-result"\n'
|
||||
' :sx-swap "innerHTML"\n'
|
||||
@@ -1133,7 +1133,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
';; Left box: sx works normally\n'
|
||||
';; Right box: sx-disable prevents any sx behavior\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-swap "innerHTML"\n'
|
||||
' "Load")\n'
|
||||
@@ -1166,7 +1166,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-retry-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-retry "true"\n'
|
||||
@@ -1239,7 +1239,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-preload-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-preload "mouseover"\n'
|
||||
@@ -1275,7 +1275,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-indicator-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-indicator "#ref-spinner"\n'
|
||||
@@ -1302,7 +1302,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-validate-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-validate "true"\n'
|
||||
@@ -1340,7 +1340,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-optimistic-demo",
|
||||
"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-swap "delete"\n'
|
||||
' :sx-optimistic "remove"\n'
|
||||
@@ -1362,7 +1362,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-replace-url-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-replace-url "true"\n'
|
||||
@@ -1378,7 +1378,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-disabled-elt-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-disabled-elt "this"\n'
|
||||
@@ -1394,7 +1394,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-prompt-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-prompt "Enter your name:"\n'
|
||||
@@ -1414,7 +1414,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-params-demo",
|
||||
"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-swap "innerHTML"\n'
|
||||
' :sx-params "name"\n'
|
||||
@@ -1433,7 +1433,7 @@ ATTR_DETAILS: dict[str, dict] = {
|
||||
),
|
||||
"demo": "ref-sse-demo",
|
||||
"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-target "#ref-sse-result"\n'
|
||||
' :sx-swap "innerHTML"\n'
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
: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 (~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")
|
||||
:comp-placeholder-id "click-comp"
|
||||
:wire-placeholder-id "click-wire"
|
||||
@@ -20,7 +20,7 @@
|
||||
:description "Forms with sx-post submit via AJAX and swap the response into a target."
|
||||
:demo-description "Enter a name and submit."
|
||||
: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")
|
||||
:comp-placeholder-id "form-comp"
|
||||
:wire-placeholder-id "form-wire"))
|
||||
@@ -31,7 +31,7 @@
|
||||
:description "Use sx-trigger with \"every\" to poll the server at regular intervals."
|
||||
:demo-description "This div polls the server every 2 seconds."
|
||||
: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")
|
||||
:comp-placeholder-id "poll-comp"
|
||||
:wire-placeholder-id "poll-wire"
|
||||
@@ -48,7 +48,7 @@
|
||||
(list "3" "Write documentation")
|
||||
(list "4" "Deploy to production")
|
||||
(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")
|
||||
:comp-placeholder-id "delete-comp"
|
||||
:wire-placeholder-id "delete-wire"
|
||||
@@ -73,7 +73,7 @@
|
||||
: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 (~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")
|
||||
: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."))
|
||||
@@ -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."
|
||||
:demo-description "Content loads automatically when the page renders."
|
||||
: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")
|
||||
:comp-placeholder-id "lazy-comp"
|
||||
: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."
|
||||
:demo-description "Scroll down in the container to load more items (5 pages total)."
|
||||
: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")
|
||||
:comp-placeholder-id "scroll-comp"
|
||||
: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."
|
||||
:demo-description "Click start to begin a simulated job."
|
||||
: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"))
|
||||
:comp-placeholder-id "progress-comp"
|
||||
: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."
|
||||
:demo-description "Type to search through 20 programming languages."
|
||||
: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")
|
||||
:comp-placeholder-id "search-comp"
|
||||
: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."
|
||||
:demo-description "Enter an email and click away (blur) to validate."
|
||||
: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")
|
||||
:comp-placeholder-id "validate-comp"
|
||||
: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."
|
||||
:demo-description "Select a category to populate the item dropdown."
|
||||
: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")
|
||||
:comp-placeholder-id "values-comp"
|
||||
:wire-placeholder-id "values-wire"))
|
||||
@@ -150,7 +150,7 @@
|
||||
: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 (~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")
|
||||
:comp-placeholder-id "reset-comp"
|
||||
:wire-placeholder-id "reset-wire"))
|
||||
@@ -165,7 +165,7 @@
|
||||
(list "2" "Widget B" "24.50" "89")
|
||||
(list "3" "Widget C" "12.00" "305")
|
||||
(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"))
|
||||
:comp-placeholder-id "editrow-comp"
|
||||
:wire-placeholder-id "editrow-wire"))
|
||||
@@ -181,7 +181,7 @@
|
||||
(list "3" "Carol Zhang" "carol@example.com" "active")
|
||||
(list "4" "Dan Okafor" "dan@example.com" "inactive")
|
||||
(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")
|
||||
:comp-placeholder-id "bulk-comp"
|
||||
: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."
|
||||
:demo-description "Try each button to see different swap behaviours."
|
||||
: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")
|
||||
: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."
|
||||
:demo-description "Different buttons select different parts of the same server response."
|
||||
: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")
|
||||
: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."
|
||||
:demo-description "Click tabs to switch content. Watch the browser URL change."
|
||||
: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")
|
||||
: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."
|
||||
:demo-description "Click to swap in content with a fade-in animation."
|
||||
: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")
|
||||
:comp-placeholder-id "anim-comp"
|
||||
: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."
|
||||
:demo-description "Click to open a modal dialog."
|
||||
: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"))
|
||||
:comp-placeholder-id "dialog-comp"
|
||||
: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."
|
||||
:demo-description "Press s, n, or h on your keyboard."
|
||||
: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")
|
||||
:comp-placeholder-id "kbd-comp"
|
||||
: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."
|
||||
: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")
|
||||
: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"))
|
||||
:comp-placeholder-id "pp-comp"
|
||||
: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."
|
||||
:demo-description "Submit the form and see the JSON body the server received."
|
||||
: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")
|
||||
:comp-placeholder-id "json-comp"
|
||||
: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."
|
||||
:demo-description "Click each button to see what the server receives."
|
||||
: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"))
|
||||
:comp-placeholder-id "vals-comp"
|
||||
: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."
|
||||
:demo-description "Click the button — it shows a spinner during the 2-second request."
|
||||
: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")
|
||||
:comp-placeholder-id "loading-comp"
|
||||
: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."
|
||||
:demo-description "Type quickly — only the latest result appears despite random 0.5-2s server delays."
|
||||
: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")
|
||||
:comp-placeholder-id "sync-comp"
|
||||
: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."
|
||||
:demo-description "Click the button — watch it retry automatically after failures."
|
||||
: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")
|
||||
:comp-placeholder-id "retry-comp"
|
||||
:wire-placeholder-id "retry-wire"))
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-click
|
||||
:path "/geography/hypermedia/examples/api/click"
|
||||
:path "/(geography.(hypermedia.(example.(api.click))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -73,7 +73,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-form
|
||||
:path "/geography/hypermedia/examples/api/form"
|
||||
:path "/(geography.(hypermedia.(example.(api.form))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -92,7 +92,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-poll
|
||||
:path "/geography/hypermedia/examples/api/poll"
|
||||
:path "/(geography.(hypermedia.(example.(api.poll))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -113,7 +113,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-delete
|
||||
:path "/geography/hypermedia/examples/api/delete/<item_id>"
|
||||
:path "/(geography.(hypermedia.(example.(api.(delete.<sx:item_id>)))))"
|
||||
:method :delete
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -130,7 +130,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-edit-form
|
||||
:path "/geography/hypermedia/examples/api/edit"
|
||||
:path "/(geography.(hypermedia.(example.(api.edit))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -143,7 +143,7 @@
|
||||
:text (str "(~inline-edit-form :value \"" value "\")")))))
|
||||
|
||||
(defhandler ex-edit-save
|
||||
:path "/geography/hypermedia/examples/api/edit"
|
||||
:path "/(geography.(hypermedia.(example.(api.edit))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -157,7 +157,7 @@
|
||||
:text (str "(~inline-view :value \"" value "\")")))))
|
||||
|
||||
(defhandler ex-edit-cancel
|
||||
:path "/geography/hypermedia/examples/api/edit/cancel"
|
||||
:path "/(geography.(hypermedia.(example.(api.edit-cancel))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -175,7 +175,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-oob
|
||||
:path "/geography/hypermedia/examples/api/oob"
|
||||
:path "/(geography.(hypermedia.(example.(api.oob))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -195,7 +195,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-lazy
|
||||
:path "/geography/hypermedia/examples/api/lazy"
|
||||
:path "/(geography.(hypermedia.(example.(api.lazy))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -213,7 +213,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-scroll
|
||||
:path "/geography/hypermedia/examples/api/scroll"
|
||||
:path "/(geography.(hypermedia.(example.(api.scroll))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -227,7 +227,7 @@
|
||||
(range start (+ start 5)))
|
||||
(if (<= (+ pg 1) 6)
|
||||
(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-target "#scroll-items"
|
||||
:sx-swap "beforeend"
|
||||
@@ -244,7 +244,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-progress-start
|
||||
:path "/geography/hypermedia/examples/api/progress/start"
|
||||
:path "/(geography.(hypermedia.(example.(api.progress-start))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -261,7 +261,7 @@
|
||||
:text (str "(~progress-status :percent 0 :job-id \"" job-id "\")"))))))
|
||||
|
||||
(defhandler ex-progress-status
|
||||
:path "/geography/hypermedia/examples/api/progress/status"
|
||||
:path "/(geography.(hypermedia.(example.(api.progress-status))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -282,7 +282,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-search
|
||||
:path "/geography/hypermedia/examples/api/search"
|
||||
:path "/(geography.(hypermedia.(example.(api.search))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -304,7 +304,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-validate
|
||||
:path "/geography/hypermedia/examples/api/validate"
|
||||
:path "/(geography.(hypermedia.(example.(api.validate))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -331,7 +331,7 @@
|
||||
:text (nth result 1))))))
|
||||
|
||||
(defhandler ex-validate-submit
|
||||
:path "/geography/hypermedia/examples/api/validate/submit"
|
||||
:path "/(geography.(hypermedia.(example.(api.validate-submit))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -347,7 +347,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-values
|
||||
:path "/geography/hypermedia/examples/api/values"
|
||||
:path "/(geography.(hypermedia.(example.(api.values))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -367,7 +367,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-reset-submit
|
||||
:path "/geography/hypermedia/examples/api/reset-submit"
|
||||
:path "/(geography.(hypermedia.(example.(api.reset-submit))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -387,7 +387,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-editrow-form
|
||||
:path "/geography/hypermedia/examples/api/editrow/<row_id>"
|
||||
:path "/(geography.(hypermedia.(example.(api.(editrow.<sx:row_id>)))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key row-id)
|
||||
@@ -402,7 +402,7 @@
|
||||
:text (str "(~edit-row-form :id \"" (get row "id") "\" ...)"))))))
|
||||
|
||||
(defhandler ex-editrow-save
|
||||
:path "/geography/hypermedia/examples/api/editrow/<row_id>"
|
||||
:path "/(geography.(hypermedia.(example.(api.(editrow.<sx:row_id>)))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -420,7 +420,7 @@
|
||||
:text (str "(~edit-row-view :id \"" row-id "\" ...)")))))
|
||||
|
||||
(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
|
||||
:returns "element"
|
||||
(&key row-id)
|
||||
@@ -439,7 +439,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-bulk
|
||||
:path "/geography/hypermedia/examples/api/bulk"
|
||||
:path "/(geography.(hypermedia.(example.(api.bulk))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -476,7 +476,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-swap-log
|
||||
:path "/geography/hypermedia/examples/api/swap-log"
|
||||
:path "/(geography.(hypermedia.(example.(api.swap-log))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -500,7 +500,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-dashboard
|
||||
:path "/geography/hypermedia/examples/api/dashboard"
|
||||
:path "/(geography.(hypermedia.(example.(api.dashboard))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -530,7 +530,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-tabs
|
||||
:path "/geography/hypermedia/examples/api/tabs/<tab>"
|
||||
:path "/(geography.(hypermedia.(example.(api.(tabs.<sx:tab>)))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key tab)
|
||||
@@ -551,7 +551,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-animate
|
||||
:path "/geography/hypermedia/examples/api/animate"
|
||||
:path "/(geography.(hypermedia.(example.(api.animate))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -571,7 +571,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-dialog
|
||||
:path "/geography/hypermedia/examples/api/dialog"
|
||||
:path "/(geography.(hypermedia.(example.(api.dialog))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -584,7 +584,7 @@
|
||||
:text "(~dialog-modal :title \"Confirm Action\" :message \"...\")")))
|
||||
|
||||
(defhandler ex-dialog-close
|
||||
:path "/geography/hypermedia/examples/api/dialog/close"
|
||||
:path "/(geography.(hypermedia.(example.(api.dialog-close))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -598,7 +598,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-keyboard
|
||||
:path "/geography/hypermedia/examples/api/keyboard"
|
||||
:path "/(geography.(hypermedia.(example.(api.keyboard))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -617,7 +617,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-pp-edit-all
|
||||
:path "/geography/hypermedia/examples/api/putpatch/edit-all"
|
||||
:path "/(geography.(hypermedia.(example.(api.putpatch-edit-all))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -631,7 +631,7 @@
|
||||
:text (str "(~pp-form-full :name \"" (get p "name") "\" ...)")))))
|
||||
|
||||
(defhandler ex-pp-put
|
||||
:path "/geography/hypermedia/examples/api/putpatch"
|
||||
:path "/(geography.(hypermedia.(example.(api.putpatch))))"
|
||||
:method :put
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -648,7 +648,7 @@
|
||||
:text (str "(~pp-view :name \"" name "\" ...)")))))
|
||||
|
||||
(defhandler ex-pp-cancel
|
||||
:path "/geography/hypermedia/examples/api/putpatch/cancel"
|
||||
:path "/(geography.(hypermedia.(example.(api.putpatch-cancel))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -667,7 +667,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-json-echo
|
||||
:path "/geography/hypermedia/examples/api/json-echo"
|
||||
:path "/(geography.(hypermedia.(example.(api.json-echo))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -688,7 +688,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-echo-vals
|
||||
:path "/geography/hypermedia/examples/api/echo-vals"
|
||||
:path "/(geography.(hypermedia.(example.(api.echo-vals))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -705,7 +705,7 @@
|
||||
:text (str "(~echo-result :label \"values\" :items (list ...))")))))))
|
||||
|
||||
(defhandler ex-echo-headers
|
||||
:path "/geography/hypermedia/examples/api/echo-headers"
|
||||
:path "/(geography.(hypermedia.(example.(api.echo-headers))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -725,7 +725,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-slow
|
||||
:path "/geography/hypermedia/examples/api/slow"
|
||||
:path "/(geography.(hypermedia.(example.(api.slow))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -744,7 +744,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-slow-search
|
||||
:path "/geography/hypermedia/examples/api/slow-search"
|
||||
:path "/(geography.(hypermedia.(example.(api.slow-search))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -764,7 +764,7 @@
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(defhandler ex-flaky
|
||||
:path "/geography/hypermedia/examples/api/flaky"
|
||||
:path "/(geography.(hypermedia.(example.(api.flaky))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
;; --- sx-get demo: server time ---
|
||||
|
||||
(defhandler ref-time
|
||||
:path "/geography/hypermedia/reference/api/time"
|
||||
:path "/(geography.(hypermedia.(reference.(api.time))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -19,7 +19,7 @@
|
||||
;; --- sx-post demo: greet ---
|
||||
|
||||
(defhandler ref-greet
|
||||
:path "/geography/hypermedia/reference/api/greet"
|
||||
:path "/(geography.(hypermedia.(reference.(api.greet))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -33,7 +33,7 @@
|
||||
;; --- sx-put demo: status update ---
|
||||
|
||||
(defhandler ref-status
|
||||
:path "/geography/hypermedia/reference/api/status"
|
||||
:path "/(geography.(hypermedia.(reference.(api.status))))"
|
||||
:method :put
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -47,7 +47,7 @@
|
||||
;; --- sx-patch demo: theme ---
|
||||
|
||||
(defhandler ref-theme
|
||||
:path "/geography/hypermedia/reference/api/theme"
|
||||
:path "/(geography.(hypermedia.(reference.(api.theme))))"
|
||||
:method :patch
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -61,7 +61,7 @@
|
||||
;; --- sx-delete demo ---
|
||||
|
||||
(defhandler ref-delete-item
|
||||
:path "/geography/hypermedia/reference/api/item/<item_id>"
|
||||
:path "/(geography.(hypermedia.(reference.(api.(item.<sx:item_id>)))))"
|
||||
:method :delete
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -72,7 +72,7 @@
|
||||
;; --- sx-trigger demo: search ---
|
||||
|
||||
(defhandler ref-trigger-search
|
||||
:path "/geography/hypermedia/reference/api/trigger-search"
|
||||
:path "/(geography.(hypermedia.(reference.(api.trigger-search))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -89,7 +89,7 @@
|
||||
;; --- sx-swap demo ---
|
||||
|
||||
(defhandler ref-swap-item
|
||||
:path "/geography/hypermedia/reference/api/swap-item"
|
||||
:path "/(geography.(hypermedia.(reference.(api.swap-item))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -102,7 +102,7 @@
|
||||
;; --- sx-swap-oob demo ---
|
||||
|
||||
(defhandler ref-oob
|
||||
:path "/geography/hypermedia/reference/api/oob"
|
||||
:path "/(geography.(hypermedia.(reference.(api.oob))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -117,7 +117,7 @@
|
||||
;; --- sx-select demo ---
|
||||
|
||||
(defhandler ref-select-page
|
||||
:path "/geography/hypermedia/reference/api/select-page"
|
||||
:path "/(geography.(hypermedia.(reference.(api.select-page))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -134,7 +134,7 @@
|
||||
;; --- sx-sync demo: slow echo ---
|
||||
|
||||
(defhandler ref-slow-echo
|
||||
:path "/geography/hypermedia/reference/api/slow-echo"
|
||||
:path "/(geography.(hypermedia.(reference.(api.slow-echo))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -148,7 +148,7 @@
|
||||
;; --- sx-prompt demo ---
|
||||
|
||||
(defhandler ref-prompt-echo
|
||||
:path "/geography/hypermedia/reference/api/prompt-echo"
|
||||
:path "/(geography.(hypermedia.(reference.(api.prompt-echo))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -161,7 +161,7 @@
|
||||
;; --- Error demo ---
|
||||
|
||||
(defhandler ref-error-500
|
||||
:path "/geography/hypermedia/reference/api/error-500"
|
||||
:path "/(geography.(hypermedia.(reference.(api.error-500))))"
|
||||
:method :get
|
||||
:returns "nil"
|
||||
(&key)
|
||||
@@ -175,7 +175,7 @@
|
||||
;; --- sx-encoding demo: file upload name ---
|
||||
|
||||
(defhandler ref-upload-name
|
||||
:path "/geography/hypermedia/reference/api/upload-name"
|
||||
:path "/(geography.(hypermedia.(reference.(api.upload-name))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -190,7 +190,7 @@
|
||||
;; --- sx-headers demo: echo custom headers ---
|
||||
|
||||
(defhandler ref-echo-headers
|
||||
:path "/geography/hypermedia/reference/api/echo-headers"
|
||||
:path "/(geography.(hypermedia.(reference.(api.echo-headers))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -214,7 +214,7 @@
|
||||
;; --- sx-include demo: echo GET query params ---
|
||||
|
||||
(defhandler ref-echo-vals-get
|
||||
:path "/geography/hypermedia/reference/api/echo-vals"
|
||||
:path "/(geography.(hypermedia.(reference.(api.echo-vals))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -235,7 +235,7 @@
|
||||
;; --- sx-vals demo: echo POST form values ---
|
||||
|
||||
(defhandler ref-echo-vals-post
|
||||
:path "/geography/hypermedia/reference/api/echo-vals"
|
||||
:path "/(geography.(hypermedia.(reference.(api.echo-vals))))"
|
||||
:method :post
|
||||
:csrf false
|
||||
:returns "element"
|
||||
@@ -257,7 +257,7 @@
|
||||
;; --- sx-retry demo: flaky endpoint (fails 2/3 times) ---
|
||||
|
||||
(defhandler ref-flaky
|
||||
:path "/geography/hypermedia/reference/api/flaky"
|
||||
:path "/(geography.(hypermedia.(reference.(api.flaky))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -275,7 +275,7 @@
|
||||
;; --- sx-trigger-event demo: response header triggers ---
|
||||
|
||||
(defhandler ref-trigger-event
|
||||
:path "/geography/hypermedia/reference/api/trigger-event"
|
||||
:path "/(geography.(hypermedia.(reference.(api.trigger-event))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
@@ -287,7 +287,7 @@
|
||||
;; --- sx-retarget demo: response header retargets ---
|
||||
|
||||
(defhandler ref-retarget
|
||||
:path "/geography/hypermedia/reference/api/retarget"
|
||||
:path "/(geography.(hypermedia.(reference.(api.retarget))))"
|
||||
:method :get
|
||||
:returns "element"
|
||||
(&key)
|
||||
|
||||
@@ -291,7 +291,7 @@
|
||||
|
||||
(~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"))
|
||||
|
||||
@@ -315,7 +315,7 @@
|
||||
(div :id "marsh-flash-target"
|
||||
: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"
|
||||
:sx-get "/geography/reactive/api/flash-sale"
|
||||
:sx-get "/(geography.(reactive.(api.flash-sale)))"
|
||||
:sx-target "#marsh-flash-target"
|
||||
:sx-swap "innerHTML"
|
||||
"Fetch from server"))
|
||||
@@ -334,7 +334,7 @@
|
||||
|
||||
(~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."))
|
||||
|
||||
@@ -348,7 +348,7 @@
|
||||
|
||||
(~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."))
|
||||
|
||||
@@ -361,7 +361,7 @@
|
||||
|
||||
(~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."))
|
||||
|
||||
@@ -408,7 +408,7 @@
|
||||
(div :class "border-t border-stone-200 pt-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"
|
||||
:sx-get "/geography/reactive/api/flash-sale"
|
||||
:sx-get "/(geography.(reactive.(api.flash-sale)))"
|
||||
:sx-target "#marsh-server-msg"
|
||||
:sx-swap "innerHTML"
|
||||
"Fetch Price from Server")
|
||||
@@ -471,7 +471,7 @@
|
||||
(span :class "text-sm text-stone-600" " times")))
|
||||
(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"
|
||||
:sx-get "/geography/reactive/api/settle-data"
|
||||
:sx-get "/(geography.(reactive.(api.settle-data)))"
|
||||
:sx-target "#settle-result"
|
||||
:sx-swap "innerHTML"
|
||||
: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")
|
||||
(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 ()
|
||||
(str "/geography/reactive/api/search/" (deref mode)
|
||||
"?q=" (deref query))))
|
||||
(str "/(geography.(reactive.(api.search-" (deref mode)
|
||||
")))""?q=" (deref query))))
|
||||
:sx-target "#signal-results"
|
||||
:sx-swap "innerHTML"
|
||||
"Search"))
|
||||
;; Current URL display
|
||||
(p :class "text-xs text-stone-400 font-mono"
|
||||
"URL: " (computed (fn ()
|
||||
(str "/geography/reactive/api/search/" (deref mode) "?q=" (deref query)))))
|
||||
(str "/(geography.(reactive.(api.search-" (deref mode) ")))" "?q=" (deref query)))))
|
||||
;; Results
|
||||
(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.")))))
|
||||
@@ -561,7 +561,7 @@
|
||||
;; Fetch button — response writes structured data to store via data-init
|
||||
(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"
|
||||
:sx-get "/geography/reactive/api/catalog"
|
||||
:sx-get "/(geography.(reactive.(api.catalog)))"
|
||||
:sx-target "#catalog-msg"
|
||||
:sx-swap "innerHTML"
|
||||
"Fetch Catalog")
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
(div :id "click-result" :class "p-4 rounded bg-stone-100 text-stone-500 text-center"
|
||||
"Click the button to load content.")
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/examples/api/click"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.click))))"
|
||||
:sx-target "#click-result"
|
||||
:sx-swap "innerHTML"
|
||||
:class "px-4 py-2 bg-violet-600 text-white rounded hover:bg-violet-700 transition-colors"
|
||||
@@ -39,7 +39,7 @@
|
||||
(defcomp ~form-demo ()
|
||||
(div :class "space-y-4"
|
||||
(form
|
||||
:sx-post "/geography/hypermedia/examples/api/form"
|
||||
:sx-post "/(geography.(hypermedia.(example.(api.form))))"
|
||||
:sx-target "#form-result"
|
||||
:sx-swap "innerHTML"
|
||||
:class "space-y-3"
|
||||
@@ -63,7 +63,7 @@
|
||||
(defcomp ~polling-demo ()
|
||||
(div :class "space-y-4"
|
||||
(div :id "poll-target"
|
||||
:sx-get "/geography/hypermedia/examples/api/poll"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.poll))))"
|
||||
:sx-trigger "load, every 2s"
|
||||
:sx-swap "innerHTML"
|
||||
: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"
|
||||
(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-swap "outerHTML"
|
||||
:sx-confirm "Delete this item?"
|
||||
@@ -116,7 +116,7 @@
|
||||
(div :class "flex items-center justify-between p-3 rounded border border-stone-200"
|
||||
(span :class "text-stone-800" value)
|
||||
(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-swap "innerHTML"
|
||||
:class "text-sm text-violet-600 hover:text-violet-800"
|
||||
@@ -124,7 +124,7 @@
|
||||
|
||||
(defcomp ~inline-edit-form (&key value)
|
||||
(form
|
||||
:sx-post "/geography/hypermedia/examples/api/edit"
|
||||
:sx-post "/(geography.(hypermedia.(example.(api.edit))))"
|
||||
:sx-target "#edit-target"
|
||||
:sx-swap "innerHTML"
|
||||
: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"
|
||||
"save")
|
||||
(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-swap "innerHTML"
|
||||
: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-sm text-stone-400" "Waiting...")))
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/examples/api/oob"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.oob))))"
|
||||
:sx-target "#oob-box-a"
|
||||
:sx-swap "innerHTML"
|
||||
: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"
|
||||
(p :class "text-sm text-stone-500" "The content below loads automatically when the page renders.")
|
||||
(div :id "lazy-target"
|
||||
:sx-get "/geography/hypermedia/examples/api/lazy"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.lazy))))"
|
||||
:sx-trigger "load"
|
||||
:sx-swap "innerHTML"
|
||||
: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")))
|
||||
(list 1 2 3 4 5))
|
||||
(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-target "#scroll-items"
|
||||
:sx-swap "beforeend"
|
||||
@@ -201,7 +201,7 @@
|
||||
items)
|
||||
(when (<= page 5)
|
||||
(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-target "#scroll-items"
|
||||
:sx-swap "beforeend"
|
||||
@@ -217,7 +217,7 @@
|
||||
(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."))
|
||||
(button
|
||||
:sx-post "/geography/hypermedia/examples/api/progress/start"
|
||||
:sx-post "/(geography.(hypermedia.(example.(api.progress-start))))"
|
||||
:sx-target "#progress-target"
|
||||
:sx-swap "innerHTML"
|
||||
: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 "%")))
|
||||
(p :class "text-sm text-stone-500 text-center" (str percent "% complete"))
|
||||
(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-target "#progress-target"
|
||||
:sx-swap "innerHTML"))
|
||||
@@ -242,7 +242,7 @@
|
||||
(defcomp ~active-search-demo ()
|
||||
(div :class "space-y-3"
|
||||
(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-target "#search-results"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -262,11 +262,11 @@
|
||||
;; --- 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
|
||||
(label :class "block text-sm font-medium text-stone-700 mb-1" "Email")
|
||||
(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-target "#email-feedback"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -290,7 +290,7 @@
|
||||
(div
|
||||
(label :class "block text-sm font-medium text-stone-700 mb-1" "Category")
|
||||
(select :name "category"
|
||||
:sx-get "/geography/hypermedia/examples/api/values"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.values))))"
|
||||
:sx-trigger "change"
|
||||
:sx-target "#value-items"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -314,7 +314,7 @@
|
||||
(defcomp ~reset-on-submit-demo ()
|
||||
(div :class "space-y-3"
|
||||
(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-swap "innerHTML"
|
||||
: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"
|
||||
(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-swap "outerHTML"
|
||||
: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"))
|
||||
(td :class "px-3 py-2 space-x-1"
|
||||
(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-swap "outerHTML"
|
||||
:sx-include (str "#erow-" id)
|
||||
:class "text-sm text-emerald-600 hover:text-emerald-800"
|
||||
"save")
|
||||
(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-swap "outerHTML"
|
||||
:class "text-sm text-stone-500 hover:text-stone-700"
|
||||
@@ -393,14 +393,14 @@
|
||||
(form :id "bulk-form"
|
||||
(div :class "flex gap-2 mb-3"
|
||||
(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-swap "innerHTML"
|
||||
:sx-include "#bulk-form"
|
||||
:class "px-3 py-1.5 bg-emerald-600 text-white rounded text-sm hover:bg-emerald-700"
|
||||
"Activate")
|
||||
(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-swap "innerHTML"
|
||||
:sx-include "#bulk-form"
|
||||
@@ -437,19 +437,19 @@
|
||||
(div :class "space-y-3"
|
||||
(div :class "flex gap-2"
|
||||
(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-swap "beforeend"
|
||||
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
|
||||
"Add to End")
|
||||
(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-swap "afterbegin"
|
||||
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
|
||||
"Add to Start")
|
||||
(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-swap "none"
|
||||
: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 "flex gap-2"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/examples/api/dashboard"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.dashboard))))"
|
||||
:sx-target "#filter-target"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-select "#dash-stats"
|
||||
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
|
||||
"Stats Only")
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/examples/api/dashboard"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.dashboard))))"
|
||||
:sx-target "#filter-target"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-select "#dash-header"
|
||||
:class "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
|
||||
"Header Only")
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/examples/api/dashboard"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.dashboard))))"
|
||||
:sx-target "#filter-target"
|
||||
:sx-swap "innerHTML"
|
||||
: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)
|
||||
(button
|
||||
:sx-get (str "/geography/hypermedia/examples/api/tabs/" tab)
|
||||
:sx-get (str "/(geography.(hypermedia.(example.(api.(tabs." tab ")))))")
|
||||
:sx-target "#tab-content"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-push-url (str "/geography/hypermedia/examples/tabs?tab=" tab)
|
||||
@@ -520,7 +520,7 @@
|
||||
(defcomp ~animations-demo ()
|
||||
(div :class "space-y-4"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/examples/api/animate"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.animate))))"
|
||||
:sx-target "#anim-target"
|
||||
:sx-swap "innerHTML"
|
||||
: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 ()
|
||||
(div
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/examples/api/dialog"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.dialog))))"
|
||||
:sx-target "#dialog-container"
|
||||
:sx-swap "innerHTML"
|
||||
: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)
|
||||
(div :class "fixed inset-0 z-50 flex items-center justify-center"
|
||||
(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-swap "innerHTML")
|
||||
(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)
|
||||
(div :class "flex justify-end gap-2"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/examples/api/dialog/close"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.dialog-close))))"
|
||||
:sx-target "#dialog-container"
|
||||
:sx-swap "innerHTML"
|
||||
:class "px-4 py-2 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300"
|
||||
"Cancel")
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/examples/api/dialog/close"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.dialog-close))))"
|
||||
:sx-target "#dialog-container"
|
||||
:sx-swap "innerHTML"
|
||||
: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")
|
||||
(span :class "text-sm text-stone-500" "Help"))))
|
||||
(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-swap "innerHTML"
|
||||
:class "p-4 rounded border border-stone-200 bg-stone-100 text-center"
|
||||
(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-target "#kbd-target"
|
||||
: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-target "#kbd-target"
|
||||
:sx-swap "innerHTML")))
|
||||
@@ -619,7 +619,7 @@
|
||||
(p :class "text-sm text-stone-500" email)
|
||||
(p :class "text-sm text-stone-500" role))
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/examples/api/putpatch/edit-all"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.putpatch-edit-all))))"
|
||||
:sx-target "#pp-target"
|
||||
:sx-swap "innerHTML"
|
||||
:class "text-sm text-violet-600 hover:text-violet-800"
|
||||
@@ -627,7 +627,7 @@
|
||||
|
||||
(defcomp ~pp-form-full (&key name email role)
|
||||
(form
|
||||
:sx-put "/geography/hypermedia/examples/api/putpatch"
|
||||
:sx-put "/(geography.(hypermedia.(example.(api.putpatch))))"
|
||||
:sx-target "#pp-target"
|
||||
:sx-swap "innerHTML"
|
||||
: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"
|
||||
"Save All (PUT)")
|
||||
(button :type "button"
|
||||
:sx-get "/geography/hypermedia/examples/api/putpatch/cancel"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.putpatch-cancel))))"
|
||||
:sx-target "#pp-target"
|
||||
:sx-swap "innerHTML"
|
||||
: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 ()
|
||||
(div :class "space-y-4"
|
||||
(form
|
||||
:sx-post "/geography/hypermedia/examples/api/json-echo"
|
||||
:sx-post "/(geography.(hypermedia.(example.(api.json-echo))))"
|
||||
:sx-target "#json-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-encoding "json"
|
||||
@@ -691,7 +691,7 @@
|
||||
(div :class "space-y-2"
|
||||
(h4 :class "text-sm font-semibold text-stone-700" "sx-vals — send extra values")
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/examples/api/echo-vals"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.echo-vals))))"
|
||||
:sx-target "#vals-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-vals "{\"source\": \"button\", \"version\": \"2.0\"}"
|
||||
@@ -702,7 +702,7 @@
|
||||
(div :class "space-y-2"
|
||||
(h4 :class "text-sm font-semibold text-stone-700" "sx-headers — send custom headers")
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/examples/api/echo-headers"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.echo-headers))))"
|
||||
:sx-target "#headers-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-headers {:X-Custom-Token "abc123" :X-Request-Source "demo"}
|
||||
@@ -723,7 +723,7 @@
|
||||
(defcomp ~loading-states-demo ()
|
||||
(div :class "space-y-4"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/examples/api/slow"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.slow))))"
|
||||
:sx-target "#loading-result"
|
||||
: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"
|
||||
@@ -742,7 +742,7 @@
|
||||
(defcomp ~sync-replace-demo ()
|
||||
(div :class "space-y-3"
|
||||
(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-target "#sync-result"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -762,7 +762,7 @@
|
||||
(defcomp ~retry-demo ()
|
||||
(div :class "space-y-4"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/examples/api/flaky"
|
||||
:sx-get "/(geography.(hypermedia.(example.(api.flaky))))"
|
||||
:sx-target "#retry-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-retry "exponential:1000:8000"
|
||||
|
||||
@@ -280,6 +280,51 @@ async def eval_sx_url(raw_path: str) -> Any:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_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
|
||||
(re.compile(r"^/language/specs/explore/(.+?)/?$"),
|
||||
lambda m: f"/(language.(spec.(explore.{m.group(1)})))"),
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
(defcomp ~ref-get-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/reference/api/time"
|
||||
:sx-get "/(geography.(hypermedia.(reference.(api.time))))"
|
||||
:sx-target "#ref-get-result"
|
||||
:sx-swap "innerHTML"
|
||||
: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 ()
|
||||
(div :class "space-y-3"
|
||||
(form
|
||||
:sx-post "/geography/hypermedia/reference/api/greet"
|
||||
:sx-post "/(geography.(hypermedia.(reference.(api.greet))))"
|
||||
:sx-target "#ref-post-result"
|
||||
:sx-swap "innerHTML"
|
||||
:class "flex gap-2"
|
||||
@@ -48,7 +48,7 @@
|
||||
(div :class "flex items-center justify-between p-3 bg-stone-100 rounded"
|
||||
(span :class "text-stone-700 text-sm" "Status: " (strong "draft"))
|
||||
(button
|
||||
:sx-put "/geography/hypermedia/reference/api/status"
|
||||
:sx-put "/(geography.(hypermedia.(reference.(api.status))))"
|
||||
:sx-target "#ref-put-view"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-vals "{\"status\": \"published\"}"
|
||||
@@ -63,17 +63,17 @@
|
||||
(div :class "space-y-2"
|
||||
(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")
|
||||
(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"
|
||||
: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"
|
||||
(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"
|
||||
: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"
|
||||
(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"
|
||||
:class "text-red-500 text-sm hover:text-red-700" "Remove"))))
|
||||
|
||||
@@ -86,11 +86,11 @@
|
||||
(div :class "p-3 bg-stone-100 rounded"
|
||||
(span :class "text-stone-700 text-sm" "Theme: " (strong :id "ref-patch-val" "light")))
|
||||
(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-target "#ref-patch-val" :sx-swap "innerHTML"
|
||||
: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-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"))))
|
||||
@@ -102,7 +102,7 @@
|
||||
(defcomp ~ref-trigger-demo ()
|
||||
(div :class "space-y-3"
|
||||
(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-target "#ref-trigger-result"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -118,12 +118,12 @@
|
||||
(defcomp ~ref-target-demo ()
|
||||
(div :class "space-y-3"
|
||||
(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-swap "innerHTML"
|
||||
:class "px-3 py-1 bg-violet-600 text-white rounded text-sm hover:bg-violet-700"
|
||||
"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-swap "innerHTML"
|
||||
: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 ()
|
||||
(div :class "space-y-3"
|
||||
(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"
|
||||
: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"
|
||||
: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"
|
||||
:class "px-3 py-1 bg-blue-600 text-white rounded text-sm" "innerHTML"))
|
||||
(div :id "ref-swap-list"
|
||||
@@ -160,7 +160,7 @@
|
||||
|
||||
(defcomp ~ref-oob-demo ()
|
||||
(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-swap "innerHTML"
|
||||
: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 ()
|
||||
(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-select "#the-content"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -198,7 +198,7 @@
|
||||
(div :id "ref-confirm-item"
|
||||
:class "flex items-center justify-between p-3 border border-stone-200 rounded"
|
||||
(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-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"
|
||||
@@ -211,14 +211,14 @@
|
||||
(defcomp ~ref-pushurl-demo ()
|
||||
(div :class "space-y-3"
|
||||
(div :class "flex gap-2"
|
||||
(a :href "/geography/hypermedia/reference/attributes/sx-get"
|
||||
:sx-get "/geography/hypermedia/reference/attributes/sx-get"
|
||||
(a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
|
||||
:sx-get "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
|
||||
:sx-target "#main-panel" :sx-select "#main-panel"
|
||||
: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"
|
||||
"sx-get page")
|
||||
(a :href "/geography/hypermedia/reference/attributes/sx-post"
|
||||
:sx-get "/geography/hypermedia/reference/attributes/sx-post"
|
||||
(a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-post)))"
|
||||
:sx-get "/(geography.(hypermedia.(reference-detail.attributes.sx-post)))"
|
||||
:sx-target "#main-panel" :sx-select "#main-panel"
|
||||
: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"
|
||||
@@ -233,7 +233,7 @@
|
||||
(defcomp ~ref-sync-demo ()
|
||||
(div :class "space-y-3"
|
||||
(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-sync "replace"
|
||||
:sx-target "#ref-sync-result"
|
||||
@@ -251,7 +251,7 @@
|
||||
|
||||
(defcomp ~ref-encoding-demo ()
|
||||
(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-target "#ref-encoding-result"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -271,7 +271,7 @@
|
||||
|
||||
(defcomp ~ref-headers-demo ()
|
||||
(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-target "#ref-headers-result"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -295,7 +295,7 @@
|
||||
(option :value "all" "All")
|
||||
(option :value "books" "Books")
|
||||
(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-target "#ref-include-result"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -311,7 +311,7 @@
|
||||
|
||||
(defcomp ~ref-vals-demo ()
|
||||
(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-target "#ref-vals-result"
|
||||
:sx-swap "innerHTML"
|
||||
@@ -327,8 +327,8 @@
|
||||
|
||||
(defcomp ~ref-media-demo ()
|
||||
(div :class "space-y-3"
|
||||
(a :href "/geography/hypermedia/reference/attributes/sx-get"
|
||||
:sx-get "/geography/hypermedia/reference/attributes/sx-get"
|
||||
(a :href "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
|
||||
:sx-get "/(geography.(hypermedia.(reference-detail.attributes.sx-get)))"
|
||||
:sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
:sx-media "(min-width: 768px)"
|
||||
@@ -346,13 +346,13 @@
|
||||
(div :class "grid grid-cols-2 gap-3"
|
||||
(div :class "p-3 border border-stone-200 rounded"
|
||||
(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"
|
||||
: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 :sx-disable "true" :class "p-3 border border-stone-200 rounded"
|
||||
(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"
|
||||
: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"
|
||||
@@ -378,7 +378,7 @@
|
||||
|
||||
(defcomp ~ref-retry-demo ()
|
||||
(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-swap "innerHTML"
|
||||
:sx-retry "true"
|
||||
@@ -414,13 +414,13 @@
|
||||
(defcomp ~ref-boost-demo ()
|
||||
(div :class "space-y-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"
|
||||
"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"
|
||||
"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"
|
||||
"sx-target"))
|
||||
(p :class "text-xs text-stone-400"
|
||||
@@ -434,7 +434,7 @@
|
||||
(defcomp ~ref-preload-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/reference/api/time"
|
||||
:sx-get "/(geography.(hypermedia.(reference.(api.time))))"
|
||||
:sx-target "#ref-preload-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-preload "mouseover"
|
||||
@@ -452,7 +452,7 @@
|
||||
(div :class "space-y-3"
|
||||
(div :class "flex gap-2 items-center"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/reference/api/time"
|
||||
:sx-get "/(geography.(hypermedia.(reference.(api.time))))"
|
||||
:sx-target "#ref-preserve-container"
|
||||
:sx-swap "innerHTML"
|
||||
: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 "flex gap-3 items-center"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/reference/api/slow-echo"
|
||||
:sx-get "/(geography.(hypermedia.(reference.(api.slow-echo))))"
|
||||
:sx-target "#ref-indicator-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-indicator "#ref-spinner"
|
||||
@@ -495,7 +495,7 @@
|
||||
(defcomp ~ref-validate-demo ()
|
||||
(div :class "space-y-3"
|
||||
(form
|
||||
:sx-post "/geography/hypermedia/reference/api/greet"
|
||||
:sx-post "/(geography.(hypermedia.(reference.(api.greet))))"
|
||||
:sx-target "#ref-validate-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-validate "true"
|
||||
@@ -517,7 +517,7 @@
|
||||
(defcomp ~ref-ignore-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/reference/api/time"
|
||||
:sx-get "/(geography.(hypermedia.(reference.(api.time))))"
|
||||
:sx-target "#ref-ignore-container"
|
||||
:sx-swap "innerHTML"
|
||||
: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"
|
||||
:class "flex items-center justify-between p-2 border border-stone-200 rounded"
|
||||
(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-optimistic "remove"
|
||||
:class "text-red-500 text-sm hover:text-red-700" "Remove"))
|
||||
(div :id "ref-opt-item-2"
|
||||
:class "flex items-center justify-between p-2 border border-stone-200 rounded"
|
||||
(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-optimistic "remove"
|
||||
:class "text-red-500 text-sm hover:text-red-700" "Remove"))
|
||||
@@ -560,7 +560,7 @@
|
||||
(defcomp ~ref-replace-url-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/reference/api/time"
|
||||
:sx-get "/(geography.(hypermedia.(reference.(api.time))))"
|
||||
:sx-target "#ref-replurl-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-replace-url "true"
|
||||
@@ -578,7 +578,7 @@
|
||||
(div :class "space-y-3"
|
||||
(div :class "flex gap-3 items-center"
|
||||
(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-swap "innerHTML"
|
||||
:sx-disabled-elt "#ref-diselt-btn"
|
||||
@@ -597,7 +597,7 @@
|
||||
(defcomp ~ref-prompt-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/reference/api/prompt-echo"
|
||||
:sx-get "/(geography.(hypermedia.(reference.(api.prompt-echo))))"
|
||||
:sx-target "#ref-prompt-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-prompt "Enter your name:"
|
||||
@@ -614,7 +614,7 @@
|
||||
(defcomp ~ref-params-demo ()
|
||||
(div :class "space-y-3"
|
||||
(form
|
||||
:sx-post "/geography/hypermedia/reference/api/echo-vals"
|
||||
:sx-post "/(geography.(hypermedia.(reference.(api.echo-vals))))"
|
||||
:sx-target "#ref-params-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-params "name"
|
||||
@@ -636,7 +636,7 @@
|
||||
|
||||
(defcomp ~ref-sse-demo ()
|
||||
(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-swap "innerHTML"
|
||||
(div :id "ref-sse-result"
|
||||
@@ -656,7 +656,7 @@
|
||||
(defcomp ~ref-header-prompt-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/reference/api/prompt-echo"
|
||||
:sx-get "/(geography.(hypermedia.(reference.(api.prompt-echo))))"
|
||||
:sx-target "#ref-hdr-prompt-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-prompt "Enter your name:"
|
||||
@@ -673,7 +673,7 @@
|
||||
(defcomp ~ref-header-trigger-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/reference/api/trigger-event"
|
||||
:sx-get "/(geography.(hypermedia.(reference.(api.trigger-event))))"
|
||||
:sx-target "#ref-hdr-trigger-result"
|
||||
:sx-swap "innerHTML"
|
||||
: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 ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/reference/api/retarget"
|
||||
:sx-get "/(geography.(hypermedia.(reference.(api.retarget))))"
|
||||
:sx-target "#ref-hdr-retarget-main"
|
||||
:sx-swap "innerHTML"
|
||||
: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..."
|
||||
: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
|
||||
:sx-get "/geography/hypermedia/reference/api/time"
|
||||
:sx-get "/(geography.(hypermedia.(reference.(api.time))))"
|
||||
:sx-target "#ref-evt-br-result"
|
||||
: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!'; }"
|
||||
@@ -734,7 +734,7 @@
|
||||
(defcomp ~ref-event-after-request-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/reference/api/time"
|
||||
:sx-get "/(geography.(hypermedia.(reference.(api.time))))"
|
||||
:sx-target "#ref-evt-ar-result"
|
||||
:sx-swap "innerHTML"
|
||||
: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 ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/reference/api/swap-item"
|
||||
:sx-get "/(geography.(hypermedia.(reference.(api.swap-item))))"
|
||||
:sx-target "#ref-evt-as-list"
|
||||
: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'"
|
||||
@@ -774,7 +774,7 @@
|
||||
(defcomp ~ref-event-response-error-demo ()
|
||||
(div :class "space-y-3"
|
||||
(button
|
||||
:sx-get "/geography/hypermedia/reference/api/error-500"
|
||||
:sx-get "/(geography.(hypermedia.(reference.(api.error-500))))"
|
||||
:sx-target "#ref-evt-err-result"
|
||||
: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'"
|
||||
@@ -796,7 +796,7 @@
|
||||
(defcomp ~ref-event-validation-failed-demo ()
|
||||
(div :class "space-y-3"
|
||||
(form
|
||||
:sx-post "/geography/hypermedia/reference/api/greet"
|
||||
:sx-post "/(geography.(hypermedia.(reference.(api.greet))))"
|
||||
:sx-target "#ref-evt-vf-result"
|
||||
:sx-swap "innerHTML"
|
||||
:sx-validate "true"
|
||||
@@ -847,13 +847,13 @@
|
||||
"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.")
|
||||
(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"
|
||||
"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"
|
||||
"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"
|
||||
"Protocols"))
|
||||
(p :class "text-xs text-stone-400"
|
||||
@@ -866,7 +866,7 @@
|
||||
|
||||
(defcomp ~ref-event-sse-open-demo ()
|
||||
(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-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'"
|
||||
@@ -884,7 +884,7 @@
|
||||
|
||||
(defcomp ~ref-event-sse-message-demo ()
|
||||
(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-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'"
|
||||
@@ -902,7 +902,7 @@
|
||||
|
||||
(defcomp ~ref-event-sse-error-demo ()
|
||||
(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-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'"
|
||||
|
||||
Reference in New Issue
Block a user