bzl

self-hosted ephemeral community engine
Log | Files | Refs | README | LICENSE

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 });