Rebrand sexp → sx across web platform (173 files)
Rename all sexp directories, files, identifiers, and references to sx. artdag/ excluded (separate media processing DSL). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
177
.claude/plans/glittery-discovering-kahn.md
Normal file
177
.claude/plans/glittery-discovering-kahn.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# Sexp Fragment Protocol: Component Defs Between Services
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Fragment endpoints return raw sexp source (e.g., `(~blog-nav-wrapper :items ...)`). The consuming service embeds this in its page sexp, which the client evaluates. But blog-specific components like `~blog-nav-wrapper` are only in blog's `_COMPONENT_ENV` — not in market's. So market's `client_components_tag()` never sends them to the client, causing "Unknown component" errors.
|
||||||
|
|
||||||
|
The fix: transfer component definitions alongside fragments. Services tell the provider what they already have; the provider sends only what's missing. The consuming service registers received defs into its `_COMPONENT_ENV` so they're included in `client_components_tag()` output for the client.
|
||||||
|
|
||||||
|
## Approach: Structured Sexp Request/Response
|
||||||
|
|
||||||
|
Replace the current GET + `X-Fragment-Request` header protocol with POST + sexp body. This aligns with the vision in `docs/sexpr-internal-protocol-first.md`.
|
||||||
|
|
||||||
|
### Request format (POST body)
|
||||||
|
```scheme
|
||||||
|
(fragment-request
|
||||||
|
:type "nav-tree"
|
||||||
|
:params (:app-name "market" :path "/")
|
||||||
|
:components (~blog-nav-wrapper ~blog-nav-item-link ~header-row-sx ...))
|
||||||
|
```
|
||||||
|
|
||||||
|
`:components` lists component names already in the consumer's `_COMPONENT_ENV`. Provider skips these.
|
||||||
|
|
||||||
|
### Response format
|
||||||
|
```scheme
|
||||||
|
(fragment-response
|
||||||
|
:defs ((defcomp ~blog-nav-wrapper (&key ...) ...) (defcomp ~blog-nav-item-link ...))
|
||||||
|
:content (~blog-nav-wrapper :items ...))
|
||||||
|
```
|
||||||
|
|
||||||
|
`:defs` contains only components the consumer doesn't have. `:content` is the fragment sexp (same as current response body).
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
### 1. `shared/infrastructure/fragments.py` — Client side
|
||||||
|
|
||||||
|
**`fetch_fragment()`**: Switch from GET to POST with sexp body.
|
||||||
|
|
||||||
|
- Build request body using `sexp_call`:
|
||||||
|
```python
|
||||||
|
from shared.sexp.helpers import sexp_call, SexpExpr
|
||||||
|
from shared.sexp.jinja_bridge import _COMPONENT_ENV
|
||||||
|
|
||||||
|
comp_names = [k for k in _COMPONENT_ENV if k.startswith("~")]
|
||||||
|
body = sexp_call("fragment-request",
|
||||||
|
type=fragment_type,
|
||||||
|
params=params or {},
|
||||||
|
components=SexpExpr("(" + " ".join(comp_names) + ")"))
|
||||||
|
```
|
||||||
|
- POST to same URL, body as `text/sexp`, keep `X-Fragment-Request` header for backward compat
|
||||||
|
- Parse response: extract `:defs` and `:content` from the sexp response
|
||||||
|
- Register defs into `_COMPONENT_ENV` via `register_components()`
|
||||||
|
- Return `:content` wrapped as `SexpExpr`
|
||||||
|
|
||||||
|
**New helper `_parse_fragment_response(text)`**:
|
||||||
|
- `parse()` the response sexp
|
||||||
|
- Extract keyword args (reuse the keyword-extraction pattern from `evaluator.py`)
|
||||||
|
- Return `(defs_source, content_source)` tuple
|
||||||
|
|
||||||
|
### 2. `shared/sexp/helpers.py` — Response builder
|
||||||
|
|
||||||
|
**New `fragment_response(content, request_text)`**:
|
||||||
|
|
||||||
|
```python
|
||||||
|
def fragment_response(content: str, request_text: str) -> str:
|
||||||
|
"""Build a structured fragment response with missing component defs."""
|
||||||
|
from .parser import parse, serialize
|
||||||
|
from .types import Keyword, Component
|
||||||
|
from .jinja_bridge import _COMPONENT_ENV
|
||||||
|
|
||||||
|
# Parse request to get :components list
|
||||||
|
req = parse(request_text)
|
||||||
|
loaded = set()
|
||||||
|
# extract :components keyword value
|
||||||
|
...
|
||||||
|
|
||||||
|
# Diff against _COMPONENT_ENV, serialize missing defs
|
||||||
|
defs_parts = []
|
||||||
|
for key, val in _COMPONENT_ENV.items():
|
||||||
|
if not isinstance(val, Component):
|
||||||
|
continue
|
||||||
|
if key in loaded or f"~{val.name}" in loaded:
|
||||||
|
continue
|
||||||
|
defs_parts.append(_serialize_defcomp(val))
|
||||||
|
|
||||||
|
defs_sexp = "(" + " ".join(defs_parts) + ")" if defs_parts else "nil"
|
||||||
|
return sexp_call("fragment-response",
|
||||||
|
defs=SexpExpr(defs_sexp),
|
||||||
|
content=SexpExpr(content))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Fragment endpoints — All services
|
||||||
|
|
||||||
|
**Generic change in each `bp/fragments/routes.py`**: Update the route handler to accept POST, read sexp body, use `fragment_response()` for the response.
|
||||||
|
|
||||||
|
The `get_fragment` handler becomes:
|
||||||
|
```python
|
||||||
|
@bp.route("/<fragment_type>", methods=["GET", "POST"])
|
||||||
|
async def get_fragment(fragment_type: str):
|
||||||
|
handler = _handlers.get(fragment_type)
|
||||||
|
if handler is None:
|
||||||
|
return Response("", status=200, content_type="text/sexp")
|
||||||
|
content = await handler()
|
||||||
|
|
||||||
|
# Structured sexp protocol (POST with sexp body)
|
||||||
|
request_body = await request.get_data(as_text=True)
|
||||||
|
if request_body and request.content_type == "text/sexp":
|
||||||
|
from shared.sexp.helpers import fragment_response
|
||||||
|
body = fragment_response(content, request_body)
|
||||||
|
return Response(body, status=200, content_type="text/sexp")
|
||||||
|
|
||||||
|
# Legacy GET fallback
|
||||||
|
return Response(content, status=200, content_type="text/sexp")
|
||||||
|
```
|
||||||
|
|
||||||
|
Since all fragment endpoints follow the identical `_handlers` + `get_fragment` pattern, we can extract this into a shared helper in `fragments.py` or a new `shared/infrastructure/fragment_endpoint.py`.
|
||||||
|
|
||||||
|
### 4. Extract shared fragment endpoint helper
|
||||||
|
|
||||||
|
To avoid touching every service's fragment routes, create a shared blueprint factory:
|
||||||
|
|
||||||
|
**`shared/infrastructure/fragment_endpoint.py`**:
|
||||||
|
```python
|
||||||
|
def create_fragment_blueprint(handlers: dict) -> Blueprint:
|
||||||
|
"""Create a fragment endpoint blueprint with sexp protocol support."""
|
||||||
|
bp = Blueprint("fragments", __name__, url_prefix="/internal/fragments")
|
||||||
|
|
||||||
|
@bp.before_request
|
||||||
|
async def _require_fragment_header():
|
||||||
|
if not request.headers.get(FRAGMENT_HEADER):
|
||||||
|
return Response("", status=403)
|
||||||
|
|
||||||
|
@bp.route("/<fragment_type>", methods=["GET", "POST"])
|
||||||
|
async def get_fragment(fragment_type: str):
|
||||||
|
handler = handlers.get(fragment_type)
|
||||||
|
if handler is None:
|
||||||
|
return Response("", status=200, content_type="text/sexp")
|
||||||
|
content = await handler()
|
||||||
|
|
||||||
|
# Sexp protocol: POST with structured request/response
|
||||||
|
if request.method == "POST" and request.content_type == "text/sexp":
|
||||||
|
request_body = await request.get_data(as_text=True)
|
||||||
|
from shared.sexp.helpers import fragment_response
|
||||||
|
body = fragment_response(content, request_body)
|
||||||
|
return Response(body, status=200, content_type="text/sexp")
|
||||||
|
|
||||||
|
return Response(content, status=200, content_type="text/sexp")
|
||||||
|
|
||||||
|
return bp
|
||||||
|
```
|
||||||
|
|
||||||
|
Then each service's `register()` just returns `create_fragment_blueprint(_handlers)`. This is a small refactor since they all duplicate the same boilerplate today.
|
||||||
|
|
||||||
|
## Files to modify
|
||||||
|
|
||||||
|
| File | Change |
|
||||||
|
|------|--------|
|
||||||
|
| `shared/infrastructure/fragments.py` | POST sexp body, parse response, register defs |
|
||||||
|
| `shared/sexp/helpers.py` | `fragment_response()` builder, `_serialize_defcomp()` |
|
||||||
|
| `shared/infrastructure/fragment_endpoint.py` | **New** — shared blueprint factory |
|
||||||
|
| `blog/bp/fragments/routes.py` | Use `create_fragment_blueprint` |
|
||||||
|
| `market/bp/fragments/routes.py` | Use `create_fragment_blueprint` |
|
||||||
|
| `events/bp/fragments/routes.py` | Use `create_fragment_blueprint` |
|
||||||
|
| `cart/bp/fragments/routes.py` | Use `create_fragment_blueprint` |
|
||||||
|
| `account/bp/fragments/routes.py` | Use `create_fragment_blueprint` |
|
||||||
|
| `orders/bp/fragments/routes.py` | Use `create_fragment_blueprint` |
|
||||||
|
| `federation/bp/fragments/routes.py` | Use `create_fragment_blueprint` |
|
||||||
|
| `relations/bp/fragments/routes.py` | Use `create_fragment_blueprint` |
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
1. Start blog + market services: `./dev.sh blog market`
|
||||||
|
2. Load market page — should fetch nav-tree from blog with sexp protocol
|
||||||
|
3. Check market logs: no "Unknown component" errors
|
||||||
|
4. Inspect page source: `client_components_tag()` output includes `~blog-nav-wrapper` etc.
|
||||||
|
5. Cross-domain sx-get navigation (blog → market) works without reload
|
||||||
|
6. Run sexp tests: `python3 -m pytest shared/sexp/tests/ -x -q`
|
||||||
|
7. Second page load: `:components` list in request includes blog nav components, response `:defs` is empty
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import path_setup # noqa: F401 # adds shared/ to sys.path
|
import path_setup # noqa: F401 # adds shared/ to sys.path
|
||||||
import sexp.sexp_components as sexp_components # noqa: F401 # ensure Hypercorn --reload watches this file
|
import sx.sx_components as sx_components # noqa: F401 # ensure Hypercorn --reload watches this file
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from quart import g, request
|
from quart import g, request
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from shared.models import UserNewsletter
|
|||||||
from shared.models.ghost_membership_entities import GhostNewsletter
|
from shared.models.ghost_membership_entities import GhostNewsletter
|
||||||
from shared.infrastructure.urls import login_url
|
from shared.infrastructure.urls import login_url
|
||||||
from shared.infrastructure.fragments import fetch_fragment, fetch_fragments
|
from shared.infrastructure.fragments import fetch_fragment, fetch_fragments
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
oob = {
|
oob = {
|
||||||
"oob_extends": "oob_elements.html",
|
"oob_extends": "oob_elements.html",
|
||||||
@@ -47,8 +47,8 @@ def register(url_prefix="/"):
|
|||||||
@account_bp.get("/")
|
@account_bp.get("/")
|
||||||
async def account():
|
async def account():
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_account_page, render_account_oob
|
from sx.sx_components import render_account_page, render_account_oob
|
||||||
|
|
||||||
if not g.get("user"):
|
if not g.get("user"):
|
||||||
return redirect(login_url("/"))
|
return redirect(login_url("/"))
|
||||||
@@ -58,8 +58,8 @@ def register(url_prefix="/"):
|
|||||||
html = await render_account_page(ctx)
|
html = await render_account_page(ctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_account_oob(ctx)
|
sx_src = await render_account_oob(ctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@account_bp.get("/newsletters/")
|
@account_bp.get("/newsletters/")
|
||||||
async def newsletters():
|
async def newsletters():
|
||||||
@@ -89,16 +89,16 @@ def register(url_prefix="/"):
|
|||||||
"subscribed": un.subscribed if un else False,
|
"subscribed": un.subscribed if un else False,
|
||||||
})
|
})
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_newsletters_page, render_newsletters_oob
|
from sx.sx_components import render_newsletters_page, render_newsletters_oob
|
||||||
|
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
html = await render_newsletters_page(ctx, newsletter_list)
|
html = await render_newsletters_page(ctx, newsletter_list)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_newsletters_oob(ctx, newsletter_list)
|
sx_src = await render_newsletters_oob(ctx, newsletter_list)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@account_bp.post("/newsletter/<int:newsletter_id>/toggle/")
|
@account_bp.post("/newsletter/<int:newsletter_id>/toggle/")
|
||||||
async def toggle_newsletter(newsletter_id: int):
|
async def toggle_newsletter(newsletter_id: int):
|
||||||
@@ -125,8 +125,8 @@ def register(url_prefix="/"):
|
|||||||
|
|
||||||
await g.s.flush()
|
await g.s.flush()
|
||||||
|
|
||||||
from sexp.sexp_components import render_newsletter_toggle
|
from sx.sx_components import render_newsletter_toggle
|
||||||
return sexp_response(render_newsletter_toggle(un))
|
return sx_response(render_newsletter_toggle(un))
|
||||||
|
|
||||||
# Catch-all for fragment-provided pages — must be last
|
# Catch-all for fragment-provided pages — must be last
|
||||||
@account_bp.get("/<slug>/")
|
@account_bp.get("/<slug>/")
|
||||||
@@ -144,15 +144,15 @@ def register(url_prefix="/"):
|
|||||||
if not fragment_html:
|
if not fragment_html:
|
||||||
abort(404)
|
abort(404)
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_fragment_page, render_fragment_oob
|
from sx.sx_components import render_fragment_page, render_fragment_oob
|
||||||
|
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
html = await render_fragment_page(ctx, fragment_html)
|
html = await render_fragment_page(ctx, fragment_html)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_fragment_oob(ctx, fragment_html)
|
sx_src = await render_fragment_oob(ctx, fragment_html)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
return account_bp
|
return account_bp
|
||||||
|
|||||||
@@ -275,8 +275,8 @@ def register(url_prefix="/auth"):
|
|||||||
redirect_url = pop_login_redirect_target()
|
redirect_url = pop_login_redirect_target()
|
||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_login_page
|
from sx.sx_components import render_login_page
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
return await render_login_page(ctx)
|
return await render_login_page(ctx)
|
||||||
|
|
||||||
@@ -291,8 +291,8 @@ def register(url_prefix="/auth"):
|
|||||||
|
|
||||||
is_valid, email = validate_email(email_input)
|
is_valid, email = validate_email(email_input)
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_login_page
|
from sx.sx_components import render_login_page
|
||||||
ctx = await get_template_context(error="Please enter a valid email address.", email=email_input)
|
ctx = await get_template_context(error="Please enter a valid email address.", email=email_input)
|
||||||
return await render_login_page(ctx), 400
|
return await render_login_page(ctx), 400
|
||||||
|
|
||||||
@@ -301,8 +301,8 @@ def register(url_prefix="/auth"):
|
|||||||
try:
|
try:
|
||||||
allowed, _ = await _check_rate_limit(f"magic_email:{email}", 5, 900)
|
allowed, _ = await _check_rate_limit(f"magic_email:{email}", 5, 900)
|
||||||
if not allowed:
|
if not allowed:
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_check_email_page
|
from sx.sx_components import render_check_email_page
|
||||||
ctx = await get_template_context(email=email, email_error=None)
|
ctx = await get_template_context(email=email, email_error=None)
|
||||||
return await render_check_email_page(ctx), 200
|
return await render_check_email_page(ctx), 200
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -324,8 +324,8 @@ def register(url_prefix="/auth"):
|
|||||||
"Please try again in a moment."
|
"Please try again in a moment."
|
||||||
)
|
)
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_check_email_page
|
from sx.sx_components import render_check_email_page
|
||||||
ctx = await get_template_context(email=email, email_error=email_error)
|
ctx = await get_template_context(email=email, email_error=email_error)
|
||||||
return await render_check_email_page(ctx)
|
return await render_check_email_page(ctx)
|
||||||
|
|
||||||
@@ -340,15 +340,15 @@ def register(url_prefix="/auth"):
|
|||||||
user, error = await validate_magic_link(s, token)
|
user, error = await validate_magic_link(s, token)
|
||||||
|
|
||||||
if error:
|
if error:
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_login_page
|
from sx.sx_components import render_login_page
|
||||||
ctx = await get_template_context(error=error)
|
ctx = await get_template_context(error=error)
|
||||||
return await render_login_page(ctx), 400
|
return await render_login_page(ctx), 400
|
||||||
user_id = user.id
|
user_id = user.id
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_login_page
|
from sx.sx_components import render_login_page
|
||||||
ctx = await get_template_context(error="Could not sign you in right now. Please try again.")
|
ctx = await get_template_context(error="Could not sign you in right now. Please try again.")
|
||||||
return await render_login_page(ctx), 502
|
return await render_login_page(ctx), 502
|
||||||
|
|
||||||
@@ -679,8 +679,8 @@ def register(url_prefix="/auth"):
|
|||||||
@auth_bp.get("/device/")
|
@auth_bp.get("/device/")
|
||||||
async def device_form():
|
async def device_form():
|
||||||
"""Browser form where user enters the code displayed in terminal."""
|
"""Browser form where user enters the code displayed in terminal."""
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_device_page
|
from sx.sx_components import render_device_page
|
||||||
code = request.args.get("code", "")
|
code = request.args.get("code", "")
|
||||||
ctx = await get_template_context(code=code)
|
ctx = await get_template_context(code=code)
|
||||||
return await render_device_page(ctx)
|
return await render_device_page(ctx)
|
||||||
@@ -693,8 +693,8 @@ def register(url_prefix="/auth"):
|
|||||||
user_code = (form.get("code") or "").strip().replace("-", "").upper()
|
user_code = (form.get("code") or "").strip().replace("-", "").upper()
|
||||||
|
|
||||||
if not user_code or len(user_code) != 8:
|
if not user_code or len(user_code) != 8:
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_device_page
|
from sx.sx_components import render_device_page
|
||||||
ctx = await get_template_context(error="Please enter a valid 8-character code.", code=form.get("code", ""))
|
ctx = await get_template_context(error="Please enter a valid 8-character code.", code=form.get("code", ""))
|
||||||
return await render_device_page(ctx), 400
|
return await render_device_page(ctx), 400
|
||||||
|
|
||||||
@@ -703,8 +703,8 @@ def register(url_prefix="/auth"):
|
|||||||
r = await get_auth_redis()
|
r = await get_auth_redis()
|
||||||
device_code = await r.get(f"devflow_uc:{user_code}")
|
device_code = await r.get(f"devflow_uc:{user_code}")
|
||||||
if not device_code:
|
if not device_code:
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_device_page
|
from sx.sx_components import render_device_page
|
||||||
ctx = await get_template_context(error="Code not found or expired. Please try again.", code=form.get("code", ""))
|
ctx = await get_template_context(error="Code not found or expired. Please try again.", code=form.get("code", ""))
|
||||||
return await render_device_page(ctx), 400
|
return await render_device_page(ctx), 400
|
||||||
|
|
||||||
@@ -720,13 +720,13 @@ def register(url_prefix="/auth"):
|
|||||||
# Logged in — approve immediately
|
# Logged in — approve immediately
|
||||||
ok = await _approve_device(device_code, g.user)
|
ok = await _approve_device(device_code, g.user)
|
||||||
if not ok:
|
if not ok:
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_device_page
|
from sx.sx_components import render_device_page
|
||||||
ctx = await get_template_context(error="Code expired or already used.")
|
ctx = await get_template_context(error="Code expired or already used.")
|
||||||
return await render_device_page(ctx), 400
|
return await render_device_page(ctx), 400
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_device_approved_page
|
from sx.sx_components import render_device_approved_page
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
return await render_device_approved_page(ctx)
|
return await render_device_approved_page(ctx)
|
||||||
|
|
||||||
@@ -734,8 +734,8 @@ def register(url_prefix="/auth"):
|
|||||||
@auth_bp.get("/device/complete/")
|
@auth_bp.get("/device/complete/")
|
||||||
async def device_complete():
|
async def device_complete():
|
||||||
"""Post-login redirect — completes approval after magic link auth."""
|
"""Post-login redirect — completes approval after magic link auth."""
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_device_page, render_device_approved_page
|
from sx.sx_components import render_device_page, render_device_approved_page
|
||||||
|
|
||||||
device_code = request.args.get("code", "")
|
device_code = request.args.get("code", "")
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Account app fragment endpoints.
|
"""Account app fragment endpoints.
|
||||||
|
|
||||||
Exposes sexp fragments at ``/internal/fragments/<type>`` for consumption
|
Exposes sx fragments at ``/internal/fragments/<type>`` for consumption
|
||||||
by other coop apps via the fragment client.
|
by other coop apps via the fragment client.
|
||||||
|
|
||||||
Fragments:
|
Fragments:
|
||||||
@@ -18,15 +18,15 @@ def register():
|
|||||||
bp = Blueprint("fragments", __name__, url_prefix="/internal/fragments")
|
bp = Blueprint("fragments", __name__, url_prefix="/internal/fragments")
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# Fragment handlers — return sexp source text
|
# Fragment handlers — return sx source text
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
|
|
||||||
async def _auth_menu():
|
async def _auth_menu():
|
||||||
from shared.infrastructure.urls import account_url
|
from shared.infrastructure.urls import account_url
|
||||||
from shared.sexp.helpers import sexp_call
|
from shared.sx.helpers import sx_call
|
||||||
|
|
||||||
user_email = request.args.get("email", "")
|
user_email = request.args.get("email", "")
|
||||||
return sexp_call("auth-menu",
|
return sx_call("auth-menu",
|
||||||
user_email=user_email or None,
|
user_email=user_email or None,
|
||||||
account_url=account_url(""))
|
account_url=account_url(""))
|
||||||
|
|
||||||
@@ -47,8 +47,8 @@ def register():
|
|||||||
async def get_fragment(fragment_type: str):
|
async def get_fragment(fragment_type: str):
|
||||||
handler = _handlers.get(fragment_type)
|
handler = _handlers.get(fragment_type)
|
||||||
if handler is None:
|
if handler is None:
|
||||||
return Response("", status=200, content_type="text/sexp")
|
return Response("", status=200, content_type="text/sx")
|
||||||
src = await handler()
|
src = await handler()
|
||||||
return Response(src, status=200, content_type="text/sexp")
|
return Response(src, status=200, content_type="text/sx")
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ from __future__ import annotations
|
|||||||
import os
|
import os
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from shared.sexp.jinja_bridge import load_service_components
|
from shared.sx.jinja_bridge import load_service_components
|
||||||
from shared.sexp.helpers import (
|
from shared.sx.helpers import (
|
||||||
call_url, sexp_call, SexpExpr,
|
call_url, sx_call, SxExpr,
|
||||||
root_header_sexp, full_page_sexp, header_child_sexp, oob_page_sexp,
|
root_header_sx, full_page_sx, header_child_sx, oob_page_sx,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Load account-specific .sexpr components at import time
|
# Load account-specific .sx components at import time
|
||||||
load_service_components(os.path.dirname(os.path.dirname(__file__)))
|
load_service_components(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
|
||||||
|
|
||||||
@@ -23,10 +23,10 @@ load_service_components(os.path.dirname(os.path.dirname(__file__)))
|
|||||||
# Header helpers
|
# Header helpers
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _auth_nav_sexp(ctx: dict) -> str:
|
def _auth_nav_sx(ctx: dict) -> str:
|
||||||
"""Auth section desktop nav items."""
|
"""Auth section desktop nav items."""
|
||||||
parts = [
|
parts = [
|
||||||
sexp_call("nav-link",
|
sx_call("nav-link",
|
||||||
href=call_url(ctx, "account_url", "/newsletters/"),
|
href=call_url(ctx, "account_url", "/newsletters/"),
|
||||||
label="newsletters",
|
label="newsletters",
|
||||||
select_colours=ctx.get("select_colours", ""),
|
select_colours=ctx.get("select_colours", ""),
|
||||||
@@ -38,22 +38,22 @@ def _auth_nav_sexp(ctx: dict) -> str:
|
|||||||
return "(<> " + " ".join(parts) + ")"
|
return "(<> " + " ".join(parts) + ")"
|
||||||
|
|
||||||
|
|
||||||
def _auth_header_sexp(ctx: dict, *, oob: bool = False) -> str:
|
def _auth_header_sx(ctx: dict, *, oob: bool = False) -> str:
|
||||||
"""Build the account section header row."""
|
"""Build the account section header row."""
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"menu-row-sx",
|
"menu-row-sx",
|
||||||
id="auth-row", level=1, colour="sky",
|
id="auth-row", level=1, colour="sky",
|
||||||
link_href=call_url(ctx, "account_url", "/"),
|
link_href=call_url(ctx, "account_url", "/"),
|
||||||
link_label="account", icon="fa-solid fa-user",
|
link_label="account", icon="fa-solid fa-user",
|
||||||
nav=SexpExpr(_auth_nav_sexp(ctx)),
|
nav=SxExpr(_auth_nav_sx(ctx)),
|
||||||
child_id="auth-header-child", oob=oob,
|
child_id="auth-header-child", oob=oob,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _auth_nav_mobile_sexp(ctx: dict) -> str:
|
def _auth_nav_mobile_sx(ctx: dict) -> str:
|
||||||
"""Mobile nav menu for auth section."""
|
"""Mobile nav menu for auth section."""
|
||||||
parts = [
|
parts = [
|
||||||
sexp_call("nav-link",
|
sx_call("nav-link",
|
||||||
href=call_url(ctx, "account_url", "/newsletters/"),
|
href=call_url(ctx, "account_url", "/newsletters/"),
|
||||||
label="newsletters",
|
label="newsletters",
|
||||||
select_colours=ctx.get("select_colours", ""),
|
select_colours=ctx.get("select_colours", ""),
|
||||||
@@ -69,7 +69,7 @@ def _auth_nav_mobile_sexp(ctx: dict) -> str:
|
|||||||
# Account dashboard (GET /)
|
# Account dashboard (GET /)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _account_main_panel_sexp(ctx: dict) -> str:
|
def _account_main_panel_sx(ctx: dict) -> str:
|
||||||
"""Account info panel with user details and logout."""
|
"""Account info panel with user details and logout."""
|
||||||
from quart import g
|
from quart import g
|
||||||
from shared.browser.app.csrf import generate_csrf_token
|
from shared.browser.app.csrf import generate_csrf_token
|
||||||
@@ -77,33 +77,33 @@ def _account_main_panel_sexp(ctx: dict) -> str:
|
|||||||
user = getattr(g, "user", None)
|
user = getattr(g, "user", None)
|
||||||
error = ctx.get("error", "")
|
error = ctx.get("error", "")
|
||||||
|
|
||||||
error_sexp = sexp_call("account-error-banner", error=error) if error else ""
|
error_sx = sx_call("account-error-banner", error=error) if error else ""
|
||||||
|
|
||||||
user_email_sexp = ""
|
user_email_sx = ""
|
||||||
user_name_sexp = ""
|
user_name_sx = ""
|
||||||
if user:
|
if user:
|
||||||
user_email_sexp = sexp_call("account-user-email", email=user.email)
|
user_email_sx = sx_call("account-user-email", email=user.email)
|
||||||
if user.name:
|
if user.name:
|
||||||
user_name_sexp = sexp_call("account-user-name", name=user.name)
|
user_name_sx = sx_call("account-user-name", name=user.name)
|
||||||
|
|
||||||
logout_sexp = sexp_call("account-logout-form", csrf_token=generate_csrf_token())
|
logout_sx = sx_call("account-logout-form", csrf_token=generate_csrf_token())
|
||||||
|
|
||||||
labels_sexp = ""
|
labels_sx = ""
|
||||||
if user and hasattr(user, "labels") and user.labels:
|
if user and hasattr(user, "labels") and user.labels:
|
||||||
label_items = " ".join(
|
label_items = " ".join(
|
||||||
sexp_call("account-label-item", name=label.name)
|
sx_call("account-label-item", name=label.name)
|
||||||
for label in user.labels
|
for label in user.labels
|
||||||
)
|
)
|
||||||
labels_sexp = sexp_call("account-labels-section",
|
labels_sx = sx_call("account-labels-section",
|
||||||
items=SexpExpr("(<> " + label_items + ")"))
|
items=SxExpr("(<> " + label_items + ")"))
|
||||||
|
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"account-main-panel",
|
"account-main-panel",
|
||||||
error=SexpExpr(error_sexp) if error_sexp else None,
|
error=SxExpr(error_sx) if error_sx else None,
|
||||||
email=SexpExpr(user_email_sexp) if user_email_sexp else None,
|
email=SxExpr(user_email_sx) if user_email_sx else None,
|
||||||
name=SexpExpr(user_name_sexp) if user_name_sexp else None,
|
name=SxExpr(user_name_sx) if user_name_sx else None,
|
||||||
logout=SexpExpr(logout_sexp),
|
logout=SxExpr(logout_sx),
|
||||||
labels=SexpExpr(labels_sexp) if labels_sexp else None,
|
labels=SxExpr(labels_sx) if labels_sx else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ def _account_main_panel_sexp(ctx: dict) -> str:
|
|||||||
# Newsletters (GET /newsletters/)
|
# Newsletters (GET /newsletters/)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _newsletter_toggle_sexp(un: Any, account_url_fn: Any, csrf_token: str) -> str:
|
def _newsletter_toggle_sx(un: Any, account_url_fn: Any, csrf_token: str) -> str:
|
||||||
"""Render a single newsletter toggle switch."""
|
"""Render a single newsletter toggle switch."""
|
||||||
nid = un.newsletter_id
|
nid = un.newsletter_id
|
||||||
toggle_url = account_url_fn(f"/newsletter/{nid}/toggle/")
|
toggle_url = account_url_fn(f"/newsletter/{nid}/toggle/")
|
||||||
@@ -123,7 +123,7 @@ def _newsletter_toggle_sexp(un: Any, account_url_fn: Any, csrf_token: str) -> st
|
|||||||
bg = "bg-stone-300"
|
bg = "bg-stone-300"
|
||||||
translate = "translate-x-1"
|
translate = "translate-x-1"
|
||||||
checked = "false"
|
checked = "false"
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"account-newsletter-toggle",
|
"account-newsletter-toggle",
|
||||||
id=f"nl-{nid}", url=toggle_url,
|
id=f"nl-{nid}", url=toggle_url,
|
||||||
hdrs=f'{{"X-CSRFToken": "{csrf_token}"}}',
|
hdrs=f'{{"X-CSRFToken": "{csrf_token}"}}',
|
||||||
@@ -134,9 +134,9 @@ def _newsletter_toggle_sexp(un: Any, account_url_fn: Any, csrf_token: str) -> st
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _newsletter_toggle_off_sexp(nid: int, toggle_url: str, csrf_token: str) -> str:
|
def _newsletter_toggle_off_sx(nid: int, toggle_url: str, csrf_token: str) -> str:
|
||||||
"""Render an unsubscribed newsletter toggle (no subscription record yet)."""
|
"""Render an unsubscribed newsletter toggle (no subscription record yet)."""
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"account-newsletter-toggle-off",
|
"account-newsletter-toggle-off",
|
||||||
id=f"nl-{nid}", url=toggle_url,
|
id=f"nl-{nid}", url=toggle_url,
|
||||||
hdrs=f'{{"X-CSRFToken": "{csrf_token}"}}',
|
hdrs=f'{{"X-CSRFToken": "{csrf_token}"}}',
|
||||||
@@ -144,7 +144,7 @@ def _newsletter_toggle_off_sexp(nid: int, toggle_url: str, csrf_token: str) -> s
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _newsletters_panel_sexp(ctx: dict, newsletter_list: list) -> str:
|
def _newsletters_panel_sx(ctx: dict, newsletter_list: list) -> str:
|
||||||
"""Newsletters management panel."""
|
"""Newsletters management panel."""
|
||||||
from shared.browser.app.csrf import generate_csrf_token
|
from shared.browser.app.csrf import generate_csrf_token
|
||||||
|
|
||||||
@@ -157,30 +157,30 @@ def _newsletters_panel_sexp(ctx: dict, newsletter_list: list) -> str:
|
|||||||
nl = item["newsletter"]
|
nl = item["newsletter"]
|
||||||
un = item.get("un")
|
un = item.get("un")
|
||||||
|
|
||||||
desc_sexp = sexp_call(
|
desc_sx = sx_call(
|
||||||
"account-newsletter-desc", description=nl.description
|
"account-newsletter-desc", description=nl.description
|
||||||
) if nl.description else ""
|
) if nl.description else ""
|
||||||
|
|
||||||
if un:
|
if un:
|
||||||
toggle = _newsletter_toggle_sexp(un, account_url_fn, csrf)
|
toggle = _newsletter_toggle_sx(un, account_url_fn, csrf)
|
||||||
else:
|
else:
|
||||||
toggle_url = account_url_fn(f"/newsletter/{nl.id}/toggle/")
|
toggle_url = account_url_fn(f"/newsletter/{nl.id}/toggle/")
|
||||||
toggle = _newsletter_toggle_off_sexp(nl.id, toggle_url, csrf)
|
toggle = _newsletter_toggle_off_sx(nl.id, toggle_url, csrf)
|
||||||
|
|
||||||
items.append(sexp_call(
|
items.append(sx_call(
|
||||||
"account-newsletter-item",
|
"account-newsletter-item",
|
||||||
name=nl.name,
|
name=nl.name,
|
||||||
desc=SexpExpr(desc_sexp) if desc_sexp else None,
|
desc=SxExpr(desc_sx) if desc_sx else None,
|
||||||
toggle=SexpExpr(toggle),
|
toggle=SxExpr(toggle),
|
||||||
))
|
))
|
||||||
list_sexp = sexp_call(
|
list_sx = sx_call(
|
||||||
"account-newsletter-list",
|
"account-newsletter-list",
|
||||||
items=SexpExpr("(<> " + " ".join(items) + ")"),
|
items=SxExpr("(<> " + " ".join(items) + ")"),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
list_sexp = sexp_call("account-newsletter-empty")
|
list_sx = sx_call("account-newsletter-empty")
|
||||||
|
|
||||||
return sexp_call("account-newsletters-panel", list=SexpExpr(list_sexp))
|
return sx_call("account-newsletters-panel", list=SxExpr(list_sx))
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -196,11 +196,11 @@ def _login_page_content(ctx: dict) -> str:
|
|||||||
email = ctx.get("email", "")
|
email = ctx.get("email", "")
|
||||||
action = url_for("auth.start_login")
|
action = url_for("auth.start_login")
|
||||||
|
|
||||||
error_sexp = sexp_call("account-login-error", error=error) if error else ""
|
error_sx = sx_call("account-login-error", error=error) if error else ""
|
||||||
|
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"account-login-form",
|
"account-login-form",
|
||||||
error=SexpExpr(error_sexp) if error_sexp else None,
|
error=SxExpr(error_sx) if error_sx else None,
|
||||||
action=action,
|
action=action,
|
||||||
csrf_token=generate_csrf_token(), email=email,
|
csrf_token=generate_csrf_token(), email=email,
|
||||||
)
|
)
|
||||||
@@ -215,11 +215,11 @@ def _device_page_content(ctx: dict) -> str:
|
|||||||
code = ctx.get("code", "")
|
code = ctx.get("code", "")
|
||||||
action = url_for("auth.device_submit")
|
action = url_for("auth.device_submit")
|
||||||
|
|
||||||
error_sexp = sexp_call("account-device-error", error=error) if error else ""
|
error_sx = sx_call("account-device-error", error=error) if error else ""
|
||||||
|
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"account-device-form",
|
"account-device-form",
|
||||||
error=SexpExpr(error_sexp) if error_sexp else None,
|
error=SxExpr(error_sx) if error_sx else None,
|
||||||
action=action,
|
action=action,
|
||||||
csrf_token=generate_csrf_token(), code=code,
|
csrf_token=generate_csrf_token(), code=code,
|
||||||
)
|
)
|
||||||
@@ -227,7 +227,7 @@ def _device_page_content(ctx: dict) -> str:
|
|||||||
|
|
||||||
def _device_approved_content() -> str:
|
def _device_approved_content() -> str:
|
||||||
"""Device approved success content."""
|
"""Device approved success content."""
|
||||||
return sexp_call("account-device-approved")
|
return sx_call("account-device-approved")
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -236,26 +236,26 @@ def _device_approved_content() -> str:
|
|||||||
|
|
||||||
async def render_account_page(ctx: dict) -> str:
|
async def render_account_page(ctx: dict) -> str:
|
||||||
"""Full page: account dashboard."""
|
"""Full page: account dashboard."""
|
||||||
main = _account_main_panel_sexp(ctx)
|
main = _account_main_panel_sx(ctx)
|
||||||
|
|
||||||
hdr = root_header_sexp(ctx)
|
hdr = root_header_sx(ctx)
|
||||||
hdr_child = header_child_sexp(_auth_header_sexp(ctx))
|
hdr_child = header_child_sx(_auth_header_sx(ctx))
|
||||||
header_rows = "(<> " + hdr + " " + hdr_child + ")"
|
header_rows = "(<> " + hdr + " " + hdr_child + ")"
|
||||||
|
|
||||||
return full_page_sexp(ctx, header_rows=header_rows,
|
return full_page_sx(ctx, header_rows=header_rows,
|
||||||
content=main,
|
content=main,
|
||||||
menu=_auth_nav_mobile_sexp(ctx))
|
menu=_auth_nav_mobile_sx(ctx))
|
||||||
|
|
||||||
|
|
||||||
async def render_account_oob(ctx: dict) -> str:
|
async def render_account_oob(ctx: dict) -> str:
|
||||||
"""OOB response for account dashboard."""
|
"""OOB response for account dashboard."""
|
||||||
main = _account_main_panel_sexp(ctx)
|
main = _account_main_panel_sx(ctx)
|
||||||
|
|
||||||
oobs = "(<> " + _auth_header_sexp(ctx, oob=True) + " " + root_header_sexp(ctx, oob=True) + ")"
|
oobs = "(<> " + _auth_header_sx(ctx, oob=True) + " " + root_header_sx(ctx, oob=True) + ")"
|
||||||
|
|
||||||
return oob_page_sexp(oobs=oobs,
|
return oob_page_sx(oobs=oobs,
|
||||||
content=main,
|
content=main,
|
||||||
menu=_auth_nav_mobile_sexp(ctx))
|
menu=_auth_nav_mobile_sx(ctx))
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -264,26 +264,26 @@ async def render_account_oob(ctx: dict) -> str:
|
|||||||
|
|
||||||
async def render_newsletters_page(ctx: dict, newsletter_list: list) -> str:
|
async def render_newsletters_page(ctx: dict, newsletter_list: list) -> str:
|
||||||
"""Full page: newsletters."""
|
"""Full page: newsletters."""
|
||||||
main = _newsletters_panel_sexp(ctx, newsletter_list)
|
main = _newsletters_panel_sx(ctx, newsletter_list)
|
||||||
|
|
||||||
hdr = root_header_sexp(ctx)
|
hdr = root_header_sx(ctx)
|
||||||
hdr_child = header_child_sexp(_auth_header_sexp(ctx))
|
hdr_child = header_child_sx(_auth_header_sx(ctx))
|
||||||
header_rows = "(<> " + hdr + " " + hdr_child + ")"
|
header_rows = "(<> " + hdr + " " + hdr_child + ")"
|
||||||
|
|
||||||
return full_page_sexp(ctx, header_rows=header_rows,
|
return full_page_sx(ctx, header_rows=header_rows,
|
||||||
content=main,
|
content=main,
|
||||||
menu=_auth_nav_mobile_sexp(ctx))
|
menu=_auth_nav_mobile_sx(ctx))
|
||||||
|
|
||||||
|
|
||||||
async def render_newsletters_oob(ctx: dict, newsletter_list: list) -> str:
|
async def render_newsletters_oob(ctx: dict, newsletter_list: list) -> str:
|
||||||
"""OOB response for newsletters."""
|
"""OOB response for newsletters."""
|
||||||
main = _newsletters_panel_sexp(ctx, newsletter_list)
|
main = _newsletters_panel_sx(ctx, newsletter_list)
|
||||||
|
|
||||||
oobs = "(<> " + _auth_header_sexp(ctx, oob=True) + " " + root_header_sexp(ctx, oob=True) + ")"
|
oobs = "(<> " + _auth_header_sx(ctx, oob=True) + " " + root_header_sx(ctx, oob=True) + ")"
|
||||||
|
|
||||||
return oob_page_sexp(oobs=oobs,
|
return oob_page_sx(oobs=oobs,
|
||||||
content=main,
|
content=main,
|
||||||
menu=_auth_nav_mobile_sexp(ctx))
|
menu=_auth_nav_mobile_sx(ctx))
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -292,22 +292,22 @@ async def render_newsletters_oob(ctx: dict, newsletter_list: list) -> str:
|
|||||||
|
|
||||||
async def render_fragment_page(ctx: dict, page_fragment_html: str) -> str:
|
async def render_fragment_page(ctx: dict, page_fragment_html: str) -> str:
|
||||||
"""Full page: fragment-provided content."""
|
"""Full page: fragment-provided content."""
|
||||||
hdr = root_header_sexp(ctx)
|
hdr = root_header_sx(ctx)
|
||||||
hdr_child = header_child_sexp(_auth_header_sexp(ctx))
|
hdr_child = header_child_sx(_auth_header_sx(ctx))
|
||||||
header_rows = "(<> " + hdr + " " + hdr_child + ")"
|
header_rows = "(<> " + hdr + " " + hdr_child + ")"
|
||||||
|
|
||||||
return full_page_sexp(ctx, header_rows=header_rows,
|
return full_page_sx(ctx, header_rows=header_rows,
|
||||||
content=f'(~rich-text :html "{_sexp_escape(page_fragment_html)}")',
|
content=f'(~rich-text :html "{_sx_escape(page_fragment_html)}")',
|
||||||
menu=_auth_nav_mobile_sexp(ctx))
|
menu=_auth_nav_mobile_sx(ctx))
|
||||||
|
|
||||||
|
|
||||||
async def render_fragment_oob(ctx: dict, page_fragment_html: str) -> str:
|
async def render_fragment_oob(ctx: dict, page_fragment_html: str) -> str:
|
||||||
"""OOB response for fragment pages."""
|
"""OOB response for fragment pages."""
|
||||||
oobs = "(<> " + _auth_header_sexp(ctx, oob=True) + " " + root_header_sexp(ctx, oob=True) + ")"
|
oobs = "(<> " + _auth_header_sx(ctx, oob=True) + " " + root_header_sx(ctx, oob=True) + ")"
|
||||||
|
|
||||||
return oob_page_sexp(oobs=oobs,
|
return oob_page_sx(oobs=oobs,
|
||||||
content=f'(~rich-text :html "{_sexp_escape(page_fragment_html)}")',
|
content=f'(~rich-text :html "{_sx_escape(page_fragment_html)}")',
|
||||||
menu=_auth_nav_mobile_sexp(ctx))
|
menu=_auth_nav_mobile_sx(ctx))
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -316,24 +316,24 @@ async def render_fragment_oob(ctx: dict, page_fragment_html: str) -> str:
|
|||||||
|
|
||||||
async def render_login_page(ctx: dict) -> str:
|
async def render_login_page(ctx: dict) -> str:
|
||||||
"""Full page: login form."""
|
"""Full page: login form."""
|
||||||
hdr = root_header_sexp(ctx)
|
hdr = root_header_sx(ctx)
|
||||||
return full_page_sexp(ctx, header_rows=hdr,
|
return full_page_sx(ctx, header_rows=hdr,
|
||||||
content=_login_page_content(ctx),
|
content=_login_page_content(ctx),
|
||||||
meta_html='<title>Login \u2014 Rose Ash</title>')
|
meta_html='<title>Login \u2014 Rose Ash</title>')
|
||||||
|
|
||||||
|
|
||||||
async def render_device_page(ctx: dict) -> str:
|
async def render_device_page(ctx: dict) -> str:
|
||||||
"""Full page: device authorization form."""
|
"""Full page: device authorization form."""
|
||||||
hdr = root_header_sexp(ctx)
|
hdr = root_header_sx(ctx)
|
||||||
return full_page_sexp(ctx, header_rows=hdr,
|
return full_page_sx(ctx, header_rows=hdr,
|
||||||
content=_device_page_content(ctx),
|
content=_device_page_content(ctx),
|
||||||
meta_html='<title>Authorize Device \u2014 Rose Ash</title>')
|
meta_html='<title>Authorize Device \u2014 Rose Ash</title>')
|
||||||
|
|
||||||
|
|
||||||
async def render_device_approved_page(ctx: dict) -> str:
|
async def render_device_approved_page(ctx: dict) -> str:
|
||||||
"""Full page: device approved."""
|
"""Full page: device approved."""
|
||||||
hdr = root_header_sexp(ctx)
|
hdr = root_header_sx(ctx)
|
||||||
return full_page_sexp(ctx, header_rows=hdr,
|
return full_page_sx(ctx, header_rows=hdr,
|
||||||
content=_device_approved_content(),
|
content=_device_approved_content(),
|
||||||
meta_html='<title>Device Authorized \u2014 Rose Ash</title>')
|
meta_html='<title>Device Authorized \u2014 Rose Ash</title>')
|
||||||
|
|
||||||
@@ -346,14 +346,14 @@ def _check_email_content(email: str, email_error: str | None = None) -> str:
|
|||||||
"""Check email confirmation content."""
|
"""Check email confirmation content."""
|
||||||
from markupsafe import escape
|
from markupsafe import escape
|
||||||
|
|
||||||
error_sexp = sexp_call(
|
error_sx = sx_call(
|
||||||
"account-check-email-error", error=str(escape(email_error))
|
"account-check-email-error", error=str(escape(email_error))
|
||||||
) if email_error else ""
|
) if email_error else ""
|
||||||
|
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"account-check-email",
|
"account-check-email",
|
||||||
email=str(escape(email)),
|
email=str(escape(email)),
|
||||||
error=SexpExpr(error_sexp) if error_sexp else None,
|
error=SxExpr(error_sx) if error_sx else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -361,8 +361,8 @@ async def render_check_email_page(ctx: dict) -> str:
|
|||||||
"""Full page: check email after magic link sent."""
|
"""Full page: check email after magic link sent."""
|
||||||
email = ctx.get("email", "")
|
email = ctx.get("email", "")
|
||||||
email_error = ctx.get("email_error")
|
email_error = ctx.get("email_error")
|
||||||
hdr = root_header_sexp(ctx)
|
hdr = root_header_sx(ctx)
|
||||||
return full_page_sexp(ctx, header_rows=hdr,
|
return full_page_sx(ctx, header_rows=hdr,
|
||||||
content=_check_email_content(email, email_error),
|
content=_check_email_content(email, email_error),
|
||||||
meta_html='<title>Check your email \u2014 Rose Ash</title>')
|
meta_html='<title>Check your email \u2014 Rose Ash</title>')
|
||||||
|
|
||||||
@@ -380,13 +380,13 @@ def render_newsletter_toggle(un) -> str:
|
|||||||
if account_url_fn is None:
|
if account_url_fn is None:
|
||||||
from shared.infrastructure.urls import account_url
|
from shared.infrastructure.urls import account_url
|
||||||
account_url_fn = account_url
|
account_url_fn = account_url
|
||||||
return _newsletter_toggle_sexp(un, account_url_fn, generate_csrf_token())
|
return _newsletter_toggle_sx(un, account_url_fn, generate_csrf_token())
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Internal helpers
|
# Internal helpers
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _sexp_escape(s: str) -> str:
|
def _sx_escape(s: str) -> str:
|
||||||
"""Escape a string for embedding in sexp string literals."""
|
"""Escape a string for embedding in sx string literals."""
|
||||||
return s.replace("\\", "\\\\").replace('"', '\\"')
|
return s.replace("\\", "\\\\").replace('"', '\\"')
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import path_setup # noqa: F401 # adds shared/ to sys.path
|
import path_setup # noqa: F401 # adds shared/ to sys.path
|
||||||
import sexp.sexp_components as sexp_components # noqa: F401 # ensure Hypercorn --reload watches this file
|
import sx.sx_components as sx_components # noqa: F401 # ensure Hypercorn --reload watches this file
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from quart import g, request
|
from quart import g, request
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from quart import (
|
|||||||
from shared.browser.app.redis_cacher import clear_all_cache
|
from shared.browser.app.redis_cacher import clear_all_cache
|
||||||
from shared.browser.app.authz import require_admin
|
from shared.browser.app.authz import require_admin
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
from shared.config import config
|
from shared.config import config
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
@@ -30,30 +30,30 @@ def register(url_prefix):
|
|||||||
@bp.get("/")
|
@bp.get("/")
|
||||||
@require_admin
|
@require_admin
|
||||||
async def home():
|
async def home():
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_settings_page, render_settings_oob
|
from sx.sx_components import render_settings_page, render_settings_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
html = await render_settings_page(tctx)
|
html = await render_settings_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_settings_oob(tctx)
|
sx_src = await render_settings_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.get("/cache/")
|
@bp.get("/cache/")
|
||||||
@require_admin
|
@require_admin
|
||||||
async def cache():
|
async def cache():
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_cache_page, render_cache_oob
|
from sx.sx_components import render_cache_page, render_cache_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
html = await render_cache_page(tctx)
|
html = await render_cache_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_cache_oob(tctx)
|
sx_src = await render_cache_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.post("/cache_clear/")
|
@bp.post("/cache_clear/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -61,9 +61,9 @@ def register(url_prefix):
|
|||||||
await clear_all_cache()
|
await clear_all_cache()
|
||||||
if is_htmx_request():
|
if is_htmx_request():
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
from shared.sexp.jinja_bridge import render as render_comp
|
from shared.sx.jinja_bridge import render as render_comp
|
||||||
html = render_comp("cache-cleared", time_str=now.strftime("%H:%M:%S"))
|
html = render_comp("cache-cleared", time_str=now.strftime("%H:%M:%S"))
|
||||||
return sexp_response(html)
|
return sx_response(html)
|
||||||
|
|
||||||
return redirect(url_for("settings.cache"))
|
return redirect(url_for("settings.cache"))
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from sqlalchemy import select, delete
|
|||||||
from shared.browser.app.authz import require_admin
|
from shared.browser.app.authz import require_admin
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.browser.app.redis_cacher import invalidate_tag_cache
|
from shared.browser.app.redis_cacher import invalidate_tag_cache
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
from models.tag_group import TagGroup, TagGroupTag
|
from models.tag_group import TagGroup, TagGroupTag
|
||||||
from models.ghost_content import Tag
|
from models.ghost_content import Tag
|
||||||
@@ -58,15 +58,15 @@ def register():
|
|||||||
|
|
||||||
ctx = {"groups": groups, "unassigned_tags": unassigned}
|
ctx = {"groups": groups, "unassigned_tags": unassigned}
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_tag_groups_page, render_tag_groups_oob
|
from sx.sx_components import render_tag_groups_page, render_tag_groups_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx.update(ctx)
|
tctx.update(ctx)
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
return await make_response(await render_tag_groups_page(tctx))
|
return await make_response(await render_tag_groups_page(tctx))
|
||||||
else:
|
else:
|
||||||
return sexp_response(await render_tag_groups_oob(tctx))
|
return sx_response(await render_tag_groups_oob(tctx))
|
||||||
|
|
||||||
@bp.post("/")
|
@bp.post("/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -123,15 +123,15 @@ def register():
|
|||||||
"assigned_tag_ids": assigned_tag_ids,
|
"assigned_tag_ids": assigned_tag_ids,
|
||||||
}
|
}
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_tag_group_edit_page, render_tag_group_edit_oob
|
from sx.sx_components import render_tag_group_edit_page, render_tag_group_edit_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx.update(ctx)
|
tctx.update(ctx)
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
return await make_response(await render_tag_group_edit_page(tctx))
|
return await make_response(await render_tag_group_edit_page(tctx))
|
||||||
else:
|
else:
|
||||||
return sexp_response(await render_tag_group_edit_oob(tctx))
|
return sx_response(await render_tag_group_edit_oob(tctx))
|
||||||
|
|
||||||
@bp.post("/<int:id>/")
|
@bp.post("/<int:id>/")
|
||||||
@require_admin
|
@require_admin
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from .services.pages_data import pages_data
|
|||||||
from shared.browser.app.redis_cacher import cache_page, invalidate_tag_cache
|
from shared.browser.app.redis_cacher import cache_page, invalidate_tag_cache
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.browser.app.authz import require_admin
|
from shared.browser.app.authz import require_admin
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
from shared.utils import host_url
|
from shared.utils import host_url
|
||||||
|
|
||||||
def register(url_prefix, title):
|
def register(url_prefix, title):
|
||||||
@@ -143,8 +143,8 @@ def register(url_prefix, title):
|
|||||||
ctx["page_cart_count"] = page_summary.count + page_summary.calendar_count + page_summary.ticket_count
|
ctx["page_cart_count"] = page_summary.count + page_summary.calendar_count + page_summary.ticket_count
|
||||||
ctx["page_cart_total"] = float(page_summary.total + page_summary.calendar_total + page_summary.ticket_total)
|
ctx["page_cart_total"] = float(page_summary.total + page_summary.calendar_total + page_summary.ticket_total)
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_home_page, render_home_oob
|
from sx.sx_components import render_home_page, render_home_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx.update(ctx)
|
tctx.update(ctx)
|
||||||
@@ -152,8 +152,8 @@ def register(url_prefix, title):
|
|||||||
html = await render_home_page(tctx)
|
html = await render_home_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_home_oob(tctx)
|
sx_src = await render_home_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@blogs_bp.get("/index")
|
@blogs_bp.get("/index")
|
||||||
@blogs_bp.get("/index/")
|
@blogs_bp.get("/index/")
|
||||||
@@ -181,8 +181,8 @@ def register(url_prefix, title):
|
|||||||
"tag_groups": [],
|
"tag_groups": [],
|
||||||
"posts": data.get("pages", []),
|
"posts": data.get("pages", []),
|
||||||
}
|
}
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_blog_page, render_blog_oob, render_blog_page_cards
|
from sx.sx_components import render_blog_page, render_blog_oob, render_blog_page_cards
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx.update(context)
|
tctx.update(context)
|
||||||
@@ -190,11 +190,11 @@ def register(url_prefix, title):
|
|||||||
html = await render_blog_page(tctx)
|
html = await render_blog_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
elif q.page > 1:
|
elif q.page > 1:
|
||||||
sexp_src = await render_blog_page_cards(tctx)
|
sx_src = await render_blog_page_cards(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_blog_oob(tctx)
|
sx_src = await render_blog_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
# Default: posts listing
|
# Default: posts listing
|
||||||
# Drafts filter requires login; ignore if not logged in
|
# Drafts filter requires login; ignore if not logged in
|
||||||
@@ -224,8 +224,8 @@ def register(url_prefix, title):
|
|||||||
"drafts": q.drafts if show_drafts else None,
|
"drafts": q.drafts if show_drafts else None,
|
||||||
}
|
}
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_blog_page, render_blog_oob, render_blog_cards
|
from sx.sx_components import render_blog_page, render_blog_oob, render_blog_cards
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx.update(context)
|
tctx.update(context)
|
||||||
@@ -233,18 +233,18 @@ def register(url_prefix, title):
|
|||||||
html = await render_blog_page(tctx)
|
html = await render_blog_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
elif q.page > 1:
|
elif q.page > 1:
|
||||||
# Sexp wire format — client renders blog cards
|
# Sx wire format — client renders blog cards
|
||||||
sexp_src = await render_blog_cards(tctx)
|
sx_src = await render_blog_cards(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_blog_oob(tctx)
|
sx_src = await render_blog_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@blogs_bp.get("/new/")
|
@blogs_bp.get("/new/")
|
||||||
@require_admin
|
@require_admin
|
||||||
async def new_post():
|
async def new_post():
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_new_post_page, render_new_post_oob, render_editor_panel
|
from sx.sx_components import render_new_post_page, render_new_post_oob, render_editor_panel
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx["editor_html"] = render_editor_panel()
|
tctx["editor_html"] = render_editor_panel()
|
||||||
@@ -252,8 +252,8 @@ def register(url_prefix, title):
|
|||||||
html = await render_new_post_page(tctx)
|
html = await render_new_post_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_new_post_oob(tctx)
|
sx_src = await render_new_post_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@blogs_bp.post("/new/")
|
@blogs_bp.post("/new/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -274,8 +274,8 @@ def register(url_prefix, title):
|
|||||||
try:
|
try:
|
||||||
lexical_doc = json.loads(lexical_raw)
|
lexical_doc = json.loads(lexical_raw)
|
||||||
except (json.JSONDecodeError, TypeError):
|
except (json.JSONDecodeError, TypeError):
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_new_post_page, render_editor_panel
|
from sx.sx_components import render_new_post_page, render_editor_panel
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx["editor_html"] = render_editor_panel(save_error="Invalid JSON in editor content.")
|
tctx["editor_html"] = render_editor_panel(save_error="Invalid JSON in editor content.")
|
||||||
html = await render_new_post_page(tctx)
|
html = await render_new_post_page(tctx)
|
||||||
@@ -283,8 +283,8 @@ def register(url_prefix, title):
|
|||||||
|
|
||||||
ok, reason = validate_lexical(lexical_doc)
|
ok, reason = validate_lexical(lexical_doc)
|
||||||
if not ok:
|
if not ok:
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_new_post_page, render_editor_panel
|
from sx.sx_components import render_new_post_page, render_editor_panel
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx["editor_html"] = render_editor_panel(save_error=reason)
|
tctx["editor_html"] = render_editor_panel(save_error=reason)
|
||||||
html = await render_new_post_page(tctx)
|
html = await render_new_post_page(tctx)
|
||||||
@@ -324,8 +324,8 @@ def register(url_prefix, title):
|
|||||||
@blogs_bp.get("/new-page/")
|
@blogs_bp.get("/new-page/")
|
||||||
@require_admin
|
@require_admin
|
||||||
async def new_page():
|
async def new_page():
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_new_post_page, render_new_post_oob, render_editor_panel
|
from sx.sx_components import render_new_post_page, render_new_post_oob, render_editor_panel
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx["editor_html"] = render_editor_panel(is_page=True)
|
tctx["editor_html"] = render_editor_panel(is_page=True)
|
||||||
@@ -334,8 +334,8 @@ def register(url_prefix, title):
|
|||||||
html = await render_new_post_page(tctx)
|
html = await render_new_post_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_new_post_oob(tctx)
|
sx_src = await render_new_post_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@blogs_bp.post("/new-page/")
|
@blogs_bp.post("/new-page/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -356,8 +356,8 @@ def register(url_prefix, title):
|
|||||||
try:
|
try:
|
||||||
lexical_doc = json.loads(lexical_raw)
|
lexical_doc = json.loads(lexical_raw)
|
||||||
except (json.JSONDecodeError, TypeError):
|
except (json.JSONDecodeError, TypeError):
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_new_post_page, render_editor_panel
|
from sx.sx_components import render_new_post_page, render_editor_panel
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx["editor_html"] = render_editor_panel(save_error="Invalid JSON in editor content.", is_page=True)
|
tctx["editor_html"] = render_editor_panel(save_error="Invalid JSON in editor content.", is_page=True)
|
||||||
tctx["is_page"] = True
|
tctx["is_page"] = True
|
||||||
@@ -366,8 +366,8 @@ def register(url_prefix, title):
|
|||||||
|
|
||||||
ok, reason = validate_lexical(lexical_doc)
|
ok, reason = validate_lexical(lexical_doc)
|
||||||
if not ok:
|
if not ok:
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_new_post_page, render_editor_panel
|
from sx.sx_components import render_new_post_page, render_editor_panel
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx["editor_html"] = render_editor_panel(save_error=reason, is_page=True)
|
tctx["editor_html"] = render_editor_panel(save_error=reason, is_page=True)
|
||||||
tctx["is_page"] = True
|
tctx["is_page"] = True
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Blog app fragment endpoints.
|
"""Blog app fragment endpoints.
|
||||||
|
|
||||||
Exposes sexp fragments at ``/internal/fragments/<type>`` for consumption
|
Exposes sx fragments at ``/internal/fragments/<type>`` for consumption
|
||||||
by other coop apps via the fragment client.
|
by other coop apps via the fragment client.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -26,13 +26,13 @@ def register():
|
|||||||
async def get_fragment(fragment_type: str):
|
async def get_fragment(fragment_type: str):
|
||||||
handler = _handlers.get(fragment_type)
|
handler = _handlers.get(fragment_type)
|
||||||
if handler is None:
|
if handler is None:
|
||||||
return Response("", status=200, content_type="text/sexp")
|
return Response("", status=200, content_type="text/sx")
|
||||||
result = await handler()
|
result = await handler()
|
||||||
return Response(result, status=200, content_type="text/sexp")
|
return Response(result, status=200, content_type="text/sx")
|
||||||
|
|
||||||
# --- nav-tree fragment — returns sexp source ---
|
# --- nav-tree fragment — returns sx source ---
|
||||||
async def _nav_tree_handler():
|
async def _nav_tree_handler():
|
||||||
from shared.sexp.helpers import sexp_call, SexpExpr
|
from shared.sx.helpers import sx_call, SxExpr
|
||||||
from shared.infrastructure.urls import (
|
from shared.infrastructure.urls import (
|
||||||
blog_url, cart_url, market_url, events_url,
|
blog_url, cart_url, market_url, events_url,
|
||||||
federation_url, account_url, artdag_url,
|
federation_url, account_url, artdag_url,
|
||||||
@@ -54,36 +54,36 @@ def register():
|
|||||||
|
|
||||||
nav_cls = "whitespace-nowrap flex items-center gap-2 rounded p-2 text-sm"
|
nav_cls = "whitespace-nowrap flex items-center gap-2 rounded p-2 text-sm"
|
||||||
|
|
||||||
item_sexps = []
|
item_sxs = []
|
||||||
for item in menu_items:
|
for item in menu_items:
|
||||||
href = app_slugs.get(item.slug, blog_url(f"/{item.slug}/"))
|
href = app_slugs.get(item.slug, blog_url(f"/{item.slug}/"))
|
||||||
selected = "true" if (item.slug == first_seg
|
selected = "true" if (item.slug == first_seg
|
||||||
or item.slug == app_name) else "false"
|
or item.slug == app_name) else "false"
|
||||||
img = sexp_call("blog-nav-item-image",
|
img = sx_call("blog-nav-item-image",
|
||||||
src=getattr(item, "feature_image", None),
|
src=getattr(item, "feature_image", None),
|
||||||
label=getattr(item, "label", item.slug))
|
label=getattr(item, "label", item.slug))
|
||||||
item_sexps.append(sexp_call(
|
item_sxs.append(sx_call(
|
||||||
"blog-nav-item-link",
|
"blog-nav-item-link",
|
||||||
href=href, hx_get=href, selected=selected, nav_cls=nav_cls,
|
href=href, hx_get=href, selected=selected, nav_cls=nav_cls,
|
||||||
img=SexpExpr(img), label=getattr(item, "label", item.slug),
|
img=SxExpr(img), label=getattr(item, "label", item.slug),
|
||||||
))
|
))
|
||||||
|
|
||||||
# artdag link
|
# artdag link
|
||||||
href = artdag_url("/")
|
href = artdag_url("/")
|
||||||
selected = "true" if ("artdag" == first_seg
|
selected = "true" if ("artdag" == first_seg
|
||||||
or "artdag" == app_name) else "false"
|
or "artdag" == app_name) else "false"
|
||||||
img = sexp_call("blog-nav-item-image", src=None, label="art-dag")
|
img = sx_call("blog-nav-item-image", src=None, label="art-dag")
|
||||||
item_sexps.append(sexp_call(
|
item_sxs.append(sx_call(
|
||||||
"blog-nav-item-link",
|
"blog-nav-item-link",
|
||||||
href=href, hx_get=href, selected=selected, nav_cls=nav_cls,
|
href=href, hx_get=href, selected=selected, nav_cls=nav_cls,
|
||||||
img=SexpExpr(img), label="art-dag",
|
img=SxExpr(img), label="art-dag",
|
||||||
))
|
))
|
||||||
|
|
||||||
if not item_sexps:
|
if not item_sxs:
|
||||||
return sexp_call("blog-nav-empty",
|
return sx_call("blog-nav-empty",
|
||||||
wrapper_id="menu-items-nav-wrapper")
|
wrapper_id="menu-items-nav-wrapper")
|
||||||
|
|
||||||
items_frag = "(<> " + " ".join(item_sexps) + ")"
|
items_frag = "(<> " + " ".join(item_sxs) + ")"
|
||||||
|
|
||||||
arrow_cls = "scrolling-menu-arrow-menu-items-container"
|
arrow_cls = "scrolling-menu-arrow-menu-items-container"
|
||||||
container_id = "menu-items-container"
|
container_id = "menu-items-container"
|
||||||
@@ -101,21 +101,21 @@ def register():
|
|||||||
right_hs = ("on click set #" + container_id
|
right_hs = ("on click set #" + container_id
|
||||||
+ ".scrollLeft to #" + container_id + ".scrollLeft + 200")
|
+ ".scrollLeft to #" + container_id + ".scrollLeft + 200")
|
||||||
|
|
||||||
return sexp_call("blog-nav-wrapper",
|
return sx_call("blog-nav-wrapper",
|
||||||
arrow_cls=arrow_cls,
|
arrow_cls=arrow_cls,
|
||||||
container_id=container_id,
|
container_id=container_id,
|
||||||
left_hs=left_hs,
|
left_hs=left_hs,
|
||||||
scroll_hs=scroll_hs,
|
scroll_hs=scroll_hs,
|
||||||
right_hs=right_hs,
|
right_hs=right_hs,
|
||||||
items=SexpExpr(items_frag))
|
items=SxExpr(items_frag))
|
||||||
|
|
||||||
_handlers["nav-tree"] = _nav_tree_handler
|
_handlers["nav-tree"] = _nav_tree_handler
|
||||||
|
|
||||||
# --- link-card fragment — returns sexp source ---
|
# --- link-card fragment — returns sx source ---
|
||||||
def _blog_link_card_sexp(post, link: str) -> str:
|
def _blog_link_card_sx(post, link: str) -> str:
|
||||||
from shared.sexp.helpers import sexp_call
|
from shared.sx.helpers import sx_call
|
||||||
published = post.published_at.strftime("%d %b %Y") if post.published_at else None
|
published = post.published_at.strftime("%d %b %Y") if post.published_at else None
|
||||||
return sexp_call("link-card",
|
return sx_call("link-card",
|
||||||
link=link,
|
link=link,
|
||||||
title=post.title,
|
title=post.title,
|
||||||
image=post.feature_image,
|
image=post.feature_image,
|
||||||
@@ -139,7 +139,7 @@ def register():
|
|||||||
parts.append(f"<!-- fragment:{s} -->")
|
parts.append(f"<!-- fragment:{s} -->")
|
||||||
post = await services.blog.get_post_by_slug(g.s, s)
|
post = await services.blog.get_post_by_slug(g.s, s)
|
||||||
if post:
|
if post:
|
||||||
parts.append(_blog_link_card_sexp(post, blog_url(f"/{post.slug}")))
|
parts.append(_blog_link_card_sx(post, blog_url(f"/{post.slug}")))
|
||||||
return "\n".join(parts)
|
return "\n".join(parts)
|
||||||
|
|
||||||
# Single mode
|
# Single mode
|
||||||
@@ -148,7 +148,7 @@ def register():
|
|||||||
post = await services.blog.get_post_by_slug(g.s, slug)
|
post = await services.blog.get_post_by_slug(g.s, slug)
|
||||||
if not post:
|
if not post:
|
||||||
return ""
|
return ""
|
||||||
return _blog_link_card_sexp(post, blog_url(f"/{post.slug}"))
|
return _blog_link_card_sx(post, blog_url(f"/{post.slug}"))
|
||||||
|
|
||||||
_handlers["link-card"] = _link_card_handler
|
_handlers["link-card"] = _link_card_handler
|
||||||
|
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ from .services.menu_items import (
|
|||||||
MenuItemError,
|
MenuItemError,
|
||||||
)
|
)
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
bp = Blueprint("menu_items", __name__, url_prefix='/settings/menu_items')
|
bp = Blueprint("menu_items", __name__, url_prefix='/settings/menu_items')
|
||||||
|
|
||||||
def get_menu_items_nav_oob_sync(menu_items):
|
def get_menu_items_nav_oob_sync(menu_items):
|
||||||
"""Helper to generate OOB update for root nav menu items"""
|
"""Helper to generate OOB update for root nav menu items"""
|
||||||
from sexp.sexp_components import render_menu_items_nav_oob
|
from sx.sx_components import render_menu_items_nav_oob
|
||||||
return render_menu_items_nav_oob(menu_items)
|
return render_menu_items_nav_oob(menu_items)
|
||||||
|
|
||||||
@bp.get("/")
|
@bp.get("/")
|
||||||
@@ -30,8 +30,8 @@ def register():
|
|||||||
menu_items = await get_all_menu_items(g.s)
|
menu_items = await get_all_menu_items(g.s)
|
||||||
|
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_menu_items_page, render_menu_items_oob
|
from sx.sx_components import render_menu_items_page, render_menu_items_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx["menu_items"] = menu_items
|
tctx["menu_items"] = menu_items
|
||||||
@@ -39,8 +39,8 @@ def register():
|
|||||||
html = await render_menu_items_page(tctx)
|
html = await render_menu_items_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_menu_items_oob(tctx)
|
sx_src = await render_menu_items_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.get("/new/")
|
@bp.get("/new/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -73,10 +73,10 @@ def register():
|
|||||||
|
|
||||||
# Get updated list and nav OOB
|
# Get updated list and nav OOB
|
||||||
menu_items = await get_all_menu_items(g.s)
|
menu_items = await get_all_menu_items(g.s)
|
||||||
from sexp.sexp_components import render_menu_items_list
|
from sx.sx_components import render_menu_items_list
|
||||||
html = render_menu_items_list(menu_items)
|
html = render_menu_items_list(menu_items)
|
||||||
nav_oob = get_menu_items_nav_oob_sync(menu_items)
|
nav_oob = get_menu_items_nav_oob_sync(menu_items)
|
||||||
return sexp_response(html + nav_oob)
|
return sx_response(html + nav_oob)
|
||||||
|
|
||||||
except MenuItemError as e:
|
except MenuItemError as e:
|
||||||
return jsonify({"message": str(e), "errors": {}}), 400
|
return jsonify({"message": str(e), "errors": {}}), 400
|
||||||
@@ -116,10 +116,10 @@ def register():
|
|||||||
|
|
||||||
# Get updated list and nav OOB
|
# Get updated list and nav OOB
|
||||||
menu_items = await get_all_menu_items(g.s)
|
menu_items = await get_all_menu_items(g.s)
|
||||||
from sexp.sexp_components import render_menu_items_list
|
from sx.sx_components import render_menu_items_list
|
||||||
html = render_menu_items_list(menu_items)
|
html = render_menu_items_list(menu_items)
|
||||||
nav_oob = get_menu_items_nav_oob_sync(menu_items)
|
nav_oob = get_menu_items_nav_oob_sync(menu_items)
|
||||||
return sexp_response(html + nav_oob)
|
return sx_response(html + nav_oob)
|
||||||
|
|
||||||
except MenuItemError as e:
|
except MenuItemError as e:
|
||||||
return jsonify({"message": str(e), "errors": {}}), 400
|
return jsonify({"message": str(e), "errors": {}}), 400
|
||||||
@@ -137,10 +137,10 @@ def register():
|
|||||||
|
|
||||||
# Get updated list and nav OOB
|
# Get updated list and nav OOB
|
||||||
menu_items = await get_all_menu_items(g.s)
|
menu_items = await get_all_menu_items(g.s)
|
||||||
from sexp.sexp_components import render_menu_items_list
|
from sx.sx_components import render_menu_items_list
|
||||||
html = render_menu_items_list(menu_items)
|
html = render_menu_items_list(menu_items)
|
||||||
nav_oob = get_menu_items_nav_oob_sync(menu_items)
|
nav_oob = get_menu_items_nav_oob_sync(menu_items)
|
||||||
return sexp_response(html + nav_oob)
|
return sx_response(html + nav_oob)
|
||||||
|
|
||||||
@bp.get("/pages/search/")
|
@bp.get("/pages/search/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -184,9 +184,9 @@ def register():
|
|||||||
|
|
||||||
# Get updated list and nav OOB
|
# Get updated list and nav OOB
|
||||||
menu_items = await get_all_menu_items(g.s)
|
menu_items = await get_all_menu_items(g.s)
|
||||||
from sexp.sexp_components import render_menu_items_list
|
from sx.sx_components import render_menu_items_list
|
||||||
html = render_menu_items_list(menu_items)
|
html = render_menu_items_list(menu_items)
|
||||||
nav_oob = get_menu_items_nav_oob_sync(menu_items)
|
nav_oob = get_menu_items_nav_oob_sync(menu_items)
|
||||||
return sexp_response(html + nav_oob)
|
return sx_response(html + nav_oob)
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from quart import (
|
|||||||
)
|
)
|
||||||
from shared.browser.app.authz import require_admin, require_post_author
|
from shared.browser.app.authz import require_admin, require_post_author
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
from shared.utils import host_url
|
from shared.utils import host_url
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
@@ -52,8 +52,8 @@ def register():
|
|||||||
"sumup_checkout_prefix": sumup_checkout_prefix,
|
"sumup_checkout_prefix": sumup_checkout_prefix,
|
||||||
}
|
}
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_post_admin_page, render_post_admin_oob
|
from sx.sx_components import render_post_admin_page, render_post_admin_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx.update(ctx)
|
tctx.update(ctx)
|
||||||
@@ -61,8 +61,8 @@ def register():
|
|||||||
html = await render_post_admin_page(tctx)
|
html = await render_post_admin_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_post_admin_oob(tctx)
|
sx_src = await render_post_admin_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.put("/features/")
|
@bp.put("/features/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -99,14 +99,14 @@ def register():
|
|||||||
|
|
||||||
features = result.get("features", {})
|
features = result.get("features", {})
|
||||||
|
|
||||||
from sexp.sexp_components import render_features_panel
|
from sx.sx_components import render_features_panel
|
||||||
html = render_features_panel(
|
html = render_features_panel(
|
||||||
features, post,
|
features, post,
|
||||||
sumup_configured=result.get("sumup_configured", False),
|
sumup_configured=result.get("sumup_configured", False),
|
||||||
sumup_merchant_code=result.get("sumup_merchant_code") or "",
|
sumup_merchant_code=result.get("sumup_merchant_code") or "",
|
||||||
sumup_checkout_prefix=result.get("sumup_checkout_prefix") or "",
|
sumup_checkout_prefix=result.get("sumup_checkout_prefix") or "",
|
||||||
)
|
)
|
||||||
return sexp_response(html)
|
return sx_response(html)
|
||||||
|
|
||||||
@bp.put("/admin/sumup/")
|
@bp.put("/admin/sumup/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -138,20 +138,20 @@ def register():
|
|||||||
result = await call_action("blog", "update-page-config", payload=payload)
|
result = await call_action("blog", "update-page-config", payload=payload)
|
||||||
|
|
||||||
features = result.get("features", {})
|
features = result.get("features", {})
|
||||||
from sexp.sexp_components import render_features_panel
|
from sx.sx_components import render_features_panel
|
||||||
html = render_features_panel(
|
html = render_features_panel(
|
||||||
features, post,
|
features, post,
|
||||||
sumup_configured=result.get("sumup_configured", False),
|
sumup_configured=result.get("sumup_configured", False),
|
||||||
sumup_merchant_code=result.get("sumup_merchant_code") or "",
|
sumup_merchant_code=result.get("sumup_merchant_code") or "",
|
||||||
sumup_checkout_prefix=result.get("sumup_checkout_prefix") or "",
|
sumup_checkout_prefix=result.get("sumup_checkout_prefix") or "",
|
||||||
)
|
)
|
||||||
return sexp_response(html)
|
return sx_response(html)
|
||||||
|
|
||||||
@bp.get("/data/")
|
@bp.get("/data/")
|
||||||
@require_admin
|
@require_admin
|
||||||
async def data(slug: str):
|
async def data(slug: str):
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_post_data_page, render_post_data_oob
|
from sx.sx_components import render_post_data_page, render_post_data_oob
|
||||||
|
|
||||||
data_html = await render_template("_types/post_data/_main_panel.html")
|
data_html = await render_template("_types/post_data/_main_panel.html")
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
@@ -160,8 +160,8 @@ def register():
|
|||||||
html = await render_post_data_page(tctx)
|
html = await render_post_data_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_post_data_oob(tctx)
|
sx_src = await render_post_data_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.get("/entries/calendar/<int:calendar_id>/")
|
@bp.get("/entries/calendar/<int:calendar_id>/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -269,8 +269,8 @@ def register():
|
|||||||
# Load entries and post for each calendar
|
# Load entries and post for each calendar
|
||||||
for calendar in all_calendars:
|
for calendar in all_calendars:
|
||||||
await g.s.refresh(calendar, ["entries", "post"])
|
await g.s.refresh(calendar, ["entries", "post"])
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_post_entries_page, render_post_entries_oob
|
from sx.sx_components import render_post_entries_page, render_post_entries_oob
|
||||||
|
|
||||||
entries_html = await render_template(
|
entries_html = await render_template(
|
||||||
"_types/post_entries/_main_panel.html",
|
"_types/post_entries/_main_panel.html",
|
||||||
@@ -283,8 +283,8 @@ def register():
|
|||||||
html = await render_post_entries_page(tctx)
|
html = await render_post_entries_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_post_entries_oob(tctx)
|
sx_src = await render_post_entries_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.post("/entries/<int:entry_id>/toggle/")
|
@bp.post("/entries/<int:entry_id>/toggle/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -330,13 +330,13 @@ def register():
|
|||||||
).scalars().all()
|
).scalars().all()
|
||||||
|
|
||||||
# Return the associated entries admin list + OOB update for nav entries
|
# Return the associated entries admin list + OOB update for nav entries
|
||||||
from sexp.sexp_components import render_associated_entries, render_nav_entries_oob
|
from sx.sx_components import render_associated_entries, render_nav_entries_oob
|
||||||
|
|
||||||
post = g.post_data["post"]
|
post = g.post_data["post"]
|
||||||
admin_list = render_associated_entries(all_calendars, associated_entry_ids, post["slug"])
|
admin_list = render_associated_entries(all_calendars, associated_entry_ids, post["slug"])
|
||||||
nav_entries_html = render_nav_entries_oob(associated_entries, calendars, post)
|
nav_entries_html = render_nav_entries_oob(associated_entries, calendars, post)
|
||||||
|
|
||||||
return sexp_response(admin_list + nav_entries_html)
|
return sx_response(admin_list + nav_entries_html)
|
||||||
|
|
||||||
@bp.get("/settings/")
|
@bp.get("/settings/")
|
||||||
@require_post_author
|
@require_post_author
|
||||||
@@ -348,8 +348,8 @@ def register():
|
|||||||
ghost_post = await get_post_for_edit(ghost_id, is_page=is_page)
|
ghost_post = await get_post_for_edit(ghost_id, is_page=is_page)
|
||||||
save_success = request.args.get("saved") == "1"
|
save_success = request.args.get("saved") == "1"
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_post_settings_page, render_post_settings_oob
|
from sx.sx_components import render_post_settings_page, render_post_settings_oob
|
||||||
|
|
||||||
settings_html = await render_template(
|
settings_html = await render_template(
|
||||||
"_types/post_settings/_main_panel.html",
|
"_types/post_settings/_main_panel.html",
|
||||||
@@ -362,8 +362,8 @@ def register():
|
|||||||
html = await render_post_settings_page(tctx)
|
html = await render_post_settings_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_post_settings_oob(tctx)
|
sx_src = await render_post_settings_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.post("/settings/")
|
@bp.post("/settings/")
|
||||||
@require_post_author
|
@require_post_author
|
||||||
@@ -452,8 +452,8 @@ def register():
|
|||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
newsletters = [SimpleNamespace(**nl) for nl in raw_newsletters]
|
newsletters = [SimpleNamespace(**nl) for nl in raw_newsletters]
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_post_edit_page, render_post_edit_oob
|
from sx.sx_components import render_post_edit_page, render_post_edit_oob
|
||||||
|
|
||||||
edit_html = await render_template(
|
edit_html = await render_template(
|
||||||
"_types/post_edit/_main_panel.html",
|
"_types/post_edit/_main_panel.html",
|
||||||
@@ -468,8 +468,8 @@ def register():
|
|||||||
html = await render_post_edit_page(tctx)
|
html = await render_post_edit_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_post_edit_oob(tctx)
|
sx_src = await render_post_edit_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.post("/edit/")
|
@bp.post("/edit/")
|
||||||
@require_post_author
|
@require_post_author
|
||||||
@@ -598,8 +598,8 @@ def register():
|
|||||||
|
|
||||||
page_markets = await _fetch_page_markets(post_id)
|
page_markets = await _fetch_page_markets(post_id)
|
||||||
|
|
||||||
from sexp.sexp_components import render_markets_panel
|
from sx.sx_components import render_markets_panel
|
||||||
return sexp_response(render_markets_panel(page_markets, post))
|
return sx_response(render_markets_panel(page_markets, post))
|
||||||
|
|
||||||
@bp.post("/markets/new/")
|
@bp.post("/markets/new/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -624,8 +624,8 @@ def register():
|
|||||||
# Return updated markets list
|
# Return updated markets list
|
||||||
page_markets = await _fetch_page_markets(post_id)
|
page_markets = await _fetch_page_markets(post_id)
|
||||||
|
|
||||||
from sexp.sexp_components import render_markets_panel
|
from sx.sx_components import render_markets_panel
|
||||||
return sexp_response(render_markets_panel(page_markets, post))
|
return sx_response(render_markets_panel(page_markets, post))
|
||||||
|
|
||||||
@bp.delete("/markets/<market_slug>/")
|
@bp.delete("/markets/<market_slug>/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -644,7 +644,7 @@ def register():
|
|||||||
# Return updated markets list
|
# Return updated markets list
|
||||||
page_markets = await _fetch_page_markets(post_id)
|
page_markets = await _fetch_page_markets(post_id)
|
||||||
|
|
||||||
from sexp.sexp_components import render_markets_panel
|
from sx.sx_components import render_markets_panel
|
||||||
return sexp_response(render_markets_panel(page_markets, post))
|
return sx_response(render_markets_panel(page_markets, post))
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from shared.browser.app.redis_cacher import cache_page, clear_cache
|
|||||||
from .admin.routes import register as register_admin
|
from .admin.routes import register as register_admin
|
||||||
from shared.config import config
|
from shared.config import config
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
bp = Blueprint("post", __name__, url_prefix='/<slug>')
|
bp = Blueprint("post", __name__, url_prefix='/<slug>')
|
||||||
@@ -104,28 +104,28 @@ def register():
|
|||||||
@bp.get("/")
|
@bp.get("/")
|
||||||
@cache_page(tag="post.post_detail")
|
@cache_page(tag="post.post_detail")
|
||||||
async def post_detail(slug: str):
|
async def post_detail(slug: str):
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_post_page, render_post_oob
|
from sx.sx_components import render_post_page, render_post_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
html = await render_post_page(tctx)
|
html = await render_post_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_post_oob(tctx)
|
sx_src = await render_post_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.post("/like/toggle/")
|
@bp.post("/like/toggle/")
|
||||||
@clear_cache(tag="post.post_detail", tag_scope="user")
|
@clear_cache(tag="post.post_detail", tag_scope="user")
|
||||||
async def like_toggle(slug: str):
|
async def like_toggle(slug: str):
|
||||||
from shared.utils import host_url
|
from shared.utils import host_url
|
||||||
from sexp.sexp_components import render_like_toggle_button
|
from sx.sx_components import render_like_toggle_button
|
||||||
|
|
||||||
like_url = host_url(url_for('blog.post.like_toggle', slug=slug))
|
like_url = host_url(url_for('blog.post.like_toggle', slug=slug))
|
||||||
|
|
||||||
# Get post_id from g.post_data
|
# Get post_id from g.post_data
|
||||||
if not g.user:
|
if not g.user:
|
||||||
return sexp_response(render_like_toggle_button(slug, False, like_url), status=403)
|
return sx_response(render_like_toggle_button(slug, False, like_url), status=403)
|
||||||
|
|
||||||
post_id = g.post_data["post"]["id"]
|
post_id = g.post_data["post"]["id"]
|
||||||
user_id = g.user.id
|
user_id = g.user.id
|
||||||
@@ -135,7 +135,7 @@ def register():
|
|||||||
})
|
})
|
||||||
liked = result["liked"]
|
liked = result["liked"]
|
||||||
|
|
||||||
return sexp_response(render_like_toggle_button(slug, liked, like_url))
|
return sx_response(render_like_toggle_button(slug, liked, like_url))
|
||||||
|
|
||||||
@bp.get("/w/<widget_domain>/")
|
@bp.get("/w/<widget_domain>/")
|
||||||
async def widget_paginate(slug: str, widget_domain: str):
|
async def widget_paginate(slug: str, widget_domain: str):
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from sqlalchemy.orm import selectinload
|
|||||||
|
|
||||||
from shared.browser.app.authz import require_login
|
from shared.browser.app.authz import require_login
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
from models import Snippet
|
from models import Snippet
|
||||||
|
|
||||||
|
|
||||||
@@ -39,8 +39,8 @@ def register():
|
|||||||
snippets = await _visible_snippets(g.s)
|
snippets = await _visible_snippets(g.s)
|
||||||
is_admin = g.rights.get("admin")
|
is_admin = g.rights.get("admin")
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_snippets_page, render_snippets_oob
|
from sx.sx_components import render_snippets_page, render_snippets_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx["snippets"] = snippets
|
tctx["snippets"] = snippets
|
||||||
@@ -49,8 +49,8 @@ def register():
|
|||||||
html = await render_snippets_page(tctx)
|
html = await render_snippets_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_snippets_oob(tctx)
|
sx_src = await render_snippets_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.delete("/<int:snippet_id>/")
|
@bp.delete("/<int:snippet_id>/")
|
||||||
@require_login
|
@require_login
|
||||||
@@ -68,8 +68,8 @@ def register():
|
|||||||
await g.s.flush()
|
await g.s.flush()
|
||||||
|
|
||||||
snippets = await _visible_snippets(g.s)
|
snippets = await _visible_snippets(g.s)
|
||||||
from sexp.sexp_components import render_snippets_list
|
from sx.sx_components import render_snippets_list
|
||||||
return sexp_response(render_snippets_list(snippets, is_admin))
|
return sx_response(render_snippets_list(snippets, is_admin))
|
||||||
|
|
||||||
@bp.patch("/<int:snippet_id>/visibility/")
|
@bp.patch("/<int:snippet_id>/visibility/")
|
||||||
@require_login
|
@require_login
|
||||||
@@ -92,7 +92,7 @@ def register():
|
|||||||
await g.s.flush()
|
await g.s.flush()
|
||||||
|
|
||||||
snippets = await _visible_snippets(g.s)
|
snippets = await _visible_snippets(g.s)
|
||||||
from sexp.sexp_components import render_snippets_list
|
from sx.sx_components import render_snippets_list
|
||||||
return sexp_response(render_snippets_list(snippets, True))
|
return sx_response(render_snippets_list(snippets, True))
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import path_setup # noqa: F401 # adds shared/ to sys.path
|
import path_setup # noqa: F401 # adds shared/ to sys.path
|
||||||
import sexp.sexp_components as sexp_components # noqa: F401 # ensure Hypercorn --reload watches this file
|
import sx.sx_components as sx_components # noqa: F401 # ensure Hypercorn --reload watches this file
|
||||||
|
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|||||||
@@ -150,8 +150,8 @@ def register(url_prefix: str) -> Blueprint:
|
|||||||
try:
|
try:
|
||||||
page_config = await resolve_page_config(g.s, cart, calendar_entries, tickets)
|
page_config = await resolve_page_config(g.s, cart, calendar_entries, tickets)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_checkout_error_page
|
from sx.sx_components import render_checkout_error_page
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
html = await render_checkout_error_page(tctx, error=str(e))
|
html = await render_checkout_error_page(tctx, error=str(e))
|
||||||
return await make_response(html, 400)
|
return await make_response(html, 400)
|
||||||
@@ -207,8 +207,8 @@ def register(url_prefix: str) -> Blueprint:
|
|||||||
hosted_url = result.get("sumup_hosted_url")
|
hosted_url = result.get("sumup_hosted_url")
|
||||||
|
|
||||||
if not hosted_url:
|
if not hosted_url:
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_checkout_error_page
|
from sx.sx_components import render_checkout_error_page
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
html = await render_checkout_error_page(tctx, error="No hosted checkout URL returned from SumUp.")
|
html = await render_checkout_error_page(tctx, error="No hosted checkout URL returned from SumUp.")
|
||||||
return await make_response(html, 500)
|
return await make_response(html, 500)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
from quart import Blueprint, render_template, make_response
|
from quart import Blueprint, render_template, make_response
|
||||||
|
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
from .services import get_cart_grouped_by_page
|
from .services import get_cart_grouped_by_page
|
||||||
|
|
||||||
|
|
||||||
@@ -15,8 +15,8 @@ def register(url_prefix: str) -> Blueprint:
|
|||||||
@bp.get("/")
|
@bp.get("/")
|
||||||
async def overview():
|
async def overview():
|
||||||
from quart import g
|
from quart import g
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_overview_page, render_overview_oob
|
from sx.sx_components import render_overview_page, render_overview_oob
|
||||||
|
|
||||||
page_groups = await get_cart_grouped_by_page(g.s)
|
page_groups = await get_cart_grouped_by_page(g.s)
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
@@ -25,7 +25,7 @@ def register(url_prefix: str) -> Blueprint:
|
|||||||
html = await render_overview_page(ctx, page_groups)
|
html = await render_overview_page(ctx, page_groups)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_overview_oob(ctx, page_groups)
|
sx_src = await render_overview_oob(ctx, page_groups)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from __future__ import annotations
|
|||||||
from quart import Blueprint, g, redirect, make_response, url_for
|
from quart import Blueprint, g, redirect, make_response, url_for
|
||||||
|
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
from shared.infrastructure.actions import call_action
|
from shared.infrastructure.actions import call_action
|
||||||
from .services import (
|
from .services import (
|
||||||
total,
|
total,
|
||||||
@@ -41,8 +41,8 @@ def register(url_prefix: str) -> Blueprint:
|
|||||||
ticket_total=ticket_total,
|
ticket_total=ticket_total,
|
||||||
)
|
)
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_page_cart_page, render_page_cart_oob
|
from sx.sx_components import render_page_cart_page, render_page_cart_oob
|
||||||
|
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
@@ -52,11 +52,11 @@ def register(url_prefix: str) -> Blueprint:
|
|||||||
)
|
)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_page_cart_oob(
|
sx_src = await render_page_cart_oob(
|
||||||
ctx, post, cart, cal_entries, page_tickets,
|
ctx, post, cart, cal_entries, page_tickets,
|
||||||
ticket_groups, total, calendar_total, ticket_total,
|
ticket_groups, total, calendar_total, ticket_total,
|
||||||
)
|
)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.post("/checkout/")
|
@bp.post("/checkout/")
|
||||||
async def page_checkout():
|
async def page_checkout():
|
||||||
@@ -111,8 +111,8 @@ def register(url_prefix: str) -> Blueprint:
|
|||||||
hosted_url = result.get("sumup_hosted_url")
|
hosted_url = result.get("sumup_hosted_url")
|
||||||
|
|
||||||
if not hosted_url:
|
if not hosted_url:
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_checkout_error_page
|
from sx.sx_components import render_checkout_error_page
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
html = await render_checkout_error_page(tctx, error="No hosted checkout URL returned from SumUp.")
|
html = await render_checkout_error_page(tctx, error="No hosted checkout URL returned from SumUp.")
|
||||||
return await make_response(html, 500)
|
return await make_response(html, 500)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Cart app fragment endpoints.
|
"""Cart app fragment endpoints.
|
||||||
|
|
||||||
Exposes sexp fragments at ``/internal/fragments/<type>`` for consumption
|
Exposes sx fragments at ``/internal/fragments/<type>`` for consumption
|
||||||
by other coop apps via the fragment client.
|
by other coop apps via the fragment client.
|
||||||
|
|
||||||
Fragments:
|
Fragments:
|
||||||
@@ -19,13 +19,13 @@ def register():
|
|||||||
bp = Blueprint("fragments", __name__, url_prefix="/internal/fragments")
|
bp = Blueprint("fragments", __name__, url_prefix="/internal/fragments")
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# Fragment handlers — return sexp source text
|
# Fragment handlers — return sx source text
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
|
|
||||||
async def _cart_mini():
|
async def _cart_mini():
|
||||||
from shared.services.registry import services
|
from shared.services.registry import services
|
||||||
from shared.infrastructure.urls import blog_url, cart_url
|
from shared.infrastructure.urls import blog_url, cart_url
|
||||||
from shared.sexp.helpers import sexp_call
|
from shared.sx.helpers import sx_call
|
||||||
|
|
||||||
user_id = request.args.get("user_id", type=int)
|
user_id = request.args.get("user_id", type=int)
|
||||||
session_id = request.args.get("session_id")
|
session_id = request.args.get("session_id")
|
||||||
@@ -35,7 +35,7 @@ def register():
|
|||||||
)
|
)
|
||||||
count = summary.count + summary.calendar_count + summary.ticket_count
|
count = summary.count + summary.calendar_count + summary.ticket_count
|
||||||
oob = request.args.get("oob", "")
|
oob = request.args.get("oob", "")
|
||||||
return sexp_call("cart-mini",
|
return sx_call("cart-mini",
|
||||||
cart_count=count,
|
cart_count=count,
|
||||||
blog_url=blog_url(""),
|
blog_url=blog_url(""),
|
||||||
cart_url=cart_url(""),
|
cart_url=cart_url(""),
|
||||||
@@ -43,9 +43,9 @@ def register():
|
|||||||
|
|
||||||
async def _account_nav_item():
|
async def _account_nav_item():
|
||||||
from shared.infrastructure.urls import cart_url
|
from shared.infrastructure.urls import cart_url
|
||||||
from shared.sexp.helpers import sexp_call
|
from shared.sx.helpers import sx_call
|
||||||
|
|
||||||
return sexp_call("account-nav-item",
|
return sx_call("account-nav-item",
|
||||||
href=cart_url("/orders/"),
|
href=cart_url("/orders/"),
|
||||||
label="orders")
|
label="orders")
|
||||||
|
|
||||||
@@ -67,8 +67,8 @@ def register():
|
|||||||
async def get_fragment(fragment_type: str):
|
async def get_fragment(fragment_type: str):
|
||||||
handler = _handlers.get(fragment_type)
|
handler = _handlers.get(fragment_type)
|
||||||
if handler is None:
|
if handler is None:
|
||||||
return Response("", status=200, content_type="text/sexp")
|
return Response("", status=200, content_type="text/sx")
|
||||||
src = await handler()
|
src = await handler()
|
||||||
return Response(src, status=200, content_type="text/sexp")
|
return Response(src, status=200, content_type="text/sx")
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from shared.infrastructure.http_utils import vary as _vary, current_url_without_
|
|||||||
from shared.infrastructure.cart_identity import current_cart_identity
|
from shared.infrastructure.cart_identity import current_cart_identity
|
||||||
from bp.cart.services import check_sumup_status
|
from bp.cart.services import check_sumup_status
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
from .filters.qs import makeqs_factory, decode
|
from .filters.qs import makeqs_factory, decode
|
||||||
|
|
||||||
@@ -56,8 +56,8 @@ def register() -> Blueprint:
|
|||||||
order = result.scalar_one_or_none()
|
order = result.scalar_one_or_none()
|
||||||
if not order:
|
if not order:
|
||||||
return await make_response("Order not found", 404)
|
return await make_response("Order not found", 404)
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_order_page, render_order_oob
|
from sx.sx_components import render_order_page, render_order_oob
|
||||||
|
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
calendar_entries = ctx.get("calendar_entries")
|
calendar_entries = ctx.get("calendar_entries")
|
||||||
@@ -66,8 +66,8 @@ def register() -> Blueprint:
|
|||||||
html = await render_order_page(ctx, order, calendar_entries, url_for)
|
html = await render_order_page(ctx, order, calendar_entries, url_for)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_order_oob(ctx, order, calendar_entries, url_for)
|
sx_src = await render_order_oob(ctx, order, calendar_entries, url_for)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.get("/pay/")
|
@bp.get("/pay/")
|
||||||
async def order_pay(order_id: int):
|
async def order_pay(order_id: int):
|
||||||
@@ -121,8 +121,8 @@ def register() -> Blueprint:
|
|||||||
await g.s.flush()
|
await g.s.flush()
|
||||||
|
|
||||||
if not hosted_url:
|
if not hosted_url:
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_checkout_error_page
|
from sx.sx_components import render_checkout_error_page
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
html = await render_checkout_error_page(tctx, error="No hosted checkout URL returned from SumUp when trying to reopen payment.", order=order)
|
html = await render_checkout_error_page(tctx, error="No hosted checkout URL returned from SumUp when trying to reopen payment.", order=order)
|
||||||
return await make_response(html, 500)
|
return await make_response(html, 500)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ from shared.infrastructure.http_utils import vary as _vary, current_url_without_
|
|||||||
from shared.infrastructure.cart_identity import current_cart_identity
|
from shared.infrastructure.cart_identity import current_cart_identity
|
||||||
from bp.cart.services import check_sumup_status
|
from bp.cart.services import check_sumup_status
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
from bp import register_order
|
from bp import register_order
|
||||||
|
|
||||||
from .filters.qs import makeqs_factory, decode
|
from .filters.qs import makeqs_factory, decode
|
||||||
@@ -137,8 +137,8 @@ def register(url_prefix: str) -> Blueprint:
|
|||||||
result = await g.s.execute(stmt)
|
result = await g.s.execute(stmt)
|
||||||
orders = result.scalars().all()
|
orders = result.scalars().all()
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import (
|
from sx.sx_components import (
|
||||||
render_orders_page,
|
render_orders_page,
|
||||||
render_orders_rows,
|
render_orders_rows,
|
||||||
render_orders_oob,
|
render_orders_oob,
|
||||||
@@ -154,16 +154,16 @@ def register(url_prefix: str) -> Blueprint:
|
|||||||
)
|
)
|
||||||
resp = await make_response(html)
|
resp = await make_response(html)
|
||||||
elif page > 1:
|
elif page > 1:
|
||||||
sexp_src = await render_orders_rows(
|
sx_src = await render_orders_rows(
|
||||||
ctx, orders, page, total_pages, url_for, qs_fn,
|
ctx, orders, page, total_pages, url_for, qs_fn,
|
||||||
)
|
)
|
||||||
resp = sexp_response(sexp_src)
|
resp = sx_response(sx_src)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_orders_oob(
|
sx_src = await render_orders_oob(
|
||||||
ctx, orders, page, total_pages, search, total_count,
|
ctx, orders, page, total_pages, search, total_count,
|
||||||
url_for, qs_fn,
|
url_for, qs_fn,
|
||||||
)
|
)
|
||||||
resp = sexp_response(sexp_src)
|
resp = sx_response(sx_src)
|
||||||
resp.headers["Hx-Push-Url"] = _current_url_without_page()
|
resp.headers["Hx-Push-Url"] = _current_url_without_page()
|
||||||
return _vary(resp)
|
return _vary(resp)
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from shared.infrastructure.actions import call_action
|
|||||||
from shared.infrastructure.data_client import fetch_data
|
from shared.infrastructure.data_client import fetch_data
|
||||||
from shared.browser.app.authz import require_admin
|
from shared.browser.app.authz import require_admin
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
@@ -17,8 +17,8 @@ def register():
|
|||||||
@bp.get("/")
|
@bp.get("/")
|
||||||
@require_admin
|
@require_admin
|
||||||
async def admin(**kwargs):
|
async def admin(**kwargs):
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_cart_admin_page, render_cart_admin_oob
|
from sx.sx_components import render_cart_admin_page, render_cart_admin_oob
|
||||||
|
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
page_post = getattr(g, "page_post", None)
|
page_post = getattr(g, "page_post", None)
|
||||||
@@ -26,14 +26,14 @@ def register():
|
|||||||
html = await render_cart_admin_page(ctx, page_post)
|
html = await render_cart_admin_page(ctx, page_post)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_cart_admin_oob(ctx, page_post)
|
sx_src = await render_cart_admin_oob(ctx, page_post)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.get("/payments/")
|
@bp.get("/payments/")
|
||||||
@require_admin
|
@require_admin
|
||||||
async def payments(**kwargs):
|
async def payments(**kwargs):
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_cart_payments_page, render_cart_payments_oob
|
from sx.sx_components import render_cart_payments_page, render_cart_payments_oob
|
||||||
|
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
page_post = getattr(g, "page_post", None)
|
page_post = getattr(g, "page_post", None)
|
||||||
@@ -41,8 +41,8 @@ def register():
|
|||||||
html = await render_cart_payments_page(ctx, page_post)
|
html = await render_cart_payments_page(ctx, page_post)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_cart_payments_oob(ctx, page_post)
|
sx_src = await render_cart_payments_oob(ctx, page_post)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.put("/payments/")
|
@bp.put("/payments/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -77,10 +77,10 @@ def register():
|
|||||||
)
|
)
|
||||||
g.page_config = SimpleNamespace(**raw_pc) if raw_pc else None
|
g.page_config = SimpleNamespace(**raw_pc) if raw_pc else None
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_cart_payments_panel
|
from sx.sx_components import render_cart_payments_panel
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
html = render_cart_payments_panel(ctx)
|
html = render_cart_payments_panel(ctx)
|
||||||
return sexp_response(html)
|
return sx_response(html)
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -10,17 +10,17 @@ import os
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
from markupsafe import escape
|
from markupsafe import escape
|
||||||
|
|
||||||
from shared.sexp.jinja_bridge import load_service_components
|
from shared.sx.jinja_bridge import load_service_components
|
||||||
from shared.sexp.helpers import (
|
from shared.sx.helpers import (
|
||||||
call_url, root_header_sexp, post_admin_header_sexp,
|
call_url, root_header_sx, post_admin_header_sx,
|
||||||
post_header_sexp as _shared_post_header_sexp,
|
post_header_sx as _shared_post_header_sx,
|
||||||
search_desktop_sexp, search_mobile_sexp,
|
search_desktop_sx, search_mobile_sx,
|
||||||
full_page_sexp, oob_page_sexp, header_child_sexp,
|
full_page_sx, oob_page_sx, header_child_sx,
|
||||||
sexp_call, SexpExpr,
|
sx_call, SxExpr,
|
||||||
)
|
)
|
||||||
from shared.infrastructure.urls import market_product_url, cart_url
|
from shared.infrastructure.urls import market_product_url, cart_url
|
||||||
|
|
||||||
# Load cart-specific .sexpr components at import time
|
# Load cart-specific .sx components at import time
|
||||||
load_service_components(os.path.dirname(os.path.dirname(__file__)))
|
load_service_components(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ load_service_components(os.path.dirname(os.path.dirname(__file__)))
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _ensure_post_ctx(ctx: dict, page_post: Any) -> dict:
|
def _ensure_post_ctx(ctx: dict, page_post: Any) -> dict:
|
||||||
"""Ensure ctx has a 'post' dict from page_post DTO (for shared post_header_sexp)."""
|
"""Ensure ctx has a 'post' dict from page_post DTO (for shared post_header_sx)."""
|
||||||
if ctx.get("post") or not page_post:
|
if ctx.get("post") or not page_post:
|
||||||
return ctx
|
return ctx
|
||||||
ctx = {**ctx, "post": {
|
ctx = {**ctx, "post": {
|
||||||
@@ -63,16 +63,16 @@ async def _ensure_container_nav(ctx: dict) -> dict:
|
|||||||
return {**ctx, "container_nav": events_nav + market_nav}
|
return {**ctx, "container_nav": events_nav + market_nav}
|
||||||
|
|
||||||
|
|
||||||
async def _post_header_sexp(ctx: dict, page_post: Any, *, oob: bool = False) -> str:
|
async def _post_header_sx(ctx: dict, page_post: Any, *, oob: bool = False) -> str:
|
||||||
"""Build post-level header row from page_post DTO, using shared helper."""
|
"""Build post-level header row from page_post DTO, using shared helper."""
|
||||||
ctx = _ensure_post_ctx(ctx, page_post)
|
ctx = _ensure_post_ctx(ctx, page_post)
|
||||||
ctx = await _ensure_container_nav(ctx)
|
ctx = await _ensure_container_nav(ctx)
|
||||||
return _shared_post_header_sexp(ctx, oob=oob)
|
return _shared_post_header_sx(ctx, oob=oob)
|
||||||
|
|
||||||
|
|
||||||
def _cart_header_sexp(ctx: dict, *, oob: bool = False) -> str:
|
def _cart_header_sx(ctx: dict, *, oob: bool = False) -> str:
|
||||||
"""Build the cart section header row."""
|
"""Build the cart section header row."""
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"menu-row-sx",
|
"menu-row-sx",
|
||||||
id="cart-row", level=1, colour="sky",
|
id="cart-row", level=1, colour="sky",
|
||||||
link_href=call_url(ctx, "cart_url", "/"),
|
link_href=call_url(ctx, "cart_url", "/"),
|
||||||
@@ -81,28 +81,28 @@ def _cart_header_sexp(ctx: dict, *, oob: bool = False) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _page_cart_header_sexp(ctx: dict, page_post: Any, *, oob: bool = False) -> str:
|
def _page_cart_header_sx(ctx: dict, page_post: Any, *, oob: bool = False) -> str:
|
||||||
"""Build the per-page cart header row."""
|
"""Build the per-page cart header row."""
|
||||||
slug = page_post.slug if page_post else ""
|
slug = page_post.slug if page_post else ""
|
||||||
title = ((page_post.title if page_post else None) or "")[:160]
|
title = ((page_post.title if page_post else None) or "")[:160]
|
||||||
label_parts = []
|
label_parts = []
|
||||||
if page_post and page_post.feature_image:
|
if page_post and page_post.feature_image:
|
||||||
label_parts.append(sexp_call("cart-page-label-img", src=page_post.feature_image))
|
label_parts.append(sx_call("cart-page-label-img", src=page_post.feature_image))
|
||||||
label_parts.append(f'(span "{escape(title)}")')
|
label_parts.append(f'(span "{escape(title)}")')
|
||||||
label_sexp = "(<> " + " ".join(label_parts) + ")"
|
label_sx = "(<> " + " ".join(label_parts) + ")"
|
||||||
nav_sexp = sexp_call("cart-all-carts-link", href=call_url(ctx, "cart_url", "/"))
|
nav_sx = sx_call("cart-all-carts-link", href=call_url(ctx, "cart_url", "/"))
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"menu-row-sx",
|
"menu-row-sx",
|
||||||
id="page-cart-row", level=2, colour="sky",
|
id="page-cart-row", level=2, colour="sky",
|
||||||
link_href=call_url(ctx, "cart_url", f"/{slug}/"),
|
link_href=call_url(ctx, "cart_url", f"/{slug}/"),
|
||||||
link_label_content=SexpExpr(label_sexp),
|
link_label_content=SxExpr(label_sx),
|
||||||
nav=SexpExpr(nav_sexp), oob=oob,
|
nav=SxExpr(nav_sx), oob=oob,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _auth_header_sexp(ctx: dict, *, oob: bool = False) -> str:
|
def _auth_header_sx(ctx: dict, *, oob: bool = False) -> str:
|
||||||
"""Build the account section header row (for orders)."""
|
"""Build the account section header row (for orders)."""
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"menu-row-sx",
|
"menu-row-sx",
|
||||||
id="auth-row", level=1, colour="sky",
|
id="auth-row", level=1, colour="sky",
|
||||||
link_href=call_url(ctx, "account_url", "/"),
|
link_href=call_url(ctx, "account_url", "/"),
|
||||||
@@ -111,9 +111,9 @@ def _auth_header_sexp(ctx: dict, *, oob: bool = False) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _orders_header_sexp(ctx: dict, list_url: str) -> str:
|
def _orders_header_sx(ctx: dict, list_url: str) -> str:
|
||||||
"""Build the orders section header row."""
|
"""Build the orders section header row."""
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"menu-row-sx",
|
"menu-row-sx",
|
||||||
id="orders-row", level=2, colour="sky",
|
id="orders-row", level=2, colour="sky",
|
||||||
link_href=list_url, link_label="Orders", icon="fa fa-gbp",
|
link_href=list_url, link_label="Orders", icon="fa fa-gbp",
|
||||||
@@ -125,13 +125,13 @@ def _orders_header_sexp(ctx: dict, list_url: str) -> str:
|
|||||||
# Cart overview
|
# Cart overview
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _badge_sexp(icon: str, count: int, label: str) -> str:
|
def _badge_sx(icon: str, count: int, label: str) -> str:
|
||||||
"""Render a count badge."""
|
"""Render a count badge."""
|
||||||
s = "s" if count != 1 else ""
|
s = "s" if count != 1 else ""
|
||||||
return sexp_call("cart-badge", icon=icon, text=f"{count} {label}{s}")
|
return sx_call("cart-badge", icon=icon, text=f"{count} {label}{s}")
|
||||||
|
|
||||||
|
|
||||||
def _page_group_card_sexp(grp: Any, ctx: dict) -> str:
|
def _page_group_card_sx(grp: Any, ctx: dict) -> str:
|
||||||
"""Render a single page group card for cart overview."""
|
"""Render a single page group card for cart overview."""
|
||||||
post = grp.get("post") if isinstance(grp, dict) else getattr(grp, "post", None)
|
post = grp.get("post") if isinstance(grp, dict) else getattr(grp, "post", None)
|
||||||
cart_items = grp.get("cart_items", []) if isinstance(grp, dict) else getattr(grp, "cart_items", [])
|
cart_items = grp.get("cart_items", []) if isinstance(grp, dict) else getattr(grp, "cart_items", [])
|
||||||
@@ -149,13 +149,13 @@ def _page_group_card_sexp(grp: Any, ctx: dict) -> str:
|
|||||||
# Count badges
|
# Count badges
|
||||||
badge_parts = []
|
badge_parts = []
|
||||||
if product_count > 0:
|
if product_count > 0:
|
||||||
badge_parts.append(_badge_sexp("fa fa-box-open", product_count, "item"))
|
badge_parts.append(_badge_sx("fa fa-box-open", product_count, "item"))
|
||||||
if calendar_count > 0:
|
if calendar_count > 0:
|
||||||
badge_parts.append(_badge_sexp("fa fa-calendar", calendar_count, "booking"))
|
badge_parts.append(_badge_sx("fa fa-calendar", calendar_count, "booking"))
|
||||||
if ticket_count > 0:
|
if ticket_count > 0:
|
||||||
badge_parts.append(_badge_sexp("fa fa-ticket", ticket_count, "ticket"))
|
badge_parts.append(_badge_sx("fa fa-ticket", ticket_count, "ticket"))
|
||||||
badges_sexp = "(<> " + " ".join(badge_parts) + ")" if badge_parts else '""'
|
badges_sx = "(<> " + " ".join(badge_parts) + ")" if badge_parts else '""'
|
||||||
badges_wrap = sexp_call("cart-badges-wrap", badges=SexpExpr(badges_sexp))
|
badges_wrap = sx_call("cart-badges-wrap", badges=SxExpr(badges_sx))
|
||||||
|
|
||||||
if post:
|
if post:
|
||||||
slug = post.slug if hasattr(post, "slug") else post.get("slug", "")
|
slug = post.slug if hasattr(post, "slug") else post.get("slug", "")
|
||||||
@@ -164,58 +164,58 @@ def _page_group_card_sexp(grp: Any, ctx: dict) -> str:
|
|||||||
cart_href = call_url(ctx, "cart_url", f"/{slug}/")
|
cart_href = call_url(ctx, "cart_url", f"/{slug}/")
|
||||||
|
|
||||||
if feature_image:
|
if feature_image:
|
||||||
img = sexp_call("cart-group-card-img", src=feature_image, alt=title)
|
img = sx_call("cart-group-card-img", src=feature_image, alt=title)
|
||||||
else:
|
else:
|
||||||
img = sexp_call("cart-group-card-placeholder")
|
img = sx_call("cart-group-card-placeholder")
|
||||||
|
|
||||||
mp_sub = ""
|
mp_sub = ""
|
||||||
if market_place:
|
if market_place:
|
||||||
mp_name = market_place.name if hasattr(market_place, "name") else market_place.get("name", "")
|
mp_name = market_place.name if hasattr(market_place, "name") else market_place.get("name", "")
|
||||||
mp_sub = sexp_call("cart-mp-subtitle", title=title)
|
mp_sub = sx_call("cart-mp-subtitle", title=title)
|
||||||
else:
|
else:
|
||||||
mp_name = ""
|
mp_name = ""
|
||||||
display_title = mp_name or title
|
display_title = mp_name or title
|
||||||
|
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"cart-group-card",
|
"cart-group-card",
|
||||||
href=cart_href, img=SexpExpr(img), display_title=display_title,
|
href=cart_href, img=SxExpr(img), display_title=display_title,
|
||||||
subtitle=SexpExpr(mp_sub) if mp_sub else None,
|
subtitle=SxExpr(mp_sub) if mp_sub else None,
|
||||||
badges=SexpExpr(badges_wrap),
|
badges=SxExpr(badges_wrap),
|
||||||
total=f"\u00a3{total:.2f}",
|
total=f"\u00a3{total:.2f}",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Orphan items
|
# Orphan items
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"cart-orphan-card",
|
"cart-orphan-card",
|
||||||
badges=SexpExpr(badges_wrap),
|
badges=SxExpr(badges_wrap),
|
||||||
total=f"\u00a3{total:.2f}",
|
total=f"\u00a3{total:.2f}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _empty_cart_sexp() -> str:
|
def _empty_cart_sx() -> str:
|
||||||
"""Empty cart state."""
|
"""Empty cart state."""
|
||||||
return sexp_call("cart-empty")
|
return sx_call("cart-empty")
|
||||||
|
|
||||||
|
|
||||||
def _overview_main_panel_sexp(page_groups: list, ctx: dict) -> str:
|
def _overview_main_panel_sx(page_groups: list, ctx: dict) -> str:
|
||||||
"""Cart overview main panel."""
|
"""Cart overview main panel."""
|
||||||
if not page_groups:
|
if not page_groups:
|
||||||
return _empty_cart_sexp()
|
return _empty_cart_sx()
|
||||||
|
|
||||||
cards = [_page_group_card_sexp(grp, ctx) for grp in page_groups]
|
cards = [_page_group_card_sx(grp, ctx) for grp in page_groups]
|
||||||
has_items = any(c for c in cards)
|
has_items = any(c for c in cards)
|
||||||
if not has_items:
|
if not has_items:
|
||||||
return _empty_cart_sexp()
|
return _empty_cart_sx()
|
||||||
|
|
||||||
cards_sexp = "(<> " + " ".join(c for c in cards if c) + ")"
|
cards_sx = "(<> " + " ".join(c for c in cards if c) + ")"
|
||||||
return sexp_call("cart-overview-panel", cards=SexpExpr(cards_sexp))
|
return sx_call("cart-overview-panel", cards=SxExpr(cards_sx))
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Page cart
|
# Page cart
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _cart_item_sexp(item: Any, ctx: dict) -> str:
|
def _cart_item_sx(item: Any, ctx: dict) -> str:
|
||||||
"""Render a single product cart item."""
|
"""Render a single product cart item."""
|
||||||
from shared.browser.app.csrf import generate_csrf_token
|
from shared.browser.app.csrf import generate_csrf_token
|
||||||
from quart import url_for
|
from quart import url_for
|
||||||
@@ -230,41 +230,41 @@ def _cart_item_sexp(item: Any, ctx: dict) -> str:
|
|||||||
prod_url = market_product_url(slug)
|
prod_url = market_product_url(slug)
|
||||||
|
|
||||||
if p.image:
|
if p.image:
|
||||||
img = sexp_call("cart-item-img", src=p.image, alt=p.title)
|
img = sx_call("cart-item-img", src=p.image, alt=p.title)
|
||||||
else:
|
else:
|
||||||
img = sexp_call("cart-item-no-img")
|
img = sx_call("cart-item-no-img")
|
||||||
|
|
||||||
price_parts = []
|
price_parts = []
|
||||||
if unit_price:
|
if unit_price:
|
||||||
price_parts.append(sexp_call("cart-item-price", text=f"{symbol}{unit_price:.2f}"))
|
price_parts.append(sx_call("cart-item-price", text=f"{symbol}{unit_price:.2f}"))
|
||||||
if p.special_price and p.special_price != p.regular_price:
|
if p.special_price and p.special_price != p.regular_price:
|
||||||
price_parts.append(sexp_call("cart-item-price-was", text=f"{symbol}{p.regular_price:.2f}"))
|
price_parts.append(sx_call("cart-item-price-was", text=f"{symbol}{p.regular_price:.2f}"))
|
||||||
else:
|
else:
|
||||||
price_parts.append(sexp_call("cart-item-no-price"))
|
price_parts.append(sx_call("cart-item-no-price"))
|
||||||
price_sexp = "(<> " + " ".join(price_parts) + ")" if len(price_parts) > 1 else price_parts[0]
|
price_sx = "(<> " + " ".join(price_parts) + ")" if len(price_parts) > 1 else price_parts[0]
|
||||||
|
|
||||||
deleted_sexp = sexp_call("cart-item-deleted") if getattr(item, "is_deleted", False) else None
|
deleted_sx = sx_call("cart-item-deleted") if getattr(item, "is_deleted", False) else None
|
||||||
|
|
||||||
brand_sexp = sexp_call("cart-item-brand", brand=p.brand) if getattr(p, "brand", None) else None
|
brand_sx = sx_call("cart-item-brand", brand=p.brand) if getattr(p, "brand", None) else None
|
||||||
|
|
||||||
line_total_sexp = None
|
line_total_sx = None
|
||||||
if unit_price:
|
if unit_price:
|
||||||
lt = unit_price * item.quantity
|
lt = unit_price * item.quantity
|
||||||
line_total_sexp = sexp_call("cart-item-line-total", text=f"Line total: {symbol}{lt:.2f}")
|
line_total_sx = sx_call("cart-item-line-total", text=f"Line total: {symbol}{lt:.2f}")
|
||||||
|
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"cart-item",
|
"cart-item",
|
||||||
id=f"cart-item-{slug}", img=SexpExpr(img), prod_url=prod_url, title=p.title,
|
id=f"cart-item-{slug}", img=SxExpr(img), prod_url=prod_url, title=p.title,
|
||||||
brand=SexpExpr(brand_sexp) if brand_sexp else None,
|
brand=SxExpr(brand_sx) if brand_sx else None,
|
||||||
deleted=SexpExpr(deleted_sexp) if deleted_sexp else None,
|
deleted=SxExpr(deleted_sx) if deleted_sx else None,
|
||||||
price=SexpExpr(price_sexp),
|
price=SxExpr(price_sx),
|
||||||
qty_url=qty_url, csrf=csrf, minus=str(item.quantity - 1),
|
qty_url=qty_url, csrf=csrf, minus=str(item.quantity - 1),
|
||||||
qty=str(item.quantity), plus=str(item.quantity + 1),
|
qty=str(item.quantity), plus=str(item.quantity + 1),
|
||||||
line_total=SexpExpr(line_total_sexp) if line_total_sexp else None,
|
line_total=SxExpr(line_total_sx) if line_total_sx else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _calendar_entries_sexp(entries: list) -> str:
|
def _calendar_entries_sx(entries: list) -> str:
|
||||||
"""Render calendar booking entries in cart."""
|
"""Render calendar booking entries in cart."""
|
||||||
if not entries:
|
if not entries:
|
||||||
return ""
|
return ""
|
||||||
@@ -275,15 +275,15 @@ def _calendar_entries_sexp(entries: list) -> str:
|
|||||||
end = getattr(e, "end_at", None)
|
end = getattr(e, "end_at", None)
|
||||||
cost = getattr(e, "cost", 0) or 0
|
cost = getattr(e, "cost", 0) or 0
|
||||||
end_str = f" \u2013 {end}" if end else ""
|
end_str = f" \u2013 {end}" if end else ""
|
||||||
parts.append(sexp_call(
|
parts.append(sx_call(
|
||||||
"cart-cal-entry",
|
"cart-cal-entry",
|
||||||
name=name, date_str=f"{start}{end_str}", cost=f"\u00a3{cost:.2f}",
|
name=name, date_str=f"{start}{end_str}", cost=f"\u00a3{cost:.2f}",
|
||||||
))
|
))
|
||||||
items_sexp = "(<> " + " ".join(parts) + ")"
|
items_sx = "(<> " + " ".join(parts) + ")"
|
||||||
return sexp_call("cart-cal-section", items=SexpExpr(items_sexp))
|
return sx_call("cart-cal-section", items=SxExpr(items_sx))
|
||||||
|
|
||||||
|
|
||||||
def _ticket_groups_sexp(ticket_groups: list, ctx: dict) -> str:
|
def _ticket_groups_sx(ticket_groups: list, ctx: dict) -> str:
|
||||||
"""Render ticket groups in cart."""
|
"""Render ticket groups in cart."""
|
||||||
if not ticket_groups:
|
if not ticket_groups:
|
||||||
return ""
|
return ""
|
||||||
@@ -309,26 +309,26 @@ def _ticket_groups_sexp(ticket_groups: list, ctx: dict) -> str:
|
|||||||
if end_at:
|
if end_at:
|
||||||
date_str += f" \u2013 {end_at.strftime('%-d %b %Y, %H:%M')}"
|
date_str += f" \u2013 {end_at.strftime('%-d %b %Y, %H:%M')}"
|
||||||
|
|
||||||
tt_name_sexp = sexp_call("cart-ticket-type-name", name=tt_name) if tt_name else None
|
tt_name_sx = sx_call("cart-ticket-type-name", name=tt_name) if tt_name else None
|
||||||
tt_hidden_sexp = sexp_call("cart-ticket-type-hidden", value=str(tt_id)) if tt_id else None
|
tt_hidden_sx = sx_call("cart-ticket-type-hidden", value=str(tt_id)) if tt_id else None
|
||||||
|
|
||||||
parts.append(sexp_call(
|
parts.append(sx_call(
|
||||||
"cart-ticket-article",
|
"cart-ticket-article",
|
||||||
name=name,
|
name=name,
|
||||||
type_name=SexpExpr(tt_name_sexp) if tt_name_sexp else None,
|
type_name=SxExpr(tt_name_sx) if tt_name_sx else None,
|
||||||
date_str=date_str,
|
date_str=date_str,
|
||||||
price=f"\u00a3{price or 0:.2f}", qty_url=qty_url, csrf=csrf,
|
price=f"\u00a3{price or 0:.2f}", qty_url=qty_url, csrf=csrf,
|
||||||
entry_id=str(entry_id),
|
entry_id=str(entry_id),
|
||||||
type_hidden=SexpExpr(tt_hidden_sexp) if tt_hidden_sexp else None,
|
type_hidden=SxExpr(tt_hidden_sx) if tt_hidden_sx else None,
|
||||||
minus=str(max(quantity - 1, 0)), qty=str(quantity),
|
minus=str(max(quantity - 1, 0)), qty=str(quantity),
|
||||||
plus=str(quantity + 1), line_total=f"Line total: \u00a3{line_total:.2f}",
|
plus=str(quantity + 1), line_total=f"Line total: \u00a3{line_total:.2f}",
|
||||||
))
|
))
|
||||||
|
|
||||||
items_sexp = "(<> " + " ".join(parts) + ")"
|
items_sx = "(<> " + " ".join(parts) + ")"
|
||||||
return sexp_call("cart-tickets-section", items=SexpExpr(items_sexp))
|
return sx_call("cart-tickets-section", items=SxExpr(items_sx))
|
||||||
|
|
||||||
|
|
||||||
def _cart_summary_sexp(ctx: dict, cart: list, cal_entries: list, tickets: list,
|
def _cart_summary_sx(ctx: dict, cart: list, cal_entries: list, tickets: list,
|
||||||
total_fn: Any, cal_total_fn: Any, ticket_total_fn: Any) -> str:
|
total_fn: Any, cal_total_fn: Any, ticket_total_fn: Any) -> str:
|
||||||
"""Render the order summary sidebar."""
|
"""Render the order summary sidebar."""
|
||||||
from shared.browser.app.csrf import generate_csrf_token
|
from shared.browser.app.csrf import generate_csrf_token
|
||||||
@@ -360,41 +360,41 @@ def _cart_summary_sexp(ctx: dict, cart: list, cal_entries: list, tickets: list,
|
|||||||
action = url_for("cart_global.checkout")
|
action = url_for("cart_global.checkout")
|
||||||
from shared.utils import route_prefix
|
from shared.utils import route_prefix
|
||||||
action = route_prefix() + action
|
action = route_prefix() + action
|
||||||
checkout_sexp = sexp_call(
|
checkout_sx = sx_call(
|
||||||
"cart-checkout-form",
|
"cart-checkout-form",
|
||||||
action=action, csrf=csrf, label=f" Checkout as {user.email}",
|
action=action, csrf=csrf, label=f" Checkout as {user.email}",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
href = login_url(request.url)
|
href = login_url(request.url)
|
||||||
checkout_sexp = sexp_call("cart-checkout-signin", href=href)
|
checkout_sx = sx_call("cart-checkout-signin", href=href)
|
||||||
|
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"cart-summary-panel",
|
"cart-summary-panel",
|
||||||
item_count=str(item_count), subtotal=f"{symbol}{grand:.2f}",
|
item_count=str(item_count), subtotal=f"{symbol}{grand:.2f}",
|
||||||
checkout=SexpExpr(checkout_sexp),
|
checkout=SxExpr(checkout_sx),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _page_cart_main_panel_sexp(ctx: dict, cart: list, cal_entries: list,
|
def _page_cart_main_panel_sx(ctx: dict, cart: list, cal_entries: list,
|
||||||
tickets: list, ticket_groups: list,
|
tickets: list, ticket_groups: list,
|
||||||
total_fn: Any, cal_total_fn: Any,
|
total_fn: Any, cal_total_fn: Any,
|
||||||
ticket_total_fn: Any) -> str:
|
ticket_total_fn: Any) -> str:
|
||||||
"""Page cart main panel."""
|
"""Page cart main panel."""
|
||||||
if not cart and not cal_entries and not tickets:
|
if not cart and not cal_entries and not tickets:
|
||||||
return sexp_call("cart-page-empty")
|
return sx_call("cart-page-empty")
|
||||||
|
|
||||||
item_parts = [_cart_item_sexp(item, ctx) for item in cart]
|
item_parts = [_cart_item_sx(item, ctx) for item in cart]
|
||||||
items_sexp = "(<> " + " ".join(item_parts) + ")" if item_parts else '""'
|
items_sx = "(<> " + " ".join(item_parts) + ")" if item_parts else '""'
|
||||||
cal_sexp = _calendar_entries_sexp(cal_entries)
|
cal_sx = _calendar_entries_sx(cal_entries)
|
||||||
tickets_sexp = _ticket_groups_sexp(ticket_groups, ctx)
|
tickets_sx = _ticket_groups_sx(ticket_groups, ctx)
|
||||||
summary_sexp = _cart_summary_sexp(ctx, cart, cal_entries, tickets, total_fn, cal_total_fn, ticket_total_fn)
|
summary_sx = _cart_summary_sx(ctx, cart, cal_entries, tickets, total_fn, cal_total_fn, ticket_total_fn)
|
||||||
|
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"cart-page-panel",
|
"cart-page-panel",
|
||||||
items=SexpExpr(items_sexp),
|
items=SxExpr(items_sx),
|
||||||
cal=SexpExpr(cal_sexp) if cal_sexp else None,
|
cal=SxExpr(cal_sx) if cal_sx else None,
|
||||||
tickets=SexpExpr(tickets_sexp) if tickets_sexp else None,
|
tickets=SxExpr(tickets_sx) if tickets_sx else None,
|
||||||
summary=SexpExpr(summary_sexp),
|
summary=SxExpr(summary_sx),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -402,7 +402,7 @@ def _page_cart_main_panel_sexp(ctx: dict, cart: list, cal_entries: list,
|
|||||||
# Orders list (same pattern as orders service)
|
# Orders list (same pattern as orders service)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _order_row_sexp(order: Any, detail_url: str) -> str:
|
def _order_row_sx(order: Any, detail_url: str) -> str:
|
||||||
"""Render a single order as desktop table row + mobile card."""
|
"""Render a single order as desktop table row + mobile card."""
|
||||||
status = order.status or "pending"
|
status = order.status or "pending"
|
||||||
sl = status.lower()
|
sl = status.lower()
|
||||||
@@ -415,14 +415,14 @@ def _order_row_sexp(order: Any, detail_url: str) -> str:
|
|||||||
created = order.created_at.strftime("%-d %b %Y, %H:%M") if order.created_at else "\u2014"
|
created = order.created_at.strftime("%-d %b %Y, %H:%M") if order.created_at else "\u2014"
|
||||||
total = f"{order.currency or 'GBP'} {order.total_amount or 0:.2f}"
|
total = f"{order.currency or 'GBP'} {order.total_amount or 0:.2f}"
|
||||||
|
|
||||||
desktop = sexp_call(
|
desktop = sx_call(
|
||||||
"cart-order-row-desktop",
|
"cart-order-row-desktop",
|
||||||
order_id=f"#{order.id}", created=created, desc=order.description or "",
|
order_id=f"#{order.id}", created=created, desc=order.description or "",
|
||||||
total=total, pill=pill_cls, status=status, detail_url=detail_url,
|
total=total, pill=pill_cls, status=status, detail_url=detail_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
mobile_pill = f"inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] {pill}"
|
mobile_pill = f"inline-flex items-center rounded-full border px-2 py-0.5 text-[11px] {pill}"
|
||||||
mobile = sexp_call(
|
mobile = sx_call(
|
||||||
"cart-order-row-mobile",
|
"cart-order-row-mobile",
|
||||||
order_id=f"#{order.id}", pill=mobile_pill, status=status,
|
order_id=f"#{order.id}", pill=mobile_pill, status=status,
|
||||||
created=created, total=total, detail_url=detail_url,
|
created=created, total=total, detail_url=detail_url,
|
||||||
@@ -431,47 +431,47 @@ def _order_row_sexp(order: Any, detail_url: str) -> str:
|
|||||||
return "(<> " + desktop + " " + mobile + ")"
|
return "(<> " + desktop + " " + mobile + ")"
|
||||||
|
|
||||||
|
|
||||||
def _orders_rows_sexp(orders: list, page: int, total_pages: int,
|
def _orders_rows_sx(orders: list, page: int, total_pages: int,
|
||||||
url_for_fn: Any, qs_fn: Any) -> str:
|
url_for_fn: Any, qs_fn: Any) -> str:
|
||||||
"""Render order rows + infinite scroll sentinel."""
|
"""Render order rows + infinite scroll sentinel."""
|
||||||
from shared.utils import route_prefix
|
from shared.utils import route_prefix
|
||||||
pfx = route_prefix()
|
pfx = route_prefix()
|
||||||
|
|
||||||
parts = [
|
parts = [
|
||||||
_order_row_sexp(o, pfx + url_for_fn("orders.order.order_detail", order_id=o.id))
|
_order_row_sx(o, pfx + url_for_fn("orders.order.order_detail", order_id=o.id))
|
||||||
for o in orders
|
for o in orders
|
||||||
]
|
]
|
||||||
|
|
||||||
if page < total_pages:
|
if page < total_pages:
|
||||||
next_url = pfx + url_for_fn("orders.list_orders") + qs_fn(page=page + 1)
|
next_url = pfx + url_for_fn("orders.list_orders") + qs_fn(page=page + 1)
|
||||||
parts.append(sexp_call(
|
parts.append(sx_call(
|
||||||
"infinite-scroll",
|
"infinite-scroll",
|
||||||
url=next_url, page=page, total_pages=total_pages,
|
url=next_url, page=page, total_pages=total_pages,
|
||||||
id_prefix="orders", colspan=5,
|
id_prefix="orders", colspan=5,
|
||||||
))
|
))
|
||||||
else:
|
else:
|
||||||
parts.append(sexp_call("cart-orders-end"))
|
parts.append(sx_call("cart-orders-end"))
|
||||||
|
|
||||||
return "(<> " + " ".join(parts) + ")"
|
return "(<> " + " ".join(parts) + ")"
|
||||||
|
|
||||||
|
|
||||||
def _orders_main_panel_sexp(orders: list, rows_sexp: str) -> str:
|
def _orders_main_panel_sx(orders: list, rows_sx: str) -> str:
|
||||||
"""Main panel for orders list."""
|
"""Main panel for orders list."""
|
||||||
if not orders:
|
if not orders:
|
||||||
return sexp_call("cart-orders-empty")
|
return sx_call("cart-orders-empty")
|
||||||
return sexp_call("cart-orders-table", rows=SexpExpr(rows_sexp))
|
return sx_call("cart-orders-table", rows=SxExpr(rows_sx))
|
||||||
|
|
||||||
|
|
||||||
def _orders_summary_sexp(ctx: dict) -> str:
|
def _orders_summary_sx(ctx: dict) -> str:
|
||||||
"""Filter section for orders list."""
|
"""Filter section for orders list."""
|
||||||
return sexp_call("cart-orders-filter", search_mobile=SexpExpr(search_mobile_sexp(ctx)))
|
return sx_call("cart-orders-filter", search_mobile=SxExpr(search_mobile_sx(ctx)))
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Single order detail
|
# Single order detail
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _order_items_sexp(order: Any) -> str:
|
def _order_items_sx(order: Any) -> str:
|
||||||
"""Render order items list."""
|
"""Render order items list."""
|
||||||
if not order or not order.items:
|
if not order or not order.items:
|
||||||
return ""
|
return ""
|
||||||
@@ -479,27 +479,27 @@ def _order_items_sexp(order: Any) -> str:
|
|||||||
for item in order.items:
|
for item in order.items:
|
||||||
prod_url = market_product_url(item.product_slug)
|
prod_url = market_product_url(item.product_slug)
|
||||||
if item.product_image:
|
if item.product_image:
|
||||||
img = sexp_call(
|
img = sx_call(
|
||||||
"cart-order-item-img",
|
"cart-order-item-img",
|
||||||
src=item.product_image, alt=item.product_title or "Product image",
|
src=item.product_image, alt=item.product_title or "Product image",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
img = sexp_call("cart-order-item-no-img")
|
img = sx_call("cart-order-item-no-img")
|
||||||
parts.append(sexp_call(
|
parts.append(sx_call(
|
||||||
"cart-order-item",
|
"cart-order-item",
|
||||||
prod_url=prod_url, img=SexpExpr(img),
|
prod_url=prod_url, img=SxExpr(img),
|
||||||
title=item.product_title or "Unknown product",
|
title=item.product_title or "Unknown product",
|
||||||
product_id=f"Product ID: {item.product_id}",
|
product_id=f"Product ID: {item.product_id}",
|
||||||
qty=f"Qty: {item.quantity}",
|
qty=f"Qty: {item.quantity}",
|
||||||
price=f"{item.currency or order.currency or 'GBP'} {item.unit_price or 0:.2f}",
|
price=f"{item.currency or order.currency or 'GBP'} {item.unit_price or 0:.2f}",
|
||||||
))
|
))
|
||||||
items_sexp = "(<> " + " ".join(parts) + ")"
|
items_sx = "(<> " + " ".join(parts) + ")"
|
||||||
return sexp_call("cart-order-items-panel", items=SexpExpr(items_sexp))
|
return sx_call("cart-order-items-panel", items=SxExpr(items_sx))
|
||||||
|
|
||||||
|
|
||||||
def _order_summary_sexp(order: Any) -> str:
|
def _order_summary_sx(order: Any) -> str:
|
||||||
"""Order summary card."""
|
"""Order summary card."""
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"order-summary-card",
|
"order-summary-card",
|
||||||
order_id=order.id,
|
order_id=order.id,
|
||||||
created_at=order.created_at.strftime("%-d %b %Y, %H:%M") if order.created_at else None,
|
created_at=order.created_at.strftime("%-d %b %Y, %H:%M") if order.created_at else None,
|
||||||
@@ -508,7 +508,7 @@ def _order_summary_sexp(order: Any) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _order_calendar_items_sexp(calendar_entries: list | None) -> str:
|
def _order_calendar_items_sx(calendar_entries: list | None) -> str:
|
||||||
"""Render calendar bookings for an order."""
|
"""Render calendar bookings for an order."""
|
||||||
if not calendar_entries:
|
if not calendar_entries:
|
||||||
return ""
|
return ""
|
||||||
@@ -525,43 +525,43 @@ def _order_calendar_items_sexp(calendar_entries: list | None) -> str:
|
|||||||
ds = e.start_at.strftime("%-d %b %Y, %H:%M") if e.start_at else ""
|
ds = e.start_at.strftime("%-d %b %Y, %H:%M") if e.start_at else ""
|
||||||
if e.end_at:
|
if e.end_at:
|
||||||
ds += f" \u2013 {e.end_at.strftime('%-d %b %Y, %H:%M')}"
|
ds += f" \u2013 {e.end_at.strftime('%-d %b %Y, %H:%M')}"
|
||||||
parts.append(sexp_call(
|
parts.append(sx_call(
|
||||||
"cart-order-cal-entry",
|
"cart-order-cal-entry",
|
||||||
name=e.name, pill=pill_cls, status=st.capitalize(),
|
name=e.name, pill=pill_cls, status=st.capitalize(),
|
||||||
date_str=ds, cost=f"\u00a3{e.cost or 0:.2f}",
|
date_str=ds, cost=f"\u00a3{e.cost or 0:.2f}",
|
||||||
))
|
))
|
||||||
items_sexp = "(<> " + " ".join(parts) + ")"
|
items_sx = "(<> " + " ".join(parts) + ")"
|
||||||
return sexp_call("cart-order-cal-section", items=SexpExpr(items_sexp))
|
return sx_call("cart-order-cal-section", items=SxExpr(items_sx))
|
||||||
|
|
||||||
|
|
||||||
def _order_main_sexp(order: Any, calendar_entries: list | None) -> str:
|
def _order_main_sx(order: Any, calendar_entries: list | None) -> str:
|
||||||
"""Main panel for single order detail."""
|
"""Main panel for single order detail."""
|
||||||
summary = _order_summary_sexp(order)
|
summary = _order_summary_sx(order)
|
||||||
items = _order_items_sexp(order)
|
items = _order_items_sx(order)
|
||||||
cal = _order_calendar_items_sexp(calendar_entries)
|
cal = _order_calendar_items_sx(calendar_entries)
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"cart-order-main",
|
"cart-order-main",
|
||||||
summary=SexpExpr(summary),
|
summary=SxExpr(summary),
|
||||||
items=SexpExpr(items) if items else None,
|
items=SxExpr(items) if items else None,
|
||||||
cal=SexpExpr(cal) if cal else None,
|
cal=SxExpr(cal) if cal else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _order_filter_sexp(order: Any, list_url: str, recheck_url: str,
|
def _order_filter_sx(order: Any, list_url: str, recheck_url: str,
|
||||||
pay_url: str, csrf_token: str) -> str:
|
pay_url: str, csrf_token: str) -> str:
|
||||||
"""Filter section for single order detail."""
|
"""Filter section for single order detail."""
|
||||||
created = order.created_at.strftime("%-d %b %Y, %H:%M") if order.created_at else "\u2014"
|
created = order.created_at.strftime("%-d %b %Y, %H:%M") if order.created_at else "\u2014"
|
||||||
status = order.status or "pending"
|
status = order.status or "pending"
|
||||||
|
|
||||||
pay_sexp = None
|
pay_sx = None
|
||||||
if status != "paid":
|
if status != "paid":
|
||||||
pay_sexp = sexp_call("cart-order-pay-btn", url=pay_url)
|
pay_sx = sx_call("cart-order-pay-btn", url=pay_url)
|
||||||
|
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"cart-order-filter",
|
"cart-order-filter",
|
||||||
info=f"Placed {created} \u00b7 Status: {status}",
|
info=f"Placed {created} \u00b7 Status: {status}",
|
||||||
list_url=list_url, recheck_url=recheck_url, csrf=csrf_token,
|
list_url=list_url, recheck_url=recheck_url, csrf=csrf_token,
|
||||||
pay=SexpExpr(pay_sexp) if pay_sexp else None,
|
pay=SxExpr(pay_sx) if pay_sx else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -571,16 +571,16 @@ def _order_filter_sexp(order: Any, list_url: str, recheck_url: str,
|
|||||||
|
|
||||||
async def render_overview_page(ctx: dict, page_groups: list) -> str:
|
async def render_overview_page(ctx: dict, page_groups: list) -> str:
|
||||||
"""Full page: cart overview."""
|
"""Full page: cart overview."""
|
||||||
main = _overview_main_panel_sexp(page_groups, ctx)
|
main = _overview_main_panel_sx(page_groups, ctx)
|
||||||
hdr = root_header_sexp(ctx)
|
hdr = root_header_sx(ctx)
|
||||||
return full_page_sexp(ctx, header_rows=hdr, content=main)
|
return full_page_sx(ctx, header_rows=hdr, content=main)
|
||||||
|
|
||||||
|
|
||||||
async def render_overview_oob(ctx: dict, page_groups: list) -> str:
|
async def render_overview_oob(ctx: dict, page_groups: list) -> str:
|
||||||
"""OOB response for cart overview."""
|
"""OOB response for cart overview."""
|
||||||
main = _overview_main_panel_sexp(page_groups, ctx)
|
main = _overview_main_panel_sx(page_groups, ctx)
|
||||||
oobs = root_header_sexp(ctx, oob=True)
|
oobs = root_header_sx(ctx, oob=True)
|
||||||
return oob_page_sexp(oobs=oobs, content=main)
|
return oob_page_sx(oobs=oobs, content=main)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -592,17 +592,17 @@ async def render_page_cart_page(ctx: dict, page_post: Any,
|
|||||||
ticket_groups: list, total_fn: Any,
|
ticket_groups: list, total_fn: Any,
|
||||||
cal_total_fn: Any, ticket_total_fn: Any) -> str:
|
cal_total_fn: Any, ticket_total_fn: Any) -> str:
|
||||||
"""Full page: page-specific cart."""
|
"""Full page: page-specific cart."""
|
||||||
main = _page_cart_main_panel_sexp(ctx, cart, cal_entries, tickets, ticket_groups,
|
main = _page_cart_main_panel_sx(ctx, cart, cal_entries, tickets, ticket_groups,
|
||||||
total_fn, cal_total_fn, ticket_total_fn)
|
total_fn, cal_total_fn, ticket_total_fn)
|
||||||
hdr = root_header_sexp(ctx)
|
hdr = root_header_sx(ctx)
|
||||||
child = _cart_header_sexp(ctx)
|
child = _cart_header_sx(ctx)
|
||||||
page_hdr = _page_cart_header_sexp(ctx, page_post)
|
page_hdr = _page_cart_header_sx(ctx, page_post)
|
||||||
nested = sexp_call(
|
nested = sx_call(
|
||||||
"cart-header-child-nested",
|
"cart-header-child-nested",
|
||||||
outer=SexpExpr(child), inner=SexpExpr(page_hdr),
|
outer=SxExpr(child), inner=SxExpr(page_hdr),
|
||||||
)
|
)
|
||||||
header_rows = "(<> " + hdr + " " + nested + ")"
|
header_rows = "(<> " + hdr + " " + nested + ")"
|
||||||
return full_page_sexp(ctx, header_rows=header_rows, content=main)
|
return full_page_sx(ctx, header_rows=header_rows, content=main)
|
||||||
|
|
||||||
|
|
||||||
async def render_page_cart_oob(ctx: dict, page_post: Any,
|
async def render_page_cart_oob(ctx: dict, page_post: Any,
|
||||||
@@ -610,14 +610,14 @@ async def render_page_cart_oob(ctx: dict, page_post: Any,
|
|||||||
ticket_groups: list, total_fn: Any,
|
ticket_groups: list, total_fn: Any,
|
||||||
cal_total_fn: Any, ticket_total_fn: Any) -> str:
|
cal_total_fn: Any, ticket_total_fn: Any) -> str:
|
||||||
"""OOB response for page cart."""
|
"""OOB response for page cart."""
|
||||||
main = _page_cart_main_panel_sexp(ctx, cart, cal_entries, tickets, ticket_groups,
|
main = _page_cart_main_panel_sx(ctx, cart, cal_entries, tickets, ticket_groups,
|
||||||
total_fn, cal_total_fn, ticket_total_fn)
|
total_fn, cal_total_fn, ticket_total_fn)
|
||||||
child_oob = sexp_call("cart-header-child-oob",
|
child_oob = sx_call("cart-header-child-oob",
|
||||||
inner=SexpExpr(_page_cart_header_sexp(ctx, page_post)))
|
inner=SxExpr(_page_cart_header_sx(ctx, page_post)))
|
||||||
cart_hdr_oob = _cart_header_sexp(ctx, oob=True)
|
cart_hdr_oob = _cart_header_sx(ctx, oob=True)
|
||||||
root_hdr_oob = root_header_sexp(ctx, oob=True)
|
root_hdr_oob = root_header_sx(ctx, oob=True)
|
||||||
oobs = "(<> " + child_oob + " " + cart_hdr_oob + " " + root_hdr_oob + ")"
|
oobs = "(<> " + child_oob + " " + cart_hdr_oob + " " + root_hdr_oob + ")"
|
||||||
return oob_page_sexp(oobs=oobs, content=main)
|
return oob_page_sx(oobs=oobs, content=main)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -635,21 +635,21 @@ async def render_orders_page(ctx: dict, orders: list, page: int,
|
|||||||
ctx["search_count"] = search_count
|
ctx["search_count"] = search_count
|
||||||
list_url = route_prefix() + url_for_fn("orders.list_orders")
|
list_url = route_prefix() + url_for_fn("orders.list_orders")
|
||||||
|
|
||||||
rows = _orders_rows_sexp(orders, page, total_pages, url_for_fn, qs_fn)
|
rows = _orders_rows_sx(orders, page, total_pages, url_for_fn, qs_fn)
|
||||||
main = _orders_main_panel_sexp(orders, rows)
|
main = _orders_main_panel_sx(orders, rows)
|
||||||
|
|
||||||
hdr = root_header_sexp(ctx)
|
hdr = root_header_sx(ctx)
|
||||||
auth = _auth_header_sexp(ctx)
|
auth = _auth_header_sx(ctx)
|
||||||
orders_hdr = _orders_header_sexp(ctx, list_url)
|
orders_hdr = _orders_header_sx(ctx, list_url)
|
||||||
auth_child = sexp_call(
|
auth_child = sx_call(
|
||||||
"cart-auth-header-child",
|
"cart-auth-header-child",
|
||||||
auth=SexpExpr(auth), orders=SexpExpr(orders_hdr),
|
auth=SxExpr(auth), orders=SxExpr(orders_hdr),
|
||||||
)
|
)
|
||||||
header_rows = "(<> " + hdr + " " + auth_child + ")"
|
header_rows = "(<> " + hdr + " " + auth_child + ")"
|
||||||
|
|
||||||
return full_page_sexp(ctx, header_rows=header_rows,
|
return full_page_sx(ctx, header_rows=header_rows,
|
||||||
filter=_orders_summary_sexp(ctx),
|
filter=_orders_summary_sx(ctx),
|
||||||
aside=search_desktop_sexp(ctx),
|
aside=search_desktop_sx(ctx),
|
||||||
content=main)
|
content=main)
|
||||||
|
|
||||||
|
|
||||||
@@ -657,7 +657,7 @@ async def render_orders_rows(ctx: dict, orders: list, page: int,
|
|||||||
total_pages: int, url_for_fn: Any,
|
total_pages: int, url_for_fn: Any,
|
||||||
qs_fn: Any) -> str:
|
qs_fn: Any) -> str:
|
||||||
"""Pagination: just the table rows."""
|
"""Pagination: just the table rows."""
|
||||||
return _orders_rows_sexp(orders, page, total_pages, url_for_fn, qs_fn)
|
return _orders_rows_sx(orders, page, total_pages, url_for_fn, qs_fn)
|
||||||
|
|
||||||
|
|
||||||
async def render_orders_oob(ctx: dict, orders: list, page: int,
|
async def render_orders_oob(ctx: dict, orders: list, page: int,
|
||||||
@@ -671,20 +671,20 @@ async def render_orders_oob(ctx: dict, orders: list, page: int,
|
|||||||
ctx["search_count"] = search_count
|
ctx["search_count"] = search_count
|
||||||
list_url = route_prefix() + url_for_fn("orders.list_orders")
|
list_url = route_prefix() + url_for_fn("orders.list_orders")
|
||||||
|
|
||||||
rows = _orders_rows_sexp(orders, page, total_pages, url_for_fn, qs_fn)
|
rows = _orders_rows_sx(orders, page, total_pages, url_for_fn, qs_fn)
|
||||||
main = _orders_main_panel_sexp(orders, rows)
|
main = _orders_main_panel_sx(orders, rows)
|
||||||
|
|
||||||
auth_oob = _auth_header_sexp(ctx, oob=True)
|
auth_oob = _auth_header_sx(ctx, oob=True)
|
||||||
auth_child_oob = sexp_call(
|
auth_child_oob = sx_call(
|
||||||
"cart-auth-header-child-oob",
|
"cart-auth-header-child-oob",
|
||||||
inner=SexpExpr(_orders_header_sexp(ctx, list_url)),
|
inner=SxExpr(_orders_header_sx(ctx, list_url)),
|
||||||
)
|
)
|
||||||
root_oob = root_header_sexp(ctx, oob=True)
|
root_oob = root_header_sx(ctx, oob=True)
|
||||||
oobs = "(<> " + auth_oob + " " + auth_child_oob + " " + root_oob + ")"
|
oobs = "(<> " + auth_oob + " " + auth_child_oob + " " + root_oob + ")"
|
||||||
|
|
||||||
return oob_page_sexp(oobs=oobs,
|
return oob_page_sx(oobs=oobs,
|
||||||
filter=_orders_summary_sexp(ctx),
|
filter=_orders_summary_sx(ctx),
|
||||||
aside=search_desktop_sexp(ctx),
|
aside=search_desktop_sx(ctx),
|
||||||
content=main)
|
content=main)
|
||||||
|
|
||||||
|
|
||||||
@@ -705,24 +705,24 @@ async def render_order_page(ctx: dict, order: Any,
|
|||||||
recheck_url = pfx + url_for_fn("orders.order.order_recheck", order_id=order.id)
|
recheck_url = pfx + url_for_fn("orders.order.order_recheck", order_id=order.id)
|
||||||
pay_url = pfx + url_for_fn("orders.order.order_pay", order_id=order.id)
|
pay_url = pfx + url_for_fn("orders.order.order_pay", order_id=order.id)
|
||||||
|
|
||||||
main = _order_main_sexp(order, calendar_entries)
|
main = _order_main_sx(order, calendar_entries)
|
||||||
filt = _order_filter_sexp(order, list_url, recheck_url, pay_url, generate_csrf_token())
|
filt = _order_filter_sx(order, list_url, recheck_url, pay_url, generate_csrf_token())
|
||||||
|
|
||||||
hdr = root_header_sexp(ctx)
|
hdr = root_header_sx(ctx)
|
||||||
order_row = sexp_call(
|
order_row = sx_call(
|
||||||
"menu-row-sx",
|
"menu-row-sx",
|
||||||
id="order-row", level=3, colour="sky",
|
id="order-row", level=3, colour="sky",
|
||||||
link_href=detail_url, link_label=f"Order {order.id}", icon="fa fa-gbp",
|
link_href=detail_url, link_label=f"Order {order.id}", icon="fa fa-gbp",
|
||||||
)
|
)
|
||||||
order_child = sexp_call(
|
order_child = sx_call(
|
||||||
"cart-order-header-child",
|
"cart-order-header-child",
|
||||||
auth=SexpExpr(_auth_header_sexp(ctx)),
|
auth=SxExpr(_auth_header_sx(ctx)),
|
||||||
orders=SexpExpr(_orders_header_sexp(ctx, list_url)),
|
orders=SxExpr(_orders_header_sx(ctx, list_url)),
|
||||||
order=SexpExpr(order_row),
|
order=SxExpr(order_row),
|
||||||
)
|
)
|
||||||
header_rows = "(<> " + hdr + " " + order_child + ")"
|
header_rows = "(<> " + hdr + " " + order_child + ")"
|
||||||
|
|
||||||
return full_page_sexp(ctx, header_rows=header_rows, filter=filt, content=main)
|
return full_page_sx(ctx, header_rows=header_rows, filter=filt, content=main)
|
||||||
|
|
||||||
|
|
||||||
async def render_order_oob(ctx: dict, order: Any,
|
async def render_order_oob(ctx: dict, order: Any,
|
||||||
@@ -738,66 +738,66 @@ async def render_order_oob(ctx: dict, order: Any,
|
|||||||
recheck_url = pfx + url_for_fn("orders.order.order_recheck", order_id=order.id)
|
recheck_url = pfx + url_for_fn("orders.order.order_recheck", order_id=order.id)
|
||||||
pay_url = pfx + url_for_fn("orders.order.order_pay", order_id=order.id)
|
pay_url = pfx + url_for_fn("orders.order.order_pay", order_id=order.id)
|
||||||
|
|
||||||
main = _order_main_sexp(order, calendar_entries)
|
main = _order_main_sx(order, calendar_entries)
|
||||||
filt = _order_filter_sexp(order, list_url, recheck_url, pay_url, generate_csrf_token())
|
filt = _order_filter_sx(order, list_url, recheck_url, pay_url, generate_csrf_token())
|
||||||
|
|
||||||
order_row_oob = sexp_call(
|
order_row_oob = sx_call(
|
||||||
"menu-row-sx",
|
"menu-row-sx",
|
||||||
id="order-row", level=3, colour="sky",
|
id="order-row", level=3, colour="sky",
|
||||||
link_href=detail_url, link_label=f"Order {order.id}", icon="fa fa-gbp",
|
link_href=detail_url, link_label=f"Order {order.id}", icon="fa fa-gbp",
|
||||||
oob=True,
|
oob=True,
|
||||||
)
|
)
|
||||||
orders_child_oob = sexp_call("cart-orders-header-child-oob",
|
orders_child_oob = sx_call("cart-orders-header-child-oob",
|
||||||
inner=SexpExpr(order_row_oob))
|
inner=SxExpr(order_row_oob))
|
||||||
root_oob = root_header_sexp(ctx, oob=True)
|
root_oob = root_header_sx(ctx, oob=True)
|
||||||
oobs = "(<> " + orders_child_oob + " " + root_oob + ")"
|
oobs = "(<> " + orders_child_oob + " " + root_oob + ")"
|
||||||
|
|
||||||
return oob_page_sexp(oobs=oobs, filter=filt, content=main)
|
return oob_page_sx(oobs=oobs, filter=filt, content=main)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Public API: Checkout error
|
# Public API: Checkout error
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _checkout_error_filter_sexp() -> str:
|
def _checkout_error_filter_sx() -> str:
|
||||||
return sexp_call("cart-checkout-error-filter")
|
return sx_call("cart-checkout-error-filter")
|
||||||
|
|
||||||
|
|
||||||
def _checkout_error_content_sexp(error: str | None, order: Any | None) -> str:
|
def _checkout_error_content_sx(error: str | None, order: Any | None) -> str:
|
||||||
err_msg = error or "Unexpected error while creating the hosted checkout session."
|
err_msg = error or "Unexpected error while creating the hosted checkout session."
|
||||||
order_sexp = None
|
order_sx = None
|
||||||
if order:
|
if order:
|
||||||
order_sexp = sexp_call("cart-checkout-error-order-id", order_id=f"#{order.id}")
|
order_sx = sx_call("cart-checkout-error-order-id", order_id=f"#{order.id}")
|
||||||
back_url = cart_url("/")
|
back_url = cart_url("/")
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"cart-checkout-error-content",
|
"cart-checkout-error-content",
|
||||||
error_msg=err_msg,
|
error_msg=err_msg,
|
||||||
order=SexpExpr(order_sexp) if order_sexp else None,
|
order=SxExpr(order_sx) if order_sx else None,
|
||||||
back_url=back_url,
|
back_url=back_url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def render_checkout_error_page(ctx: dict, error: str | None = None, order: Any | None = None) -> str:
|
async def render_checkout_error_page(ctx: dict, error: str | None = None, order: Any | None = None) -> str:
|
||||||
"""Full page: checkout error."""
|
"""Full page: checkout error."""
|
||||||
hdr = root_header_sexp(ctx)
|
hdr = root_header_sx(ctx)
|
||||||
filt = _checkout_error_filter_sexp()
|
filt = _checkout_error_filter_sx()
|
||||||
content = _checkout_error_content_sexp(error, order)
|
content = _checkout_error_content_sx(error, order)
|
||||||
return full_page_sexp(ctx, header_rows=hdr, filter=filt, content=content)
|
return full_page_sx(ctx, header_rows=hdr, filter=filt, content=content)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Page admin (/<page_slug>/admin/)
|
# Page admin (/<page_slug>/admin/)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _cart_page_admin_header_sexp(ctx: dict, page_post: Any, *, oob: bool = False,
|
def _cart_page_admin_header_sx(ctx: dict, page_post: Any, *, oob: bool = False,
|
||||||
selected: str = "") -> str:
|
selected: str = "") -> str:
|
||||||
"""Build the page-level admin header row -- delegates to shared helper."""
|
"""Build the page-level admin header row -- delegates to shared helper."""
|
||||||
slug = page_post.slug if page_post else ""
|
slug = page_post.slug if page_post else ""
|
||||||
ctx = _ensure_post_ctx(ctx, page_post)
|
ctx = _ensure_post_ctx(ctx, page_post)
|
||||||
return post_admin_header_sexp(ctx, slug, oob=oob, selected=selected)
|
return post_admin_header_sx(ctx, slug, oob=oob, selected=selected)
|
||||||
|
|
||||||
|
|
||||||
def _cart_admin_main_panel_sexp(ctx: dict) -> str:
|
def _cart_admin_main_panel_sx(ctx: dict) -> str:
|
||||||
"""Admin overview panel -- links to sub-admin pages."""
|
"""Admin overview panel -- links to sub-admin pages."""
|
||||||
from quart import url_for
|
from quart import url_for
|
||||||
payments_href = url_for("page_admin.payments")
|
payments_href = url_for("page_admin.payments")
|
||||||
@@ -809,7 +809,7 @@ def _cart_admin_main_panel_sexp(ctx: dict) -> str:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _cart_payments_main_panel_sexp(ctx: dict) -> str:
|
def _cart_payments_main_panel_sx(ctx: dict) -> str:
|
||||||
"""Render SumUp payment config form."""
|
"""Render SumUp payment config form."""
|
||||||
from quart import url_for
|
from quart import url_for
|
||||||
csrf_token = ctx.get("csrf_token")
|
csrf_token = ctx.get("csrf_token")
|
||||||
@@ -823,7 +823,7 @@ def _cart_payments_main_panel_sexp(ctx: dict) -> str:
|
|||||||
placeholder = "--------" if sumup_configured else "sup_sk_..."
|
placeholder = "--------" if sumup_configured else "sup_sk_..."
|
||||||
input_cls = "w-full px-3 py-1.5 text-sm border border-stone-300 rounded focus:ring-purple-500 focus:border-purple-500"
|
input_cls = "w-full px-3 py-1.5 text-sm border border-stone-300 rounded focus:ring-purple-500 focus:border-purple-500"
|
||||||
|
|
||||||
return sexp_call("cart-payments-panel",
|
return sx_call("cart-payments-panel",
|
||||||
update_url=update_url, csrf=csrf,
|
update_url=update_url, csrf=csrf,
|
||||||
merchant_code=merchant_code, placeholder=placeholder,
|
merchant_code=merchant_code, placeholder=placeholder,
|
||||||
input_cls=input_cls, sumup_configured=sumup_configured,
|
input_cls=input_cls, sumup_configured=sumup_configured,
|
||||||
@@ -836,19 +836,19 @@ def _cart_payments_main_panel_sexp(ctx: dict) -> str:
|
|||||||
|
|
||||||
async def render_cart_admin_page(ctx: dict, page_post: Any) -> str:
|
async def render_cart_admin_page(ctx: dict, page_post: Any) -> str:
|
||||||
"""Full page: cart page admin overview."""
|
"""Full page: cart page admin overview."""
|
||||||
content = _cart_admin_main_panel_sexp(ctx)
|
content = _cart_admin_main_panel_sx(ctx)
|
||||||
root_hdr = root_header_sexp(ctx)
|
root_hdr = root_header_sx(ctx)
|
||||||
post_hdr = await _post_header_sexp(ctx, page_post)
|
post_hdr = await _post_header_sx(ctx, page_post)
|
||||||
admin_hdr = _cart_page_admin_header_sexp(ctx, page_post)
|
admin_hdr = _cart_page_admin_header_sx(ctx, page_post)
|
||||||
header_rows = "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")"
|
header_rows = "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")"
|
||||||
return full_page_sexp(ctx, header_rows=header_rows, content=content)
|
return full_page_sx(ctx, header_rows=header_rows, content=content)
|
||||||
|
|
||||||
|
|
||||||
async def render_cart_admin_oob(ctx: dict, page_post: Any) -> str:
|
async def render_cart_admin_oob(ctx: dict, page_post: Any) -> str:
|
||||||
"""OOB response: cart page admin overview."""
|
"""OOB response: cart page admin overview."""
|
||||||
content = _cart_admin_main_panel_sexp(ctx)
|
content = _cart_admin_main_panel_sx(ctx)
|
||||||
oobs = _cart_page_admin_header_sexp(ctx, page_post, oob=True)
|
oobs = _cart_page_admin_header_sx(ctx, page_post, oob=True)
|
||||||
return oob_page_sexp(oobs=oobs, content=content)
|
return oob_page_sx(oobs=oobs, content=content)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -857,21 +857,21 @@ async def render_cart_admin_oob(ctx: dict, page_post: Any) -> str:
|
|||||||
|
|
||||||
async def render_cart_payments_page(ctx: dict, page_post: Any) -> str:
|
async def render_cart_payments_page(ctx: dict, page_post: Any) -> str:
|
||||||
"""Full page: payments config."""
|
"""Full page: payments config."""
|
||||||
content = _cart_payments_main_panel_sexp(ctx)
|
content = _cart_payments_main_panel_sx(ctx)
|
||||||
root_hdr = root_header_sexp(ctx)
|
root_hdr = root_header_sx(ctx)
|
||||||
post_hdr = await _post_header_sexp(ctx, page_post)
|
post_hdr = await _post_header_sx(ctx, page_post)
|
||||||
admin_hdr = _cart_page_admin_header_sexp(ctx, page_post, selected="payments")
|
admin_hdr = _cart_page_admin_header_sx(ctx, page_post, selected="payments")
|
||||||
header_rows = "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")"
|
header_rows = "(<> " + root_hdr + " " + post_hdr + " " + admin_hdr + ")"
|
||||||
return full_page_sexp(ctx, header_rows=header_rows, content=content)
|
return full_page_sx(ctx, header_rows=header_rows, content=content)
|
||||||
|
|
||||||
|
|
||||||
async def render_cart_payments_oob(ctx: dict, page_post: Any) -> str:
|
async def render_cart_payments_oob(ctx: dict, page_post: Any) -> str:
|
||||||
"""OOB response: payments config."""
|
"""OOB response: payments config."""
|
||||||
content = _cart_payments_main_panel_sexp(ctx)
|
content = _cart_payments_main_panel_sx(ctx)
|
||||||
oobs = _cart_page_admin_header_sexp(ctx, page_post, oob=True, selected="payments")
|
oobs = _cart_page_admin_header_sx(ctx, page_post, oob=True, selected="payments")
|
||||||
return oob_page_sexp(oobs=oobs, content=content)
|
return oob_page_sx(oobs=oobs, content=content)
|
||||||
|
|
||||||
|
|
||||||
def render_cart_payments_panel(ctx: dict) -> str:
|
def render_cart_payments_panel(ctx: dict) -> str:
|
||||||
"""Render the payments config panel for PUT response."""
|
"""Render the payments config panel for PUT response."""
|
||||||
return _cart_payments_main_panel_sexp(ctx)
|
return _cart_payments_main_panel_sx(ctx)
|
||||||
@@ -45,7 +45,7 @@ services:
|
|||||||
- ./blog/alembic.ini:/app/blog/alembic.ini:ro
|
- ./blog/alembic.ini:/app/blog/alembic.ini:ro
|
||||||
- ./blog/alembic:/app/blog/alembic:ro
|
- ./blog/alembic:/app/blog/alembic:ro
|
||||||
- ./blog/app.py:/app/app.py
|
- ./blog/app.py:/app/app.py
|
||||||
- ./blog/sexp:/app/sexp
|
- ./blog/sx:/app/sx
|
||||||
- ./blog/bp:/app/bp
|
- ./blog/bp:/app/bp
|
||||||
- ./blog/services:/app/services
|
- ./blog/services:/app/services
|
||||||
- ./blog/templates:/app/templates
|
- ./blog/templates:/app/templates
|
||||||
@@ -83,7 +83,7 @@ services:
|
|||||||
- ./market/alembic.ini:/app/market/alembic.ini:ro
|
- ./market/alembic.ini:/app/market/alembic.ini:ro
|
||||||
- ./market/alembic:/app/market/alembic:ro
|
- ./market/alembic:/app/market/alembic:ro
|
||||||
- ./market/app.py:/app/app.py
|
- ./market/app.py:/app/app.py
|
||||||
- ./market/sexp:/app/sexp
|
- ./market/sx:/app/sx
|
||||||
- ./market/bp:/app/bp
|
- ./market/bp:/app/bp
|
||||||
- ./market/services:/app/services
|
- ./market/services:/app/services
|
||||||
- ./market/templates:/app/templates
|
- ./market/templates:/app/templates
|
||||||
@@ -120,7 +120,7 @@ services:
|
|||||||
- ./cart/alembic.ini:/app/cart/alembic.ini:ro
|
- ./cart/alembic.ini:/app/cart/alembic.ini:ro
|
||||||
- ./cart/alembic:/app/cart/alembic:ro
|
- ./cart/alembic:/app/cart/alembic:ro
|
||||||
- ./cart/app.py:/app/app.py
|
- ./cart/app.py:/app/app.py
|
||||||
- ./cart/sexp:/app/sexp
|
- ./cart/sx:/app/sx
|
||||||
- ./cart/bp:/app/bp
|
- ./cart/bp:/app/bp
|
||||||
- ./cart/services:/app/services
|
- ./cart/services:/app/services
|
||||||
- ./cart/templates:/app/templates
|
- ./cart/templates:/app/templates
|
||||||
@@ -157,7 +157,7 @@ services:
|
|||||||
- ./events/alembic.ini:/app/events/alembic.ini:ro
|
- ./events/alembic.ini:/app/events/alembic.ini:ro
|
||||||
- ./events/alembic:/app/events/alembic:ro
|
- ./events/alembic:/app/events/alembic:ro
|
||||||
- ./events/app.py:/app/app.py
|
- ./events/app.py:/app/app.py
|
||||||
- ./events/sexp:/app/sexp
|
- ./events/sx:/app/sx
|
||||||
- ./events/bp:/app/bp
|
- ./events/bp:/app/bp
|
||||||
- ./events/services:/app/services
|
- ./events/services:/app/services
|
||||||
- ./events/templates:/app/templates
|
- ./events/templates:/app/templates
|
||||||
@@ -194,7 +194,7 @@ services:
|
|||||||
- ./federation/alembic.ini:/app/federation/alembic.ini:ro
|
- ./federation/alembic.ini:/app/federation/alembic.ini:ro
|
||||||
- ./federation/alembic:/app/federation/alembic:ro
|
- ./federation/alembic:/app/federation/alembic:ro
|
||||||
- ./federation/app.py:/app/app.py
|
- ./federation/app.py:/app/app.py
|
||||||
- ./federation/sexp:/app/sexp
|
- ./federation/sx:/app/sx
|
||||||
- ./federation/bp:/app/bp
|
- ./federation/bp:/app/bp
|
||||||
- ./federation/services:/app/services
|
- ./federation/services:/app/services
|
||||||
- ./federation/templates:/app/templates
|
- ./federation/templates:/app/templates
|
||||||
@@ -231,7 +231,7 @@ services:
|
|||||||
- ./account/alembic.ini:/app/account/alembic.ini:ro
|
- ./account/alembic.ini:/app/account/alembic.ini:ro
|
||||||
- ./account/alembic:/app/account/alembic:ro
|
- ./account/alembic:/app/account/alembic:ro
|
||||||
- ./account/app.py:/app/app.py
|
- ./account/app.py:/app/app.py
|
||||||
- ./account/sexp:/app/sexp
|
- ./account/sx:/app/sx
|
||||||
- ./account/bp:/app/bp
|
- ./account/bp:/app/bp
|
||||||
- ./account/services:/app/services
|
- ./account/services:/app/services
|
||||||
- ./account/templates:/app/templates
|
- ./account/templates:/app/templates
|
||||||
@@ -330,7 +330,7 @@ services:
|
|||||||
- ./orders/alembic.ini:/app/orders/alembic.ini:ro
|
- ./orders/alembic.ini:/app/orders/alembic.ini:ro
|
||||||
- ./orders/alembic:/app/orders/alembic:ro
|
- ./orders/alembic:/app/orders/alembic:ro
|
||||||
- ./orders/app.py:/app/app.py
|
- ./orders/app.py:/app/app.py
|
||||||
- ./orders/sexp:/app/sexp
|
- ./orders/sx:/app/sx
|
||||||
- ./orders/bp:/app/bp
|
- ./orders/bp:/app/bp
|
||||||
- ./orders/services:/app/services
|
- ./orders/services:/app/services
|
||||||
- ./orders/templates:/app/templates
|
- ./orders/templates:/app/templates
|
||||||
@@ -361,7 +361,7 @@ services:
|
|||||||
- /root/rose-ash/_config/app-config.yaml:/app/config/app-config.yaml:ro
|
- /root/rose-ash/_config/app-config.yaml:/app/config/app-config.yaml:ro
|
||||||
- ./shared:/app/shared
|
- ./shared:/app/shared
|
||||||
- ./test/app.py:/app/app.py
|
- ./test/app.py:/app/app.py
|
||||||
- ./test/sexp:/app/sexp
|
- ./test/sx:/app/sx
|
||||||
- ./test/bp:/app/bp
|
- ./test/bp:/app/bp
|
||||||
- ./test/services:/app/services
|
- ./test/services:/app/services
|
||||||
- ./test/runner.py:/app/runner.py
|
- ./test/runner.py:/app/runner.py
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import path_setup # noqa: F401 # adds shared/ to sys.path
|
import path_setup # noqa: F401 # adds shared/ to sys.path
|
||||||
import sexp.sexp_components as sexp_components # noqa: F401 # ensure Hypercorn --reload watches this file
|
import sx.sx_components as sx_components # noqa: F401 # ensure Hypercorn --reload watches this file
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from quart import g, abort, request
|
from quart import g, abort, request
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ from __future__ import annotations
|
|||||||
from quart import Blueprint, g, request, render_template, make_response
|
from quart import Blueprint, g, request, render_template, make_response
|
||||||
|
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
from shared.infrastructure.cart_identity import current_cart_identity
|
from shared.infrastructure.cart_identity import current_cart_identity
|
||||||
from shared.infrastructure.data_client import fetch_data
|
from shared.infrastructure.data_client import fetch_data
|
||||||
from shared.contracts.dtos import PostDTO, dto_from_dict
|
from shared.contracts.dtos import PostDTO, dto_from_dict
|
||||||
@@ -66,13 +66,13 @@ def register() -> Blueprint:
|
|||||||
|
|
||||||
entries, has_more, pending_tickets, page_info = await _load_entries(page)
|
entries, has_more, pending_tickets, page_info = await _load_entries(page)
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_all_events_page, render_all_events_oob
|
from sx.sx_components import render_all_events_page, render_all_events_oob
|
||||||
|
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
if is_htmx_request():
|
if is_htmx_request():
|
||||||
sexp_src = await render_all_events_oob(ctx, entries, has_more, pending_tickets, page_info, page, view)
|
sx_src = await render_all_events_oob(ctx, entries, has_more, pending_tickets, page_info, page, view)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
else:
|
else:
|
||||||
html = await render_all_events_page(ctx, entries, has_more, pending_tickets, page_info, page, view)
|
html = await render_all_events_page(ctx, entries, has_more, pending_tickets, page_info, page, view)
|
||||||
return await make_response(html, 200)
|
return await make_response(html, 200)
|
||||||
@@ -84,9 +84,9 @@ def register() -> Blueprint:
|
|||||||
|
|
||||||
entries, has_more, pending_tickets, page_info = await _load_entries(page)
|
entries, has_more, pending_tickets, page_info = await _load_entries(page)
|
||||||
|
|
||||||
from sexp.sexp_components import render_all_events_cards
|
from sx.sx_components import render_all_events_cards
|
||||||
sexp_src = await render_all_events_cards(entries, has_more, pending_tickets, page_info, page, view)
|
sx_src = await render_all_events_cards(entries, has_more, pending_tickets, page_info, page, view)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.post("/all-tickets/adjust")
|
@bp.post("/all-tickets/adjust")
|
||||||
async def adjust_ticket():
|
async def adjust_ticket():
|
||||||
@@ -125,9 +125,9 @@ def register() -> Blueprint:
|
|||||||
if ident["session_id"] is not None:
|
if ident["session_id"] is not None:
|
||||||
frag_params["session_id"] = ident["session_id"]
|
frag_params["session_id"] = ident["session_id"]
|
||||||
|
|
||||||
from sexp.sexp_components import render_ticket_widget
|
from sx.sx_components import render_ticket_widget
|
||||||
widget_html = render_ticket_widget(entry, qty, "/all-tickets/adjust")
|
widget_html = render_ticket_widget(entry, qty, "/all-tickets/adjust")
|
||||||
mini_html = await fetch_fragment("cart", "cart-mini", params=frag_params, required=False)
|
mini_html = await fetch_fragment("cart", "cart-mini", params=frag_params, required=False)
|
||||||
return sexp_response(widget_html + (mini_html or ""))
|
return sx_response(widget_html + (mini_html or ""))
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from quart import (
|
|||||||
|
|
||||||
from shared.browser.app.authz import require_admin
|
from shared.browser.app.authz import require_admin
|
||||||
from shared.browser.app.redis_cacher import clear_cache
|
from shared.browser.app.redis_cacher import clear_cache
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -20,24 +20,24 @@ def register():
|
|||||||
async def admin(calendar_slug: str, **kwargs):
|
async def admin(calendar_slug: str, **kwargs):
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_calendar_admin_page, render_calendar_admin_oob
|
from sx.sx_components import render_calendar_admin_page, render_calendar_admin_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
html = await render_calendar_admin_page(tctx)
|
html = await render_calendar_admin_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_calendar_admin_oob(tctx)
|
sx_src = await render_calendar_admin_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/description/")
|
@bp.get("/description/")
|
||||||
@require_admin
|
@require_admin
|
||||||
async def calendar_description_edit(calendar_slug: str, **kwargs):
|
async def calendar_description_edit(calendar_slug: str, **kwargs):
|
||||||
from sexp.sexp_components import render_calendar_description_edit
|
from sx.sx_components import render_calendar_description_edit
|
||||||
html = render_calendar_description_edit(g.calendar)
|
html = render_calendar_description_edit(g.calendar)
|
||||||
return sexp_response(html)
|
return sx_response(html)
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/description/")
|
@bp.post("/description/")
|
||||||
@@ -51,16 +51,16 @@ def register():
|
|||||||
g.calendar.description = description
|
g.calendar.description = description
|
||||||
await g.s.flush()
|
await g.s.flush()
|
||||||
|
|
||||||
from sexp.sexp_components import render_calendar_description
|
from sx.sx_components import render_calendar_description
|
||||||
html = render_calendar_description(g.calendar, oob=True)
|
html = render_calendar_description(g.calendar, oob=True)
|
||||||
return sexp_response(html)
|
return sx_response(html)
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/description/view/")
|
@bp.get("/description/view/")
|
||||||
@require_admin
|
@require_admin
|
||||||
async def calendar_description_view(calendar_slug: str, **kwargs):
|
async def calendar_description_view(calendar_slug: str, **kwargs):
|
||||||
from sexp.sexp_components import render_calendar_description
|
from sx.sx_components import render_calendar_description
|
||||||
html = render_calendar_description(g.calendar)
|
html = render_calendar_description(g.calendar)
|
||||||
return sexp_response(html)
|
return sx_response(html)
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from .services.calendar_view import (
|
|||||||
update_calendar_description,
|
update_calendar_description,
|
||||||
)
|
)
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
from ..slots.routes import register as register_slots
|
from ..slots.routes import register as register_slots
|
||||||
|
|
||||||
@@ -157,8 +157,8 @@ def register():
|
|||||||
user_entries = visible.user_entries
|
user_entries = visible.user_entries
|
||||||
confirmed_entries = visible.confirmed_entries
|
confirmed_entries = visible.confirmed_entries
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_calendar_page, render_calendar_oob
|
from sx.sx_components import render_calendar_page, render_calendar_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx.update(dict(
|
tctx.update(dict(
|
||||||
@@ -175,8 +175,8 @@ def register():
|
|||||||
html = await render_calendar_page(tctx)
|
html = await render_calendar_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_calendar_oob(tctx)
|
sx_src = await render_calendar_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
|
|
||||||
@bp.put("/")
|
@bp.put("/")
|
||||||
@@ -198,11 +198,11 @@ def register():
|
|||||||
description = (form.get("description") or "").strip()
|
description = (form.get("description") or "").strip()
|
||||||
|
|
||||||
await update_calendar_description(g.calendar, description)
|
await update_calendar_description(g.calendar, description)
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import _calendar_admin_main_panel_html
|
from sx.sx_components import _calendar_admin_main_panel_html
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
html = _calendar_admin_main_panel_html(ctx)
|
html = _calendar_admin_main_panel_html(ctx)
|
||||||
return sexp_response(html)
|
return sx_response(html)
|
||||||
|
|
||||||
|
|
||||||
@bp.delete("/")
|
@bp.delete("/")
|
||||||
@@ -217,14 +217,14 @@ def register():
|
|||||||
|
|
||||||
# If we have post context (blog-embedded mode), update nav
|
# If we have post context (blog-embedded mode), update nav
|
||||||
post_data = getattr(g, "post_data", None)
|
post_data = getattr(g, "post_data", None)
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_calendars_list_panel
|
from sx.sx_components import render_calendars_list_panel
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
html = render_calendars_list_panel(ctx)
|
html = render_calendars_list_panel(ctx)
|
||||||
|
|
||||||
if post_data:
|
if post_data:
|
||||||
from shared.services.entry_associations import get_associated_entries
|
from shared.services.entry_associations import get_associated_entries
|
||||||
from sexp.sexp_components import render_post_nav_entries_oob
|
from sx.sx_components import render_post_nav_entries_oob
|
||||||
|
|
||||||
post_id = (post_data.get("post") or {}).get("id")
|
post_id = (post_data.get("post") or {}).get("id")
|
||||||
cals = (
|
cals = (
|
||||||
@@ -239,7 +239,7 @@ def register():
|
|||||||
nav_oob = render_post_nav_entries_oob(associated_entries, cals, post_data["post"])
|
nav_oob = render_post_nav_entries_oob(associated_entries, cals, post_data["post"])
|
||||||
html = html + nav_oob
|
html = html + nav_oob
|
||||||
|
|
||||||
return sexp_response(html)
|
return sx_response(html)
|
||||||
|
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from .services.entries import (
|
|||||||
)
|
)
|
||||||
from shared.browser.app.authz import require_admin
|
from shared.browser.app.authz import require_admin
|
||||||
from shared.browser.app.redis_cacher import clear_cache
|
from shared.browser.app.redis_cacher import clear_cache
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
|
|
||||||
from bp.calendar_entry.routes import register as register_calendar_entry
|
from bp.calendar_entry.routes import register as register_calendar_entry
|
||||||
@@ -217,7 +217,7 @@ def register():
|
|||||||
if ident["session_id"] is not None:
|
if ident["session_id"] is not None:
|
||||||
frag_params["session_id"] = ident["session_id"]
|
frag_params["session_id"] = ident["session_id"]
|
||||||
|
|
||||||
# Re-query day entries for the sexp component
|
# Re-query day entries for the sx component
|
||||||
from datetime import date as date_cls, timedelta
|
from datetime import date as date_cls, timedelta
|
||||||
from bp.calendar.services import get_visible_entries_for_period
|
from bp.calendar.services import get_visible_entries_for_period
|
||||||
from quart import session as qsession
|
from quart import session as qsession
|
||||||
@@ -258,10 +258,10 @@ def register():
|
|||||||
"styles": styles,
|
"styles": styles,
|
||||||
}
|
}
|
||||||
|
|
||||||
from sexp.sexp_components import render_day_main_panel
|
from sx.sx_components import render_day_main_panel
|
||||||
html = render_day_main_panel(ctx)
|
html = render_day_main_panel(ctx)
|
||||||
mini_html = await fetch_fragment("cart", "cart-mini", params=frag_params, required=False)
|
mini_html = await fetch_fragment("cart", "cart-mini", params=frag_params, required=False)
|
||||||
return sexp_response(html + (mini_html or ""))
|
return sx_response(html + (mini_html or ""))
|
||||||
|
|
||||||
@bp.get("/add/")
|
@bp.get("/add/")
|
||||||
async def add_form(day: int, month: int, year: int, **kwargs):
|
async def add_form(day: int, month: int, year: int, **kwargs):
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import math
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from shared.infrastructure.fragments import fetch_fragment
|
from shared.infrastructure.fragments import fetch_fragment
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
from ..ticket_types.routes import register as register_ticket_types
|
from ..ticket_types.routes import register as register_ticket_types
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ def register():
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Render OOB nav
|
# Render OOB nav
|
||||||
from sexp.sexp_components import render_day_entries_nav_oob
|
from sx.sx_components import render_day_entries_nav_oob
|
||||||
return render_day_entries_nav_oob(visible.confirmed_entries, calendar, day_date)
|
return render_day_entries_nav_oob(visible.confirmed_entries, calendar, day_date)
|
||||||
|
|
||||||
async def get_post_nav_oob(entry_id: int):
|
async def get_post_nav_oob(entry_id: int):
|
||||||
@@ -148,7 +148,7 @@ def register():
|
|||||||
).scalars().all()
|
).scalars().all()
|
||||||
|
|
||||||
# Render OOB nav for this post
|
# Render OOB nav for this post
|
||||||
from sexp.sexp_components import render_post_nav_entries_oob
|
from sx.sx_components import render_post_nav_entries_oob
|
||||||
nav_oob = render_post_nav_entries_oob(associated_entries, calendars, post)
|
nav_oob = render_post_nav_entries_oob(associated_entries, calendars, post)
|
||||||
nav_oobs.append(nav_oob)
|
nav_oobs.append(nav_oob)
|
||||||
|
|
||||||
@@ -242,16 +242,16 @@ def register():
|
|||||||
@require_admin
|
@require_admin
|
||||||
async def get(entry_id: int, **rest):
|
async def get(entry_id: int, **rest):
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_entry_page, render_entry_oob
|
from sx.sx_components import render_entry_page, render_entry_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
html = await render_entry_page(tctx)
|
html = await render_entry_page(tctx)
|
||||||
return await make_response(html, 200)
|
return await make_response(html, 200)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_entry_oob(tctx)
|
sx_src = await render_entry_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.get("/edit/")
|
@bp.get("/edit/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -419,12 +419,12 @@ def register():
|
|||||||
# Get nav OOB update
|
# Get nav OOB update
|
||||||
nav_oob = await get_day_nav_oob(year, month, day)
|
nav_oob = await get_day_nav_oob(year, month, day)
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_entry_page
|
from sx.sx_components import render_entry_page
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
html = await render_entry_page(tctx)
|
html = await render_entry_page(tctx)
|
||||||
return sexp_response(html + nav_oob)
|
return sx_response(html + nav_oob)
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/confirm/")
|
@bp.post("/confirm/")
|
||||||
@@ -448,9 +448,9 @@ def register():
|
|||||||
|
|
||||||
# Re-read entry to get updated state
|
# Re-read entry to get updated state
|
||||||
await g.s.refresh(g.entry)
|
await g.s.refresh(g.entry)
|
||||||
from sexp.sexp_components import render_entry_optioned
|
from sx.sx_components import render_entry_optioned
|
||||||
html = render_entry_optioned(g.entry, g.calendar, day, month, year)
|
html = render_entry_optioned(g.entry, g.calendar, day, month, year)
|
||||||
return sexp_response(html + day_nav_oob + post_nav_oob)
|
return sx_response(html + day_nav_oob + post_nav_oob)
|
||||||
|
|
||||||
@bp.post("/decline/")
|
@bp.post("/decline/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -473,9 +473,9 @@ def register():
|
|||||||
|
|
||||||
# Re-read entry to get updated state
|
# Re-read entry to get updated state
|
||||||
await g.s.refresh(g.entry)
|
await g.s.refresh(g.entry)
|
||||||
from sexp.sexp_components import render_entry_optioned
|
from sx.sx_components import render_entry_optioned
|
||||||
html = render_entry_optioned(g.entry, g.calendar, day, month, year)
|
html = render_entry_optioned(g.entry, g.calendar, day, month, year)
|
||||||
return sexp_response(html + day_nav_oob + post_nav_oob)
|
return sx_response(html + day_nav_oob + post_nav_oob)
|
||||||
|
|
||||||
@bp.post("/provisional/")
|
@bp.post("/provisional/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -498,9 +498,9 @@ def register():
|
|||||||
|
|
||||||
# Re-read entry to get updated state
|
# Re-read entry to get updated state
|
||||||
await g.s.refresh(g.entry)
|
await g.s.refresh(g.entry)
|
||||||
from sexp.sexp_components import render_entry_optioned
|
from sx.sx_components import render_entry_optioned
|
||||||
html = render_entry_optioned(g.entry, g.calendar, day, month, year)
|
html = render_entry_optioned(g.entry, g.calendar, day, month, year)
|
||||||
return sexp_response(html + day_nav_oob + post_nav_oob)
|
return sx_response(html + day_nav_oob + post_nav_oob)
|
||||||
|
|
||||||
@bp.post("/tickets/")
|
@bp.post("/tickets/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -542,9 +542,9 @@ def register():
|
|||||||
|
|
||||||
# Return just the tickets fragment (targeted by hx-target="#entry-tickets-...")
|
# Return just the tickets fragment (targeted by hx-target="#entry-tickets-...")
|
||||||
await g.s.refresh(g.entry)
|
await g.s.refresh(g.entry)
|
||||||
from sexp.sexp_components import render_entry_tickets_config
|
from sx.sx_components import render_entry_tickets_config
|
||||||
html = render_entry_tickets_config(g.entry, g.calendar, request.view_args.get("day"), request.view_args.get("month"), request.view_args.get("year"))
|
html = render_entry_tickets_config(g.entry, g.calendar, request.view_args.get("day"), request.view_args.get("month"), request.view_args.get("year"))
|
||||||
return sexp_response(html)
|
return sx_response(html)
|
||||||
|
|
||||||
@bp.get("/posts/search/")
|
@bp.get("/posts/search/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -593,11 +593,11 @@ def register():
|
|||||||
entry_posts = await get_entry_posts(g.s, entry_id)
|
entry_posts = await get_entry_posts(g.s, entry_id)
|
||||||
|
|
||||||
# Return updated posts list + OOB nav update
|
# Return updated posts list + OOB nav update
|
||||||
from sexp.sexp_components import render_entry_posts_panel, render_entry_posts_nav_oob
|
from sx.sx_components import render_entry_posts_panel, render_entry_posts_nav_oob
|
||||||
va = request.view_args or {}
|
va = request.view_args or {}
|
||||||
html = render_entry_posts_panel(entry_posts, g.entry, g.calendar, va.get("day"), va.get("month"), va.get("year"))
|
html = render_entry_posts_panel(entry_posts, g.entry, g.calendar, va.get("day"), va.get("month"), va.get("year"))
|
||||||
nav_oob = render_entry_posts_nav_oob(entry_posts)
|
nav_oob = render_entry_posts_nav_oob(entry_posts)
|
||||||
return sexp_response(html + nav_oob)
|
return sx_response(html + nav_oob)
|
||||||
|
|
||||||
@bp.delete("/posts/<int:post_id>/")
|
@bp.delete("/posts/<int:post_id>/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -615,10 +615,10 @@ def register():
|
|||||||
entry_posts = await get_entry_posts(g.s, entry_id)
|
entry_posts = await get_entry_posts(g.s, entry_id)
|
||||||
|
|
||||||
# Return updated posts list + OOB nav update
|
# Return updated posts list + OOB nav update
|
||||||
from sexp.sexp_components import render_entry_posts_panel, render_entry_posts_nav_oob
|
from sx.sx_components import render_entry_posts_panel, render_entry_posts_nav_oob
|
||||||
va = request.view_args or {}
|
va = request.view_args or {}
|
||||||
html = render_entry_posts_panel(entry_posts, g.entry, g.calendar, va.get("day"), va.get("month"), va.get("year"))
|
html = render_entry_posts_panel(entry_posts, g.entry, g.calendar, va.get("day"), va.get("month"), va.get("year"))
|
||||||
nav_oob = render_entry_posts_nav_oob(entry_posts)
|
nav_oob = render_entry_posts_nav_oob(entry_posts)
|
||||||
return sexp_response(html + nav_oob)
|
return sx_response(html + nav_oob)
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ from shared.browser.app.redis_cacher import cache_page, clear_cache
|
|||||||
|
|
||||||
from shared.browser.app.authz import require_admin
|
from shared.browser.app.authz import require_admin
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
@@ -31,16 +31,16 @@ def register():
|
|||||||
@bp.get("/")
|
@bp.get("/")
|
||||||
@cache_page(tag="calendars")
|
@cache_page(tag="calendars")
|
||||||
async def home(**kwargs):
|
async def home(**kwargs):
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_calendars_page, render_calendars_oob
|
from sx.sx_components import render_calendars_page, render_calendars_oob
|
||||||
|
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
html = await render_calendars_page(ctx)
|
html = await render_calendars_page(ctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_calendars_oob(ctx)
|
sx_src = await render_calendars_oob(ctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/new/")
|
@bp.post("/new/")
|
||||||
@@ -63,18 +63,18 @@ def register():
|
|||||||
try:
|
try:
|
||||||
await svc_create_calendar(g.s, post_id, name)
|
await svc_create_calendar(g.s, post_id, name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
from shared.sexp.jinja_bridge import render as render_comp
|
from shared.sx.jinja_bridge import render as render_comp
|
||||||
return await make_response(render_comp("error-inline", message=str(e)), 422)
|
return await make_response(render_comp("error-inline", message=str(e)), 422)
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_calendars_list_panel
|
from sx.sx_components import render_calendars_list_panel
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
html = render_calendars_list_panel(ctx)
|
html = render_calendars_list_panel(ctx)
|
||||||
|
|
||||||
# Blog-embedded mode: also update post nav
|
# Blog-embedded mode: also update post nav
|
||||||
if post_data:
|
if post_data:
|
||||||
from shared.services.entry_associations import get_associated_entries
|
from shared.services.entry_associations import get_associated_entries
|
||||||
from sexp.sexp_components import render_post_nav_entries_oob
|
from sx.sx_components import render_post_nav_entries_oob
|
||||||
|
|
||||||
cals = (
|
cals = (
|
||||||
await g.s.execute(
|
await g.s.execute(
|
||||||
@@ -88,5 +88,5 @@ def register():
|
|||||||
nav_oob = render_post_nav_entries_oob(associated_entries, cals, post_data["post"])
|
nav_oob = render_post_nav_entries_oob(associated_entries, cals, post_data["post"])
|
||||||
html = html + nav_oob
|
html = html + nav_oob
|
||||||
|
|
||||||
return sexp_response(html)
|
return sx_response(html)
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from quart import (
|
|||||||
|
|
||||||
|
|
||||||
from shared.browser.app.authz import require_admin
|
from shared.browser.app.authz import require_admin
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
@@ -18,14 +18,14 @@ def register():
|
|||||||
async def admin(year: int, month: int, day: int, **kwargs):
|
async def admin(year: int, month: int, day: int, **kwargs):
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_day_admin_page, render_day_admin_oob
|
from sx.sx_components import render_day_admin_page, render_day_admin_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
html = await render_day_admin_page(tctx)
|
html = await render_day_admin_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_day_admin_oob(tctx)
|
sx_src = await render_day_admin_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from models.calendars import CalendarSlot # add this import
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
@@ -122,16 +122,16 @@ def register():
|
|||||||
- all confirmed + provisional + ordered entries for that day (all users)
|
- all confirmed + provisional + ordered entries for that day (all users)
|
||||||
- pending only for current user/session
|
- pending only for current user/session
|
||||||
"""
|
"""
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_day_page, render_day_oob
|
from sx.sx_components import render_day_page, render_day_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
html = await render_day_page(tctx)
|
html = await render_day_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_day_oob(tctx)
|
sx_src = await render_day_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.get("/w/<widget_domain>/")
|
@bp.get("/w/<widget_domain>/")
|
||||||
async def widget_paginate(widget_domain: str, **kwargs):
|
async def widget_paginate(widget_domain: str, **kwargs):
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Events app fragment endpoints.
|
"""Events app fragment endpoints.
|
||||||
|
|
||||||
Exposes sexp fragments at ``/internal/fragments/<type>`` for consumption
|
Exposes sx fragments at ``/internal/fragments/<type>`` for consumption
|
||||||
by other coop apps via the fragment client.
|
by other coop apps via the fragment client.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -31,9 +31,9 @@ def register():
|
|||||||
async def get_fragment(fragment_type: str):
|
async def get_fragment(fragment_type: str):
|
||||||
handler = _handlers.get(fragment_type)
|
handler = _handlers.get(fragment_type)
|
||||||
if handler is None:
|
if handler is None:
|
||||||
return Response("", status=200, content_type="text/sexp")
|
return Response("", status=200, content_type="text/sx")
|
||||||
result = await handler()
|
result = await handler()
|
||||||
ct = "text/html" if fragment_type in _html_types else "text/sexp"
|
ct = "text/html" if fragment_type in _html_types else "text/sx"
|
||||||
return Response(result, status=200, content_type=ct)
|
return Response(result, status=200, content_type=ct)
|
||||||
|
|
||||||
# --- container-nav fragment: calendar entries + calendar links -----------
|
# --- container-nav fragment: calendar entries + calendar links -----------
|
||||||
@@ -41,7 +41,7 @@ def register():
|
|||||||
async def _container_nav_handler():
|
async def _container_nav_handler():
|
||||||
from quart import current_app
|
from quart import current_app
|
||||||
from shared.infrastructure.urls import events_url
|
from shared.infrastructure.urls import events_url
|
||||||
from shared.sexp.helpers import sexp_call
|
from shared.sx.helpers import sx_call
|
||||||
|
|
||||||
container_type = request.args.get("container_type", "page")
|
container_type = request.args.get("container_type", "page")
|
||||||
container_id = int(request.args.get("container_id", 0))
|
container_id = int(request.args.get("container_id", 0))
|
||||||
@@ -69,11 +69,11 @@ def register():
|
|||||||
date_str = entry.start_at.strftime("%b %d, %Y at %H:%M")
|
date_str = entry.start_at.strftime("%b %d, %Y at %H:%M")
|
||||||
if entry.end_at:
|
if entry.end_at:
|
||||||
date_str += f" – {entry.end_at.strftime('%H:%M')}"
|
date_str += f" – {entry.end_at.strftime('%H:%M')}"
|
||||||
parts.append(sexp_call("calendar-entry-nav",
|
parts.append(sx_call("calendar-entry-nav",
|
||||||
href=events_url(entry_path), name=entry.name,
|
href=events_url(entry_path), name=entry.name,
|
||||||
date_str=date_str, nav_class=nav_class))
|
date_str=date_str, nav_class=nav_class))
|
||||||
if has_more and paginate_url_base:
|
if has_more and paginate_url_base:
|
||||||
parts.append(sexp_call("htmx-sentinel",
|
parts.append(sx_call("htmx-sentinel",
|
||||||
id=f"entries-load-sentinel-{page}",
|
id=f"entries-load-sentinel-{page}",
|
||||||
hx_get=f"{paginate_url_base}?page={page + 1}",
|
hx_get=f"{paginate_url_base}?page={page + 1}",
|
||||||
hx_trigger="intersect once",
|
hx_trigger="intersect once",
|
||||||
@@ -87,7 +87,7 @@ def register():
|
|||||||
)
|
)
|
||||||
for cal in calendars:
|
for cal in calendars:
|
||||||
href = events_url(f"/{post_slug}/{cal.slug}/")
|
href = events_url(f"/{post_slug}/{cal.slug}/")
|
||||||
parts.append(sexp_call("calendar-link-nav",
|
parts.append(sx_call("calendar-link-nav",
|
||||||
href=href, name=cal.name, nav_class=nav_class))
|
href=href, name=cal.name, nav_class=nav_class))
|
||||||
|
|
||||||
if not parts:
|
if not parts:
|
||||||
@@ -123,7 +123,7 @@ def register():
|
|||||||
async def _account_nav_item_handler():
|
async def _account_nav_item_handler():
|
||||||
from quart import current_app
|
from quart import current_app
|
||||||
from shared.infrastructure.urls import account_url
|
from shared.infrastructure.urls import account_url
|
||||||
from shared.sexp.helpers import sexp_call
|
from shared.sx.helpers import sx_call
|
||||||
|
|
||||||
styles = current_app.jinja_env.globals.get("styles", {})
|
styles = current_app.jinja_env.globals.get("styles", {})
|
||||||
nav_class = styles.get("nav_button", "")
|
nav_class = styles.get("nav_button", "")
|
||||||
@@ -135,7 +135,7 @@ def register():
|
|||||||
bookings_url = account_url("/bookings/")
|
bookings_url = account_url("/bookings/")
|
||||||
parts = []
|
parts = []
|
||||||
for href, label in [(tickets_url, "tickets"), (bookings_url, "bookings")]:
|
for href, label in [(tickets_url, "tickets"), (bookings_url, "bookings")]:
|
||||||
parts.append(sexp_call("nav-group-link",
|
parts.append(sx_call("nav-group-link",
|
||||||
href=href, hx_select=hx_select, nav_class=nav_class, label=label))
|
href=href, hx_select=hx_select, nav_class=nav_class, label=label))
|
||||||
return "(<> " + " ".join(parts) + ")"
|
return "(<> " + " ".join(parts) + ")"
|
||||||
|
|
||||||
@@ -169,13 +169,13 @@ def register():
|
|||||||
|
|
||||||
async def _link_card_handler():
|
async def _link_card_handler():
|
||||||
from shared.infrastructure.urls import events_url
|
from shared.infrastructure.urls import events_url
|
||||||
from shared.sexp.helpers import sexp_call
|
from shared.sx.helpers import sx_call
|
||||||
|
|
||||||
slug = request.args.get("slug", "")
|
slug = request.args.get("slug", "")
|
||||||
keys_raw = request.args.get("keys", "")
|
keys_raw = request.args.get("keys", "")
|
||||||
|
|
||||||
def _event_link_card_sexp(post, cal_names: str) -> str:
|
def _event_link_card_sx(post, cal_names: str) -> str:
|
||||||
return sexp_call("link-card",
|
return sx_call("link-card",
|
||||||
title=post.title, image=post.feature_image,
|
title=post.title, image=post.feature_image,
|
||||||
subtitle=cal_names,
|
subtitle=cal_names,
|
||||||
link=events_url(f"/{post.slug}"))
|
link=events_url(f"/{post.slug}"))
|
||||||
@@ -193,7 +193,7 @@ def register():
|
|||||||
g.s, "page", post.id,
|
g.s, "page", post.id,
|
||||||
)
|
)
|
||||||
cal_names = ", ".join(c.name for c in calendars) if calendars else ""
|
cal_names = ", ".join(c.name for c in calendars) if calendars else ""
|
||||||
parts.append(_event_link_card_sexp(post, cal_names))
|
parts.append(_event_link_card_sx(post, cal_names))
|
||||||
return "\n".join(parts)
|
return "\n".join(parts)
|
||||||
|
|
||||||
# Single mode
|
# Single mode
|
||||||
@@ -207,7 +207,7 @@ def register():
|
|||||||
g.s, "page", post.id,
|
g.s, "page", post.id,
|
||||||
)
|
)
|
||||||
cal_names = ", ".join(c.name for c in calendars) if calendars else ""
|
cal_names = ", ".join(c.name for c in calendars) if calendars else ""
|
||||||
return _event_link_card_sexp(post, cal_names)
|
return _event_link_card_sx(post, cal_names)
|
||||||
|
|
||||||
_handlers["link-card"] = _link_card_handler
|
_handlers["link-card"] = _link_card_handler
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from .services.markets import (
|
|||||||
from shared.browser.app.redis_cacher import cache_page, clear_cache
|
from shared.browser.app.redis_cacher import cache_page, clear_cache
|
||||||
from shared.browser.app.authz import require_admin
|
from shared.browser.app.authz import require_admin
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
@@ -24,16 +24,16 @@ def register():
|
|||||||
|
|
||||||
@bp.get("/")
|
@bp.get("/")
|
||||||
async def home(**kwargs):
|
async def home(**kwargs):
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_markets_page, render_markets_oob
|
from sx.sx_components import render_markets_page, render_markets_oob
|
||||||
|
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
html = await render_markets_page(ctx)
|
html = await render_markets_page(ctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_markets_oob(ctx)
|
sx_src = await render_markets_oob(ctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.post("/new/")
|
@bp.post("/new/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -52,13 +52,13 @@ def register():
|
|||||||
try:
|
try:
|
||||||
await svc_create_market(g.s, post_id, name)
|
await svc_create_market(g.s, post_id, name)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
from shared.sexp.jinja_bridge import render as render_comp
|
from shared.sx.jinja_bridge import render as render_comp
|
||||||
return await make_response(render_comp("error-inline", message=str(e)), 422)
|
return await make_response(render_comp("error-inline", message=str(e)), 422)
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_markets_list_panel
|
from sx.sx_components import render_markets_list_panel
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
return sexp_response(render_markets_list_panel(ctx))
|
return sx_response(render_markets_list_panel(ctx))
|
||||||
|
|
||||||
@bp.delete("/<market_slug>/")
|
@bp.delete("/<market_slug>/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -68,9 +68,9 @@ def register():
|
|||||||
if not deleted:
|
if not deleted:
|
||||||
return await make_response("Market not found", 404)
|
return await make_response("Market not found", 404)
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_markets_list_panel
|
from sx.sx_components import render_markets_list_panel
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
return sexp_response(render_markets_list_panel(ctx))
|
return sx_response(render_markets_list_panel(ctx))
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from __future__ import annotations
|
|||||||
from quart import Blueprint, g, request, render_template, make_response
|
from quart import Blueprint, g, request, render_template, make_response
|
||||||
|
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
from shared.infrastructure.cart_identity import current_cart_identity
|
from shared.infrastructure.cart_identity import current_cart_identity
|
||||||
from shared.services.registry import services
|
from shared.services.registry import services
|
||||||
|
|
||||||
@@ -46,13 +46,13 @@ def register() -> Blueprint:
|
|||||||
|
|
||||||
entries, has_more, pending_tickets = await _load_entries(post["id"], page)
|
entries, has_more, pending_tickets = await _load_entries(post["id"], page)
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_page_summary_page, render_page_summary_oob
|
from sx.sx_components import render_page_summary_page, render_page_summary_oob
|
||||||
|
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
if is_htmx_request():
|
if is_htmx_request():
|
||||||
sexp_src = await render_page_summary_oob(ctx, entries, has_more, pending_tickets, {}, page, view)
|
sx_src = await render_page_summary_oob(ctx, entries, has_more, pending_tickets, {}, page, view)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
else:
|
else:
|
||||||
html = await render_page_summary_page(ctx, entries, has_more, pending_tickets, {}, page, view)
|
html = await render_page_summary_page(ctx, entries, has_more, pending_tickets, {}, page, view)
|
||||||
return await make_response(html, 200)
|
return await make_response(html, 200)
|
||||||
@@ -65,9 +65,9 @@ def register() -> Blueprint:
|
|||||||
|
|
||||||
entries, has_more, pending_tickets = await _load_entries(post["id"], page)
|
entries, has_more, pending_tickets = await _load_entries(post["id"], page)
|
||||||
|
|
||||||
from sexp.sexp_components import render_page_summary_cards
|
from sx.sx_components import render_page_summary_cards
|
||||||
sexp_src = await render_page_summary_cards(entries, has_more, pending_tickets, {}, page, view, post)
|
sx_src = await render_page_summary_cards(entries, has_more, pending_tickets, {}, page, view, post)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.post("/tickets/adjust")
|
@bp.post("/tickets/adjust")
|
||||||
async def adjust_ticket():
|
async def adjust_ticket():
|
||||||
@@ -106,9 +106,9 @@ def register() -> Blueprint:
|
|||||||
if ident["session_id"] is not None:
|
if ident["session_id"] is not None:
|
||||||
frag_params["session_id"] = ident["session_id"]
|
frag_params["session_id"] = ident["session_id"]
|
||||||
|
|
||||||
from sexp.sexp_components import render_ticket_widget
|
from sx.sx_components import render_ticket_widget
|
||||||
widget_html = render_ticket_widget(entry, qty, f"/{g.post_slug}/tickets/adjust")
|
widget_html = render_ticket_widget(entry, qty, f"/{g.post_slug}/tickets/adjust")
|
||||||
mini_html = await fetch_fragment("cart", "cart-mini", params=frag_params, required=False)
|
mini_html = await fetch_fragment("cart", "cart-mini", params=frag_params, required=False)
|
||||||
return sexp_response(widget_html + (mini_html or ""))
|
return sx_response(widget_html + (mini_html or ""))
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from shared.browser.app.utils import (
|
|||||||
parse_cost
|
parse_cost
|
||||||
)
|
)
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
@@ -75,8 +75,8 @@ def register():
|
|||||||
slot = await svc_get_slot(g.s, slot_id)
|
slot = await svc_get_slot(g.s, slot_id)
|
||||||
if not slot:
|
if not slot:
|
||||||
return await make_response("Not found", 404)
|
return await make_response("Not found", 404)
|
||||||
from sexp.sexp_components import render_slot_main_panel
|
from sx.sx_components import render_slot_main_panel
|
||||||
return sexp_response(render_slot_main_panel(slot, g.calendar))
|
return sx_response(render_slot_main_panel(slot, g.calendar))
|
||||||
|
|
||||||
@bp.delete("/")
|
@bp.delete("/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -84,8 +84,8 @@ def register():
|
|||||||
async def slot_delete(slot_id: int, **kwargs):
|
async def slot_delete(slot_id: int, **kwargs):
|
||||||
await svc_delete_slot(g.s, slot_id)
|
await svc_delete_slot(g.s, slot_id)
|
||||||
slots = await svc_list_slots(g.s, g.calendar.id)
|
slots = await svc_list_slots(g.s, g.calendar.id)
|
||||||
from sexp.sexp_components import render_slots_table
|
from sx.sx_components import render_slots_table
|
||||||
return sexp_response(render_slots_table(slots, g.calendar))
|
return sx_response(render_slots_table(slots, g.calendar))
|
||||||
|
|
||||||
@bp.put("/")
|
@bp.put("/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -166,8 +166,8 @@ def register():
|
|||||||
}
|
}
|
||||||
), 422
|
), 422
|
||||||
|
|
||||||
from sexp.sexp_components import render_slot_main_panel
|
from sx.sx_components import render_slot_main_panel
|
||||||
return sexp_response(render_slot_main_panel(slot, g.calendar, oob=True))
|
return sx_response(render_slot_main_panel(slot, g.calendar, oob=True))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from shared.browser.app.utils import (
|
|||||||
parse_cost
|
parse_cost
|
||||||
)
|
)
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
@@ -45,16 +45,16 @@ def register():
|
|||||||
|
|
||||||
@bp.get("/")
|
@bp.get("/")
|
||||||
async def get(**kwargs):
|
async def get(**kwargs):
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_slots_page, render_slots_oob
|
from sx.sx_components import render_slots_page, render_slots_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
html = await render_slots_page(tctx)
|
html = await render_slots_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_slots_oob(tctx)
|
sx_src = await render_slots_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
|
|
||||||
@bp.post("/")
|
@bp.post("/")
|
||||||
@@ -129,8 +129,8 @@ def register():
|
|||||||
|
|
||||||
# Success → re-render the slots table
|
# Success → re-render the slots table
|
||||||
slots = await svc_list_slots(g.s, g.calendar.id)
|
slots = await svc_list_slots(g.s, g.calendar.id)
|
||||||
from sexp.sexp_components import render_slots_table
|
from sx.sx_components import render_slots_table
|
||||||
return sexp_response(render_slots_table(slots, g.calendar))
|
return sx_response(render_slots_table(slots, g.calendar))
|
||||||
|
|
||||||
|
|
||||||
@bp.get("/add")
|
@bp.get("/add")
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from sqlalchemy.orm import selectinload
|
|||||||
from models.calendars import CalendarEntry, Ticket, TicketType
|
from models.calendars import CalendarEntry, Ticket, TicketType
|
||||||
from shared.browser.app.authz import require_admin
|
from shared.browser.app.authz import require_admin
|
||||||
from shared.browser.app.redis_cacher import clear_cache
|
from shared.browser.app.redis_cacher import clear_cache
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
from ..tickets.services.tickets import (
|
from ..tickets.services.tickets import (
|
||||||
get_ticket_by_code,
|
get_ticket_by_code,
|
||||||
@@ -71,16 +71,16 @@ def register() -> Blueprint:
|
|||||||
"reserved": reserved or 0,
|
"reserved": reserved or 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_ticket_admin_page, render_ticket_admin_oob
|
from sx.sx_components import render_ticket_admin_page, render_ticket_admin_oob
|
||||||
|
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
html = await render_ticket_admin_page(ctx, tickets, stats)
|
html = await render_ticket_admin_page(ctx, tickets, stats)
|
||||||
return await make_response(html, 200)
|
return await make_response(html, 200)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_ticket_admin_oob(ctx, tickets, stats)
|
sx_src = await render_ticket_admin_oob(ctx, tickets, stats)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.get("/entry/<int:entry_id>/")
|
@bp.get("/entry/<int:entry_id>/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -101,9 +101,9 @@ def register() -> Blueprint:
|
|||||||
|
|
||||||
tickets = await get_tickets_for_entry(g.s, entry_id)
|
tickets = await get_tickets_for_entry(g.s, entry_id)
|
||||||
|
|
||||||
from sexp.sexp_components import render_entry_tickets_admin
|
from sx.sx_components import render_entry_tickets_admin
|
||||||
html = render_entry_tickets_admin(entry, tickets)
|
html = render_entry_tickets_admin(entry, tickets)
|
||||||
return sexp_response(html)
|
return sx_response(html)
|
||||||
|
|
||||||
@bp.get("/lookup/")
|
@bp.get("/lookup/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -117,11 +117,11 @@ def register() -> Blueprint:
|
|||||||
)
|
)
|
||||||
|
|
||||||
ticket = await get_ticket_by_code(g.s, code)
|
ticket = await get_ticket_by_code(g.s, code)
|
||||||
from sexp.sexp_components import render_lookup_result
|
from sx.sx_components import render_lookup_result
|
||||||
if not ticket:
|
if not ticket:
|
||||||
return sexp_response(render_lookup_result(None, "Ticket not found"))
|
return sx_response(render_lookup_result(None, "Ticket not found"))
|
||||||
|
|
||||||
return sexp_response(render_lookup_result(ticket, None))
|
return sx_response(render_lookup_result(ticket, None))
|
||||||
|
|
||||||
@bp.post("/<code>/checkin/")
|
@bp.post("/<code>/checkin/")
|
||||||
@require_admin
|
@require_admin
|
||||||
@@ -130,11 +130,11 @@ def register() -> Blueprint:
|
|||||||
"""Check in a ticket by its code."""
|
"""Check in a ticket by its code."""
|
||||||
success, error = await checkin_ticket(g.s, code)
|
success, error = await checkin_ticket(g.s, code)
|
||||||
|
|
||||||
from sexp.sexp_components import render_checkin_result
|
from sx.sx_components import render_checkin_result
|
||||||
if not success:
|
if not success:
|
||||||
return sexp_response(render_checkin_result(False, error, None))
|
return sx_response(render_checkin_result(False, error, None))
|
||||||
|
|
||||||
ticket = await get_ticket_by_code(g.s, code)
|
ticket = await get_ticket_by_code(g.s, code)
|
||||||
return sexp_response(render_checkin_result(True, None, ticket))
|
return sx_response(render_checkin_result(True, None, ticket))
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from ..ticket_types.services.tickets import (
|
|||||||
list_ticket_types as svc_list_ticket_types,
|
list_ticket_types as svc_list_ticket_types,
|
||||||
)
|
)
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
@@ -67,9 +67,9 @@ def register():
|
|||||||
if not ticket_type:
|
if not ticket_type:
|
||||||
return await make_response("Not found", 404)
|
return await make_response("Not found", 404)
|
||||||
|
|
||||||
from sexp.sexp_components import render_ticket_type_main_panel
|
from sx.sx_components import render_ticket_type_main_panel
|
||||||
va = request.view_args or {}
|
va = request.view_args or {}
|
||||||
return sexp_response(render_ticket_type_main_panel(
|
return sx_response(render_ticket_type_main_panel(
|
||||||
ticket_type, g.entry, g.calendar,
|
ticket_type, g.entry, g.calendar,
|
||||||
va.get("day"), va.get("month"), va.get("year"),
|
va.get("day"), va.get("month"), va.get("year"),
|
||||||
))
|
))
|
||||||
@@ -134,9 +134,9 @@ def register():
|
|||||||
return await make_response("Not found", 404)
|
return await make_response("Not found", 404)
|
||||||
|
|
||||||
# Return updated view with OOB flag
|
# Return updated view with OOB flag
|
||||||
from sexp.sexp_components import render_ticket_type_main_panel
|
from sx.sx_components import render_ticket_type_main_panel
|
||||||
va = request.view_args or {}
|
va = request.view_args or {}
|
||||||
return sexp_response(render_ticket_type_main_panel(
|
return sx_response(render_ticket_type_main_panel(
|
||||||
ticket_type, g.entry, g.calendar,
|
ticket_type, g.entry, g.calendar,
|
||||||
va.get("day"), va.get("month"), va.get("year"),
|
va.get("day"), va.get("month"), va.get("year"),
|
||||||
oob=True,
|
oob=True,
|
||||||
@@ -153,9 +153,9 @@ def register():
|
|||||||
|
|
||||||
# Re-render the ticket types list
|
# Re-render the ticket types list
|
||||||
ticket_types = await svc_list_ticket_types(g.s, g.entry.id)
|
ticket_types = await svc_list_ticket_types(g.s, g.entry.id)
|
||||||
from sexp.sexp_components import render_ticket_types_table
|
from sx.sx_components import render_ticket_types_table
|
||||||
va = request.view_args or {}
|
va = request.view_args or {}
|
||||||
return sexp_response(render_ticket_types_table(
|
return sx_response(render_ticket_types_table(
|
||||||
ticket_types, g.entry, g.calendar,
|
ticket_types, g.entry, g.calendar,
|
||||||
va.get("day"), va.get("month"), va.get("year"),
|
va.get("day"), va.get("month"), va.get("year"),
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from .services.tickets import (
|
|||||||
from ..ticket_type.routes import register as register_ticket_type
|
from ..ticket_type.routes import register as register_ticket_type
|
||||||
|
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
@@ -110,9 +110,9 @@ def register():
|
|||||||
|
|
||||||
# Success → re-render the ticket types table
|
# Success → re-render the ticket types table
|
||||||
ticket_types = await svc_list_ticket_types(g.s, g.entry.id)
|
ticket_types = await svc_list_ticket_types(g.s, g.entry.id)
|
||||||
from sexp.sexp_components import render_ticket_types_table
|
from sx.sx_components import render_ticket_types_table
|
||||||
va = request.view_args or {}
|
va = request.view_args or {}
|
||||||
return sexp_response(render_ticket_types_table(
|
return sx_response(render_ticket_types_table(
|
||||||
ticket_types, g.entry, g.calendar,
|
ticket_types, g.entry, g.calendar,
|
||||||
va.get("day"), va.get("month"), va.get("year"),
|
va.get("day"), va.get("month"), va.get("year"),
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from sqlalchemy.orm import selectinload
|
|||||||
from models.calendars import CalendarEntry
|
from models.calendars import CalendarEntry
|
||||||
from shared.infrastructure.cart_identity import current_cart_identity
|
from shared.infrastructure.cart_identity import current_cart_identity
|
||||||
from shared.browser.app.redis_cacher import clear_cache
|
from shared.browser.app.redis_cacher import clear_cache
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
from .services.tickets import (
|
from .services.tickets import (
|
||||||
create_ticket,
|
create_ticket,
|
||||||
@@ -51,16 +51,16 @@ def register() -> Blueprint:
|
|||||||
session_id=ident["session_id"],
|
session_id=ident["session_id"],
|
||||||
)
|
)
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_tickets_page, render_tickets_oob
|
from sx.sx_components import render_tickets_page, render_tickets_oob
|
||||||
|
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
html = await render_tickets_page(ctx, tickets)
|
html = await render_tickets_page(ctx, tickets)
|
||||||
return await make_response(html, 200)
|
return await make_response(html, 200)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_tickets_oob(ctx, tickets)
|
sx_src = await render_tickets_oob(ctx, tickets)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.get("/<code>/")
|
@bp.get("/<code>/")
|
||||||
async def ticket_detail(code: str):
|
async def ticket_detail(code: str):
|
||||||
@@ -82,16 +82,16 @@ def register() -> Blueprint:
|
|||||||
else:
|
else:
|
||||||
return await make_response("Ticket not found", 404)
|
return await make_response("Ticket not found", 404)
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_ticket_detail_page, render_ticket_detail_oob
|
from sx.sx_components import render_ticket_detail_page, render_ticket_detail_oob
|
||||||
|
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
html = await render_ticket_detail_page(ctx, ticket)
|
html = await render_ticket_detail_page(ctx, ticket)
|
||||||
return await make_response(html, 200)
|
return await make_response(html, 200)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_ticket_detail_oob(ctx, ticket)
|
sx_src = await render_ticket_detail_oob(ctx, ticket)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.post("/buy/")
|
@bp.post("/buy/")
|
||||||
@clear_cache(tag="calendars", tag_scope="all")
|
@clear_cache(tag="calendars", tag_scope="all")
|
||||||
@@ -182,8 +182,8 @@ def register() -> Blueprint:
|
|||||||
summary = dto_from_dict(CartSummaryDTO, raw_summary) if raw_summary else CartSummaryDTO()
|
summary = dto_from_dict(CartSummaryDTO, raw_summary) if raw_summary else CartSummaryDTO()
|
||||||
cart_count = summary.count + summary.calendar_count + summary.ticket_count
|
cart_count = summary.count + summary.calendar_count + summary.ticket_count
|
||||||
|
|
||||||
from sexp.sexp_components import render_buy_result
|
from sx.sx_components import render_buy_result
|
||||||
return sexp_response(render_buy_result(entry, created, remaining, cart_count))
|
return sx_response(render_buy_result(entry, created, remaining, cart_count))
|
||||||
|
|
||||||
@bp.post("/adjust/")
|
@bp.post("/adjust/")
|
||||||
@clear_cache(tag="calendars", tag_scope="all")
|
@clear_cache(tag="calendars", tag_scope="all")
|
||||||
@@ -305,8 +305,8 @@ def register() -> Blueprint:
|
|||||||
summary = dto_from_dict(CartSummaryDTO, raw_summary) if raw_summary else CartSummaryDTO()
|
summary = dto_from_dict(CartSummaryDTO, raw_summary) if raw_summary else CartSummaryDTO()
|
||||||
cart_count = summary.count + summary.calendar_count + summary.ticket_count
|
cart_count = summary.count + summary.calendar_count + summary.ticket_count
|
||||||
|
|
||||||
from sexp.sexp_components import render_adjust_response
|
from sx.sx_components import render_adjust_response
|
||||||
return sexp_response(render_adjust_response(
|
return sx_response(render_adjust_response(
|
||||||
entry, ticket_remaining, ticket_sold_count,
|
entry, ticket_remaining, ticket_sold_count,
|
||||||
user_ticket_count, user_ticket_counts_by_type, cart_count,
|
user_ticket_count, user_ticket_counts_by_type, cart_count,
|
||||||
))
|
))
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import path_setup # noqa: F401 # adds shared/ to sys.path
|
import path_setup # noqa: F401 # adds shared/ to sys.path
|
||||||
import sexp.sexp_components as sexp_components # noqa: F401 # ensure Hypercorn --reload watches this file
|
import sx.sx_components as sx_components # noqa: F401 # ensure Hypercorn --reload watches this file
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from quart import g, request
|
from quart import g, request
|
||||||
@@ -95,8 +95,8 @@ def create_app() -> "Quart":
|
|||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def home():
|
async def home():
|
||||||
from quart import make_response
|
from quart import make_response
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_federation_home
|
from sx.sx_components import render_federation_home
|
||||||
|
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
html = await render_federation_home(ctx)
|
html = await render_federation_home(ctx)
|
||||||
|
|||||||
@@ -99,8 +99,8 @@ def register(url_prefix="/auth"):
|
|||||||
# If there's a pending redirect (e.g. OAuth authorize), follow it
|
# If there's a pending redirect (e.g. OAuth authorize), follow it
|
||||||
redirect_url = pop_login_redirect_target()
|
redirect_url = pop_login_redirect_target()
|
||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_login_page
|
from sx.sx_components import render_login_page
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
return await render_login_page(ctx)
|
return await render_login_page(ctx)
|
||||||
|
|
||||||
@@ -111,8 +111,8 @@ def register(url_prefix="/auth"):
|
|||||||
|
|
||||||
is_valid, email = validate_email(email_input)
|
is_valid, email = validate_email(email_input)
|
||||||
if not is_valid:
|
if not is_valid:
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_login_page
|
from sx.sx_components import render_login_page
|
||||||
ctx = await get_template_context(error="Please enter a valid email address.", email=email_input)
|
ctx = await get_template_context(error="Please enter a valid email address.", email=email_input)
|
||||||
return await render_login_page(ctx), 400
|
return await render_login_page(ctx), 400
|
||||||
|
|
||||||
@@ -132,8 +132,8 @@ def register(url_prefix="/auth"):
|
|||||||
"Please try again in a moment."
|
"Please try again in a moment."
|
||||||
)
|
)
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_check_email_page
|
from sx.sx_components import render_check_email_page
|
||||||
ctx = await get_template_context(email=email, email_error=email_error)
|
ctx = await get_template_context(email=email, email_error=email_error)
|
||||||
return await render_check_email_page(ctx)
|
return await render_check_email_page(ctx)
|
||||||
|
|
||||||
@@ -148,15 +148,15 @@ def register(url_prefix="/auth"):
|
|||||||
user, error = await validate_magic_link(s, token)
|
user, error = await validate_magic_link(s, token)
|
||||||
|
|
||||||
if error:
|
if error:
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_login_page
|
from sx.sx_components import render_login_page
|
||||||
ctx = await get_template_context(error=error)
|
ctx = await get_template_context(error=error)
|
||||||
return await render_login_page(ctx), 400
|
return await render_login_page(ctx), 400
|
||||||
user_id = user.id
|
user_id = user.id
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_login_page
|
from sx.sx_components import render_login_page
|
||||||
ctx = await get_template_context(error="Could not sign you in right now. Please try again.")
|
ctx = await get_template_context(error="Could not sign you in right now. Please try again.")
|
||||||
return await render_login_page(ctx), 502
|
return await render_login_page(ctx), 502
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Federation app fragment endpoints.
|
"""Federation app fragment endpoints.
|
||||||
|
|
||||||
Exposes sexp fragments at ``/internal/fragments/<type>`` for consumption
|
Exposes sx fragments at ``/internal/fragments/<type>`` for consumption
|
||||||
by other coop apps via the fragment client.
|
by other coop apps via the fragment client.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -25,15 +25,15 @@ def register():
|
|||||||
async def get_fragment(fragment_type: str):
|
async def get_fragment(fragment_type: str):
|
||||||
handler = _handlers.get(fragment_type)
|
handler = _handlers.get(fragment_type)
|
||||||
if handler is None:
|
if handler is None:
|
||||||
return Response("", status=200, content_type="text/sexp")
|
return Response("", status=200, content_type="text/sx")
|
||||||
src = await handler()
|
src = await handler()
|
||||||
return Response(src, status=200, content_type="text/sexp")
|
return Response(src, status=200, content_type="text/sx")
|
||||||
|
|
||||||
# --- link-card fragment: actor profile preview card --------------------------
|
# --- link-card fragment: actor profile preview card --------------------------
|
||||||
|
|
||||||
def _federation_link_card_sexp(actor, link: str) -> str:
|
def _federation_link_card_sx(actor, link: str) -> str:
|
||||||
from shared.sexp.helpers import sexp_call
|
from shared.sx.helpers import sx_call
|
||||||
return sexp_call("link-card",
|
return sx_call("link-card",
|
||||||
link=link,
|
link=link,
|
||||||
title=actor.display_name or actor.preferred_username,
|
title=actor.display_name or actor.preferred_username,
|
||||||
image=None,
|
image=None,
|
||||||
@@ -59,7 +59,7 @@ def register():
|
|||||||
parts.append(f"<!-- fragment:{u} -->")
|
parts.append(f"<!-- fragment:{u} -->")
|
||||||
actor = await services.federation.get_actor_by_username(g.s, u)
|
actor = await services.federation.get_actor_by_username(g.s, u)
|
||||||
if actor:
|
if actor:
|
||||||
parts.append(_federation_link_card_sexp(
|
parts.append(_federation_link_card_sx(
|
||||||
actor, federation_url(f"/users/{actor.preferred_username}"),
|
actor, federation_url(f"/users/{actor.preferred_username}"),
|
||||||
))
|
))
|
||||||
return "\n".join(parts)
|
return "\n".join(parts)
|
||||||
@@ -71,7 +71,7 @@ def register():
|
|||||||
actor = await services.federation.get_actor_by_username(g.s, lookup)
|
actor = await services.federation.get_actor_by_username(g.s, lookup)
|
||||||
if not actor:
|
if not actor:
|
||||||
return ""
|
return ""
|
||||||
return _federation_link_card_sexp(
|
return _federation_link_card_sx(
|
||||||
actor, federation_url(f"/users/{actor.preferred_username}"),
|
actor, federation_url(f"/users/{actor.preferred_username}"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ def register(url_prefix="/identity"):
|
|||||||
if actor:
|
if actor:
|
||||||
return redirect(url_for("activitypub.actor_profile", username=actor.preferred_username))
|
return redirect(url_for("activitypub.actor_profile", username=actor.preferred_username))
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_choose_username_page
|
from sx.sx_components import render_choose_username_page
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
ctx["actor"] = actor
|
ctx["actor"] = actor
|
||||||
return await render_choose_username_page(ctx)
|
return await render_choose_username_page(ctx)
|
||||||
@@ -71,8 +71,8 @@ def register(url_prefix="/identity"):
|
|||||||
error = "This username is already taken."
|
error = "This username is already taken."
|
||||||
|
|
||||||
if error:
|
if error:
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_choose_username_page
|
from sx.sx_components import render_choose_username_page
|
||||||
ctx = await get_template_context(error=error, username=username)
|
ctx = await get_template_context(error=error, username=username)
|
||||||
ctx["actor"] = None
|
ctx["actor"] = None
|
||||||
return await render_choose_username_page(ctx), 400
|
return await render_choose_username_page(ctx), 400
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from datetime import datetime
|
|||||||
from quart import Blueprint, request, g, redirect, url_for, abort, Response
|
from quart import Blueprint, request, g, redirect, url_for, abort, Response
|
||||||
|
|
||||||
from shared.services.registry import services
|
from shared.services.registry import services
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -40,8 +40,8 @@ def register(url_prefix="/social"):
|
|||||||
return redirect(url_for("auth.login_form"))
|
return redirect(url_for("auth.login_form"))
|
||||||
actor = _require_actor()
|
actor = _require_actor()
|
||||||
items = await services.federation.get_home_timeline(g.s, actor.id)
|
items = await services.federation.get_home_timeline(g.s, actor.id)
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_timeline_page
|
from sx.sx_components import render_timeline_page
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
return await render_timeline_page(ctx, items, "home", actor)
|
return await render_timeline_page(ctx, items, "home", actor)
|
||||||
|
|
||||||
@@ -58,16 +58,16 @@ def register(url_prefix="/social"):
|
|||||||
items = await services.federation.get_home_timeline(
|
items = await services.federation.get_home_timeline(
|
||||||
g.s, actor.id, before=before,
|
g.s, actor.id, before=before,
|
||||||
)
|
)
|
||||||
from sexp.sexp_components import render_timeline_items
|
from sx.sx_components import render_timeline_items
|
||||||
sexp_src = await render_timeline_items(items, "home", actor)
|
sx_src = await render_timeline_items(items, "home", actor)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.get("/public")
|
@bp.get("/public")
|
||||||
async def public_timeline():
|
async def public_timeline():
|
||||||
items = await services.federation.get_public_timeline(g.s)
|
items = await services.federation.get_public_timeline(g.s)
|
||||||
actor = getattr(g, "_social_actor", None)
|
actor = getattr(g, "_social_actor", None)
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_timeline_page
|
from sx.sx_components import render_timeline_page
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
return await render_timeline_page(ctx, items, "public", actor)
|
return await render_timeline_page(ctx, items, "public", actor)
|
||||||
|
|
||||||
@@ -82,9 +82,9 @@ def register(url_prefix="/social"):
|
|||||||
pass
|
pass
|
||||||
items = await services.federation.get_public_timeline(g.s, before=before)
|
items = await services.federation.get_public_timeline(g.s, before=before)
|
||||||
actor = getattr(g, "_social_actor", None)
|
actor = getattr(g, "_social_actor", None)
|
||||||
from sexp.sexp_components import render_timeline_items
|
from sx.sx_components import render_timeline_items
|
||||||
sexp_src = await render_timeline_items(items, "public", actor)
|
sx_src = await render_timeline_items(items, "public", actor)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
# -- Compose --------------------------------------------------------------
|
# -- Compose --------------------------------------------------------------
|
||||||
|
|
||||||
@@ -92,8 +92,8 @@ def register(url_prefix="/social"):
|
|||||||
async def compose_form():
|
async def compose_form():
|
||||||
actor = _require_actor()
|
actor = _require_actor()
|
||||||
reply_to = request.args.get("reply_to")
|
reply_to = request.args.get("reply_to")
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_compose_page
|
from sx.sx_components import render_compose_page
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
return await render_compose_page(ctx, actor, reply_to)
|
return await render_compose_page(ctx, actor, reply_to)
|
||||||
|
|
||||||
@@ -138,8 +138,8 @@ def register(url_prefix="/social"):
|
|||||||
g.s, actor.preferred_username, page=1, per_page=1000,
|
g.s, actor.preferred_username, page=1, per_page=1000,
|
||||||
)
|
)
|
||||||
followed_urls = {a.actor_url for a in following}
|
followed_urls = {a.actor_url for a in following}
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_search_page
|
from sx.sx_components import render_search_page
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
return await render_search_page(ctx, query, actors, total, 1, followed_urls, actor)
|
return await render_search_page(ctx, query, actors, total, 1, followed_urls, actor)
|
||||||
|
|
||||||
@@ -160,9 +160,9 @@ def register(url_prefix="/social"):
|
|||||||
g.s, actor.preferred_username, page=1, per_page=1000,
|
g.s, actor.preferred_username, page=1, per_page=1000,
|
||||||
)
|
)
|
||||||
followed_urls = {a.actor_url for a in following}
|
followed_urls = {a.actor_url for a in following}
|
||||||
from sexp.sexp_components import render_search_results
|
from sx.sx_components import render_search_results
|
||||||
sexp_src = await render_search_results(actors, query, page, followed_urls, actor)
|
sx_src = await render_search_results(actors, query, page, followed_urls, actor)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.post("/follow")
|
@bp.post("/follow")
|
||||||
async def follow():
|
async def follow():
|
||||||
@@ -204,8 +204,8 @@ def register(url_prefix="/social"):
|
|||||||
list_type = "followers"
|
list_type = "followers"
|
||||||
else:
|
else:
|
||||||
list_type = "following"
|
list_type = "following"
|
||||||
from sexp.sexp_components import render_actor_card
|
from sx.sx_components import render_actor_card
|
||||||
return sexp_response(render_actor_card(remote_dto, actor, followed_urls, list_type=list_type))
|
return sx_response(render_actor_card(remote_dto, actor, followed_urls, list_type=list_type))
|
||||||
|
|
||||||
# -- Interactions ---------------------------------------------------------
|
# -- Interactions ---------------------------------------------------------
|
||||||
|
|
||||||
@@ -293,8 +293,8 @@ def register(url_prefix="/social"):
|
|||||||
).limit(1)
|
).limit(1)
|
||||||
)).scalar())
|
)).scalar())
|
||||||
|
|
||||||
from sexp.sexp_components import render_interaction_buttons
|
from sx.sx_components import render_interaction_buttons
|
||||||
return sexp_response(render_interaction_buttons(
|
return sx_response(render_interaction_buttons(
|
||||||
object_id=object_id,
|
object_id=object_id,
|
||||||
author_inbox=author_inbox,
|
author_inbox=author_inbox,
|
||||||
like_count=like_count,
|
like_count=like_count,
|
||||||
@@ -312,8 +312,8 @@ def register(url_prefix="/social"):
|
|||||||
actors, total = await services.federation.get_following(
|
actors, total = await services.federation.get_following(
|
||||||
g.s, actor.preferred_username,
|
g.s, actor.preferred_username,
|
||||||
)
|
)
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_following_page
|
from sx.sx_components import render_following_page
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
return await render_following_page(ctx, actors, total, actor)
|
return await render_following_page(ctx, actors, total, actor)
|
||||||
|
|
||||||
@@ -324,9 +324,9 @@ def register(url_prefix="/social"):
|
|||||||
actors, total = await services.federation.get_following(
|
actors, total = await services.federation.get_following(
|
||||||
g.s, actor.preferred_username, page=page,
|
g.s, actor.preferred_username, page=page,
|
||||||
)
|
)
|
||||||
from sexp.sexp_components import render_following_items
|
from sx.sx_components import render_following_items
|
||||||
sexp_src = await render_following_items(actors, page, actor)
|
sx_src = await render_following_items(actors, page, actor)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.get("/followers")
|
@bp.get("/followers")
|
||||||
async def followers_list():
|
async def followers_list():
|
||||||
@@ -339,8 +339,8 @@ def register(url_prefix="/social"):
|
|||||||
g.s, actor.preferred_username, page=1, per_page=1000,
|
g.s, actor.preferred_username, page=1, per_page=1000,
|
||||||
)
|
)
|
||||||
followed_urls = {a.actor_url for a in following}
|
followed_urls = {a.actor_url for a in following}
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_followers_page
|
from sx.sx_components import render_followers_page
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
return await render_followers_page(ctx, actors, total, followed_urls, actor)
|
return await render_followers_page(ctx, actors, total, followed_urls, actor)
|
||||||
|
|
||||||
@@ -355,9 +355,9 @@ def register(url_prefix="/social"):
|
|||||||
g.s, actor.preferred_username, page=1, per_page=1000,
|
g.s, actor.preferred_username, page=1, per_page=1000,
|
||||||
)
|
)
|
||||||
followed_urls = {a.actor_url for a in following}
|
followed_urls = {a.actor_url for a in following}
|
||||||
from sexp.sexp_components import render_followers_items
|
from sx.sx_components import render_followers_items
|
||||||
sexp_src = await render_followers_items(actors, page, followed_urls, actor)
|
sx_src = await render_followers_items(actors, page, followed_urls, actor)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@bp.get("/actor/<int:id>")
|
@bp.get("/actor/<int:id>")
|
||||||
async def actor_timeline(id: int):
|
async def actor_timeline(id: int):
|
||||||
@@ -388,8 +388,8 @@ def register(url_prefix="/social"):
|
|||||||
)
|
)
|
||||||
).scalar_one_or_none()
|
).scalar_one_or_none()
|
||||||
is_following = existing is not None
|
is_following = existing is not None
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_actor_timeline_page
|
from sx.sx_components import render_actor_timeline_page
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
return await render_actor_timeline_page(ctx, remote_dto, items, is_following, actor)
|
return await render_actor_timeline_page(ctx, remote_dto, items, is_following, actor)
|
||||||
|
|
||||||
@@ -406,9 +406,9 @@ def register(url_prefix="/social"):
|
|||||||
items = await services.federation.get_actor_timeline(
|
items = await services.federation.get_actor_timeline(
|
||||||
g.s, id, before=before,
|
g.s, id, before=before,
|
||||||
)
|
)
|
||||||
from sexp.sexp_components import render_actor_timeline_items
|
from sx.sx_components import render_actor_timeline_items
|
||||||
sexp_src = await render_actor_timeline_items(items, id, actor)
|
sx_src = await render_actor_timeline_items(items, id, actor)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
# -- Notifications --------------------------------------------------------
|
# -- Notifications --------------------------------------------------------
|
||||||
|
|
||||||
@@ -417,8 +417,8 @@ def register(url_prefix="/social"):
|
|||||||
actor = _require_actor()
|
actor = _require_actor()
|
||||||
items = await services.federation.get_notifications(g.s, actor.id)
|
items = await services.federation.get_notifications(g.s, actor.id)
|
||||||
await services.federation.mark_notifications_read(g.s, actor.id)
|
await services.federation.mark_notifications_read(g.s, actor.id)
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_notifications_page
|
from sx.sx_components import render_notifications_page
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
return await render_notifications_page(ctx, items, actor)
|
return await render_notifications_page(ctx, items, actor)
|
||||||
|
|
||||||
@@ -429,7 +429,7 @@ def register(url_prefix="/social"):
|
|||||||
return Response("0", content_type="text/plain")
|
return Response("0", content_type="text/plain")
|
||||||
count = await services.federation.unread_notification_count(g.s, actor.id)
|
count = await services.federation.unread_notification_count(g.s, actor.id)
|
||||||
if count > 0:
|
if count > 0:
|
||||||
from shared.sexp.jinja_bridge import render as render_comp
|
from shared.sx.jinja_bridge import render as render_comp
|
||||||
return Response(
|
return Response(
|
||||||
render_comp("notification-badge", count=str(count)),
|
render_comp("notification-badge", count=str(count)),
|
||||||
content_type="text/html",
|
content_type="text/html",
|
||||||
|
|||||||
@@ -10,13 +10,13 @@ import os
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
from markupsafe import escape
|
from markupsafe import escape
|
||||||
|
|
||||||
from shared.sexp.jinja_bridge import load_service_components
|
from shared.sx.jinja_bridge import load_service_components
|
||||||
from shared.sexp.helpers import (
|
from shared.sx.helpers import (
|
||||||
sexp_call, SexpExpr,
|
sx_call, SxExpr,
|
||||||
root_header_sexp, full_page_sexp, header_child_sexp,
|
root_header_sx, full_page_sx, header_child_sx,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Load federation-specific .sexpr components at import time
|
# Load federation-specific .sx components at import time
|
||||||
load_service_components(os.path.dirname(os.path.dirname(__file__)))
|
load_service_components(os.path.dirname(os.path.dirname(__file__)))
|
||||||
|
|
||||||
|
|
||||||
@@ -24,13 +24,13 @@ load_service_components(os.path.dirname(os.path.dirname(__file__)))
|
|||||||
# Social header nav
|
# Social header nav
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _social_nav_sexp(actor: Any) -> str:
|
def _social_nav_sx(actor: Any) -> str:
|
||||||
"""Build the social header nav bar content."""
|
"""Build the social header nav bar content."""
|
||||||
from quart import url_for, request
|
from quart import url_for, request
|
||||||
|
|
||||||
if not actor:
|
if not actor:
|
||||||
choose_url = url_for("identity.choose_username_form")
|
choose_url = url_for("identity.choose_username_form")
|
||||||
return sexp_call("federation-nav-choose-username", url=choose_url)
|
return sx_call("federation-nav-choose-username", url=choose_url)
|
||||||
|
|
||||||
links = [
|
links = [
|
||||||
("social.home_timeline", "Timeline"),
|
("social.home_timeline", "Timeline"),
|
||||||
@@ -45,7 +45,7 @@ def _social_nav_sexp(actor: Any) -> str:
|
|||||||
for endpoint, label in links:
|
for endpoint, label in links:
|
||||||
href = url_for(endpoint)
|
href = url_for(endpoint)
|
||||||
bold = " font-bold" if request.path == href else ""
|
bold = " font-bold" if request.path == href else ""
|
||||||
parts.append(sexp_call(
|
parts.append(sx_call(
|
||||||
"federation-nav-link",
|
"federation-nav-link",
|
||||||
href=href,
|
href=href,
|
||||||
cls=f"px-2 py-1 rounded hover:bg-stone-200{bold}",
|
cls=f"px-2 py-1 rounded hover:bg-stone-200{bold}",
|
||||||
@@ -56,7 +56,7 @@ def _social_nav_sexp(actor: Any) -> str:
|
|||||||
notif_url = url_for("social.notifications")
|
notif_url = url_for("social.notifications")
|
||||||
notif_count_url = url_for("social.notification_count")
|
notif_count_url = url_for("social.notification_count")
|
||||||
notif_bold = " font-bold" if request.path == notif_url else ""
|
notif_bold = " font-bold" if request.path == notif_url else ""
|
||||||
parts.append(sexp_call(
|
parts.append(sx_call(
|
||||||
"federation-nav-notification-link",
|
"federation-nav-notification-link",
|
||||||
href=notif_url,
|
href=notif_url,
|
||||||
cls=f"px-2 py-1 rounded hover:bg-stone-200 relative{notif_bold}",
|
cls=f"px-2 py-1 rounded hover:bg-stone-200 relative{notif_bold}",
|
||||||
@@ -65,31 +65,31 @@ def _social_nav_sexp(actor: Any) -> str:
|
|||||||
|
|
||||||
# Profile link
|
# Profile link
|
||||||
profile_url = url_for("activitypub.actor_profile", username=actor.preferred_username)
|
profile_url = url_for("activitypub.actor_profile", username=actor.preferred_username)
|
||||||
parts.append(sexp_call(
|
parts.append(sx_call(
|
||||||
"federation-nav-link",
|
"federation-nav-link",
|
||||||
href=profile_url,
|
href=profile_url,
|
||||||
cls="px-2 py-1 rounded hover:bg-stone-200",
|
cls="px-2 py-1 rounded hover:bg-stone-200",
|
||||||
label=f"@{actor.preferred_username}",
|
label=f"@{actor.preferred_username}",
|
||||||
))
|
))
|
||||||
|
|
||||||
items_sexp = "(<> " + " ".join(parts) + ")"
|
items_sx = "(<> " + " ".join(parts) + ")"
|
||||||
return sexp_call("federation-nav-bar", items=SexpExpr(items_sexp))
|
return sx_call("federation-nav-bar", items=SxExpr(items_sx))
|
||||||
|
|
||||||
|
|
||||||
def _social_header_sexp(actor: Any) -> str:
|
def _social_header_sx(actor: Any) -> str:
|
||||||
"""Build the social section header row."""
|
"""Build the social section header row."""
|
||||||
nav_sexp = _social_nav_sexp(actor)
|
nav_sx = _social_nav_sx(actor)
|
||||||
return sexp_call("federation-social-header", nav=SexpExpr(nav_sexp))
|
return sx_call("federation-social-header", nav=SxExpr(nav_sx))
|
||||||
|
|
||||||
|
|
||||||
def _social_page(ctx: dict, actor: Any, *, content: str,
|
def _social_page(ctx: dict, actor: Any, *, content: str,
|
||||||
title: str = "Rose Ash", meta_html: str = "") -> str:
|
title: str = "Rose Ash", meta_html: str = "") -> str:
|
||||||
"""Render a social page with header and content."""
|
"""Render a social page with header and content."""
|
||||||
hdr = root_header_sexp(ctx)
|
hdr = root_header_sx(ctx)
|
||||||
social_hdr = _social_header_sexp(actor)
|
social_hdr = _social_header_sx(actor)
|
||||||
child = header_child_sexp(social_hdr)
|
child = header_child_sx(social_hdr)
|
||||||
header_rows = "(<> " + hdr + " " + child + ")"
|
header_rows = "(<> " + hdr + " " + child + ")"
|
||||||
return full_page_sexp(ctx, header_rows=header_rows, content=content,
|
return full_page_sx(ctx, header_rows=header_rows, content=content,
|
||||||
meta_html=meta_html or f'<title>{escape(title)}</title>')
|
meta_html=meta_html or f'<title>{escape(title)}</title>')
|
||||||
|
|
||||||
|
|
||||||
@@ -97,7 +97,7 @@ def _social_page(ctx: dict, actor: Any, *, content: str,
|
|||||||
# Post card
|
# Post card
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _interaction_buttons_sexp(item: Any, actor: Any) -> str:
|
def _interaction_buttons_sx(item: Any, actor: Any) -> str:
|
||||||
"""Render like/boost/reply buttons for a post."""
|
"""Render like/boost/reply buttons for a post."""
|
||||||
from shared.browser.app.csrf import generate_csrf_token
|
from shared.browser.app.csrf import generate_csrf_token
|
||||||
from quart import url_for
|
from quart import url_for
|
||||||
@@ -130,31 +130,31 @@ def _interaction_buttons_sexp(item: Any, actor: Any) -> str:
|
|||||||
boost_cls = "hover:text-green-600"
|
boost_cls = "hover:text-green-600"
|
||||||
|
|
||||||
reply_url = url_for("social.compose_form", reply_to=oid) if oid else ""
|
reply_url = url_for("social.compose_form", reply_to=oid) if oid else ""
|
||||||
reply_sexp = sexp_call("federation-reply-link", url=reply_url) if reply_url else ""
|
reply_sx = sx_call("federation-reply-link", url=reply_url) if reply_url else ""
|
||||||
|
|
||||||
like_form = sexp_call(
|
like_form = sx_call(
|
||||||
"federation-like-form",
|
"federation-like-form",
|
||||||
action=like_action, target=target, oid=oid, ainbox=ainbox,
|
action=like_action, target=target, oid=oid, ainbox=ainbox,
|
||||||
csrf=csrf, cls=f"flex items-center gap-1 {like_cls}",
|
csrf=csrf, cls=f"flex items-center gap-1 {like_cls}",
|
||||||
icon=like_icon, count=str(lcount),
|
icon=like_icon, count=str(lcount),
|
||||||
)
|
)
|
||||||
|
|
||||||
boost_form = sexp_call(
|
boost_form = sx_call(
|
||||||
"federation-boost-form",
|
"federation-boost-form",
|
||||||
action=boost_action, target=target, oid=oid, ainbox=ainbox,
|
action=boost_action, target=target, oid=oid, ainbox=ainbox,
|
||||||
csrf=csrf, cls=f"flex items-center gap-1 {boost_cls}",
|
csrf=csrf, cls=f"flex items-center gap-1 {boost_cls}",
|
||||||
count=str(bcount),
|
count=str(bcount),
|
||||||
)
|
)
|
||||||
|
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"federation-interaction-buttons",
|
"federation-interaction-buttons",
|
||||||
like=SexpExpr(like_form),
|
like=SxExpr(like_form),
|
||||||
boost=SexpExpr(boost_form),
|
boost=SxExpr(boost_form),
|
||||||
reply=SexpExpr(reply_sexp) if reply_sexp else None,
|
reply=SxExpr(reply_sx) if reply_sx else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _post_card_sexp(item: Any, actor: Any) -> str:
|
def _post_card_sx(item: Any, actor: Any) -> str:
|
||||||
"""Render a single timeline post card."""
|
"""Render a single timeline post card."""
|
||||||
boosted_by = getattr(item, "boosted_by", None)
|
boosted_by = getattr(item, "boosted_by", None)
|
||||||
actor_icon = getattr(item, "actor_icon", None)
|
actor_icon = getattr(item, "actor_icon", None)
|
||||||
@@ -167,15 +167,15 @@ def _post_card_sexp(item: Any, actor: Any) -> str:
|
|||||||
url = getattr(item, "url", None)
|
url = getattr(item, "url", None)
|
||||||
post_type = getattr(item, "post_type", "")
|
post_type = getattr(item, "post_type", "")
|
||||||
|
|
||||||
boost_sexp = sexp_call(
|
boost_sx = sx_call(
|
||||||
"federation-boost-label", name=str(escape(boosted_by)),
|
"federation-boost-label", name=str(escape(boosted_by)),
|
||||||
) if boosted_by else ""
|
) if boosted_by else ""
|
||||||
|
|
||||||
if actor_icon:
|
if actor_icon:
|
||||||
avatar = sexp_call("federation-avatar-img", src=actor_icon, cls="w-10 h-10 rounded-full")
|
avatar = sx_call("federation-avatar-img", src=actor_icon, cls="w-10 h-10 rounded-full")
|
||||||
else:
|
else:
|
||||||
initial = actor_name[0].upper() if actor_name else "?"
|
initial = actor_name[0].upper() if actor_name else "?"
|
||||||
avatar = sexp_call(
|
avatar = sx_call(
|
||||||
"federation-avatar-placeholder",
|
"federation-avatar-placeholder",
|
||||||
cls="w-10 h-10 rounded-full bg-stone-300 flex items-center justify-center text-stone-600 font-bold text-sm",
|
cls="w-10 h-10 rounded-full bg-stone-300 flex items-center justify-center text-stone-600 font-bold text-sm",
|
||||||
initial=initial,
|
initial=initial,
|
||||||
@@ -185,37 +185,37 @@ def _post_card_sexp(item: Any, actor: Any) -> str:
|
|||||||
time_str = published.strftime("%b %d, %H:%M") if published else ""
|
time_str = published.strftime("%b %d, %H:%M") if published else ""
|
||||||
|
|
||||||
if summary:
|
if summary:
|
||||||
content_sexp = sexp_call(
|
content_sx = sx_call(
|
||||||
"federation-content-cw",
|
"federation-content-cw",
|
||||||
summary=str(escape(summary)), content=content,
|
summary=str(escape(summary)), content=content,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
content_sexp = sexp_call("federation-content-plain", content=content)
|
content_sx = sx_call("federation-content-plain", content=content)
|
||||||
|
|
||||||
original_sexp = ""
|
original_sx = ""
|
||||||
if url and post_type == "remote":
|
if url and post_type == "remote":
|
||||||
original_sexp = sexp_call("federation-original-link", url=url)
|
original_sx = sx_call("federation-original-link", url=url)
|
||||||
|
|
||||||
interactions_sexp = ""
|
interactions_sx = ""
|
||||||
if actor:
|
if actor:
|
||||||
oid = getattr(item, "object_id", "") or ""
|
oid = getattr(item, "object_id", "") or ""
|
||||||
safe_id = oid.replace("/", "_").replace(":", "_")
|
safe_id = oid.replace("/", "_").replace(":", "_")
|
||||||
interactions_sexp = sexp_call(
|
interactions_sx = sx_call(
|
||||||
"federation-interactions-wrap",
|
"federation-interactions-wrap",
|
||||||
id=f"interactions-{safe_id}",
|
id=f"interactions-{safe_id}",
|
||||||
buttons=SexpExpr(_interaction_buttons_sexp(item, actor)),
|
buttons=SxExpr(_interaction_buttons_sx(item, actor)),
|
||||||
)
|
)
|
||||||
|
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"federation-post-card",
|
"federation-post-card",
|
||||||
boost=SexpExpr(boost_sexp) if boost_sexp else None,
|
boost=SxExpr(boost_sx) if boost_sx else None,
|
||||||
avatar=SexpExpr(avatar),
|
avatar=SxExpr(avatar),
|
||||||
actor_name=str(escape(actor_name)),
|
actor_name=str(escape(actor_name)),
|
||||||
actor_username=str(escape(actor_username)),
|
actor_username=str(escape(actor_username)),
|
||||||
domain=domain_str, time=time_str,
|
domain=domain_str, time=time_str,
|
||||||
content=SexpExpr(content_sexp),
|
content=SxExpr(content_sx),
|
||||||
original=SexpExpr(original_sexp) if original_sexp else None,
|
original=SxExpr(original_sx) if original_sx else None,
|
||||||
interactions=SexpExpr(interactions_sexp) if interactions_sexp else None,
|
interactions=SxExpr(interactions_sx) if interactions_sx else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -223,12 +223,12 @@ def _post_card_sexp(item: Any, actor: Any) -> str:
|
|||||||
# Timeline items (pagination fragment)
|
# Timeline items (pagination fragment)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _timeline_items_sexp(items: list, timeline_type: str, actor: Any,
|
def _timeline_items_sx(items: list, timeline_type: str, actor: Any,
|
||||||
actor_id: int | None = None) -> str:
|
actor_id: int | None = None) -> str:
|
||||||
"""Render timeline items with infinite scroll sentinel."""
|
"""Render timeline items with infinite scroll sentinel."""
|
||||||
from quart import url_for
|
from quart import url_for
|
||||||
|
|
||||||
parts = [_post_card_sexp(item, actor) for item in items]
|
parts = [_post_card_sx(item, actor) for item in items]
|
||||||
|
|
||||||
if items:
|
if items:
|
||||||
last = items[-1]
|
last = items[-1]
|
||||||
@@ -237,7 +237,7 @@ def _timeline_items_sexp(items: list, timeline_type: str, actor: Any,
|
|||||||
next_url = url_for("social.actor_timeline_page", id=actor_id, before=before)
|
next_url = url_for("social.actor_timeline_page", id=actor_id, before=before)
|
||||||
else:
|
else:
|
||||||
next_url = url_for(f"social.{timeline_type}_timeline_page", before=before)
|
next_url = url_for(f"social.{timeline_type}_timeline_page", before=before)
|
||||||
parts.append(sexp_call("federation-scroll-sentinel", url=next_url))
|
parts.append(sx_call("federation-scroll-sentinel", url=next_url))
|
||||||
|
|
||||||
return "(<> " + " ".join(parts) + ")" if parts else ""
|
return "(<> " + " ".join(parts) + ")" if parts else ""
|
||||||
|
|
||||||
@@ -246,7 +246,7 @@ def _timeline_items_sexp(items: list, timeline_type: str, actor: Any,
|
|||||||
# Search results (pagination fragment)
|
# Search results (pagination fragment)
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _actor_card_sexp(a: Any, actor: Any, followed_urls: set,
|
def _actor_card_sx(a: Any, actor: Any, followed_urls: set,
|
||||||
*, list_type: str = "search") -> str:
|
*, list_type: str = "search") -> str:
|
||||||
"""Render a single actor card with follow/unfollow button."""
|
"""Render a single actor card with follow/unfollow button."""
|
||||||
from shared.browser.app.csrf import generate_csrf_token
|
from shared.browser.app.csrf import generate_csrf_token
|
||||||
@@ -264,10 +264,10 @@ def _actor_card_sexp(a: Any, actor: Any, followed_urls: set,
|
|||||||
safe_id = actor_url.replace("/", "_").replace(":", "_")
|
safe_id = actor_url.replace("/", "_").replace(":", "_")
|
||||||
|
|
||||||
if icon_url:
|
if icon_url:
|
||||||
avatar = sexp_call("federation-actor-avatar-img", src=icon_url, cls="w-12 h-12 rounded-full")
|
avatar = sx_call("federation-actor-avatar-img", src=icon_url, cls="w-12 h-12 rounded-full")
|
||||||
else:
|
else:
|
||||||
initial = (display_name or username)[0].upper() if (display_name or username) else "?"
|
initial = (display_name or username)[0].upper() if (display_name or username) else "?"
|
||||||
avatar = sexp_call(
|
avatar = sx_call(
|
||||||
"federation-actor-avatar-placeholder",
|
"federation-actor-avatar-placeholder",
|
||||||
cls="w-12 h-12 rounded-full bg-stone-300 flex items-center justify-center text-stone-600 font-bold",
|
cls="w-12 h-12 rounded-full bg-stone-300 flex items-center justify-center text-stone-600 font-bold",
|
||||||
initial=initial,
|
initial=initial,
|
||||||
@@ -275,69 +275,69 @@ def _actor_card_sexp(a: Any, actor: Any, followed_urls: set,
|
|||||||
|
|
||||||
# Name link
|
# Name link
|
||||||
if (list_type in ("following", "search")) and aid:
|
if (list_type in ("following", "search")) and aid:
|
||||||
name_sexp = sexp_call(
|
name_sx = sx_call(
|
||||||
"federation-actor-name-link",
|
"federation-actor-name-link",
|
||||||
href=url_for("social.actor_timeline", id=aid),
|
href=url_for("social.actor_timeline", id=aid),
|
||||||
name=str(escape(display_name)),
|
name=str(escape(display_name)),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
name_sexp = sexp_call(
|
name_sx = sx_call(
|
||||||
"federation-actor-name-link-external",
|
"federation-actor-name-link-external",
|
||||||
href=f"https://{domain}/@{username}",
|
href=f"https://{domain}/@{username}",
|
||||||
name=str(escape(display_name)),
|
name=str(escape(display_name)),
|
||||||
)
|
)
|
||||||
|
|
||||||
summary_sexp = sexp_call("federation-actor-summary", summary=summary) if summary else ""
|
summary_sx = sx_call("federation-actor-summary", summary=summary) if summary else ""
|
||||||
|
|
||||||
# Follow/unfollow button
|
# Follow/unfollow button
|
||||||
button_sexp = ""
|
button_sx = ""
|
||||||
if actor:
|
if actor:
|
||||||
is_followed = actor_url in (followed_urls or set())
|
is_followed = actor_url in (followed_urls or set())
|
||||||
if list_type == "following" or is_followed:
|
if list_type == "following" or is_followed:
|
||||||
button_sexp = sexp_call(
|
button_sx = sx_call(
|
||||||
"federation-unfollow-button",
|
"federation-unfollow-button",
|
||||||
action=url_for("social.unfollow"), csrf=csrf, actor_url=actor_url,
|
action=url_for("social.unfollow"), csrf=csrf, actor_url=actor_url,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
label = "Follow Back" if list_type == "followers" else "Follow"
|
label = "Follow Back" if list_type == "followers" else "Follow"
|
||||||
button_sexp = sexp_call(
|
button_sx = sx_call(
|
||||||
"federation-follow-button",
|
"federation-follow-button",
|
||||||
action=url_for("social.follow"), csrf=csrf, actor_url=actor_url, label=label,
|
action=url_for("social.follow"), csrf=csrf, actor_url=actor_url, label=label,
|
||||||
)
|
)
|
||||||
|
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"federation-actor-card",
|
"federation-actor-card",
|
||||||
cls="bg-white rounded-lg shadow-sm border border-stone-200 p-4 mb-3 flex items-center gap-4",
|
cls="bg-white rounded-lg shadow-sm border border-stone-200 p-4 mb-3 flex items-center gap-4",
|
||||||
id=f"actor-{safe_id}",
|
id=f"actor-{safe_id}",
|
||||||
avatar=SexpExpr(avatar),
|
avatar=SxExpr(avatar),
|
||||||
name=SexpExpr(name_sexp),
|
name=SxExpr(name_sx),
|
||||||
username=str(escape(username)), domain=str(escape(domain)),
|
username=str(escape(username)), domain=str(escape(domain)),
|
||||||
summary=SexpExpr(summary_sexp) if summary_sexp else None,
|
summary=SxExpr(summary_sx) if summary_sx else None,
|
||||||
button=SexpExpr(button_sexp) if button_sexp else None,
|
button=SxExpr(button_sx) if button_sx else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _search_results_sexp(actors: list, query: str, page: int,
|
def _search_results_sx(actors: list, query: str, page: int,
|
||||||
followed_urls: set, actor: Any) -> str:
|
followed_urls: set, actor: Any) -> str:
|
||||||
"""Render search results with pagination sentinel."""
|
"""Render search results with pagination sentinel."""
|
||||||
from quart import url_for
|
from quart import url_for
|
||||||
|
|
||||||
parts = [_actor_card_sexp(a, actor, followed_urls, list_type="search") for a in actors]
|
parts = [_actor_card_sx(a, actor, followed_urls, list_type="search") for a in actors]
|
||||||
if len(actors) >= 20:
|
if len(actors) >= 20:
|
||||||
next_url = url_for("social.search_page", q=query, page=page + 1)
|
next_url = url_for("social.search_page", q=query, page=page + 1)
|
||||||
parts.append(sexp_call("federation-scroll-sentinel", url=next_url))
|
parts.append(sx_call("federation-scroll-sentinel", url=next_url))
|
||||||
return "(<> " + " ".join(parts) + ")" if parts else ""
|
return "(<> " + " ".join(parts) + ")" if parts else ""
|
||||||
|
|
||||||
|
|
||||||
def _actor_list_items_sexp(actors: list, page: int, list_type: str,
|
def _actor_list_items_sx(actors: list, page: int, list_type: str,
|
||||||
followed_urls: set, actor: Any) -> str:
|
followed_urls: set, actor: Any) -> str:
|
||||||
"""Render actor list items (following/followers) with pagination sentinel."""
|
"""Render actor list items (following/followers) with pagination sentinel."""
|
||||||
from quart import url_for
|
from quart import url_for
|
||||||
|
|
||||||
parts = [_actor_card_sexp(a, actor, followed_urls, list_type=list_type) for a in actors]
|
parts = [_actor_card_sx(a, actor, followed_urls, list_type=list_type) for a in actors]
|
||||||
if len(actors) >= 20:
|
if len(actors) >= 20:
|
||||||
next_url = url_for(f"social.{list_type}_list_page", page=page + 1)
|
next_url = url_for(f"social.{list_type}_list_page", page=page + 1)
|
||||||
parts.append(sexp_call("federation-scroll-sentinel", url=next_url))
|
parts.append(sx_call("federation-scroll-sentinel", url=next_url))
|
||||||
return "(<> " + " ".join(parts) + ")" if parts else ""
|
return "(<> " + " ".join(parts) + ")" if parts else ""
|
||||||
|
|
||||||
|
|
||||||
@@ -345,7 +345,7 @@ def _actor_list_items_sexp(actors: list, page: int, list_type: str,
|
|||||||
# Notification card
|
# Notification card
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
def _notification_sexp(notif: Any) -> str:
|
def _notification_sx(notif: Any) -> str:
|
||||||
"""Render a single notification."""
|
"""Render a single notification."""
|
||||||
from_name = getattr(notif, "from_actor_name", "?")
|
from_name = getattr(notif, "from_actor_name", "?")
|
||||||
from_username = getattr(notif, "from_actor_username", "")
|
from_username = getattr(notif, "from_actor_username", "")
|
||||||
@@ -360,10 +360,10 @@ def _notification_sexp(notif: Any) -> str:
|
|||||||
border = " border-l-4 border-l-stone-400" if not read else ""
|
border = " border-l-4 border-l-stone-400" if not read else ""
|
||||||
|
|
||||||
if from_icon:
|
if from_icon:
|
||||||
avatar = sexp_call("federation-avatar-img", src=from_icon, cls="w-8 h-8 rounded-full")
|
avatar = sx_call("federation-avatar-img", src=from_icon, cls="w-8 h-8 rounded-full")
|
||||||
else:
|
else:
|
||||||
initial = from_name[0].upper() if from_name else "?"
|
initial = from_name[0].upper() if from_name else "?"
|
||||||
avatar = sexp_call(
|
avatar = sx_call(
|
||||||
"federation-avatar-placeholder",
|
"federation-avatar-placeholder",
|
||||||
cls="w-8 h-8 rounded-full bg-stone-300 flex items-center justify-center text-stone-600 font-bold text-xs",
|
cls="w-8 h-8 rounded-full bg-stone-300 flex items-center justify-center text-stone-600 font-bold text-xs",
|
||||||
initial=initial,
|
initial=initial,
|
||||||
@@ -382,19 +382,19 @@ def _notification_sexp(notif: Any) -> str:
|
|||||||
if ntype == "follow" and app_domain and app_domain != "federation":
|
if ntype == "follow" and app_domain and app_domain != "federation":
|
||||||
action += f" on {escape(app_domain)}"
|
action += f" on {escape(app_domain)}"
|
||||||
|
|
||||||
preview_sexp = sexp_call(
|
preview_sx = sx_call(
|
||||||
"federation-notification-preview", preview=str(escape(preview)),
|
"federation-notification-preview", preview=str(escape(preview)),
|
||||||
) if preview else ""
|
) if preview else ""
|
||||||
time_str = created.strftime("%b %d, %H:%M") if created else ""
|
time_str = created.strftime("%b %d, %H:%M") if created else ""
|
||||||
|
|
||||||
return sexp_call(
|
return sx_call(
|
||||||
"federation-notification-card",
|
"federation-notification-card",
|
||||||
cls=f"bg-white rounded-lg shadow-sm border border-stone-200 p-4{border}",
|
cls=f"bg-white rounded-lg shadow-sm border border-stone-200 p-4{border}",
|
||||||
avatar=SexpExpr(avatar),
|
avatar=SxExpr(avatar),
|
||||||
from_name=str(escape(from_name)),
|
from_name=str(escape(from_name)),
|
||||||
from_username=str(escape(from_username)),
|
from_username=str(escape(from_username)),
|
||||||
from_domain=domain_str, action_text=action,
|
from_domain=domain_str, action_text=action,
|
||||||
preview=SexpExpr(preview_sexp) if preview_sexp else None,
|
preview=SxExpr(preview_sx) if preview_sx else None,
|
||||||
time=time_str,
|
time=time_str,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -405,8 +405,8 @@ def _notification_sexp(notif: Any) -> str:
|
|||||||
|
|
||||||
async def render_federation_home(ctx: dict) -> str:
|
async def render_federation_home(ctx: dict) -> str:
|
||||||
"""Full page: federation home (minimal)."""
|
"""Full page: federation home (minimal)."""
|
||||||
hdr = root_header_sexp(ctx)
|
hdr = root_header_sx(ctx)
|
||||||
return full_page_sexp(ctx, header_rows=hdr)
|
return full_page_sx(ctx, header_rows=hdr)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -423,11 +423,11 @@ async def render_login_page(ctx: dict) -> str:
|
|||||||
action = url_for("auth.start_login")
|
action = url_for("auth.start_login")
|
||||||
csrf = generate_csrf_token()
|
csrf = generate_csrf_token()
|
||||||
|
|
||||||
error_sexp = sexp_call("federation-error-banner", error=error) if error else ""
|
error_sx = sx_call("federation-error-banner", error=error) if error else ""
|
||||||
|
|
||||||
content = sexp_call(
|
content = sx_call(
|
||||||
"federation-login-form",
|
"federation-login-form",
|
||||||
error=SexpExpr(error_sexp) if error_sexp else None,
|
error=SxExpr(error_sx) if error_sx else None,
|
||||||
action=action, csrf=csrf,
|
action=action, csrf=csrf,
|
||||||
email=str(escape(email)),
|
email=str(escape(email)),
|
||||||
)
|
)
|
||||||
@@ -441,14 +441,14 @@ async def render_check_email_page(ctx: dict) -> str:
|
|||||||
email = ctx.get("email", "")
|
email = ctx.get("email", "")
|
||||||
email_error = ctx.get("email_error")
|
email_error = ctx.get("email_error")
|
||||||
|
|
||||||
error_sexp = sexp_call(
|
error_sx = sx_call(
|
||||||
"federation-check-email-error", error=str(escape(email_error)),
|
"federation-check-email-error", error=str(escape(email_error)),
|
||||||
) if email_error else ""
|
) if email_error else ""
|
||||||
|
|
||||||
content = sexp_call(
|
content = sx_call(
|
||||||
"federation-check-email",
|
"federation-check-email",
|
||||||
email=str(escape(email)),
|
email=str(escape(email)),
|
||||||
error=SexpExpr(error_sexp) if error_sexp else None,
|
error=SxExpr(error_sx) if error_sx else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
return _social_page(ctx, None, content=content,
|
return _social_page(ctx, None, content=content,
|
||||||
@@ -465,18 +465,18 @@ async def render_timeline_page(ctx: dict, items: list, timeline_type: str,
|
|||||||
from quart import url_for
|
from quart import url_for
|
||||||
|
|
||||||
label = "Home" if timeline_type == "home" else "Public"
|
label = "Home" if timeline_type == "home" else "Public"
|
||||||
compose_sexp = ""
|
compose_sx = ""
|
||||||
if actor:
|
if actor:
|
||||||
compose_url = url_for("social.compose_form")
|
compose_url = url_for("social.compose_form")
|
||||||
compose_sexp = sexp_call("federation-compose-button", url=compose_url)
|
compose_sx = sx_call("federation-compose-button", url=compose_url)
|
||||||
|
|
||||||
timeline_sexp = _timeline_items_sexp(items, timeline_type, actor)
|
timeline_sx = _timeline_items_sx(items, timeline_type, actor)
|
||||||
|
|
||||||
content = sexp_call(
|
content = sx_call(
|
||||||
"federation-timeline-page",
|
"federation-timeline-page",
|
||||||
label=label,
|
label=label,
|
||||||
compose=SexpExpr(compose_sexp) if compose_sexp else None,
|
compose=SxExpr(compose_sx) if compose_sx else None,
|
||||||
timeline=SexpExpr(timeline_sexp) if timeline_sexp else None,
|
timeline=SxExpr(timeline_sx) if timeline_sx else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
return _social_page(ctx, actor, content=content,
|
return _social_page(ctx, actor, content=content,
|
||||||
@@ -486,7 +486,7 @@ async def render_timeline_page(ctx: dict, items: list, timeline_type: str,
|
|||||||
async def render_timeline_items(items: list, timeline_type: str,
|
async def render_timeline_items(items: list, timeline_type: str,
|
||||||
actor: Any, actor_id: int | None = None) -> str:
|
actor: Any, actor_id: int | None = None) -> str:
|
||||||
"""Pagination fragment: timeline items."""
|
"""Pagination fragment: timeline items."""
|
||||||
return _timeline_items_sexp(items, timeline_type, actor, actor_id)
|
return _timeline_items_sx(items, timeline_type, actor, actor_id)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -501,17 +501,17 @@ async def render_compose_page(ctx: dict, actor: Any, reply_to: str | None) -> st
|
|||||||
csrf = generate_csrf_token()
|
csrf = generate_csrf_token()
|
||||||
action = url_for("social.compose_submit")
|
action = url_for("social.compose_submit")
|
||||||
|
|
||||||
reply_sexp = ""
|
reply_sx = ""
|
||||||
if reply_to:
|
if reply_to:
|
||||||
reply_sexp = sexp_call(
|
reply_sx = sx_call(
|
||||||
"federation-compose-reply",
|
"federation-compose-reply",
|
||||||
reply_to=str(escape(reply_to)),
|
reply_to=str(escape(reply_to)),
|
||||||
)
|
)
|
||||||
|
|
||||||
content = sexp_call(
|
content = sx_call(
|
||||||
"federation-compose-form",
|
"federation-compose-form",
|
||||||
action=action, csrf=csrf,
|
action=action, csrf=csrf,
|
||||||
reply=SexpExpr(reply_sexp) if reply_sexp else None,
|
reply=SxExpr(reply_sx) if reply_sx else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
return _social_page(ctx, actor, content=content,
|
return _social_page(ctx, actor, content=content,
|
||||||
@@ -530,29 +530,29 @@ async def render_search_page(ctx: dict, query: str, actors: list, total: int,
|
|||||||
search_url = url_for("social.search")
|
search_url = url_for("social.search")
|
||||||
search_page_url = url_for("social.search_page")
|
search_page_url = url_for("social.search_page")
|
||||||
|
|
||||||
results_sexp = _search_results_sexp(actors, query, page, followed_urls, actor)
|
results_sx = _search_results_sx(actors, query, page, followed_urls, actor)
|
||||||
|
|
||||||
info_sexp = ""
|
info_sx = ""
|
||||||
if query and total:
|
if query and total:
|
||||||
s = "s" if total != 1 else ""
|
s = "s" if total != 1 else ""
|
||||||
info_sexp = sexp_call(
|
info_sx = sx_call(
|
||||||
"federation-search-info",
|
"federation-search-info",
|
||||||
cls="text-sm text-stone-500 mb-4",
|
cls="text-sm text-stone-500 mb-4",
|
||||||
text=f"{total} result{s} for <strong>{escape(query)}</strong>",
|
text=f"{total} result{s} for <strong>{escape(query)}</strong>",
|
||||||
)
|
)
|
||||||
elif query:
|
elif query:
|
||||||
info_sexp = sexp_call(
|
info_sx = sx_call(
|
||||||
"federation-search-info",
|
"federation-search-info",
|
||||||
cls="text-stone-500 mb-4",
|
cls="text-stone-500 mb-4",
|
||||||
text=f"No results found for <strong>{escape(query)}</strong>",
|
text=f"No results found for <strong>{escape(query)}</strong>",
|
||||||
)
|
)
|
||||||
|
|
||||||
content = sexp_call(
|
content = sx_call(
|
||||||
"federation-search-page",
|
"federation-search-page",
|
||||||
search_url=search_url, search_page_url=search_page_url,
|
search_url=search_url, search_page_url=search_page_url,
|
||||||
query=str(escape(query)),
|
query=str(escape(query)),
|
||||||
info=SexpExpr(info_sexp) if info_sexp else None,
|
info=SxExpr(info_sx) if info_sx else None,
|
||||||
results=SexpExpr(results_sexp) if results_sexp else None,
|
results=SxExpr(results_sx) if results_sx else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
return _social_page(ctx, actor, content=content,
|
return _social_page(ctx, actor, content=content,
|
||||||
@@ -562,7 +562,7 @@ async def render_search_page(ctx: dict, query: str, actors: list, total: int,
|
|||||||
async def render_search_results(actors: list, query: str, page: int,
|
async def render_search_results(actors: list, query: str, page: int,
|
||||||
followed_urls: set, actor: Any) -> str:
|
followed_urls: set, actor: Any) -> str:
|
||||||
"""Pagination fragment: search results."""
|
"""Pagination fragment: search results."""
|
||||||
return _search_results_sexp(actors, query, page, followed_urls, actor)
|
return _search_results_sx(actors, query, page, followed_urls, actor)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -572,11 +572,11 @@ async def render_search_results(actors: list, query: str, page: int,
|
|||||||
async def render_following_page(ctx: dict, actors: list, total: int,
|
async def render_following_page(ctx: dict, actors: list, total: int,
|
||||||
actor: Any) -> str:
|
actor: Any) -> str:
|
||||||
"""Full page: following list."""
|
"""Full page: following list."""
|
||||||
items_sexp = _actor_list_items_sexp(actors, 1, "following", set(), actor)
|
items_sx = _actor_list_items_sx(actors, 1, "following", set(), actor)
|
||||||
content = sexp_call(
|
content = sx_call(
|
||||||
"federation-actor-list-page",
|
"federation-actor-list-page",
|
||||||
title="Following", count_str=f"({total})",
|
title="Following", count_str=f"({total})",
|
||||||
items=SexpExpr(items_sexp) if items_sexp else None,
|
items=SxExpr(items_sx) if items_sx else None,
|
||||||
)
|
)
|
||||||
return _social_page(ctx, actor, content=content,
|
return _social_page(ctx, actor, content=content,
|
||||||
title="Following \u2014 Rose Ash")
|
title="Following \u2014 Rose Ash")
|
||||||
@@ -584,17 +584,17 @@ async def render_following_page(ctx: dict, actors: list, total: int,
|
|||||||
|
|
||||||
async def render_following_items(actors: list, page: int, actor: Any) -> str:
|
async def render_following_items(actors: list, page: int, actor: Any) -> str:
|
||||||
"""Pagination fragment: following items."""
|
"""Pagination fragment: following items."""
|
||||||
return _actor_list_items_sexp(actors, page, "following", set(), actor)
|
return _actor_list_items_sx(actors, page, "following", set(), actor)
|
||||||
|
|
||||||
|
|
||||||
async def render_followers_page(ctx: dict, actors: list, total: int,
|
async def render_followers_page(ctx: dict, actors: list, total: int,
|
||||||
followed_urls: set, actor: Any) -> str:
|
followed_urls: set, actor: Any) -> str:
|
||||||
"""Full page: followers list."""
|
"""Full page: followers list."""
|
||||||
items_sexp = _actor_list_items_sexp(actors, 1, "followers", followed_urls, actor)
|
items_sx = _actor_list_items_sx(actors, 1, "followers", followed_urls, actor)
|
||||||
content = sexp_call(
|
content = sx_call(
|
||||||
"federation-actor-list-page",
|
"federation-actor-list-page",
|
||||||
title="Followers", count_str=f"({total})",
|
title="Followers", count_str=f"({total})",
|
||||||
items=SexpExpr(items_sexp) if items_sexp else None,
|
items=SxExpr(items_sx) if items_sx else None,
|
||||||
)
|
)
|
||||||
return _social_page(ctx, actor, content=content,
|
return _social_page(ctx, actor, content=content,
|
||||||
title="Followers \u2014 Rose Ash")
|
title="Followers \u2014 Rose Ash")
|
||||||
@@ -603,7 +603,7 @@ async def render_followers_page(ctx: dict, actors: list, total: int,
|
|||||||
async def render_followers_items(actors: list, page: int,
|
async def render_followers_items(actors: list, page: int,
|
||||||
followed_urls: set, actor: Any) -> str:
|
followed_urls: set, actor: Any) -> str:
|
||||||
"""Pagination fragment: followers items."""
|
"""Pagination fragment: followers items."""
|
||||||
return _actor_list_items_sexp(actors, page, "followers", followed_urls, actor)
|
return _actor_list_items_sx(actors, page, "followers", followed_urls, actor)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -623,50 +623,50 @@ async def render_actor_timeline_page(ctx: dict, remote_actor: Any, items: list,
|
|||||||
actor_url = getattr(remote_actor, "actor_url", "")
|
actor_url = getattr(remote_actor, "actor_url", "")
|
||||||
|
|
||||||
if icon_url:
|
if icon_url:
|
||||||
avatar = sexp_call("federation-avatar-img", src=icon_url, cls="w-16 h-16 rounded-full")
|
avatar = sx_call("federation-avatar-img", src=icon_url, cls="w-16 h-16 rounded-full")
|
||||||
else:
|
else:
|
||||||
initial = display_name[0].upper() if display_name else "?"
|
initial = display_name[0].upper() if display_name else "?"
|
||||||
avatar = sexp_call(
|
avatar = sx_call(
|
||||||
"federation-avatar-placeholder",
|
"federation-avatar-placeholder",
|
||||||
cls="w-16 h-16 rounded-full bg-stone-300 flex items-center justify-center text-stone-600 font-bold text-xl",
|
cls="w-16 h-16 rounded-full bg-stone-300 flex items-center justify-center text-stone-600 font-bold text-xl",
|
||||||
initial=initial,
|
initial=initial,
|
||||||
)
|
)
|
||||||
|
|
||||||
summary_sexp = sexp_call("federation-profile-summary", summary=summary) if summary else ""
|
summary_sx = sx_call("federation-profile-summary", summary=summary) if summary else ""
|
||||||
|
|
||||||
follow_sexp = ""
|
follow_sx = ""
|
||||||
if actor:
|
if actor:
|
||||||
if is_following:
|
if is_following:
|
||||||
follow_sexp = sexp_call(
|
follow_sx = sx_call(
|
||||||
"federation-follow-form",
|
"federation-follow-form",
|
||||||
action=url_for("social.unfollow"), csrf=csrf, actor_url=actor_url,
|
action=url_for("social.unfollow"), csrf=csrf, actor_url=actor_url,
|
||||||
label="Unfollow",
|
label="Unfollow",
|
||||||
cls="border border-stone-300 rounded px-4 py-2 hover:bg-stone-100",
|
cls="border border-stone-300 rounded px-4 py-2 hover:bg-stone-100",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
follow_sexp = sexp_call(
|
follow_sx = sx_call(
|
||||||
"federation-follow-form",
|
"federation-follow-form",
|
||||||
action=url_for("social.follow"), csrf=csrf, actor_url=actor_url,
|
action=url_for("social.follow"), csrf=csrf, actor_url=actor_url,
|
||||||
label="Follow",
|
label="Follow",
|
||||||
cls="bg-stone-800 text-white rounded px-4 py-2 hover:bg-stone-700",
|
cls="bg-stone-800 text-white rounded px-4 py-2 hover:bg-stone-700",
|
||||||
)
|
)
|
||||||
|
|
||||||
timeline_sexp = _timeline_items_sexp(items, "actor", actor, remote_actor.id)
|
timeline_sx = _timeline_items_sx(items, "actor", actor, remote_actor.id)
|
||||||
|
|
||||||
header_sexp = sexp_call(
|
header_sx = sx_call(
|
||||||
"federation-actor-profile-header",
|
"federation-actor-profile-header",
|
||||||
avatar=SexpExpr(avatar),
|
avatar=SxExpr(avatar),
|
||||||
display_name=str(escape(display_name)),
|
display_name=str(escape(display_name)),
|
||||||
username=str(escape(remote_actor.preferred_username)),
|
username=str(escape(remote_actor.preferred_username)),
|
||||||
domain=str(escape(remote_actor.domain)),
|
domain=str(escape(remote_actor.domain)),
|
||||||
summary=SexpExpr(summary_sexp) if summary_sexp else None,
|
summary=SxExpr(summary_sx) if summary_sx else None,
|
||||||
follow=SexpExpr(follow_sexp) if follow_sexp else None,
|
follow=SxExpr(follow_sx) if follow_sx else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
content = sexp_call(
|
content = sx_call(
|
||||||
"federation-actor-timeline-layout",
|
"federation-actor-timeline-layout",
|
||||||
header=SexpExpr(header_sexp),
|
header=SxExpr(header_sx),
|
||||||
timeline=SexpExpr(timeline_sexp) if timeline_sexp else None,
|
timeline=SxExpr(timeline_sx) if timeline_sx else None,
|
||||||
)
|
)
|
||||||
|
|
||||||
return _social_page(ctx, actor, content=content,
|
return _social_page(ctx, actor, content=content,
|
||||||
@@ -676,7 +676,7 @@ async def render_actor_timeline_page(ctx: dict, remote_actor: Any, items: list,
|
|||||||
async def render_actor_timeline_items(items: list, actor_id: int,
|
async def render_actor_timeline_items(items: list, actor_id: int,
|
||||||
actor: Any) -> str:
|
actor: Any) -> str:
|
||||||
"""Pagination fragment: actor timeline items."""
|
"""Pagination fragment: actor timeline items."""
|
||||||
return _timeline_items_sexp(items, "actor", actor, actor_id)
|
return _timeline_items_sx(items, "actor", actor, actor_id)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -687,15 +687,15 @@ async def render_notifications_page(ctx: dict, notifications: list,
|
|||||||
actor: Any) -> str:
|
actor: Any) -> str:
|
||||||
"""Full page: notifications."""
|
"""Full page: notifications."""
|
||||||
if not notifications:
|
if not notifications:
|
||||||
notif_sexp = sexp_call("federation-notifications-empty")
|
notif_sx = sx_call("federation-notifications-empty")
|
||||||
else:
|
else:
|
||||||
items_sexp = "(<> " + " ".join(_notification_sexp(n) for n in notifications) + ")"
|
items_sx = "(<> " + " ".join(_notification_sx(n) for n in notifications) + ")"
|
||||||
notif_sexp = sexp_call(
|
notif_sx = sx_call(
|
||||||
"federation-notifications-list",
|
"federation-notifications-list",
|
||||||
items=SexpExpr(items_sexp),
|
items=SxExpr(items_sx),
|
||||||
)
|
)
|
||||||
|
|
||||||
content = sexp_call("federation-notifications-page", notifs=SexpExpr(notif_sexp))
|
content = sx_call("federation-notifications-page", notifs=SxExpr(notif_sx))
|
||||||
return _social_page(ctx, actor, content=content,
|
return _social_page(ctx, actor, content=content,
|
||||||
title="Notifications \u2014 Rose Ash")
|
title="Notifications \u2014 Rose Ash")
|
||||||
|
|
||||||
@@ -717,12 +717,12 @@ async def render_choose_username_page(ctx: dict) -> str:
|
|||||||
check_url = url_for("identity.check_username")
|
check_url = url_for("identity.check_username")
|
||||||
actor = ctx.get("actor")
|
actor = ctx.get("actor")
|
||||||
|
|
||||||
error_sexp = sexp_call("federation-error-banner", error=error) if error else ""
|
error_sx = sx_call("federation-error-banner", error=error) if error else ""
|
||||||
|
|
||||||
content = sexp_call(
|
content = sx_call(
|
||||||
"federation-choose-username",
|
"federation-choose-username",
|
||||||
domain=str(escape(ap_domain)),
|
domain=str(escape(ap_domain)),
|
||||||
error=SexpExpr(error_sexp) if error_sexp else None,
|
error=SxExpr(error_sx) if error_sx else None,
|
||||||
csrf=csrf, username=str(escape(username)),
|
csrf=csrf, username=str(escape(username)),
|
||||||
check_url=check_url,
|
check_url=check_url,
|
||||||
)
|
)
|
||||||
@@ -742,36 +742,36 @@ async def render_profile_page(ctx: dict, actor: Any, activities: list,
|
|||||||
|
|
||||||
ap_domain = config().get("ap_domain", "rose-ash.com")
|
ap_domain = config().get("ap_domain", "rose-ash.com")
|
||||||
display_name = actor.display_name or actor.preferred_username
|
display_name = actor.display_name or actor.preferred_username
|
||||||
summary_sexp = sexp_call(
|
summary_sx = sx_call(
|
||||||
"federation-profile-summary-text", text=str(escape(actor.summary)),
|
"federation-profile-summary-text", text=str(escape(actor.summary)),
|
||||||
) if actor.summary else ""
|
) if actor.summary else ""
|
||||||
|
|
||||||
activities_sexp = ""
|
activities_sx = ""
|
||||||
if activities:
|
if activities:
|
||||||
parts = []
|
parts = []
|
||||||
for a in activities:
|
for a in activities:
|
||||||
published = a.published.strftime("%Y-%m-%d %H:%M") if a.published else ""
|
published = a.published.strftime("%Y-%m-%d %H:%M") if a.published else ""
|
||||||
obj_type_sexp = sexp_call(
|
obj_type_sx = sx_call(
|
||||||
"federation-activity-obj-type", obj_type=a.object_type,
|
"federation-activity-obj-type", obj_type=a.object_type,
|
||||||
) if a.object_type else ""
|
) if a.object_type else ""
|
||||||
parts.append(sexp_call(
|
parts.append(sx_call(
|
||||||
"federation-activity-card",
|
"federation-activity-card",
|
||||||
activity_type=a.activity_type, published=published,
|
activity_type=a.activity_type, published=published,
|
||||||
obj_type=SexpExpr(obj_type_sexp) if obj_type_sexp else None,
|
obj_type=SxExpr(obj_type_sx) if obj_type_sx else None,
|
||||||
))
|
))
|
||||||
items_sexp = "(<> " + " ".join(parts) + ")"
|
items_sx = "(<> " + " ".join(parts) + ")"
|
||||||
activities_sexp = sexp_call("federation-activities-list", items=SexpExpr(items_sexp))
|
activities_sx = sx_call("federation-activities-list", items=SxExpr(items_sx))
|
||||||
else:
|
else:
|
||||||
activities_sexp = sexp_call("federation-activities-empty")
|
activities_sx = sx_call("federation-activities-empty")
|
||||||
|
|
||||||
content = sexp_call(
|
content = sx_call(
|
||||||
"federation-profile-page",
|
"federation-profile-page",
|
||||||
display_name=str(escape(display_name)),
|
display_name=str(escape(display_name)),
|
||||||
username=str(escape(actor.preferred_username)),
|
username=str(escape(actor.preferred_username)),
|
||||||
domain=str(escape(ap_domain)),
|
domain=str(escape(ap_domain)),
|
||||||
summary=SexpExpr(summary_sexp) if summary_sexp else None,
|
summary=SxExpr(summary_sx) if summary_sx else None,
|
||||||
activities_heading=f"Activities ({total})",
|
activities_heading=f"Activities ({total})",
|
||||||
activities=SexpExpr(activities_sexp),
|
activities=SxExpr(activities_sx),
|
||||||
)
|
)
|
||||||
|
|
||||||
return _social_page(ctx, actor, content=content,
|
return _social_page(ctx, actor, content=content,
|
||||||
@@ -796,10 +796,10 @@ def render_interaction_buttons(object_id: str, author_inbox: str,
|
|||||||
liked_by_me=liked_by_me,
|
liked_by_me=liked_by_me,
|
||||||
boosted_by_me=boosted_by_me,
|
boosted_by_me=boosted_by_me,
|
||||||
)
|
)
|
||||||
return _interaction_buttons_sexp(item, actor)
|
return _interaction_buttons_sx(item, actor)
|
||||||
|
|
||||||
|
|
||||||
def render_actor_card(actor_dto: Any, actor: Any, followed_urls: set,
|
def render_actor_card(actor_dto: Any, actor: Any, followed_urls: set,
|
||||||
*, list_type: str = "following") -> str:
|
*, list_type: str = "following") -> str:
|
||||||
"""Render a single actor card fragment for HTMX POST response."""
|
"""Render a single actor card fragment for HTMX POST response."""
|
||||||
return _actor_card_sexp(actor_dto, actor, followed_urls, list_type=list_type)
|
return _actor_card_sx(actor_dto, actor, followed_urls, list_type=list_type)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import path_setup # noqa: F401 # adds shared/ to sys.path
|
import path_setup # noqa: F401 # adds shared/ to sys.path
|
||||||
import sexp.sexp_components as sexp_components # noqa: F401 # ensure Hypercorn --reload watches this file
|
import sx.sx_components as sx_components # noqa: F401 # ensure Hypercorn --reload watches this file
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from __future__ import annotations
|
|||||||
from quart import Blueprint, g, request, render_template, make_response
|
from quart import Blueprint, g, request, render_template, make_response
|
||||||
|
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
from shared.infrastructure.data_client import fetch_data
|
from shared.infrastructure.data_client import fetch_data
|
||||||
from shared.contracts.dtos import PostDTO, dto_from_dict
|
from shared.contracts.dtos import PostDTO, dto_from_dict
|
||||||
from shared.services.registry import services
|
from shared.services.registry import services
|
||||||
@@ -56,13 +56,13 @@ def register() -> Blueprint:
|
|||||||
page=page,
|
page=page,
|
||||||
)
|
)
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_all_markets_page, render_all_markets_oob
|
from sx.sx_components import render_all_markets_page, render_all_markets_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
if is_htmx_request():
|
if is_htmx_request():
|
||||||
sexp_src = await render_all_markets_oob(tctx, markets, has_more, page_info, page)
|
sx_src = await render_all_markets_oob(tctx, markets, has_more, page_info, page)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
else:
|
else:
|
||||||
html = await render_all_markets_page(tctx, markets, has_more, page_info, page)
|
html = await render_all_markets_page(tctx, markets, has_more, page_info, page)
|
||||||
return await make_response(html, 200)
|
return await make_response(html, 200)
|
||||||
@@ -72,8 +72,8 @@ def register() -> Blueprint:
|
|||||||
page = int(request.args.get("page", 1))
|
page = int(request.args.get("page", 1))
|
||||||
markets, has_more, page_info = await _load_markets(page)
|
markets, has_more, page_info = await _load_markets(page)
|
||||||
|
|
||||||
from sexp.sexp_components import render_all_markets_cards
|
from sx.sx_components import render_all_markets_cards
|
||||||
sexp_src = await render_all_markets_cards(markets, has_more, page_info, page)
|
sx_src = await render_all_markets_cards(markets, has_more, page_info, page)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ from .services import (
|
|||||||
|
|
||||||
from shared.browser.app.redis_cacher import cache_page
|
from shared.browser.app.redis_cacher import cache_page
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
|
|
||||||
def register():
|
def register():
|
||||||
browse_bp = Blueprint("browse", __name__)
|
browse_bp = Blueprint("browse", __name__)
|
||||||
@@ -43,8 +43,8 @@ def register():
|
|||||||
p_data = getattr(g, "post_data", None) or {}
|
p_data = getattr(g, "post_data", None) or {}
|
||||||
|
|
||||||
# Determine which template to use based on request type
|
# Determine which template to use based on request type
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_market_home_page, render_market_home_oob
|
from sx.sx_components import render_market_home_page, render_market_home_oob
|
||||||
|
|
||||||
ctx = await get_template_context()
|
ctx = await get_template_context()
|
||||||
ctx.update(p_data)
|
ctx.update(p_data)
|
||||||
@@ -52,8 +52,8 @@ def register():
|
|||||||
html = await render_market_home_page(ctx)
|
html = await render_market_home_page(ctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_market_home_oob(ctx)
|
sx_src = await render_market_home_oob(ctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
@browse_bp.get("/all/")
|
@browse_bp.get("/all/")
|
||||||
@cache_page(tag="browse")
|
@cache_page(tag="browse")
|
||||||
@@ -74,8 +74,8 @@ def register():
|
|||||||
product_info = await _productInfo()
|
product_info = await _productInfo()
|
||||||
full_context = {**product_info, **ctx}
|
full_context = {**product_info, **ctx}
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_browse_page, render_browse_oob, render_browse_cards
|
from sx.sx_components import render_browse_page, render_browse_oob, render_browse_cards
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx.update(full_context)
|
tctx.update(full_context)
|
||||||
@@ -84,11 +84,11 @@ def register():
|
|||||||
resp = await make_response(html)
|
resp = await make_response(html)
|
||||||
elif product_info["page"] > 1:
|
elif product_info["page"] > 1:
|
||||||
tctx.update(product_info)
|
tctx.update(product_info)
|
||||||
sexp_src = await render_browse_cards(tctx)
|
sx_src = await render_browse_cards(tctx)
|
||||||
resp = sexp_response(sexp_src)
|
resp = sx_response(sx_src)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_browse_oob(tctx)
|
sx_src = await render_browse_oob(tctx)
|
||||||
resp = sexp_response(sexp_src)
|
resp = sx_response(sx_src)
|
||||||
|
|
||||||
resp.headers["Hx-Push-Url"] = _current_url_without_page()
|
resp.headers["Hx-Push-Url"] = _current_url_without_page()
|
||||||
return _vary(resp)
|
return _vary(resp)
|
||||||
@@ -115,8 +115,8 @@ def register():
|
|||||||
product_info = await _productInfo(top_slug)
|
product_info = await _productInfo(top_slug)
|
||||||
full_context = {**product_info, **ctx}
|
full_context = {**product_info, **ctx}
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_browse_page, render_browse_oob, render_browse_cards
|
from sx.sx_components import render_browse_page, render_browse_oob, render_browse_cards
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx.update(full_context)
|
tctx.update(full_context)
|
||||||
@@ -125,11 +125,11 @@ def register():
|
|||||||
resp = await make_response(html)
|
resp = await make_response(html)
|
||||||
elif product_info["page"] > 1:
|
elif product_info["page"] > 1:
|
||||||
tctx.update(product_info)
|
tctx.update(product_info)
|
||||||
sexp_src = await render_browse_cards(tctx)
|
sx_src = await render_browse_cards(tctx)
|
||||||
resp = sexp_response(sexp_src)
|
resp = sx_response(sx_src)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_browse_oob(tctx)
|
sx_src = await render_browse_oob(tctx)
|
||||||
resp = sexp_response(sexp_src)
|
resp = sx_response(sx_src)
|
||||||
|
|
||||||
resp.headers["Hx-Push-Url"] = _current_url_without_page()
|
resp.headers["Hx-Push-Url"] = _current_url_without_page()
|
||||||
return _vary(resp)
|
return _vary(resp)
|
||||||
@@ -156,8 +156,8 @@ def register():
|
|||||||
product_info = await _productInfo(top_slug, sub_slug)
|
product_info = await _productInfo(top_slug, sub_slug)
|
||||||
full_context = {**product_info, **ctx}
|
full_context = {**product_info, **ctx}
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_browse_page, render_browse_oob, render_browse_cards
|
from sx.sx_components import render_browse_page, render_browse_oob, render_browse_cards
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
tctx.update(full_context)
|
tctx.update(full_context)
|
||||||
@@ -166,11 +166,11 @@ def register():
|
|||||||
resp = await make_response(html)
|
resp = await make_response(html)
|
||||||
elif product_info["page"] > 1:
|
elif product_info["page"] > 1:
|
||||||
tctx.update(product_info)
|
tctx.update(product_info)
|
||||||
sexp_src = await render_browse_cards(tctx)
|
sx_src = await render_browse_cards(tctx)
|
||||||
resp = sexp_response(sexp_src)
|
resp = sx_response(sx_src)
|
||||||
else:
|
else:
|
||||||
sexp_src = await render_browse_oob(tctx)
|
sx_src = await render_browse_oob(tctx)
|
||||||
resp = sexp_response(sexp_src)
|
resp = sx_response(sx_src)
|
||||||
|
|
||||||
resp.headers["Hx-Push-Url"] = _current_url_without_page()
|
resp.headers["Hx-Push-Url"] = _current_url_without_page()
|
||||||
return _vary(resp)
|
return _vary(resp)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"""Market app fragment endpoints.
|
"""Market app fragment endpoints.
|
||||||
|
|
||||||
Exposes sexp fragments at ``/internal/fragments/<type>`` for consumption
|
Exposes sx fragments at ``/internal/fragments/<type>`` for consumption
|
||||||
by other coop apps via the fragment client.
|
by other coop apps via the fragment client.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@@ -26,16 +26,16 @@ def register():
|
|||||||
async def get_fragment(fragment_type: str):
|
async def get_fragment(fragment_type: str):
|
||||||
handler = _handlers.get(fragment_type)
|
handler = _handlers.get(fragment_type)
|
||||||
if handler is None:
|
if handler is None:
|
||||||
return Response("", status=200, content_type="text/sexp")
|
return Response("", status=200, content_type="text/sx")
|
||||||
src = await handler()
|
src = await handler()
|
||||||
return Response(src, status=200, content_type="text/sexp")
|
return Response(src, status=200, content_type="text/sx")
|
||||||
|
|
||||||
# --- container-nav fragment: market links --------------------------------
|
# --- container-nav fragment: market links --------------------------------
|
||||||
|
|
||||||
async def _container_nav_handler():
|
async def _container_nav_handler():
|
||||||
from quart import current_app
|
from quart import current_app
|
||||||
from shared.infrastructure.urls import market_url
|
from shared.infrastructure.urls import market_url
|
||||||
from shared.sexp.helpers import sexp_call
|
from shared.sx.helpers import sx_call
|
||||||
|
|
||||||
container_type = request.args.get("container_type", "page")
|
container_type = request.args.get("container_type", "page")
|
||||||
container_id = int(request.args.get("container_id", 0))
|
container_id = int(request.args.get("container_id", 0))
|
||||||
@@ -51,7 +51,7 @@ def register():
|
|||||||
parts = []
|
parts = []
|
||||||
for m in markets:
|
for m in markets:
|
||||||
href = market_url(f"/{post_slug}/{m.slug}/")
|
href = market_url(f"/{post_slug}/{m.slug}/")
|
||||||
parts.append(sexp_call("market-link-nav",
|
parts.append(sx_call("market-link-nav",
|
||||||
href=href, name=m.name, nav_class=nav_class))
|
href=href, name=m.name, nav_class=nav_class))
|
||||||
return "(<> " + " ".join(parts) + ")"
|
return "(<> " + " ".join(parts) + ")"
|
||||||
|
|
||||||
@@ -59,15 +59,15 @@ def register():
|
|||||||
|
|
||||||
# --- link-card fragment: product preview card --------------------------------
|
# --- link-card fragment: product preview card --------------------------------
|
||||||
|
|
||||||
def _product_link_card_sexp(product, link: str) -> str:
|
def _product_link_card_sx(product, link: str) -> str:
|
||||||
from shared.sexp.helpers import sexp_call
|
from shared.sx.helpers import sx_call
|
||||||
subtitle = product.brand or ""
|
subtitle = product.brand or ""
|
||||||
detail = ""
|
detail = ""
|
||||||
if product.special_price:
|
if product.special_price:
|
||||||
detail = f"{product.regular_price} → {product.special_price}"
|
detail = f"{product.regular_price} → {product.special_price}"
|
||||||
elif product.regular_price:
|
elif product.regular_price:
|
||||||
detail = str(product.regular_price)
|
detail = str(product.regular_price)
|
||||||
return sexp_call("link-card",
|
return sx_call("link-card",
|
||||||
title=product.title, image=product.image,
|
title=product.title, image=product.image,
|
||||||
subtitle=subtitle, detail=detail,
|
subtitle=subtitle, detail=detail,
|
||||||
link=link)
|
link=link)
|
||||||
@@ -90,7 +90,7 @@ def register():
|
|||||||
await g.s.execute(select(Product).where(Product.slug == s))
|
await g.s.execute(select(Product).where(Product.slug == s))
|
||||||
).scalar_one_or_none()
|
).scalar_one_or_none()
|
||||||
if product:
|
if product:
|
||||||
parts.append(_product_link_card_sexp(
|
parts.append(_product_link_card_sx(
|
||||||
product, market_url(f"/product/{product.slug}/")))
|
product, market_url(f"/product/{product.slug}/")))
|
||||||
return "\n".join(parts)
|
return "\n".join(parts)
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ def register():
|
|||||||
).scalar_one_or_none()
|
).scalar_one_or_none()
|
||||||
if not product:
|
if not product:
|
||||||
return ""
|
return ""
|
||||||
return _product_link_card_sexp(product, market_url(f"/product/{product.slug}/"))
|
return _product_link_card_sx(product, market_url(f"/product/{product.slug}/"))
|
||||||
|
|
||||||
_handlers["link-card"] = _link_card_handler
|
_handlers["link-card"] = _link_card_handler
|
||||||
|
|
||||||
|
|||||||
@@ -17,15 +17,15 @@ def register():
|
|||||||
async def admin():
|
async def admin():
|
||||||
from shared.browser.app.utils.htmx import is_htmx_request
|
from shared.browser.app.utils.htmx import is_htmx_request
|
||||||
|
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_market_admin_page, render_market_admin_oob
|
from sx.sx_components import render_market_admin_page, render_market_admin_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
html = await render_market_admin_page(tctx)
|
html = await render_market_admin_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
sexp_src = await render_market_admin_oob(tctx)
|
sx_src = await render_market_admin_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
@@ -12,16 +12,16 @@ def register():
|
|||||||
@bp.get("/")
|
@bp.get("/")
|
||||||
@require_admin
|
@require_admin
|
||||||
async def admin(**kwargs):
|
async def admin(**kwargs):
|
||||||
from shared.sexp.page import get_template_context
|
from shared.sx.page import get_template_context
|
||||||
from sexp.sexp_components import render_page_admin_page, render_page_admin_oob
|
from sx.sx_components import render_page_admin_page, render_page_admin_oob
|
||||||
|
|
||||||
tctx = await get_template_context()
|
tctx = await get_template_context()
|
||||||
if not is_htmx_request():
|
if not is_htmx_request():
|
||||||
html = await render_page_admin_page(tctx)
|
html = await render_page_admin_page(tctx)
|
||||||
return await make_response(html)
|
return await make_response(html)
|
||||||
else:
|
else:
|
||||||
from shared.sexp.helpers import sexp_response
|
from shared.sx.helpers import sx_response
|
||||||
sexp_src = await render_page_admin_oob(tctx)
|
sx_src = await render_page_admin_oob(tctx)
|
||||||
return sexp_response(sexp_src)
|
return sx_response(sx_src)
|
||||||
|
|
||||||
return bp
|
return bp
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user