bzl

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

client.js (4857B)


      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) {
     18       if (c instanceof Node) node.appendChild(c);
     19     }
     20     return node;
     21   };
     22 
     23   const safe = (v) => String(v ?? "");
     24 
     25   function render() {
     26     if (!mountEl) return;
     27     mountEl.innerHTML = "";
     28 
     29     const c = config && typeof config === "object" ? config : {};
     30     const inst = c.instance && typeof c.instance === "object" ? c.instance : {};
     31 
     32     const directoryUrl = el("input", { value: safe(c.directoryUrl), placeholder: "https://chat.bzl.one" });
     33     const name = el("input", { value: safe(inst.name), placeholder: "Display name" });
     34     const description = el("input", { value: safe(inst.description), placeholder: "Short description" });
     35     const bzlVersion = el("input", { value: safe(inst.bzlVersion), placeholder: "bzl version (optional)" });
     36     const requiresReg = el("input", { type: "checkbox", checked: inst.requiresRegistrationCode ? "checked" : null });
     37 
     38     const statusText =
     39       lastResult && typeof lastResult === "object"
     40         ? lastResult.ok
     41           ? `Published (HTTP ${lastResult.status || 200})`
     42           : `Publish failed: ${safe(lastResult.error || lastResult.body || `HTTP ${lastResult.status || 0}`)}`
     43         : "";
     44 
     45     const status = statusText ? el("div", { className: lastResult?.ok ? "good small" : "bad small", text: statusText, style: "margin-top:10px" }) : null;
     46 
     47     const saveBtn = el("button", {
     48       type: "button",
     49       className: "primary",
     50       text: "Save",
     51       onclick: () => {
     52         ctx.send("setConfig", {
     53           config: {
     54             directoryUrl: safe(directoryUrl.value).trim(),
     55             instance: {
     56               id: "",
     57               url: window.location.origin,
     58               name: safe(name.value).trim(),
     59               description: safe(description.value).trim(),
     60               bzlVersion: safe(bzlVersion.value).trim(),
     61               requiresRegistrationCode: Boolean(requiresReg.checked),
     62             },
     63           },
     64         });
     65       },
     66     });
     67 
     68     const publishBtn = el("button", { type: "button", className: "ghost", text: "Publish now", onclick: () => ctx.send("publishNow", {}) });
     69 
     70     mountEl.appendChild(
     71       el("div", { className: "panel", style: "padding:12px" }, [
     72         el("div", { text: "Directory publisher", style: "font-weight:700; margin-bottom:8px" }),
     73         el("div", { className: "muted small", text: "Announces this instance to a directory (owner-only)." }),
     74         el("div", { className: "row", style: "gap:10px; margin-top:10px" }, [
     75           el("label", { style: "flex:1" }, [el("span", { className: "muted small", text: "Directory URL" }), directoryUrl]),
     76         ]),
     77         el("div", { className: "muted small", text: `Instance URL: ${window.location.origin}`, style: "margin-top:10px" }),
     78         el("div", { className: "muted small", text: "Instance metadata", style: "margin-top:14px" }),
     79         el("div", { className: "row", style: "gap:10px; margin-top:10px" }, [
     80           el("label", { style: "flex:1" }, [el("span", { className: "muted small", text: "Bzl instance name" }), name]),
     81           el("label", { style: "flex:1" }, [el("span", { className: "muted small", text: "Bzl version" }), bzlVersion]),
     82         ]),
     83         el("div", { className: "row", style: "gap:10px; margin-top:10px" }, [el("label", { style: "flex:1" }, [el("span", { className: "muted small", text: "Description" }), description])] ),
     84         el("label", { className: "row small", style: "gap:10px; align-items:center; margin-top:10px" }, [requiresReg, el("span", { text: "Requires registration code" })]),
     85         el("div", { className: "row", style: "gap:10px; margin-top:14px" }, [saveBtn, publishBtn]),
     86         ...(status ? [status] : []),
     87       ])
     88     );
     89   }
     90 
     91   ctx.on("config", (msg) => {
     92     config = msg?.config || null;
     93     render();
     94   });
     95   ctx.on("configSaved", () => {
     96     ctx.toast("Saved", "Directory publisher config saved.");
     97   });
     98   ctx.on("result", (msg) => {
     99     lastResult = msg || null;
    100     render();
    101   });
    102 
    103   ctx.ui.registerModTab({
    104     id: "directory",
    105     title: "Directory publish",
    106     ownerOnly: true,
    107     render(mount) {
    108       mountEl = mount;
    109       render();
    110       ctx.send("getConfig", {});
    111     },
    112   });
    113 });