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// & 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, "&" as *u8) }
37 else { if c == 60 { o = lp_puts(buf, o, "<" as *u8) }
38 else { if c == 62 { o = lp_puts(buf, o, ">" as *u8) }
39 else { if c == 34 { o = lp_puts(buf, o, """ 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=\"/\">← 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, " · " as *u8) }
165 if nf > 2 { bo = lp_esc(body, bo, fp[2] as *u8, fl[2]); bo = lp_puts(body, bo, " · " 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}