// components/gallery.el - "Things Neuron Said" gallery page. // Per-card auth-gated voting via supabase-js + /api/vote. // gallery_share_allowlist — same allowlist as default_share_allowlist in // main.el. Inlined here so this component is self-contained: the build // concat order puts gallery.el before main.el, so a top-level reference to // main.el's binding would forward-reference at the C level. The DB column // is already sanitized at write time; this is belt-and-braces in case a // row was inserted out-of-band. // NOTE: is intentionally excluded. Gallery cards wrap their content in // — allowing in sanitized answer HTML causes nested // anchors, which the HTML5 parser resolves via the adoption agency algorithm, // producing mismatched tags that break gallery-grid's closing tag and // pull sibling elements into the grid as spurious grid items. let gallery_share_allowlist: String = "{\"p\":[],\"br\":[],\"strong\":[],\"em\":[],\"u\":[],\"s\":[],\"code\":[],\"pre\":[],\"ul\":[],\"ol\":[],\"li\":[],\"h1\":[],\"h2\":[],\"h3\":[],\"h4\":[],\"blockquote\":[]}" fn gallery_page(cards_json: String, supabase_url: String, supabase_anon_key: String) -> String { let i: Int = 0 let cards_html: String = "" let n: Int = json_array_len(cards_json) while i < n { let card: String = json_array_get(cards_json, i) let cid: String = json_get(card, "id") let cq: String = json_get(card, "question") let ca: String = json_get(card, "answer") let ca_html_raw: String = json_get(card, "answer_html") let score_raw: String = json_get(card, "score") let score: String = if str_eq(score_raw, "") { "0" } else { score_raw } let q_html: String = str_replace(str_replace(str_replace(cq, "&", "&"), "<", "<"), ">", ">") // a_html: prefer the marked.js-rendered HTML (run through the // same sanitizer the share page uses) so the gallery preview // matches /share/. Cap at 600 rendered chars so the card // stays the right visual height. Legacy rows (answer_html null) // fall back to escaped plaintext. // json_get returns the literal string "null" for JSON null cells // (which is how legacy rows surface answer_html). Treat both empty // and "null" as the no-html fallback. let has_html: Bool = !str_eq(ca_html_raw, "") && !str_eq(ca_html_raw, "null") let a_html: String = if !has_html { str_replace(str_replace(str_replace(ca, "&", "&"), "<", "<"), ">", ">") } else { let s: String = el_html_sanitize(ca_html_raw, gallery_share_allowlist) if str_len(s) > 600 { str_slice(s, 0, 600) + "..." } else { s } } let ts_raw: String = json_get(card, "created_at") let ts_short: String = str_slice(ts_raw, 0, 10) let card_html: String = " " + q_html + " " + a_html + " ▲ " + score + " ▼ " + ts_short + " · Read ↗ " let cards_html = cards_html + card_html let i = i + 1 } let empty_html: String = if str_eq(cards_html, "") { "Nothing yetNothing shared yet." } else { "" } return " Things Neuron Said How it works Mission ▾ Our mission Safety Environment Pricing Marketplace Enterprise About Gallery Account Get Access How it works Mission - Safety - Environment Pricing Enterprise About Gallery Account Get Access Things Neuron Said Real conversations.No curation. People chatted with Neuron and shared the exchanges that mattered. Voted up by readers. The best ones rise. Top Newest " + cards_html + empty_html + " No conversations match that search. ← Back to site Sign in to vote We will email you a sign-in link. No password needed. Cancel Send link " }
Nothing yet
Nothing shared yet.
Things Neuron Said
People chatted with Neuron and shared the exchanges that mattered. Voted up by readers. The best ones rise.
No conversations match that search.
We will email you a sign-in link. No password needed.