| 1 | // moxi/moxi.js |
| 2 | (()=>{ |
| 3 | let doc = document |
| 4 | if(doc.__moxi_mo) return |
| 5 | let liveFns = new Set(), pending = false, |
| 6 | recompute = ()=>{ |
| 7 | if (pending) return |
| 8 | pending = true |
| 9 | queueMicrotask(()=>{liveFns.forEach(f=>f()); setTimeout(()=>pending = false)}) |
| 10 | } |
| 11 | doc.__moxi_mo = new MutationObserver(recs=>{ |
| 12 | recs.forEach(r=>r.type == "childList" && r.addedNodes.forEach(n=>process(n))) |
| 13 | recompute() |
| 14 | }) |
| 15 | let AF = async function(){}.constructor, HARGS = ["q" , "wait" , "trigger" , "debounce" ], |
| 16 | fire = (elt, type, detail, bub)=>elt.dispatchEvent(new CustomEvent(type, {detail, cancelable:1, bubbles:bub??1, composed:1})), |
| 17 | el = (e,n,h,o)=>e.addEventListener(n,h,o), |
| 18 | DB = Symbol(), |
| 19 | mkDb = ()=>{let last = 0, j; return ms=>new Promise((r,rj)=>{j?.(DB); j = rj; let id = ++last; setTimeout(()=>id == last && (j = null, r()), ms)})}, |
| 20 | mkWait = ctx=>x=>new Promise(r=>typeof x == "number" ? setTimeout(r,x) : el(ctx,x,r,{once:1})), |
| 21 | ignore = elt=>elt.closest("[mx-ignore]" ), |
| 22 | one = x=>x?[x]:[], |
| 23 | POS = {before:"beforebegin" ,after:"afterend" ,start:"afterbegin" ,end:"beforeend" }, |
| 24 | proxy = elts=>new Proxy({}, { |
| 25 | get:(_,p)=>{ |
| 26 | if (p == "count" ) return elts.length |
| 27 | if (p == "arr" ) return ()=>elts.slice() |
| 28 | if (p == Symbol.iterator) return ()=>elts.values() |
| 29 | if (p == "trigger" ) return (t,d,b)=>elts.forEach(e=>fire(e,t,d,b)) |
| 30 | if (p == "insert" ) return (pos,s)=>elts.forEach(e=>e.insertAdjacentHTML(POS[pos],s)) |
| 31 | if (p == "take" ) return (cls,from)=>{ |
| 32 | for (let e of typeof from == "string" ? doc.querySelectorAll(from) : from) e.classList.remove(cls) |
| 33 | for (let e of elts) e.classList.add(cls) |
| 34 | } |
| 35 | let v = elts[0]?.[p] |
| 36 | if (v?.call) return (...a)=>elts.map(e=>e[p](...a))[0] |
| 37 | if (v && typeof v == "object" ) return proxy(elts.map(e=>e[p])) |
| 38 | return v |
| 39 | }, |
| 40 | set:(_,p,v)=>(elts.forEach(e=>e[p]=v),recompute(),true) |
| 41 | }), |
| 42 | mkq = ctx=>sel=>{ |
| 43 | if (typeof sel != "string" ) return proxy(sel.nodeType ? [sel] : [...sel]) |
| 44 | let im = sel.match(/^(.+)\s+in\s+(.+)$/), root = doc |
| 45 | if (im){ sel = im[1]; root = im[2] == "this" ? ctx : doc.querySelector(im[2]) } |
| 46 | if (!root) return proxy([]) |
| 47 | let m = sel.match(/^(next|prev|closest|first|last)\s+(.+)$/), elts |
| 48 | if (m){ |
| 49 | let [,d,s] = m, cdp = e=>ctx.compareDocumentPosition(e) |
| 50 | if (d == "closest" ) elts = one(ctx.closest(s)) |
| 51 | else { |
| 52 | let all = [...root.querySelectorAll(s)] |
| 53 | if (d == "first" ) elts = all.slice(0,1) |
| 54 | else if (d == "last" ) elts = all.slice(-1) |
| 55 | else if (d == "next" ) elts = one(all.find(e=>cdp(e) & 4)) |
| 56 | else elts = one(all.reverse().find(e=>cdp(e) & 2)) |
| 57 | } |
| 58 | } else elts = [...root.querySelectorAll(sel)] |
| 59 | return proxy(elts) |
| 60 | }, |
| 61 | init = elt=>{ |
| 62 | if (elt.__moxi || ignore(elt)) return |
| 63 | if (!fire(elt, "mx:init" , {})) return |
| 64 | elt.__moxi = {} |
| 65 | let q = mkq(elt), wait = mkWait(elt), trigger = fire.bind(0,elt), liveRuns = [] |
| 66 | for (let a of elt.attributes){ |
| 67 | if (a.name == "live" ){ |
| 68 | let fn = new AF(...HARGS, a.value), |
| 69 | debounce = mkDb(), |
| 70 | run = ()=>elt.isConnected ? fn.call(elt, q, wait, trigger, debounce) : liveFns.delete(run) |
| 71 | liveFns.add(run) |
| 72 | liveRuns.push(run) |
| 73 | } else if (a.name.startsWith("on-" )){ |
| 74 | let [name, ...mods] = a.name.slice(3).split("." ), |
| 75 | has = m=>mods.includes(m), h = has("halt" ), debounce = mkDb() |
| 76 | if (has("cc" )) name = name.replace(/-([a-z])/g, (_,c)=>c.toUpperCase()) |
| 77 | let target = has("outside" ) ? doc : elt, |
| 78 | opts = {capture: has("capture" ), passive: has("passive" )}, |
| 79 | fn = new AF("event" , ...HARGS, `with(event?.detail||{}){${a.value}}` ), |
| 80 | handler = elt.__moxi[name] = evt=>{ |
| 81 | if (evt && (has("self" ) && evt.target != elt || has("outside" ) && elt.contains(evt.target))) return |
| 82 | if (h || has("prevent" )) evt?.preventDefault() |
| 83 | if (h || has("stop" )) evt?.stopPropagation() |
| 84 | if (has("once" )) target.removeEventListener(name, handler, opts) |
| 85 | return fn.call(elt, evt, q, wait, trigger, debounce).catch(e=>{if(e!=DB) throw e}) |
| 86 | } |
| 87 | if (name == "init" ) handler() |
| 88 | else el(target, name, handler, opts) |
| 89 | } |
| 90 | } |
| 91 | liveRuns.forEach(r=>r()) |
| 92 | fire(elt, "mx:inited" , {}, false) |
| 93 | }, |
| 94 | process = n=>{ |
| 95 | if (n.nodeType != 1 || ignore(n)) return |
| 96 | let r = doc.evaluate("descendant-or-self::*[@live or @*[starts-with(name(),'on-')]]" , n, null, 7, null) |
| 97 | for (let i = 0; i < r.snapshotLength; i++) init(r.snapshotItem(i)) |
| 98 | }, |
| 99 | gt = globalThis, de = doc.documentElement |
| 100 | gt.q = mkq(de) |
| 101 | gt.wait = mkWait(de) |
| 102 | gt.transition = fn=>doc.startViewTransition ? doc.startViewTransition(fn) : fn() |
| 103 | el(doc, "mx:process" , evt=>process(evt.target)) |
| 104 | el(doc, "refresh" , recompute) |
| 105 | el(doc, "DOMContentLoaded" , ()=>{ |
| 106 | doc.__moxi_mo.observe(de, {childList:1, subtree:1, attributes:1, characterData:1}) |
| 107 | el(doc, "input" , recompute, true) |
| 108 | el(doc, "change" , recompute, true) |
| 109 | process(doc.body) |
| 110 | }) |
| 111 | })() |
| 112 | ; |
| 113 | // fixi/fixi.js |
| 114 | (()=>{ |
| 115 | if(document.__fixi_mo) return; |
| 116 | document.__fixi_mo = new MutationObserver((recs)=>recs.forEach((r)=>r.type === "childList" && r.addedNodes.forEach((n)=>process(n)))) |
| 117 | let send = (elt, type, detail, bub)=>elt.dispatchEvent(new CustomEvent("fx:" + type, {detail, cancelable:true, bubbles:bub !== false, composed:true})) |
| 118 | let attr = (elt, name, defaultVal)=>elt.getAttribute(name) || defaultVal |
| 119 | let dflt = (n, d)=>(window.fixiCfg ?? {})[n] ?? d |
| 120 | let ignore = (elt)=>elt.closest("[fx-ignore]" ) != null |
| 121 | let init = (elt)=>{ |
| 122 | let options = {} |
| 123 | if (elt.__fixi || ignore(elt) || !send(elt, "init" , {options})) return |
| 124 | elt.__fixi = async(evt)=>{ |
| 125 | let reqs = elt.__fixi.requests ||= new Set() |
| 126 | let form = elt.form || elt.closest("form" ) |
| 127 | let body = new FormData(form ?? undefined, evt.submitter) |
| 128 | if (elt.name && !evt.submitter && (!form || (elt.form === form && elt.type === 'submit' ))) body.append(elt.name, elt.value) |
| 129 | let ac = new AbortController() |
| 130 | let cfg = { |
| 131 | trigger:evt, |
| 132 | action:attr(elt, "fx-action" ), |
| 133 | method:attr(elt, "fx-method" , "GET" ).toUpperCase(), |
| 134 | target:document.querySelector(attr(elt, "fx-target" )) ?? elt, |
| 135 | swap:attr(elt, "fx-swap" , dflt("swap" , "outerHTML" )), |
| 136 | body, |
| 137 | drop:reqs.size, |
| 138 | headers:{"FX-Request" :"true" , ...window.fixiCfg?.headers}, |
| 139 | abort:ac.abort.bind(ac), |
| 140 | signal:ac.signal, |
| 141 | preventTrigger:true, |
| 142 | transition:dflt("transition" , document.startViewTransition?.bind(document)), |
| 143 | fetch:fetch.bind(window) |
| 144 | } |
| 145 | let go = send(elt, "config" , {cfg, requests:reqs}) |
| 146 | if (cfg.preventTrigger) evt.preventDefault() |
| 147 | if (!go || cfg.drop) return |
| 148 | if (/GET|DELETE/.test(cfg.method)){ |
| 149 | let params = new URLSearchParams(cfg.body) |
| 150 | if (params.size) |
| 151 | cfg.action += (/\?/.test(cfg.action) ? "&" : "?" ) + params |
| 152 | cfg.body = null |
| 153 | } |
| 154 | reqs.add(cfg) |
| 155 | try { |
| 156 | if (cfg.confirm){ |
| 157 | let result = await cfg.confirm() |
| 158 | if (!result) return |
| 159 | } |
| 160 | if (!send(elt, "before" , {cfg, requests:reqs})) return |
| 161 | cfg.response = await cfg.fetch(cfg.action, cfg) |
| 162 | cfg.text = await cfg.response.text() |
| 163 | if (!send(elt, "after" , {cfg})) return |
| 164 | } catch(error) { |
| 165 | send(elt, "error" , {cfg, error}) |
| 166 | return |
| 167 | } finally { |
| 168 | reqs.delete(cfg) |
| 169 | send(elt, "finally" , {cfg}) |
| 170 | } |
| 171 | let doSwap = ()=>{ |
| 172 | if (cfg.swap instanceof Function) |
| 173 | return cfg.swap(cfg) |
| 174 | else if (/(before|after)(begin|end)/.test(cfg.swap)) |
| 175 | cfg.target.insertAdjacentHTML(cfg.swap, cfg.text) |
| 176 | else if(cfg.swap in cfg.target) |
| 177 | cfg.target[cfg.swap] = cfg.text |
| 178 | else if(cfg.swap !== 'none' ) throw cfg.swap |
| 179 | } |
| 180 | if (cfg.transition) |
| 181 | await cfg.transition(doSwap).finished |
| 182 | else |
| 183 | await doSwap() |
| 184 | send(elt, "swapped" , {cfg}) |
| 185 | if (!document.contains(elt)) send(document, "swapped" , {cfg}) |
| 186 | } |
| 187 | elt.__fixi.evt = attr(elt, "fx-trigger" , elt.matches("form" ) ? "submit" : elt.matches("input:not([type=button]),select,textarea" ) ? "change" : "click" ) |
| 188 | elt.addEventListener(elt.__fixi.evt, elt.__fixi, options) |
| 189 | send(elt, "inited" , {}, false) |
| 190 | } |
| 191 | let process = (n)=>{ |
| 192 | if (n.matches){ |
| 193 | if (ignore(n)) return |
| 194 | if (n.matches("[fx-action]" )) init(n) |
| 195 | } |
| 196 | if(n.querySelectorAll) n.querySelectorAll("[fx-action]" ).forEach(init) |
| 197 | } |
| 198 | document.addEventListener("fx:process" , (evt)=>process(evt.target)) |
| 199 | document.addEventListener("DOMContentLoaded" , ()=>{ |
| 200 | document.__fixi_mo.observe(document.documentElement, {childList:true, subtree:true}) |
| 201 | process(document.body) |
| 202 | }) |
| 203 | })() |
| 204 | ; |
| 205 | // ssexi/ssexi.js |
| 206 | (()=>{ |
| 207 | let send = (elt, type, detail)=>elt.dispatchEvent(new CustomEvent("fx:sse:" + type, {detail, cancelable:true, bubbles:true, composed:true})) |
| 208 | async function* parseSSE(reader) { |
| 209 | let buf = '' , dec = new TextDecoder(), msg = {data:'' , event:'' , id:'' , retry:null} |
| 210 | while (true) { |
| 211 | let {done, value} = await reader.read() |
| 212 | if (done) break |
| 213 | buf += dec.decode(value, {stream:true}) |
| 214 | let lines = buf.split(/\r\n|\r|\n/) |
| 215 | buf = lines.pop() |
| 216 | for (let line of lines) { |
| 217 | if (!line) { |
| 218 | if (msg.data || msg.event || msg.id || msg.retry) yield msg |
| 219 | msg = {data:'' , event:'' , id:'' , retry:null} |
| 220 | } else if (line[0] !== ':' ) { |
| 221 | let i = line.indexOf(':' ), field = i > 0 ? line.slice(0, i) : line, val = i > 0 ? line.slice(i+1).replace(/^ /, '' ) : '' |
| 222 | if (field === 'data' ) msg.data += (msg.data ? '\n' : '' ) + val |
| 223 | else if (field === 'retry' ) msg.retry = parseInt(val) || null |
| 224 | else if (field in msg) msg[field] = val |
| 225 | } |
| 226 | } |
| 227 | } |
| 228 | if (msg.data || msg.event || msg.id || msg.retry) yield msg |
| 229 | } |
| 230 | document.addEventListener("fx:config" , (evt)=>{ |
| 231 | let cfg = evt.detail.cfg, realFetch = cfg.fetch |
| 232 | cfg.headers.Accept ??= "text/html, text/event-stream" |
| 233 | cfg.fetch = async(url, options)=>{ |
| 234 | let response = await realFetch(url, options) |
| 235 | if (!response.headers.get('Content-Type' )?.includes('text/event-stream' )) return response |
| 236 | let style = cfg.sseSwap || cfg.swap, sse = cfg.sse = {lastEventId:'' , retry:null, reader:response.body.getReader(), closed:false} |
| 237 | sse.close = ()=>{ sse.closed = true; sse.reader?.cancel() } |
| 238 | if (!send(cfg.target, 'open' , {cfg, response})) { cfg.swap = 'none' ; return new Response('' ) } |
| 239 | let target = cfg.target, anchor = null |
| 240 | if (style === 'outerHTML' ) { anchor = document.createElement('i' ); anchor.style.display = 'none' ; target.after(anchor) } |
| 241 | let et = anchor || target |
| 242 | let onVis = null |
| 243 | if (cfg.sseDisconnectOnHidden) onVis = ()=>document.hidden && sse.close() |
| 244 | else if (cfg.ssePauseOnHidden) onVis = ()=>document.hidden && sse.reader?.cancel() |
| 245 | if (onVis) document.addEventListener('visibilitychange' , onVis) |
| 246 | try { |
| 247 | while (!sse.closed) { |
| 248 | try { |
| 249 | for await (let msg of parseSSE(sse.reader)) { |
| 250 | if (msg.id) sse.lastEventId = msg.id |
| 251 | if (msg.retry) sse.retry = msg.retry |
| 252 | if (!send(et, 'message' , {cfg, message:msg})) { sse.close(); break } |
| 253 | let t = target, s = style, tx = false |
| 254 | if (msg.event && msg.event[0] === '{' ) { |
| 255 | let e = JSON.parse(msg.event) |
| 256 | if (e.target) t = document.querySelector(e.target) |
| 257 | if (e.swap) s = e.swap |
| 258 | tx = e.transition |
| 259 | } else if (msg.event) { send(et, msg.event, {cfg, message:msg}); t = null } |
| 260 | if (t && msg.data) { |
| 261 | let swap = ()=>{ |
| 262 | if (s instanceof Function) s(t, msg.data) |
| 263 | else if (/(before|after)(begin|end)/i.test(s)) t.insertAdjacentHTML(s, msg.data) |
| 264 | else if (s in t) t[s] = msg.data |
| 265 | } |
| 266 | if (tx && cfg.transition) await cfg.transition(swap).finished |
| 267 | else swap() |
| 268 | send(t.isConnected ? t : et, 'swapped' , {cfg, message:msg}) |
| 269 | if (t === target && anchor) { target = anchor; style = 'beforebegin' } |
| 270 | } |
| 271 | } |
| 272 | send(et, 'close' , {cfg}) |
| 273 | } catch(error) { send(et, 'error' , {cfg, error}) } |
| 274 | if (sse.closed) break |
| 275 | if (!cfg.sseReconnect && !(cfg.ssePauseOnHidden && document.hidden)) break |
| 276 | if (cfg.ssePauseOnHidden && document.hidden) { |
| 277 | await new Promise(r=>{let h=()=>{if(!document.hidden||sse.closed){document.removeEventListener('visibilitychange' ,h);r()}};document.addEventListener('visibilitychange' ,h)}) |
| 278 | } else await new Promise(r=>setTimeout(r, sse.retry || 3000)) |
| 279 | if (sse.closed) break |
| 280 | try { |
| 281 | let hdrs = new Headers(options.headers) |
| 282 | if (sse.lastEventId) hdrs.set('Last-Event-ID' , sse.lastEventId) |
| 283 | sse.reader = (await realFetch(url, {...options, headers: hdrs})).body.getReader() |
| 284 | } catch(error) { send(et, 'error' , {cfg, error}); continue } |
| 285 | } |
| 286 | } finally { |
| 287 | anchor?.remove() |
| 288 | if (onVis) document.removeEventListener('visibilitychange' , onVis) |
| 289 | } |
| 290 | cfg.swap = 'none' |
| 291 | return new Response('' ) |
| 292 | } |
| 293 | }) |
| 294 | })() |
| 295 | ; |
| 296 | // paxi/paxi.js |
| 297 | (()=>{ |
| 298 | let mx = (o, n, ids)=>{ |
| 299 | if (o.nodeType !== n.nodeType || o.nodeName !== n.nodeName){ |
| 300 | n.querySelectorAll?.("[id]" ).forEach((ne)=>{ |
| 301 | if (!n.contains(ne)) return |
| 302 | let oe = ids[ne.id] |
| 303 | if (oe){ delete ids[ne.id]; mx(oe, ne, ids); ne.replaceWith(oe) } |
| 304 | }) |
| 305 | return o.replaceWith(n) |
| 306 | } |
| 307 | if (o.nodeType === 3 || o.nodeType === 8){ |
| 308 | if (o.nodeValue !== n.nodeValue) o.nodeValue = n.nodeValue |
| 309 | return |
| 310 | } |
| 311 | for (let a of [...o.attributes]) if (!n.hasAttribute(a.name)) o.removeAttribute(a.name) |
| 312 | for (let a of n.attributes) if (o.getAttribute(a.name) !== a.value) o.setAttribute(a.name, a.value) |
| 313 | let oIds = {} |
| 314 | for (let c of o.children) if (c.id) oIds[c.id] = c |
| 315 | let oc = o.firstChild, nc = n.firstChild, on, nn, m |
| 316 | while (oc && nc){ |
| 317 | on = oc.nextSibling; nn = nc.nextSibling |
| 318 | if (nc.id){ |
| 319 | m = oIds[nc.id] |
| 320 | if (m && m !== oc){ o.insertBefore(m, oc); mx(m, nc, ids); nc = nn; continue } |
| 321 | if (!m){ o.insertBefore(nc, oc); nc = nn; continue } |
| 322 | } |
| 323 | mx(oc, nc, ids) |
| 324 | oc = on; nc = nn |
| 325 | } |
| 326 | while (oc){ on = oc.nextSibling; oc.remove(); oc = on } |
| 327 | while (nc){ nn = nc.nextSibling; o.appendChild(nc); nc = nn } |
| 328 | } |
| 329 | window.morph = (target, html)=>{ |
| 330 | let t = document.createElement("template" ) |
| 331 | t.innerHTML = html |
| 332 | let ids = {} |
| 333 | target.querySelectorAll("[id]" ).forEach((e)=>ids[e.id] = e) |
| 334 | mx(target, t.content.firstElementChild, ids) |
| 335 | } |
| 336 | document.addEventListener("fx:config" , (e)=>{ |
| 337 | if (e.detail.cfg.swap === "morph" ) e.detail.cfg.swap = (cfg)=>morph(cfg.target, cfg.text) |
| 338 | }) |
| 339 | })(); |
| 340 | // rexi/rexi.js |
| 341 | (()=>{ |
| 342 | let QS_DEFAULT = {GET: 1, HEAD: 1, DELETE: 1} |
| 343 | let doc = document |
| 344 | |
| 345 | let isEltIter = (x)=>{ |
| 346 | if (!x || typeof x !== "object" || x instanceof FormData || x instanceof Element) return false |
| 347 | if (!(Symbol.iterator in x)) return false |
| 348 | for (let i of x) return i instanceof Element |
| 349 | return false |
| 350 | } |
| 351 | |
| 352 | let isRaw = (b)=>typeof b === "string" |
| 353 | || b instanceof Blob |
| 354 | || b instanceof URLSearchParams |
| 355 | || b instanceof ArrayBuffer |
| 356 | |
| 357 | let appendFields = (fd, el)=>{ |
| 358 | if (!el) return |
| 359 | if (el instanceof HTMLFormElement) { for (let [k, v] of new FormData(el)) fd.append(k, v); return } |
| 360 | if (!el.name) return |
| 361 | if (el instanceof HTMLInputElement) { |
| 362 | if (el.type === "file" ) { for (let f of el.files) fd.append(el.name, f) } |
| 363 | else if (el.type === "checkbox" || el.type === "radio" ) { if (el.checked) fd.append(el.name, el.value) } |
| 364 | else fd.append(el.name, el.value) |
| 365 | } else if (el instanceof HTMLSelectElement && el.multiple) { |
| 366 | for (let o of el.selectedOptions) fd.append(el.name, o.value) |
| 367 | } else if ("value" in el) fd.append(el.name, el.value) |
| 368 | } |
| 369 | |
| 370 | let toFD = (body)=>{ |
| 371 | if (body instanceof FormData) return body |
| 372 | let fd = new FormData() |
| 373 | if (body == null) return fd |
| 374 | if (body instanceof URLSearchParams) { for (let [k, v] of body) fd.append(k, v); return fd } |
| 375 | if (typeof body === "string" ) { for (let [k, v] of new URLSearchParams(body)) fd.append(k, v); return fd } |
| 376 | if (body instanceof Element) appendFields(fd, body) |
| 377 | else if (isEltIter(body)) { for (let el of body) appendFields(fd, el) } |
| 378 | else for (let [k, v] of Object.entries(body)) { |
| 379 | if (Array.isArray(v)) { for (let x of v) fd.append(k, x) } |
| 380 | else if (v != null) fd.append(k, v) |
| 381 | } |
| 382 | return fd |
| 383 | } |
| 384 | |
| 385 | let resolve = (inc)=>{ |
| 386 | if (inc == null) return [] |
| 387 | if (typeof inc === "string" ) return [...doc.querySelectorAll(inc)] |
| 388 | if (inc instanceof Element) return [inc] |
| 389 | if (Symbol.iterator in Object(inc)) { |
| 390 | let out = [] |
| 391 | for (let i of inc) for (let e of resolve(i)) out.push(e) |
| 392 | return out |
| 393 | } |
| 394 | return [] |
| 395 | } |
| 396 | |
| 397 | let isJson = (b)=>b != null && typeof b === "object" && !(b instanceof FormData) && !(b instanceof Element) && !isEltIter(b) && !isRaw(b) |
| 398 | |
| 399 | let toQS = (fd)=>{ |
| 400 | let p = new URLSearchParams() |
| 401 | for (let [k, v] of fd) if (!(v instanceof Blob)) p.append(k, v) |
| 402 | return p.toString() |
| 403 | } |
| 404 | |
| 405 | let wrap = (p, ctl)=>{ |
| 406 | p.json = ()=>p.then((r)=>r.json()) |
| 407 | p.text = ()=>p.then((r)=>r.text()) |
| 408 | p.blob = ()=>p.then((r)=>r.blob()) |
| 409 | p.html = ()=>p.then((r)=>r.text()).then((t)=>{let tpl = doc.createElement("template" ); tpl.innerHTML = t; return tpl.content}) |
| 410 | p.raw = ()=>p |
| 411 | p.abort = ()=>ctl.abort() |
| 412 | return p |
| 413 | } |
| 414 | |
| 415 | let req = async (method, url, body, ctl, {timeout, signal, include, send, headers = {}, ...rest} = {})=>{ |
| 416 | if (signal) signal.addEventListener("abort" , ()=>ctl.abort(), {once: true}) |
| 417 | if (timeout) setTimeout(()=>ctl.abort(), timeout) |
| 418 | let asQuery = (send === "query" ) || (send !== "body" && QS_DEFAULT[method]) |
| 419 | let init = {method, headers: {...headers}, ...rest, signal: ctl.signal} |
| 420 | if (asQuery) { |
| 421 | let fd = toFD(body) |
| 422 | for (let el of resolve(include)) appendFields(fd, el) |
| 423 | let qs = toQS(fd) |
| 424 | if (qs) url += (url.includes("?" ) ? "&" : "?" ) + qs |
| 425 | } else if (isRaw(body)) { |
| 426 | init.body = body |
| 427 | } else if (include == null && isJson(body)) { |
| 428 | init.body = JSON.stringify(body) |
| 429 | init.headers["Content-Type" ] ??= "application/json" |
| 430 | } else if (body != null || include != null) { |
| 431 | let fd = toFD(body) |
| 432 | for (let el of resolve(include)) appendFields(fd, el) |
| 433 | init.body = fd |
| 434 | } |
| 435 | let cfg = {url, init} |
| 436 | if (!doc.dispatchEvent(new CustomEvent("rexi:before" , {detail: {cfg}, cancelable: true}))) { |
| 437 | throw new DOMException("Request cancelled by rexi:before listener" , "AbortError" ) |
| 438 | } |
| 439 | let res = await fetch(cfg.url, cfg.init) |
| 440 | doc.dispatchEvent(new CustomEvent("rexi:after" , {detail: {cfg, response: res}})) |
| 441 | if (!res.ok) throw Object.assign(new Error(`${method} ${cfg.url} -> ${res.status}` ), {status: res.status, response: res}) |
| 442 | return res |
| 443 | } |
| 444 | |
| 445 | let make = (method)=>(url, body, opts)=>{ |
| 446 | let ctl = new AbortController() |
| 447 | return wrap(req(method, url, body, ctl, opts), ctl) |
| 448 | } |
| 449 | |
| 450 | let api = {} |
| 451 | for (let n of ["get" , "head" , "post" , "put" , "patch" ]) api[n] = make(n.toUpperCase()) |
| 452 | api.del = make("DELETE" ) |
| 453 | window.rexi = api |
| 454 | for (let k of ["get" , "head" , "post" , "put" , "patch" , "del" ]) window[k] = api[k] |
| 455 | })() |
| 456 | ; |