// 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 yet

Nothing shared yet.

" } else { "" } return " Things Neuron Said

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.

" + 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.

" }