diff --git a/bliss-cli/live-editor/slideout.eta b/bliss-cli/live-editor/slideout.eta
index 4acf7b3..e3fe878 100644
--- a/bliss-cli/live-editor/slideout.eta
+++ b/bliss-cli/live-editor/slideout.eta
@@ -7,33 +7,61 @@
- Select a route or template to edit it.
+ Select a route or template to edit it, or use the โ๏ธ ๐ ๐ ๐ฏ buttons on the page.
diff --git a/public/js/bliss_inspector.js b/public/js/bliss_inspector.js
index 7f68b6c..5b76cea 100644
--- a/public/js/bliss_inspector.js
+++ b/public/js/bliss_inspector.js
@@ -39,7 +39,8 @@
function contextIdFor(element, templateId) {
try {
const ids = JSON.parse(element.dataset.blissTemplateContextIds || "{}");
- if (Object.hasOwn(ids, String(templateId))) return ids[String(templateId)];
+ if (Object.hasOwn(ids, String(templateId)))
+ return ids[String(templateId)];
} catch (_) {}
return element.dataset.blissTemplateContextId || null;
}
@@ -116,7 +117,8 @@
function syncAttributes(element, next) {
for (const attribute of [...element.attributes]) {
- if (!next.hasAttribute(attribute.name)) element.removeAttribute(attribute.name);
+ if (!next.hasAttribute(attribute.name))
+ element.removeAttribute(attribute.name);
}
for (const attribute of [...next.attributes]) {
if (element.getAttribute(attribute.name) !== attribute.value) {
@@ -144,7 +146,8 @@
return;
}
if (current.nodeType !== Node.ELEMENT_NODE) {
- if (current.nodeValue !== next.nodeValue) current.nodeValue = next.nodeValue;
+ if (current.nodeValue !== next.nodeValue)
+ current.nodeValue = next.nodeValue;
return;
}
@@ -178,7 +181,8 @@
}
for (const child of currentChildren) {
- if (!used.has(child) && child.isConnected && !keepLiveNode(child)) child.remove();
+ if (!used.has(child) && child.isConnected && !keepLiveNode(child))
+ child.remove();
}
}
@@ -210,7 +214,8 @@
// A page root must be fetched as a document. Asking for an HTMX
// fragment here used to parse an empty and erase the page.
const response = await fetch(url);
- if (!response.ok) throw new Error(`GET ${url} returned ${response.status}`);
+ if (!response.ok)
+ throw new Error(`GET ${url} returned ${response.status}`);
await replaceBody(await response.text());
return;
}
@@ -230,7 +235,11 @@
});
}
- async function refreshTemplateElement(element, templateId, previousRoots = []) {
+ async function refreshTemplateElement(
+ element,
+ templateId,
+ previousRoots = [],
+ ) {
const response = await fetch("/_bliss/render-template", {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -244,7 +253,24 @@
throw new Error(`Template ${templateId} returned ${response.status}`);
}
const html = await response.text();
- if (element === document.body) return replaceBody(html, { morph: true });
+ // The render-template endpoint re-stamps template provenance but not the
+ // route/structure provenance (that comes from the route pipeline). Carry it
+ // over from the element being replaced so the inspector overlay and tree
+ // stay complete after a live template edit, instead of losing the element's
+ // route/copy/clone identity.
+ const provenance = [...element.attributes]
+ .filter((attribute) => attribute.name.startsWith("data-bliss-"))
+ .map((attribute) => ({ name: attribute.name, value: attribute.value }));
+ const carry = (target) => {
+ for (const { name, value } of provenance) {
+ if (!target.hasAttribute(name)) target.setAttribute(name, value);
+ }
+ };
+ if (element === document.body) {
+ await replaceBody(html, { morph: true });
+ carry(document.body);
+ return;
+ }
const fragment = document.createElement("template");
fragment.innerHTML = html;
// A template may legally have several top-level nodes. They share one
@@ -253,6 +279,7 @@
for (const root of previousRoots) {
if (root !== element && root.isConnected) root.remove();
}
+ for (const root of fragment.content.children) carry(root);
element.replaceWith(fragment.content);
htmx.process(document.body);
}
@@ -328,6 +355,11 @@
[data-bliss-structure-id], [data-bliss-template-id] {
transition: outline-color 80ms linear, background-color 80ms linear;
}
+ html.bliss-inspector-open [data-bliss-structure-id],
+ html.bliss-inspector-open [data-bliss-template-id] {
+ outline: 1px dashed rgba(255, 0, 168, .5);
+ outline-offset: -1px;
+ }
.bliss-source-highlight {
outline: 4px solid #ff00a8 !important;
outline-offset: 3px !important;
@@ -346,8 +378,12 @@
document.body.appendChild(trigger);
});
- document.addEventListener("bliss:highlight", (event) => highlight(event.detail));
- document.addEventListener("bliss:source-saved", (event) => refreshSources(event.detail));
+ document.addEventListener("bliss:highlight", (event) =>
+ highlight(event.detail),
+ );
+ document.addEventListener("bliss:source-saved", (event) =>
+ refreshSources(event.detail),
+ );
document.addEventListener("bliss:inventory-request", publishInventory);
document.addEventListener("bliss:inspector-close", hide);
document.addEventListener("htmx:afterSwap", () => {