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.
152 lines
5.4 KiB
JavaScript
152 lines
5.4 KiB
JavaScript
window.__ModuleLoader__.load({
|
|
id: "dsh-open-in-smerge",
|
|
factory: (require) => {
|
|
var module = { exports: {} };
|
|
var exports = module.exports;
|
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
const react = require("react");
|
|
const jsx = require("react/jsx-runtime");
|
|
|
|
const TRANSPORT_ERROR = { code: "route-unavailable", message: "Git target route unavailable" };
|
|
const STYLE_ID = "dsh-open-in-smerge-style";
|
|
const STYLE_TEXT = ".dsh-open-in-smerge{border:1px solid var(--dsw-alias-border-l2);max-width:260px;height:32px;color:var(--dsw-alias-label-primary);font-family:var(--dsw-font-family);cursor:pointer;background:transparent;border-radius:18px;display:inline-flex;align-items:center;justify-content:center;gap:5px;padding:6px 12px;font-size:13px;font-weight:400;line-height:20px;white-space:nowrap}.dsh-open-in-smerge:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover)}.dsh-open-in-smerge:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.dsh-open-in-smerge[data-failed=true]{color:var(--dsw-alias-status-error,var(--dsw-alias-label-primary));border-color:currentColor}.dsh-open-in-smerge__kind{font-weight:600}.dsh-open-in-smerge__directory{min-width:0;overflow:hidden;text-overflow:ellipsis}.dsh-open-in-smerge__separator{color:var(--dsw-alias-label-tertiary)}";
|
|
|
|
function installStyle() {
|
|
if (document.getElementById(STYLE_ID) !== null) return;
|
|
|
|
const style = document.createElement("style");
|
|
style.id = STYLE_ID;
|
|
style.dataset.plugin = "dsh-open-in-smerge";
|
|
style.textContent = STYLE_TEXT;
|
|
document.head.appendChild(style);
|
|
}
|
|
|
|
async function request(path, options) {
|
|
let response;
|
|
try {
|
|
response = await fetch(path, options);
|
|
} catch {
|
|
return { ok: false, error: TRANSPORT_ERROR };
|
|
}
|
|
|
|
try {
|
|
const envelope = await response.json();
|
|
if (typeof envelope !== "object" || envelope === null) return { ok: false, error: TRANSPORT_ERROR };
|
|
if (envelope.ok === true) return { ok: true, value: envelope.value };
|
|
return { ok: false, error: envelope.error ?? TRANSPORT_ERROR };
|
|
} catch {
|
|
return { ok: false, error: TRANSPORT_ERROR };
|
|
}
|
|
}
|
|
|
|
function directoryName(path) {
|
|
const trimmed = path.replace(/[/\\]+$/, "");
|
|
const parts = trimmed.split(/[/\\]/);
|
|
return parts[parts.length - 1] || trimmed;
|
|
}
|
|
|
|
function OpenInSmergeButton(props) {
|
|
const [busy, setBusy] = react.useState(false);
|
|
const [error, setError] = react.useState(null);
|
|
const [value, setValue] = react.useState(null);
|
|
|
|
const refresh = react.useCallback(async () => {
|
|
const result = await props.target(props.sessionId);
|
|
if (result.ok) {
|
|
setValue(result.value);
|
|
setError(null);
|
|
return;
|
|
}
|
|
|
|
setError(result.error);
|
|
}, [props.sessionId]);
|
|
|
|
react.useEffect(() => {
|
|
let active = true;
|
|
const update = async () => {
|
|
if (!active || document.visibilityState === "hidden") return;
|
|
await refresh();
|
|
};
|
|
const onVisibilityChange = () => {
|
|
if (document.visibilityState === "visible") void update();
|
|
};
|
|
|
|
void update();
|
|
const timer = window.setInterval(() => { void update(); }, 2500);
|
|
document.addEventListener("visibilitychange", onVisibilityChange);
|
|
|
|
return () => {
|
|
active = false;
|
|
window.clearInterval(timer);
|
|
document.removeEventListener("visibilitychange", onVisibilityChange);
|
|
};
|
|
}, [refresh]);
|
|
|
|
const onClick = async () => {
|
|
if (busy) return;
|
|
|
|
setBusy(true);
|
|
setError(null);
|
|
const result = await props.open(props.sessionId);
|
|
setBusy(false);
|
|
|
|
if (result.ok) {
|
|
setValue(result.value);
|
|
return;
|
|
}
|
|
|
|
setError(result.error);
|
|
};
|
|
|
|
const isSet = value !== null && value.set === true;
|
|
const name = isSet ? directoryName(value.workingDirectory) : "";
|
|
const title = error !== null
|
|
? `Git target error: ${error.message}`
|
|
: isSet
|
|
? `Working directory: ${value.workingDirectory}\nRepository: ${value.repository}\nClick to open in Sublime Merge`
|
|
: "No Git working directory set — the agent sets it with set_active_git_directory";
|
|
|
|
return jsx.jsxs("button", {
|
|
type: "button",
|
|
className: "dsh-open-in-smerge",
|
|
disabled: busy || !isSet,
|
|
"aria-busy": busy,
|
|
"aria-label": isSet ? `Open Git working directory ${name}` : "Git working directory not set",
|
|
"data-failed": error !== null,
|
|
title,
|
|
onClick,
|
|
children: [
|
|
jsx.jsx("span", { className: "dsh-open-in-smerge__kind", children: "Git" }),
|
|
isSet ? jsx.jsxs(react.Fragment, { children: [
|
|
jsx.jsx("span", { className: "dsh-open-in-smerge__separator", "aria-hidden": true, children: "·" }),
|
|
jsx.jsx("span", { className: "dsh-open-in-smerge__directory", children: busy ? "Opening…" : name }),
|
|
] }) : null,
|
|
],
|
|
});
|
|
}
|
|
|
|
const inject = ["slots"];
|
|
|
|
function apply(ctx) {
|
|
installStyle();
|
|
ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
|
|
name: "conversation.session.header.utilities",
|
|
id: "open-in-smerge",
|
|
order: 80,
|
|
label: "Git working directory",
|
|
inject: () => ({
|
|
target: (sessionId) => request(`/smerge/target?sessionId=${encodeURIComponent(sessionId)}`),
|
|
open: (sessionId) => request("/smerge/open", {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json" },
|
|
body: JSON.stringify({ sessionId }),
|
|
}),
|
|
}),
|
|
}, OpenInSmergeButton));
|
|
}
|
|
|
|
exports.apply = apply;
|
|
exports.inject = inject;
|
|
return module.exports;
|
|
},
|
|
});
|