client.js (5540B)
1 window.BzlPluginHost.register("directory-publisher", (ctx) => { 2 ctx.devLog("info", "directory-publisher client loaded"); 3 4 let mountEl = null; 5 let config = null; 6 let lastResult = null; 7 8 const el = (tag, props = {}, children = []) => { 9 const node = document.createElement(tag); 10 for (const [k, v] of Object.entries(props || {})) { 11 if (k === "className") node.className = String(v || ""); 12 else if (k === "text") node.textContent = String(v ?? ""); 13 else if (k.startsWith("on") && typeof v === "function") node.addEventListener(k.slice(2).toLowerCase(), v); 14 else if (v === false || v == null) continue; 15 else node.setAttribute(k, String(v)); 16 } 17 for (const c of children) node.appendChild(c); 18 return node; 19 }; 20 21 const safe = (v) => String(v ?? ""); 22 23 function render() { 24 if (!mountEl) return; 25 mountEl.innerHTML = ""; 26 27 const c = config && typeof config === "object" ? config : {}; 28 const inst = c.instance && typeof c.instance === "object" ? c.instance : {}; 29 30 const directoryUrl = el("input", { value: safe(c.directoryUrl), placeholder: "https://chat.bzl.one" }); 31 const token = el("input", { value: safe(c.token), placeholder: "Directory token (shared secret)" }); 32 33 const id = el("input", { value: safe(inst.id), placeholder: "instance id (e.g. temple)" }); 34 const url = el("input", { value: safe(inst.url), placeholder: "https://your.instance" }); 35 const name = el("input", { value: safe(inst.name), placeholder: "Display name" }); 36 const description = el("input", { value: safe(inst.description), placeholder: "Short description" }); 37 const bzlVersion = el("input", { value: safe(inst.bzlVersion), placeholder: "bzl version (optional)" }); 38 const requiresReg = el("input", { type: "checkbox", checked: inst.requiresRegistrationCode ? "checked" : null }); 39 40 const statusText = 41 lastResult && typeof lastResult === "object" 42 ? lastResult.ok 43 ? `Published (HTTP ${lastResult.status || 200})` 44 : `Publish failed: ${safe(lastResult.error || lastResult.body || `HTTP ${lastResult.status || 0}`)}` 45 : ""; 46 47 const status = statusText ? el("div", { className: lastResult?.ok ? "good small" : "bad small", text: statusText, style: "margin-top:10px" }) : null; 48 49 const saveBtn = el("button", { 50 type: "button", 51 className: "primary", 52 text: "Save", 53 onclick: () => { 54 ctx.send("setConfig", { 55 config: { 56 directoryUrl: safe(directoryUrl.value).trim(), 57 token: safe(token.value).trim(), 58 instance: { 59 id: safe(id.value).trim(), 60 url: safe(url.value).trim(), 61 name: safe(name.value).trim(), 62 description: safe(description.value).trim(), 63 bzlVersion: safe(bzlVersion.value).trim(), 64 requiresRegistrationCode: Boolean(requiresReg.checked), 65 }, 66 }, 67 }); 68 }, 69 }); 70 71 const publishBtn = el("button", { type: "button", className: "ghost", text: "Publish now", onclick: () => ctx.send("publishNow", {}) }); 72 73 mountEl.appendChild( 74 el("div", { className: "panel", style: "padding:12px" }, [ 75 el("div", { text: "Directory publisher", style: "font-weight:700; margin-bottom:8px" }), 76 el("div", { className: "muted small", text: "Announces this instance to a directory (owner-only)." }), 77 el("div", { className: "row", style: "gap:10px; margin-top:10px" }, [ 78 el("label", { style: "flex:1" }, [el("span", { className: "muted small", text: "Directory URL" }), directoryUrl]), 79 ]), 80 el("div", { className: "row", style: "gap:10px; margin-top:10px" }, [el("label", { style: "flex:1" }, [el("span", { className: "muted small", text: "Token" }), token])] ), 81 el("div", { className: "muted small", text: "Instance metadata", style: "margin-top:14px" }), 82 el("div", { className: "row", style: "gap:10px; margin-top:8px" }, [ 83 el("label", { style: "flex:1" }, [el("span", { className: "muted small", text: "Instance id" }), id]), 84 el("label", { style: "flex:1" }, [el("span", { className: "muted small", text: "Instance URL" }), url]), 85 ]), 86 el("div", { className: "row", style: "gap:10px; margin-top:10px" }, [ 87 el("label", { style: "flex:1" }, [el("span", { className: "muted small", text: "Name" }), name]), 88 el("label", { style: "flex:1" }, [el("span", { className: "muted small", text: "Bzl version" }), bzlVersion]), 89 ]), 90 el("div", { className: "row", style: "gap:10px; margin-top:10px" }, [el("label", { style: "flex:1" }, [el("span", { className: "muted small", text: "Description" }), description])] ), 91 el("label", { className: "row small", style: "gap:10px; align-items:center; margin-top:10px" }, [requiresReg, el("span", { text: "Requires registration code" })]), 92 el("div", { className: "row", style: "gap:10px; margin-top:14px" }, [saveBtn, publishBtn]), 93 ...(status ? [status] : []), 94 ]) 95 ); 96 } 97 98 ctx.on("config", (msg) => { 99 config = msg?.config || null; 100 render(); 101 }); 102 ctx.on("configSaved", () => { 103 ctx.toast("Saved", "Directory publisher config saved."); 104 }); 105 ctx.on("result", (msg) => { 106 lastResult = msg || null; 107 render(); 108 }); 109 110 ctx.ui.registerModTab({ 111 id: "directory", 112 title: "Directory publish", 113 ownerOnly: true, 114 render(mount) { 115 mountEl = mount; 116 render(); 117 ctx.send("getConfig", {}); 118 }, 119 }); 120 });