Package the portable DSH profiles, local plugins, desktop integration, Firefox wrapper, and ownership-aware installer lifecycle. Bundle checksum-pinned OpenVSCode, editor extensions, translation assets, and cloudflared so unreliable upstream artifact downloads cannot break a clean installation.
192 lines
7.8 KiB
JavaScript
192 lines
7.8 KiB
JavaScript
/**
|
|
* dsh-header-menu — browser half.
|
|
*
|
|
* The conversation header exposes a utilities region (`.headerUtilities` inside
|
|
* `.headerActions`) that plugins/packages fill via the
|
|
* `conversation.session.header.utilities` slot: Session log, Stop All, Resume
|
|
* Stopped, Resume All, the Git indicator, etc. Several standalone pills eat a
|
|
* lot of horizontal space. This plugin collapses them behind one `⋯` trigger.
|
|
*
|
|
* Strategy — no React fighting:
|
|
* • We never move or synthetically click the real buttons; their real React
|
|
* handlers stay intact. We only (a) inject global CSS that restyles the
|
|
* `.headerUtilities` container into a hidden dropdown panel, and (b) add a
|
|
* single vanilla trigger button next to it.
|
|
* • CSS module classes are content-hashed, so we match the stable suffix via
|
|
* `[class*="_headerUtilities"]` / `[class*="_headerActions"]`.
|
|
* • Open/close state and the "managed" marks are stored in imperatively-set
|
|
* `data-*` attributes, which React does not manage and therefore does not
|
|
* clobber on re-render. A MutationObserver re-applies the enhancement (and
|
|
* re-adds the trigger) whenever React rebuilds the header.
|
|
* @module dsh-header-menu/client
|
|
*/
|
|
window.__ModuleLoader__.load({
|
|
id: "dsh-header-menu",
|
|
factory: (require) => {
|
|
var module = { exports: {} };
|
|
var exports = module.exports;
|
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
|
// ---- CONFIG (edit here) -----------------------------------------------
|
|
// Label shown on the collapsed trigger button.
|
|
const TRIGGER_LABEL = "⋯"; // ⋯ (e.g. "Menu", "Actions ▾")
|
|
// Close the dropdown after clicking one of its items.
|
|
const CLOSE_ON_ITEM_CLICK = true;
|
|
// -----------------------------------------------------------------------
|
|
|
|
const STYLE_ID = "dsh-header-menu-style";
|
|
const STYLE_TEXT = [
|
|
// host anchors the absolutely-positioned panel
|
|
'[data-dsh-hdr-menu="host"]{position:relative}',
|
|
// the utility row becomes a dropdown panel
|
|
'[data-dsh-hdr-menu="panel"]{',
|
|
"position:absolute;top:calc(100% + 8px);right:0;z-index:2147483000;",
|
|
"margin-left:0 !important;flex-direction:column !important;",
|
|
"align-items:stretch !important;gap:6px !important;padding:8px;min-width:210px;",
|
|
"background:var(--dsw-alias-bg-elevated, var(--dsw-specific-sidebar-fill, var(--dsw-alias-bg-base,#1f1f1f)));",
|
|
"border:1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12));",
|
|
"border-radius:12px;box-shadow:0 12px 32px rgba(0,0,0,.45)}",
|
|
// hidden unless the host is marked open
|
|
'[data-dsh-hdr-menu="host"]:not([data-open="1"]) [data-dsh-hdr-menu="panel"]{display:none !important}',
|
|
// stack items full width, left aligned
|
|
'[data-dsh-hdr-menu="panel"] > *{width:100%;box-sizing:border-box;justify-content:flex-start !important}',
|
|
// known grouped entry (fleet-control) stacks vertically too
|
|
'[data-dsh-hdr-menu="panel"] .dsh-fleet-control{flex-direction:column;align-items:stretch;gap:6px;width:100%}',
|
|
'[data-dsh-hdr-menu="panel"] .dsh-fleet-control > *{width:100%;justify-content:flex-start}',
|
|
// the trigger pill
|
|
".dsh-hdr-menu-trigger{border:1px solid var(--dsw-alias-border-l2, rgba(255,255,255,.12));",
|
|
"height:32px;min-width:32px;color:var(--dsw-alias-label-primary);background:transparent;",
|
|
"border-radius:18px;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;",
|
|
"gap:5px;padding:6px 12px;font-size:15px;line-height:20px;white-space:nowrap;",
|
|
"font-family:var(--dsw-font-family)}",
|
|
".dsh-hdr-menu-trigger:hover{background:var(--dsw-alias-interactive-bg-hover)}",
|
|
'.dsh-hdr-menu-trigger[aria-expanded="true"]{background:var(--dsw-alias-interactive-bg-hover)}',
|
|
].join("");
|
|
|
|
function installStyle() {
|
|
if (document.getElementById(STYLE_ID)) return;
|
|
const style = document.createElement("style");
|
|
style.id = STYLE_ID;
|
|
style.dataset.plugin = "dsh-header-menu";
|
|
style.textContent = STYLE_TEXT;
|
|
(document.head || document.documentElement).appendChild(style);
|
|
}
|
|
|
|
/** Close every open header menu. */
|
|
function closeAll(except) {
|
|
document.querySelectorAll('[data-dsh-hdr-menu="host"][data-open="1"]').forEach((host) => {
|
|
if (host === except) return;
|
|
host.removeAttribute("data-open");
|
|
const t = host.querySelector(".dsh-hdr-menu-trigger");
|
|
if (t) t.setAttribute("aria-expanded", "false");
|
|
});
|
|
}
|
|
|
|
/** Build the trigger button for a given host (the .headerActions element). */
|
|
function makeTrigger(host) {
|
|
const btn = document.createElement("button");
|
|
btn.type = "button";
|
|
btn.className = "dsh-hdr-menu-trigger";
|
|
btn.textContent = TRIGGER_LABEL;
|
|
btn.title = "Session actions";
|
|
btn.setAttribute("aria-label", "Session actions menu");
|
|
btn.setAttribute("aria-expanded", "false");
|
|
btn.addEventListener("click", (e) => {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
const open = host.getAttribute("data-open") === "1";
|
|
closeAll(host);
|
|
if (open) {
|
|
host.removeAttribute("data-open");
|
|
btn.setAttribute("aria-expanded", "false");
|
|
} else {
|
|
host.setAttribute("data-open", "1");
|
|
btn.setAttribute("aria-expanded", "true");
|
|
}
|
|
});
|
|
return btn;
|
|
}
|
|
|
|
/** Enhance every header-utilities region currently in the DOM (idempotent). */
|
|
function enhance() {
|
|
const panels = document.querySelectorAll('[class*="_headerUtilities"]');
|
|
panels.forEach((panel) => {
|
|
const host = panel.parentElement;
|
|
if (!host) return;
|
|
panel.setAttribute("data-dsh-hdr-menu", "panel");
|
|
host.setAttribute("data-dsh-hdr-menu", "host");
|
|
// close-on-item-click wiring (once)
|
|
if (CLOSE_ON_ITEM_CLICK && panel.getAttribute("data-dsh-hdr-wired") !== "1") {
|
|
panel.setAttribute("data-dsh-hdr-wired", "1");
|
|
panel.addEventListener("click", (e) => {
|
|
if (e.target && e.target.closest("button,a,[role='menuitem'],[role='button']")) {
|
|
// let the real handler run, then close on next tick
|
|
setTimeout(() => closeAll(), 0);
|
|
}
|
|
});
|
|
}
|
|
// ensure exactly one trigger, placed right after the panel
|
|
if (!host.querySelector(".dsh-hdr-menu-trigger")) {
|
|
const trigger = makeTrigger(host);
|
|
if (panel.nextSibling) host.insertBefore(trigger, panel.nextSibling);
|
|
else host.appendChild(trigger);
|
|
}
|
|
});
|
|
}
|
|
|
|
let scheduled = false;
|
|
function scheduleEnhance() {
|
|
if (scheduled) return;
|
|
scheduled = true;
|
|
requestAnimationFrame(() => {
|
|
scheduled = false;
|
|
enhance();
|
|
});
|
|
}
|
|
|
|
/** No client services required. */
|
|
const inject = [];
|
|
|
|
function apply(ctx) {
|
|
const start = () => {
|
|
installStyle();
|
|
enhance();
|
|
|
|
const observer = new MutationObserver(scheduleEnhance);
|
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
|
|
const onDocClick = (e) => {
|
|
const host = e.target && e.target.closest && e.target.closest('[data-dsh-hdr-menu="host"]');
|
|
if (!host) closeAll();
|
|
};
|
|
const onKey = (e) => {
|
|
if (e.key === "Escape") closeAll();
|
|
};
|
|
document.addEventListener("click", onDocClick, true);
|
|
document.addEventListener("keydown", onKey, true);
|
|
|
|
return () => {
|
|
observer.disconnect();
|
|
document.removeEventListener("click", onDocClick, true);
|
|
document.removeEventListener("keydown", onKey, true);
|
|
closeAll();
|
|
const style = document.getElementById(STYLE_ID);
|
|
if (style) style.remove();
|
|
document.querySelectorAll(".dsh-hdr-menu-trigger").forEach((n) => n.remove());
|
|
document.querySelectorAll('[data-dsh-hdr-menu]').forEach((n) => {
|
|
n.removeAttribute("data-dsh-hdr-menu");
|
|
n.removeAttribute("data-open");
|
|
n.removeAttribute("data-dsh-hdr-wired");
|
|
});
|
|
};
|
|
};
|
|
|
|
if (ctx && typeof ctx.effect === "function") ctx.effect(start, "dsh-header-menu: collapse header utilities");
|
|
else start();
|
|
}
|
|
|
|
exports.apply = apply;
|
|
exports.inject = inject;
|
|
return module.exports;
|
|
},
|
|
});
|