"""Public render/utility functions called from bp routes.""" from __future__ import annotations from content.highlight import highlight def _code(code: str, language: str = "lisp") -> str: """Build a ~doc-code component with highlighted content.""" highlighted = highlight(code, language) return f'(~doc-code :code {highlighted})' def _example_code(code: str, language: str = "lisp") -> str: """Build an ~example-source component with highlighted content.""" highlighted = highlight(code, language) return f'(~example-source :code {highlighted})' def _placeholder(div_id: str) -> str: """Empty placeholder that will be filled by OOB swap on interaction.""" return (f'(div :id "{div_id}"' f' (div :class "bg-stone-50 border border-stone-200 rounded p-4 mt-3"' f' (p :class "text-stone-400 italic text-sm"' f' "Trigger the demo to see the actual content.")))') def _component_source_text(*names: str) -> str: """Get defcomp source text for named components.""" from shared.sx.jinja_bridge import _COMPONENT_ENV from shared.sx.types import Component from shared.sx.parser import serialize parts = [] for name in names: key = name if name.startswith("~") else f"~{name}" val = _COMPONENT_ENV.get(key) if isinstance(val, Component): param_strs = ["&key"] + list(val.params) if val.has_children: param_strs.extend(["&rest", "children"]) params_sx = "(" + " ".join(param_strs) + ")" body_sx = serialize(val.body, pretty=True) parts.append(f"(defcomp ~{val.name} {params_sx}\n{body_sx})") return "\n\n".join(parts) def _oob_code(target_id: str, text: str) -> str: """OOB swap that displays plain code in a styled block.""" escaped = text.replace('\\', '\\\\').replace('"', '\\"') return (f'(div :id "{target_id}" :sx-swap-oob "innerHTML"' f' (div :class "bg-stone-50 border border-stone-200 rounded p-4 mt-3 overflow-x-auto"' f' (pre :class "text-sm whitespace-pre-wrap"' f' (code "{escaped}"))))') def _clear_components_btn() -> str: """Button that clears the client-side component cache (localStorage + in-memory).""" js = ("localStorage.removeItem('sx-components-hash');" "localStorage.removeItem('sx-components-src');" "var e=Sx.getEnv();Object.keys(e).forEach(function(k){if(k.charAt(0)==='~')delete e[k]});" "var b=this;b.textContent='Cleared!';setTimeout(function(){b.textContent='Clear component cache'},2000)") return (f'(button :onclick "{js}"' f' :class "text-xs text-stone-400 hover:text-stone-600 border border-stone-200' f' rounded px-2 py-1 transition-colors"' f' "Clear component cache")') def _full_wire_text(sx_src: str, *comp_names: str) -> str: """Build the full wire response text showing component defs + CSS note + sx source. Only includes component definitions the client doesn't already have, matching the real behaviour of sx_response(). """ from quart import request parts = [] if comp_names: # Check which components the client already has loaded_raw = request.headers.get("SX-Components", "") loaded = set(loaded_raw.split(",")) if loaded_raw else set() missing = [n for n in comp_names if f"~{n}" not in loaded and n not in loaded] if missing: comp_text = _component_source_text(*missing) if comp_text: parts.append(f'') parts.append('') # Pretty-print the sx source for readable display try: from shared.sx.parser import parse as _parse, serialize as _serialize parts.append(_serialize(_parse(sx_src), pretty=True)) except Exception: parts.append(sx_src) return "\n\n".join(parts)