remove non kernel code from app
This commit is contained in:
parent
f6bde50ce3
commit
bba2500473
9 changed files with 0 additions and 503 deletions
|
|
@ -1,57 +0,0 @@
|
|||
<article data-bliss-artifact-editor
|
||||
data-kind="<%= it.kind %>" data-id="<%= it.artifact.id %>"
|
||||
data-structure-id="<%= it.artifact.structure_id %>" data-method="<%= it.artifact.verb || '' %>"
|
||||
class="border-2 border-black bg-white text-black shadow-[4px_4px_0_#000]">
|
||||
<header class="flex items-center gap-2 border-b-4 border-double border-black p-3">
|
||||
<strong class="min-w-0 flex-1 truncate"><% if (it.kind === 'route') { %><%= it.artifact.verb %> <%= it.artifact.path %><% } else { %><%= it.artifact.name %><% } %></strong>
|
||||
<span class="text-xs uppercase"><%= it.kind %></span><span data-status class="text-xs"></span>
|
||||
</header>
|
||||
<div class="p-3">
|
||||
<% if (it.kind === 'route' && it.artifact.verb !== 'GET') { %>
|
||||
<p class="mb-2 border border-amber-700 bg-amber-100 p-2 text-sm">Saving does not replay this <%= it.artifact.verb %> route.</p>
|
||||
<% } %>
|
||||
<label class="block text-xs font-bold uppercase tracking-widest"><%= it.kind === 'route' ? 'Handler' : 'Template' %></label>
|
||||
<div data-editor="<%= it.kind === 'route' ? 'handler' : 'content' %>" data-mode="<%= it.kind === 'route' ? 'javascript' : 'html' %>" class="h-[min(55vh,34rem)] w-full border-2 border-black"><%= it.kind === 'route' ? it.artifact.handler : it.artifact.content %></div>
|
||||
<% if (it.kind === 'template') { %>
|
||||
<details class="mt-3" open>
|
||||
<summary class="cursor-pointer text-xs font-bold uppercase tracking-widest">Rendering test</summary>
|
||||
<div data-editor="test_object" data-mode="javascript" class="mt-1 h-32 w-full border-2 border-black"><%= it.artifact.test_object || '' %></div>
|
||||
<iframe data-template-test title="Template rendering test" src="/workshop/<%= it.artifact.structure_id %>/template/<%= it.artifact.id %>/preview" class="mt-2 h-64 w-full border-2 border-black"></iframe>
|
||||
</details>
|
||||
<% } %>
|
||||
<button data-save type="button" class="mt-3 border-2 border-black bg-white px-3 py-2 font-bold shadow-[3px_3px_0_#000]">Save</button>
|
||||
</div>
|
||||
</article>
|
||||
<script>
|
||||
(function () {
|
||||
const card = document.currentScript.previousElementSibling;
|
||||
if (!card || card.dataset.ready) return;
|
||||
card.dataset.ready = "true";
|
||||
const editors = Object.fromEntries([...card.querySelectorAll("[data-editor]")].map((node) => {
|
||||
const editor = ace.edit(node);
|
||||
editor.setTheme("ace/theme/monokai");
|
||||
editor.session.setMode(`ace/mode/${node.dataset.mode}`);
|
||||
return [node.dataset.editor, editor];
|
||||
}));
|
||||
card.querySelector("[data-save]").onclick = async () => {
|
||||
const status = card.querySelector("[data-status]");
|
||||
const { kind, id, structureId } = card.dataset;
|
||||
status.textContent = "Saving…";
|
||||
const body = kind === "route"
|
||||
? { handler: editors.handler.getValue() }
|
||||
: { content: editors.content.getValue(), test_object: editors.test_object.getValue() };
|
||||
const response = await fetch(`/workshop/${structureId}/${kind}/${id}`, {
|
||||
method: "PUT", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body),
|
||||
});
|
||||
if (!response.ok) return void (status.textContent = "Save failed");
|
||||
status.textContent = "Saved";
|
||||
if (kind === "template") {
|
||||
const frame = card.querySelector("[data-template-test]");
|
||||
frame.src = `${frame.src.split("?")[0]}?t=${Date.now()}`;
|
||||
}
|
||||
document.dispatchEvent(new CustomEvent("bliss:source-saved", {
|
||||
detail: { kind, id, method: kind === "route" ? card.dataset.verb : undefined },
|
||||
}));
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
function handler(req, res) {
|
||||
res.render("inspector/slideout", {});
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
// Install/update the live editor using Bliss's public CLI workflow. This file
|
||||
// intentionally does not open the Bliss database directly.
|
||||
const { spawnSync } = require("node:child_process");
|
||||
const path = require("node:path");
|
||||
|
||||
const repo = path.resolve(__dirname, "../..");
|
||||
const cli = path.join(repo, "bliss-cli/bliss");
|
||||
|
||||
function bliss(...args) {
|
||||
const result = spawnSync(process.execPath, [cli, ...args], {
|
||||
cwd: repo,
|
||||
env: process.env,
|
||||
encoding: "utf8",
|
||||
});
|
||||
if (result.status !== 0) throw new Error(result.stderr || result.stdout);
|
||||
return result.stdout.trim();
|
||||
}
|
||||
|
||||
function json(...args) {
|
||||
return JSON.parse(bliss(...args));
|
||||
}
|
||||
|
||||
const structures = json("structures");
|
||||
let structure = structures.find((item) => item.name === "__bliss_live_editor");
|
||||
if (!structure) {
|
||||
const created = json("create-structure", "__bliss_live_editor");
|
||||
structure = { id: created.id };
|
||||
}
|
||||
|
||||
const structureId = String(structure.id);
|
||||
bliss("update-settings", structureId, "--route-prefix", "/_bliss");
|
||||
let state = json("structure", structureId);
|
||||
// No db is attached: the editor reads structure sources through the /plumbing
|
||||
// API (owner-checked), not by mounting the prime db — which is no longer
|
||||
// attachable to other structures anyway.
|
||||
|
||||
const templates = [
|
||||
["inspector/slideout", "slideout.eta"],
|
||||
["inspector/route_editor", "route-editor.eta"],
|
||||
["inspector/template_editor", "template-editor.eta"],
|
||||
["inspector/artifact_editor", "artifact-editor.eta"],
|
||||
];
|
||||
for (const [name, filename] of templates) {
|
||||
let template = state.templates.find((item) => item.name === name);
|
||||
if (!template) {
|
||||
template = json("create-template", structureId, name);
|
||||
}
|
||||
bliss(
|
||||
"update-template",
|
||||
structureId,
|
||||
String(template.id),
|
||||
"--content-file",
|
||||
path.join(__dirname, filename),
|
||||
);
|
||||
}
|
||||
|
||||
const routes = [
|
||||
["/inspector", "inspector.js"],
|
||||
["/editor/route/:id", "route-editor.js"],
|
||||
["/editor/template/:id", "template-editor.js"],
|
||||
];
|
||||
for (const [routePath, filename] of routes) {
|
||||
let route = state.routes.find(
|
||||
(item) => item.verb === "GET" && item.path === routePath,
|
||||
);
|
||||
if (!route) {
|
||||
route = json("create-route", structureId, "GET", routePath);
|
||||
}
|
||||
bliss(
|
||||
"update-route",
|
||||
structureId,
|
||||
String(route.id),
|
||||
"--handler-file",
|
||||
path.join(__dirname, filename),
|
||||
);
|
||||
}
|
||||
|
||||
console.log(`live editor installed as Structure ${structureId}`);
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
<div data-bliss-ui>
|
||||
<div class="mb-2 flex items-center justify-between border-b-4 border-double border-black pb-2"><strong><%= it.sourceRoute.verb %> <%= it.sourceRoute.path %></strong><span data-status class="text-xs"></span></div>
|
||||
<% if (it.sourceRoute.verb !== "GET") { %>
|
||||
<div class="mb-2 rounded border border-amber-600 bg-amber-950 p-2 text-amber-200">Rerunning this <%= it.sourceRoute.verb %> request can repeat writes or external side effects. Bliss will not rerun it automatically.</div>
|
||||
<% } %>
|
||||
<div data-source class="h-[55vh] w-full border-2 border-black"><%= it.sourceRoute.handler %></div>
|
||||
<button data-save type="button" class="mt-2 border-2 border-black bg-white px-3 py-2 font-bold shadow-[3px_3px_0_#000] active:translate-x-0.5 active:translate-y-0.5 active:shadow-none">Save Route</button>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
const box = document.currentScript.previousElementSibling;
|
||||
if (!box) return;
|
||||
const editor = ace.edit(box.querySelector("[data-source]"));
|
||||
editor.setTheme("ace/theme/monokai");
|
||||
editor.session.setMode("ace/mode/javascript");
|
||||
box.querySelector("[data-save]").onclick = async () => {
|
||||
const status = box.querySelector("[data-status]");
|
||||
status.textContent = "Saving…";
|
||||
const response = await fetch("/workshop/<%= it.sourceRoute.structure_id %>/route/<%= it.sourceRoute.id %>", {
|
||||
method: "PUT", headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ handler: editor.getValue() })
|
||||
});
|
||||
if (!response.ok) return void (status.textContent = "Save failed");
|
||||
status.textContent = "Saved";
|
||||
document.dispatchEvent(new CustomEvent("bliss:source-saved", { detail: { kind: "route", id: "<%= it.sourceRoute.id %>", method: "<%= it.sourceRoute.verb %>" } }));
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
// Read a route's source through the plumbing API rather than mounting the prime
|
||||
// db directly. The prime db is no longer attachable to other structures, and
|
||||
// going through /plumbing means this read is owner-checked: forwarding the
|
||||
// caller's session cookie, plumbing 404s any route in a structure they can't
|
||||
// see, so the live editor can only open what the user is already allowed to edit.
|
||||
async function handler(req, res) {
|
||||
const base = `${req.protocol}://${req.get("host")}`;
|
||||
const response = await fetch(`${base}/plumbing/routes/${req.params.id}`, {
|
||||
headers: { cookie: req.headers.cookie || "" },
|
||||
});
|
||||
if (!response.ok) return res.status(response.status).send("Route not found");
|
||||
const artifact = await response.json();
|
||||
res.render("inspector/artifact_editor", { kind: "route", artifact });
|
||||
}
|
||||
|
|
@ -1,200 +0,0 @@
|
|||
<aside id="bliss-live-editor" data-bliss-ui
|
||||
class="fixed inset-y-0 left-0 z-[2147483646] w-[min(28rem,90vw)] overflow-auto border-r-4 border-black bg-white text-black shadow-[8px_0_0_#000]"
|
||||
style="font-family:ui-sans-serif,system-ui">
|
||||
<header class="sticky top-0 flex items-center justify-between border-b-4 border-double border-black bg-white p-3">
|
||||
<div><div class="text-lg font-black tracking-tight">Live Page</div><div class="text-xs">Objects currently on this card</div></div>
|
||||
<button type="button" data-bliss-close class="h-7 w-7 border-2 border-black bg-white text-lg font-black leading-none shadow-[2px_2px_0_#000] active:translate-x-0.5 active:translate-y-0.5 active:shadow-none">×</button>
|
||||
</header>
|
||||
<div class="grid min-h-[calc(100vh-61px)] grid-rows-[auto_1fr]">
|
||||
<nav data-bliss-tree class="border-b-4 border-double border-black p-2"></nav>
|
||||
<section data-bliss-editor class="min-h-0 p-3 text-sm">Select a route or template to edit it, or use the ✏️ 📄 📋 👯 buttons on the page.</section>
|
||||
</div>
|
||||
</aside>
|
||||
<script>
|
||||
(function () {
|
||||
const root = document.getElementById("bliss-live-editor");
|
||||
if (!root) return;
|
||||
// Re-running this script (the slideout is remounted on every open) must not
|
||||
// pile up document listeners or leave a stale overlay behind.
|
||||
if (window.__blissLive) window.__blissLive();
|
||||
const cleanups = [];
|
||||
const on = (target, type, handler, opts) => {
|
||||
target.addEventListener(type, handler, opts);
|
||||
cleanups.push(() => target.removeEventListener(type, handler, opts));
|
||||
};
|
||||
window.__blissLive = () => { for (const c of cleanups.splice(0)) c(); window.__blissLive = null; };
|
||||
|
||||
const tree = root.querySelector("[data-bliss-tree]");
|
||||
const editor = root.querySelector("[data-bliss-editor]");
|
||||
root.querySelector("[data-bliss-close]").onclick = () =>
|
||||
document.dispatchEvent(new CustomEvent("bliss:inspector-close"));
|
||||
|
||||
const highlight = (kind, id, method, state) =>
|
||||
document.dispatchEvent(new CustomEvent("bliss:highlight", { detail: { kind, id, method, on: state } }));
|
||||
|
||||
function openEditor(kind, id) {
|
||||
editor.textContent = "Loading…";
|
||||
htmx.ajax("GET", `/_bliss/editor/${kind}/${id}`, { target: editor, swap: "innerHTML" });
|
||||
}
|
||||
|
||||
// Clone runs through the workshop's own modal, dropped into a full-screen host
|
||||
// we manage so it stacks above the slideout and closes on backdrop click.
|
||||
function openModal(url) {
|
||||
const host = document.createElement("div");
|
||||
host.dataset.blissUi = "";
|
||||
host.style.cssText = "position:fixed;inset:0;z-index:2147483646;background:rgba(0,0,0,.45)";
|
||||
host.onclick = (event) => { if (event.target === host) host.remove(); };
|
||||
on(document, "bliss:inspector-close", () => host.remove());
|
||||
document.body.append(host);
|
||||
htmx.ajax("GET", url, { target: host, swap: "innerHTML" });
|
||||
}
|
||||
|
||||
// T R E E -- the list of everything the current page rendered.
|
||||
function sourceButton(label, kind, id, count) {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.className = "block w-full border border-transparent px-2 py-1 text-left hover:border-black hover:bg-black hover:text-white";
|
||||
button.textContent = label + (count > 1 ? ` (${count})` : "");
|
||||
button.onmouseenter = () => highlight(kind, id, null, true);
|
||||
button.onmouseleave = () => highlight(kind, id, null, false);
|
||||
if (kind !== "structure") button.onclick = () => openEditor(kind, id);
|
||||
return button;
|
||||
}
|
||||
|
||||
function renderTree(items) {
|
||||
tree.innerHTML = "";
|
||||
const structures = new Map();
|
||||
for (const item of items) {
|
||||
if (!structures.has(item.structureId)) structures.set(item.structureId, { name: item.structureName, routes: new Map(), templates: new Map(), count: 0 });
|
||||
const structure = structures.get(item.structureId);
|
||||
structure.count++;
|
||||
if (!structure.routes.has(item.routeId)) structure.routes.set(item.routeId, { name: item.routeName, count: 0 });
|
||||
structure.routes.get(item.routeId).count++;
|
||||
if (item.templateId) {
|
||||
if (!structure.templates.has(item.templateId)) structure.templates.set(item.templateId, { name: item.templateName, count: 0 });
|
||||
structure.templates.get(item.templateId).count++;
|
||||
}
|
||||
}
|
||||
for (const [structureId, structure] of structures) {
|
||||
const group = document.createElement("details");
|
||||
group.open = true;
|
||||
const heading = document.createElement("summary");
|
||||
heading.className = "cursor-pointer list-none font-semibold";
|
||||
heading.append(sourceButton(structure.name, "structure", structureId, structure.count));
|
||||
group.append(heading);
|
||||
for (const [label, kind, entries] of [["Routes", "route", structure.routes], ["Templates", "template", structure.templates]]) {
|
||||
if (!entries.size) continue;
|
||||
const section = document.createElement("details");
|
||||
section.open = true;
|
||||
section.className = "ml-3";
|
||||
const summary = document.createElement("summary");
|
||||
summary.className = "cursor-pointer py-1 text-xs font-bold uppercase tracking-widest";
|
||||
summary.textContent = label;
|
||||
section.append(summary);
|
||||
for (const [id, entry] of entries) section.append(sourceButton(entry.name, kind, id, entry.count));
|
||||
group.append(section);
|
||||
}
|
||||
tree.append(group);
|
||||
}
|
||||
}
|
||||
|
||||
// O V E R L A Y -- little edit/copy/clone toolbars drawn on the page itself,
|
||||
// one per rendered element, anchored to its top-left corner.
|
||||
const layer = document.createElement("div");
|
||||
layer.dataset.blissUi = "";
|
||||
layer.style.cssText = "position:fixed;inset:0;z-index:2147483640;pointer-events:none";
|
||||
root.parentNode.append(layer); // lives inside the inspector root, so it is torn down on close
|
||||
const bars = new Map();
|
||||
|
||||
function sourceEls() {
|
||||
return [...document.querySelectorAll("[data-bliss-structure-id],[data-bliss-template-id]")]
|
||||
.filter((element) => !element.closest("[data-bliss-ui]"));
|
||||
}
|
||||
|
||||
function templatesOf(element) {
|
||||
try { const list = JSON.parse(element.dataset.blissTemplates || "[]"); if (list.length) return list; } catch (_) {}
|
||||
return element.dataset.blissTemplateId
|
||||
? [{ id: element.dataset.blissTemplateId, name: element.dataset.blissTemplateName }]
|
||||
: [];
|
||||
}
|
||||
|
||||
function iconButton(glyph, title, kind, id, method, action) {
|
||||
const button = document.createElement("button");
|
||||
button.type = "button";
|
||||
button.textContent = glyph;
|
||||
button.title = title;
|
||||
button.style.cssText = "pointer-events:auto;border:2px solid #000;background:#fff;border-radius:6px;min-width:20px;height:20px;padding:0 2px;font-size:11px;line-height:16px;cursor:pointer;box-shadow:1px 1px 0 #000";
|
||||
if (kind) {
|
||||
button.onmouseenter = () => highlight(kind, id, method, true);
|
||||
button.onmouseleave = () => highlight(kind, id, method, false);
|
||||
}
|
||||
button.onclick = (event) => { event.preventDefault(); event.stopPropagation(); action(button); };
|
||||
return button;
|
||||
}
|
||||
|
||||
function buildBar(element) {
|
||||
const wrap = document.createElement("div");
|
||||
wrap.dataset.blissUi = "";
|
||||
wrap.style.cssText = "position:absolute;display:flex;gap:3px;pointer-events:none";
|
||||
if (element.dataset.blissRouteId) {
|
||||
wrap.append(iconButton("✏️", "Edit route " + (element.dataset.blissRouteName || ""),
|
||||
"route", element.dataset.blissRouteId, element.dataset.blissMethod,
|
||||
() => openEditor("route", element.dataset.blissRouteId)));
|
||||
}
|
||||
for (const template of templatesOf(element)) {
|
||||
wrap.append(iconButton("📄", "Edit template " + (template.name || ""),
|
||||
"template", template.id, null, () => openEditor("template", template.id)));
|
||||
}
|
||||
if (element.dataset.blissCopy) {
|
||||
wrap.append(iconButton("📋", "Copy embed snippet", null, null, null, async (button) => {
|
||||
try { await navigator.clipboard.writeText(element.dataset.blissCopy); button.textContent = "✅"; }
|
||||
catch (_) { button.textContent = "❌"; }
|
||||
setTimeout(() => (button.textContent = "📋"), 1000);
|
||||
}));
|
||||
}
|
||||
if (element.dataset.blissClone) {
|
||||
wrap.append(iconButton("👯", "Clone structure", null, null, null,
|
||||
() => openModal(element.dataset.blissClone)));
|
||||
}
|
||||
return wrap;
|
||||
}
|
||||
|
||||
function place(element, wrap) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
if (!rect.width && !rect.height) { wrap.style.display = "none"; return; }
|
||||
wrap.style.display = "flex";
|
||||
// Keep the toolbar clear of the slideout: the page root (<body>) is not
|
||||
// itself shifted, so its top-left would otherwise hide behind the panel.
|
||||
const guard = root.getBoundingClientRect().right + 2;
|
||||
const top = rect.top - 24;
|
||||
wrap.style.left = Math.max(guard, rect.left + 2) + "px";
|
||||
wrap.style.top = (top < 2 ? rect.top + 2 : top) + "px";
|
||||
}
|
||||
|
||||
function syncBars() {
|
||||
const live = new Set();
|
||||
for (const element of sourceEls()) {
|
||||
live.add(element);
|
||||
let wrap = bars.get(element);
|
||||
if (!wrap || !wrap.isConnected) { wrap = buildBar(element); bars.set(element, wrap); layer.append(wrap); }
|
||||
place(element, wrap);
|
||||
}
|
||||
for (const [element, wrap] of bars) {
|
||||
if (!live.has(element)) { wrap.remove(); bars.delete(element); }
|
||||
}
|
||||
}
|
||||
|
||||
let frame = 0;
|
||||
function reposition() {
|
||||
cancelAnimationFrame(frame);
|
||||
frame = requestAnimationFrame(() => { for (const [element, wrap] of bars) place(element, wrap); });
|
||||
}
|
||||
|
||||
on(window, "scroll", reposition, true);
|
||||
on(window, "resize", reposition);
|
||||
on(document, "bliss:inventory", (event) => { renderTree(event.detail); syncBars(); });
|
||||
on(document, "bliss:inspector-close", () => window.__blissLive && window.__blissLive());
|
||||
|
||||
document.dispatchEvent(new CustomEvent("bliss:inventory-request"));
|
||||
})();
|
||||
</script>
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
<div data-bliss-ui>
|
||||
<div class="mb-2 flex items-center justify-between border-b-4 border-double border-black pb-2"><strong><%= it.sourceTemplate.name %></strong><span data-status class="text-xs"></span></div>
|
||||
<div data-source class="h-[55vh] w-full border-2 border-black"><%= it.sourceTemplate.content %></div>
|
||||
<button data-save type="button" class="mt-2 border-2 border-black bg-white px-3 py-2 font-bold shadow-[3px_3px_0_#000] active:translate-x-0.5 active:translate-y-0.5 active:shadow-none">Save Template</button>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
const box = document.currentScript.previousElementSibling;
|
||||
if (!box) return;
|
||||
const editor = ace.edit(box.querySelector("[data-source]"));
|
||||
editor.setTheme("ace/theme/monokai");
|
||||
editor.session.setMode("ace/mode/html");
|
||||
box.querySelector("[data-save]").onclick = async () => {
|
||||
const status = box.querySelector("[data-status]");
|
||||
status.textContent = "Saving…";
|
||||
const response = await fetch("/workshop/<%= it.sourceTemplate.structure_id %>/template/<%= it.sourceTemplate.id %>", {
|
||||
method: "PUT", headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ content: editor.getValue() })
|
||||
});
|
||||
if (!response.ok) return void (status.textContent = "Save failed");
|
||||
status.textContent = "Saved";
|
||||
document.dispatchEvent(new CustomEvent("bliss:source-saved", { detail: { kind: "template", id: "<%= it.sourceTemplate.id %>" } }));
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
// Read a template's source through the plumbing API rather than mounting the
|
||||
// prime db directly (see route-editor.js for the why). Forwarding the caller's
|
||||
// session cookie keeps the read owner-checked: plumbing 404s any template in a
|
||||
// structure the user can't see.
|
||||
async function handler(req, res) {
|
||||
const base = `${req.protocol}://${req.get("host")}`;
|
||||
const response = await fetch(`${base}/plumbing/templates/${req.params.id}`, {
|
||||
headers: { cookie: req.headers.cookie || "" },
|
||||
});
|
||||
if (!response.ok) return res.status(response.status).send("Template not found");
|
||||
const artifact = await response.json();
|
||||
res.render("inspector/artifact_editor", { kind: "template", artifact });
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue