This repository has been archived on 2026-06-07. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
master-ai/vibn-frontend/public/vibn-preview-bridge.js

145 lines
4.0 KiB
JavaScript

/**
* Vibn preview element picker — load inside the previewed app (same or cross-origin).
* Parent verifies event.origin; iframe may postMessage with targetOrigin '*'.
*/
(function () {
var NS = "vibn-preview";
var enabled = false;
var overlay = null;
var lastEl = null;
function esc(s) {
if (typeof CSS !== "undefined" && CSS.escape) return CSS.escape(s);
return String(s).replace(/[^a-zA-Z0-9_-]/g, "\\$&");
}
function shortSelector(el) {
if (!el || el.nodeType !== 1) return "(unknown)";
if (el.id) return "#" + esc(el.id);
var parts = [];
var cur = el;
var depth = 0;
while (cur && cur.nodeType === 1 && depth < 6) {
var tag = cur.tagName.toLowerCase();
var cls = cur.className && typeof cur.className === "string"
? cur.className.trim().split(/\s+/).slice(0, 2).join(".")
: "";
parts.unshift(cls ? tag + "." + cls.split(".").map(esc).join(".") : tag);
cur = cur.parentElement;
depth++;
}
return parts.join(" > ");
}
function snippetText(el, max) {
max = max || 120;
var t = (el.innerText || "").trim().replace(/\s+/g, " ");
return t.length > max ? t.slice(0, max - 1) + "…" : t;
}
function outerSnip(el, max) {
max = max || 400;
try {
var html = el.outerHTML || "";
return html.length > max ? html.slice(0, max - 1) + "…" : html;
} catch (e) {
return "";
}
}
function ensureOverlay() {
if (overlay) return overlay;
overlay = document.createElement("div");
overlay.setAttribute("data-vibn-overlay", "1");
overlay.style.cssText =
"pointer-events:none;position:fixed;z-index:2147483646;box-sizing:border-box;border:2px solid #4f46e5;border-radius:6px;background:rgba(79,70,229,0.12);display:none;transition:top .05s,left .05s,width .05s,height .05s;";
document.documentElement.appendChild(overlay);
return overlay;
}
function hideOverlay() {
if (overlay) overlay.style.display = "none";
lastEl = null;
}
function moveOverlay(el) {
if (!el || el.nodeType !== 1 || el === overlay || el.contains(overlay)) {
hideOverlay();
return;
}
lastEl = el;
var r = el.getBoundingClientRect();
var o = ensureOverlay();
o.style.display = "block";
o.style.top = r.top + "px";
o.style.left = r.left + "px";
o.style.width = r.width + "px";
o.style.height = r.height + "px";
}
window.addEventListener(
"message",
function (ev) {
var d = ev.data;
if (!d || d.source !== NS) return;
if (d.type === "CMD") {
enabled = d.cmd === "enable-select";
document.body.style.cursor = enabled ? "crosshair" : "";
if (!enabled) hideOverlay();
}
},
false,
);
document.addEventListener(
"mousemove",
function (e) {
if (!enabled) return;
var el = document.elementFromPoint(e.clientX, e.clientY);
if (!el || el === overlay || (overlay && overlay.contains(el))) return;
moveOverlay(el);
},
true,
);
document.addEventListener(
"click",
function (e) {
if (!enabled) return;
e.preventDefault();
e.stopPropagation();
if (e.stopImmediatePropagation) e.stopImmediatePropagation();
var el = lastEl || document.elementFromPoint(e.clientX, e.clientY);
if (!el || el === overlay) return;
var payload = {
selector: shortSelector(el),
tagName: el.tagName.toLowerCase(),
textSnippet: snippetText(el, 200),
outerHtmlSnippet: outerSnip(el, 500),
};
enabled = false;
document.body.style.cursor = "";
hideOverlay();
window.parent.postMessage({ source: NS, type: "PICK", payload: payload }, "*");
},
true,
);
window.addEventListener(
"scroll",
function () {
if (enabled && lastEl) moveOverlay(lastEl);
},
true,
);
try {
window.parent.postMessage(
{ source: NS, type: "BRIDGE_READY", payload: { origin: window.location.origin } },
"*",
);
} catch (e) {
/* ignore */
}
})();