code wiki / (root) / nx_lib_page.nx

nx_lib_page.nx source

↩ module page · 171 lines · 7882 B

1// nx_lib_page.nx -- sovereign ZERO-JS HTML frontend for the nishi-library. 2// Server-renders the search page + work detail straight from nx_lib_store -- 3// no app.js, no leaflet, no client JS at all. HTML-escaped at render (the 4// &amp; bug fixed RIGHT, in .nx). The daemon serves these as text/html routes: 5// GET / -> search form + (with ?q=) results 6// GET /work/<hk> -> work detail 7// license_tier: ORIGINAL | genealogy_id: nishi_library_sovereign_page_2026_07_01 8import "nx_lib_store.nx" 9import "nx_lib_search.nx" // lsr_lc, lsr_match (ci multi-term title match) 10 11func lp_puts(buf: *u8, off: i64, s: *u8) -> i64 { 12 var o: i64 = off 13 var i: i64 = 0 14 while s[i] != (0 as u8) { buf[o] = s[i]; o = o + 1; i = i + 1 } 15 return o 16} 17 18func lp_putdec(buf: *u8, off: i64, v: i64) -> i64 { 19 if v == 0 { buf[off] = 48 as u8; return off + 1 } 20 let tmp: *u8 = sys_mmap(32) 21 var k: i64 = 0 22 var x: i64 = v 23 while x > 0 { tmp[k] = (48 + (x - (x / 10) * 10)) as u8; x = x / 10; k = k + 1 } 24 var o: i64 = off 25 var ri: i64 = k - 1 26 while ri >= 0 { buf[o] = tmp[ri]; o = o + 1; ri = ri - 1 } 27 return o 28} 29 30// HTML-escape src[0..srclen) into buf at off (& < > "). ONE pass, correct. 31func lp_esc(buf: *u8, off: i64, src: *u8, srclen: i64) -> i64 { 32 var o: i64 = off 33 var i: i64 = 0 34 while i < srclen { 35 let c: i64 = src[i] as i64 36 if c == 38 { o = lp_puts(buf, o, "&amp;" as *u8) } 37 else { if c == 60 { o = lp_puts(buf, o, "&lt;" as *u8) } 38 else { if c == 62 { o = lp_puts(buf, o, "&gt;" as *u8) } 39 else { if c == 34 { o = lp_puts(buf, o, "&quot;" as *u8) } 40 else { buf[o] = src[i]; o = o + 1 } } } } 41 i = i + 1 42 } 43 return o 44} 45 46// wrap an HTML body[0..bo) in a 200 text/html response into out; return length. 47func lp_resp(out: *u8, body: *u8, bo: i64) -> i64 { 48 var o: i64 = 0 49 o = lp_puts(out, o, "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\nX-Content-Type-Options: nosniff\r\nConnection: close\r\nContent-Length: " as *u8) 50 o = lp_putdec(out, o, bo) 51 o = lp_puts(out, o, "\r\n\r\n" as *u8) 52 var i: i64 = 0 53 while i < bo { out[o] = body[i]; o = o + 1; i = i + 1 } 54 return o 55} 56 57const LP_HEAD: *u8 = "<!doctype html><meta charset=utf-8><meta name=viewport content=\"width=device-width,initial-scale=1\"><title>Nishi Library</title><style>body{font-family:-apple-system,Segoe UI,sans-serif;max-width:760px;margin:6vh auto;padding:0 20px;color:#1a1f2c}a{color:#1e40af;text-decoration:none}a:hover{text-decoration:underline}li{margin:.5em 0}input{padding:9px;width:70%}button{padding:9px}</style>" as *u8 58 59// GET / (query may be empty): search form + results. Full HTTP response. 60func lhd_page_home(query: *u8, qn: i64, out: *u8) -> i64 { 61 let body: *u8 = sys_mmap(1048576) 62 var bo: i64 = 0 63 bo = lp_puts(body, bo, LP_HEAD) 64 bo = lp_puts(body, bo, "<h1>Nishi Library <span style=\"font-size:.5em;color:#6b7280\">sovereign</span></h1><form action=\"/\"><input name=q value=\"" as *u8) 65 bo = lp_esc(body, bo, query, qn) 66 bo = lp_puts(body, bo, "\" placeholder=\"Search works...\" autofocus> <button>Search</button></form>" as *u8) 67 68 // lowercase query for matching 69 let qlow: *u8 = sys_mmap(2048) 70 var qi: i64 = 0 71 while qi < qn { qlow[qi] = lsr_lc(query[qi] as i64) as u8; qi = qi + 1 } 72 73 let idxbuf: *u8 = sys_mmap(1048576) 74 let inn: i64 = nx_lib_store_index(idxbuf) 75 let po: *i64 = sys_mmap(16) as *i64 76 let lo: *i64 = sys_mmap(16) as *i64 77 let hkbuf: *u8 = sys_mmap(1024) 78 let h: *i64 = nx_lib_store_open() // O(1) gets over the whole store (no per-work re-mmap) 79 let kbuf: *u8 = sys_mmap(512) 80 81 // no query -> browse the whole catalog; with a query -> filter (both capped) 82 if qn == 0 { bo = lp_puts(body, bo, "<p style=\"color:#6b7280\">Browse the catalog, or search above.</p>" as *u8) } 83 bo = lp_puts(body, bo, "<ul>" as *u8) 84 var nmatch: i64 = 0 85 var ls: i64 = 0 86 var i: i64 = 0 87 while i <= inn { 88 var eol: i64 = 0 89 if i == inn { eol = 1 } else { if idxbuf[i] == 10 as u8 { eol = 1 } } 90 if eol == 1 { 91 if i > ls { if nmatch < 200 { 92 var k: i64 = 0 93 var j: i64 = ls 94 while j < i { hkbuf[k] = idxbuf[j]; k = k + 1; j = j + 1 } 95 hkbuf[k] = 0 as u8 96 if nx_lib_store_get_work_h(h, kbuf, hkbuf, po, lo) >= 0 { 97 let rec: *u8 = po[0] as *u8 98 let reclen: i64 = lo[0] 99 var show: i64 = 1 100 if qn > 0 { show = lsr_match(rec, reclen, qlow, qn) } 101 if show == 1 { if nmatch < 200 { 102 var tlen: i64 = reclen 103 var t: i64 = 0 104 var tabpos: i64 = 0 - 1 105 while t < reclen { if tabpos < 0 { if rec[t] == 9 as u8 { tabpos = t } } t = t + 1 } 106 if tabpos >= 0 { tlen = tabpos } 107 bo = lp_puts(body, bo, "<li><a href=\"/work/" as *u8) 108 bo = lp_esc(body, bo, hkbuf, k) 109 bo = lp_puts(body, bo, "\">" as *u8) 110 bo = lp_esc(body, bo, rec, tlen) 111 bo = lp_puts(body, bo, "</a></li>" as *u8) 112 nmatch = nmatch + 1 113 } } 114 } 115 } } 116 ls = i + 1 117 } 118 i = i + 1 119 } 120 bo = lp_puts(body, bo, "</ul>" as *u8) 121 if qn > 0 { if nmatch == 0 { bo = lp_puts(body, bo, "<p>No matches.</p>" as *u8) } } 122 return lp_resp(out, body, bo) 123} 124 125// GET /work/<hk>: work detail. Full HTTP response. 126func lhd_page_work(hk_ptr: *u8, hk_n: i64, out: *u8) -> i64 { 127 let hkbuf: *u8 = sys_mmap(1024) 128 var k: i64 = 0 129 while k < hk_n { hkbuf[k] = hk_ptr[k]; k = k + 1 } 130 hkbuf[k] = 0 as u8 131 132 let po: *i64 = sys_mmap(16) as *i64 133 let lo: *i64 = sys_mmap(16) as *i64 134 let body: *u8 = sys_mmap(262144) 135 var bo: i64 = 0 136 bo = lp_puts(body, bo, LP_HEAD) 137 bo = lp_puts(body, bo, "<p><a href=\"/\">&larr; back to search</a></p>" as *u8) 138 if nx_lib_store_get_work(hkbuf, po, lo) < 0 { 139 bo = lp_puts(body, bo, "<h1>Not found</h1>" as *u8) 140 return lp_resp(out, body, bo) 141 } 142 let rec: *u8 = po[0] as *u8 143 let reclen: i64 = lo[0] 144 // fields: 0=title 1=doi 2=published 3=license (TAB-delimited) 145 let fp: *i64 = sys_mmap(8 * 8) as *i64 146 let fl: *i64 = sys_mmap(8 * 8) as *i64 147 var nf: i64 = 0 148 var fs: i64 = 0 149 var i: i64 = 0 150 while i <= reclen { 151 var atend: i64 = 0 152 if i == reclen { atend = 1 } else { if rec[i] == 9 as u8 { atend = 1 } } 153 if atend == 1 { 154 if nf < 8 { fp[nf] = (rec as i64) + fs; fl[nf] = i - fs; nf = nf + 1 } 155 fs = i + 1 156 } 157 i = i + 1 158 } 159 bo = lp_puts(body, bo, "<h1>" as *u8) 160 if nf > 0 { bo = lp_esc(body, bo, fp[0] as *u8, fl[0]) } 161 bo = lp_puts(body, bo, "</h1>" as *u8) 162 if nf > 5 { bo = lp_puts(body, bo, "<p style=\"color:#4a5568;font-style:italic\">" as *u8); bo = lp_esc(body, bo, fp[5] as *u8, fl[5]); bo = lp_puts(body, bo, "</p>" as *u8) } 163 bo = lp_puts(body, bo, "<p style=\"color:#4a5568\">" as *u8) 164 if nf > 1 { bo = lp_puts(body, bo, "doi: " as *u8); bo = lp_esc(body, bo, fp[1] as *u8, fl[1]); bo = lp_puts(body, bo, " &middot; " as *u8) } 165 if nf > 2 { bo = lp_esc(body, bo, fp[2] as *u8, fl[2]); bo = lp_puts(body, bo, " &middot; " as *u8) } 166 if nf > 3 { bo = lp_esc(body, bo, fp[3] as *u8, fl[3]) } 167 bo = lp_puts(body, bo, "</p>" as *u8) 168 // abstract (field 4) -> the reader body 169 if nf > 4 { bo = lp_puts(body, bo, "<div style=\"margin-top:1.2em;line-height:1.6\">" as *u8); bo = lp_esc(body, bo, fp[4] as *u8, fl[4]); bo = lp_puts(body, bo, "</div>" as *u8) } 170 return lp_resp(out, body, bo) 171}