// Floating preview banner. Injected into every SSR HTML response via nginx sub_filter. // Fetches /preview/info.json (rendered by entrypoint.sh at container start) for PR metadata. (function () { if (window.__previewBannerLoaded) return; window.__previewBannerLoaded = true; function esc(s) { return String(s == null ? "" : s).replace(/[<>&"']/g, function (c) { return { "<": "<", ">": ">", "&": "&", '"': """, "'": "'" }[c]; }); } // Shared position preference, read/written by all togetherhood-preview UI. var POSITION_KEY = "@Togetherhood:preview:position"; function readPosition() { var pos = { v: "top", h: "right" }; try { var raw = JSON.parse(localStorage.getItem(POSITION_KEY) || "{}"); if (raw.v === "top" || raw.v === "bottom") pos.v = raw.v; if (raw.h === "left" || raw.h === "right") pos.h = raw.h; } catch (e) { /* ignore malformed value, fall back to default */ } return pos; } function writePosition(pos) { try { localStorage.setItem(POSITION_KEY, JSON.stringify(pos)); } catch (e) { /* storage disabled */ } } fetch("/preview/info.json", { cache: "no-store" }) .then(function (r) { return r.ok ? r.json() : null; }) .then(function (info) { if (!info || !info.prNumber) return; var css = [ ".pp-badge{position:fixed;z-index:2147483647;font-family:-apple-system,BlinkMacSystemFont,system-ui,sans-serif;font-size:12px;color:#fff}", ".pp-badge.pp-v-top{top:14px}", ".pp-badge.pp-v-bottom{bottom:14px}", ".pp-badge.pp-h-left{left:14px}", ".pp-badge.pp-h-right{right:14px}", ".pp-pill{background:#1a1a2e;padding:8px 13px;border-radius:999px;cursor:pointer;box-shadow:0 4px 14px rgba(0,0,0,.28);user-select:none;display:flex;align-items:center;gap:8px;line-height:1}", ".pp-pill:hover{background:#252544}", ".pp-dot{width:8px;height:8px;background:#5bbe8b;border-radius:50%;box-shadow:0 0 8px #5bbe8b;flex:none}", ".pp-panel{display:none;position:absolute;background:#1a1a2e;color:#e0e0e0;padding:18px 18px 6px;border-radius:10px;min-width:300px;box-shadow:0 10px 30px rgba(0,0,0,.45)}", ".pp-v-top .pp-panel{top:42px}", ".pp-v-bottom .pp-panel{bottom:42px}", ".pp-h-left .pp-panel{left:0}", ".pp-h-right .pp-panel{right:0}", ".pp-badge.pp-open .pp-panel{display:block}", ".pp-section{margin-bottom:14px}", ".pp-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:1.2px;color:#5bbe8b;margin-bottom:6px}", ".pp-title{font-size:13px;font-weight:500;line-height:1.35;word-break:break-word}", ".pp-meta{font-size:11px;opacity:.65;margin-top:3px;word-break:break-all}", ".pp-btn{color:#a8d5f0;text-decoration:none;display:inline-block;padding:5px 9px;border:1px solid #2d2d4a;border-radius:5px;margin:3px 4px 3px 0;font-size:11.5px;transition:background .12s}", ".pp-btn:hover{background:#2d2d4a;color:#fff;border-color:#3d3d5a}", ".pp-pos-grid{display:grid;grid-template-columns:32px 32px;gap:5px}", ".pp-pos-cell{height:22px;border:1px solid #2d2d4a;border-radius:5px;cursor:pointer;transition:background .12s}", ".pp-pos-cell:hover{background:#2d2d4a;border-color:#3d3d5a}", ".pp-pos-cell.pp-active{background:#5bbe8b;border-color:#5bbe8b}", ".pp-close{float:right;cursor:pointer;color:#666;font-size:18px;line-height:1;margin-top:-6px;margin-right:-6px;padding:4px 8px}", ".pp-close:hover{color:#fff}", ].join(""); var style = document.createElement("style"); style.textContent = css; document.head.appendChild(style); var loginRoles = [ ["admin", "Admin"], ["partner", "Partner"], ["instructor", "Instructor"], ["csm", "CSM"], ["assistant", "Assistant"], ]; var loginButtons = loginRoles.map(function (r) { // Admin always lands on the admin dashboard; other roles return to the current page. var redirect = r[0] === "admin" ? "/admin" : location.pathname + location.search; return '' + r[1] + ""; }).join(""); var wrap = document.createElement("div"); wrap.className = "pp-badge"; wrap.innerHTML = '
PR #' + esc(info.prNumber) + " preview
" + '
' + '×' + '
' + '
Pull request
' + '
' + esc(info.title || ("PR #" + info.prNumber)) + "
" + (info.branch ? '
' + esc(info.branch) + "
" : "") + "
" + '
' + '
Login as
' + loginButtons + "
" + '
' + '
Tools
' + 'View emails' + (info.prUrl ? 'Open PR' : "") + "
" + '
' + '
Position
' + '
' + '' + '' + '' + '' + "
" + "
" + "
"; document.body.appendChild(wrap); var position = readPosition(); function applyPosition() { wrap.classList.remove("pp-v-top", "pp-v-bottom", "pp-h-left", "pp-h-right"); wrap.classList.add("pp-v-" + position.v, "pp-h-" + position.h); wrap.querySelectorAll(".pp-pos-cell").forEach(function (cell) { var active = cell.getAttribute("data-v") === position.v && cell.getAttribute("data-h") === position.h; cell.classList.toggle("pp-active", active); }); } applyPosition(); wrap.querySelectorAll(".pp-pos-cell").forEach(function (cell) { cell.addEventListener("click", function (e) { e.stopPropagation(); position.v = cell.getAttribute("data-v"); position.h = cell.getAttribute("data-h"); writePosition(position); applyPosition(); }); }); var pill = wrap.querySelector(".pp-pill"); var close = wrap.querySelector(".pp-close"); pill.addEventListener("click", function (e) { e.stopPropagation(); wrap.classList.toggle("pp-open"); }); close.addEventListener("click", function (e) { e.stopPropagation(); wrap.classList.remove("pp-open"); }); document.addEventListener("click", function (e) { if (!wrap.contains(e.target)) wrap.classList.remove("pp-open"); }); }) .catch(function () { /* swallow — preview banner is non-critical */ }); })();