@artenx/dshb 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +392 -29
- package/lib/client.js.map +1 -1
- package/lib/core/index.js +5 -0
- package/lib/core/index.js.map +1 -1
- package/lib/core/workspace-instruction-context.d.ts +1667 -0
- package/lib/core/workspace-instruction-context.js +3 -0
- package/lib/core/workspace-instruction-routes.d.ts +7 -0
- package/lib/core/workspace-instruction-routes.js +155 -0
- package/lib/core/workspace-instruction-routes.js.map +1 -0
- package/lib/core/workspace-instructions.d.ts +68 -0
- package/lib/core/workspace-instructions.js +191 -0
- package/lib/core/workspace-instructions.js.map +1 -0
- package/lib/router/router-fs.d.ts +1 -1
- package/lib/router/router-shell.d.ts +1 -1
- package/lib/router/router-subprocess.d.ts +1 -1
- package/lib/ui/picker.d.ts +1 -1
- package/lib/workspace-instruction-context-CxUldQkP.js +2241 -0
- package/lib/workspace-instruction-context-CxUldQkP.js.map +1 -0
- package/package.json +5 -2
package/lib/client.js
CHANGED
|
@@ -530,6 +530,8 @@ window.__ModuleLoader__.load({
|
|
|
530
530
|
const [error, setError] = (0, react.useState)(void 0);
|
|
531
531
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
532
532
|
const [newDirName, setNewDirName] = (0, react.useState)("");
|
|
533
|
+
const [pendingInstruction, setPendingInstruction] = (0, react.useState)(void 0);
|
|
534
|
+
const [instructionDialogOpen, setInstructionDialogOpen] = (0, react.useState)(false);
|
|
533
535
|
(0, react.useEffect)(() => {
|
|
534
536
|
if (!props.open) return;
|
|
535
537
|
(async () => {
|
|
@@ -602,24 +604,25 @@ window.__ModuleLoader__.load({
|
|
|
602
604
|
try {
|
|
603
605
|
const node = nodes.find((n) => n.id === nodeId);
|
|
604
606
|
if (!node) return;
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
607
|
+
let pickedPath = path;
|
|
608
|
+
if (node.type !== "local-host") {
|
|
609
|
+
const res = await fetch("/api/dshb/workspaces/bind", {
|
|
610
|
+
method: "POST",
|
|
611
|
+
headers: { "content-type": "application/json" },
|
|
612
|
+
body: JSON.stringify({
|
|
613
|
+
nodeId,
|
|
614
|
+
remotePath: path
|
|
615
|
+
})
|
|
616
|
+
});
|
|
617
|
+
const data = await res.json();
|
|
618
|
+
if (!res.ok || !data.ok || !data.mirrorPath) {
|
|
619
|
+
setError(data.error ?? "工作区注册失败");
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
pickedPath = data.mirrorPath;
|
|
621
623
|
}
|
|
622
|
-
props.onPicked(
|
|
624
|
+
props.onPicked(pickedPath);
|
|
625
|
+
if (pendingInstruction !== void 0 && pendingInstruction.trim()) savePendingInstruction(pickedPath, pendingInstruction);
|
|
623
626
|
} catch {
|
|
624
627
|
setError("网络错误");
|
|
625
628
|
} finally {
|
|
@@ -629,15 +632,16 @@ window.__ModuleLoader__.load({
|
|
|
629
632
|
nodeId,
|
|
630
633
|
path,
|
|
631
634
|
nodes,
|
|
632
|
-
props
|
|
635
|
+
props,
|
|
636
|
+
pendingInstruction
|
|
633
637
|
]);
|
|
634
638
|
if (!props.open) return null;
|
|
635
639
|
const node = nodes.find((n) => n.id === nodeId);
|
|
636
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.
|
|
640
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
637
641
|
style: overlayStyle,
|
|
638
642
|
role: "dialog",
|
|
639
643
|
"aria-label": "添加工作区",
|
|
640
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
644
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
641
645
|
style: dialogStyle,
|
|
642
646
|
children: [
|
|
643
647
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -858,28 +862,177 @@ window.__ModuleLoader__.load({
|
|
|
858
862
|
justifyContent: "flex-end",
|
|
859
863
|
gap: 10
|
|
860
864
|
},
|
|
865
|
+
children: [
|
|
866
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
867
|
+
type: "button",
|
|
868
|
+
onClick: props.onCancel,
|
|
869
|
+
style: secondaryButtonStyle$1,
|
|
870
|
+
children: "取消"
|
|
871
|
+
}),
|
|
872
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
873
|
+
type: "button",
|
|
874
|
+
onClick: () => setInstructionDialogOpen(true),
|
|
875
|
+
disabled: busy || props.busy || !nodeId || !path,
|
|
876
|
+
style: {
|
|
877
|
+
...secondaryButtonStyle$1,
|
|
878
|
+
borderColor: pendingInstruction !== void 0 && pendingInstruction.trim() ? "var(--dsw-alias-state-business-primary)" : "var(--dsw-alias-border-l2)",
|
|
879
|
+
color: pendingInstruction !== void 0 && pendingInstruction.trim() ? "var(--dsw-alias-state-business-primary)" : "var(--dsw-alias-label-primary)"
|
|
880
|
+
},
|
|
881
|
+
children: ["指令", pendingInstruction !== void 0 && pendingInstruction.trim() ? " ●" : ""]
|
|
882
|
+
}),
|
|
883
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
884
|
+
type: "button",
|
|
885
|
+
onClick: () => void confirm(),
|
|
886
|
+
disabled: busy || props.busy || !nodeId || !path,
|
|
887
|
+
style: primaryButtonStyle$1,
|
|
888
|
+
children: "设为工作区"
|
|
889
|
+
})
|
|
890
|
+
]
|
|
891
|
+
})
|
|
892
|
+
]
|
|
893
|
+
}), instructionDialogOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstructionPreviewDialog, {
|
|
894
|
+
text: pendingInstruction ?? "",
|
|
895
|
+
onClose: () => setInstructionDialogOpen(false),
|
|
896
|
+
onSave: (t) => {
|
|
897
|
+
setPendingInstruction(t);
|
|
898
|
+
setInstructionDialogOpen(false);
|
|
899
|
+
}
|
|
900
|
+
})]
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
function dirnameOf(path) {
|
|
904
|
+
const parts = path.split("/").filter(Boolean);
|
|
905
|
+
parts.pop();
|
|
906
|
+
return parts.length === 0 ? "/" : `/${parts.join("/")}`;
|
|
907
|
+
}
|
|
908
|
+
const pendingInstructions = /* @__PURE__ */ new Map();
|
|
909
|
+
function savePendingInstruction(path, text) {
|
|
910
|
+
pendingInstructions.set(path, text);
|
|
911
|
+
(async () => {
|
|
912
|
+
try {
|
|
913
|
+
let attempts = 0;
|
|
914
|
+
const tryResolve = async () => {
|
|
915
|
+
if (attempts++ > 10) return;
|
|
916
|
+
const ws = ((window.workspaces?.list?.getSnapshot?.())?.items ?? []).find((w) => w.path === path);
|
|
917
|
+
if (!ws) {
|
|
918
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
919
|
+
return tryResolve();
|
|
920
|
+
}
|
|
921
|
+
await fetch(`/api/dshb/instructions/${ws.workspaceId}`, {
|
|
922
|
+
method: "PUT",
|
|
923
|
+
headers: { "content-type": "application/json" },
|
|
924
|
+
body: JSON.stringify({
|
|
925
|
+
text,
|
|
926
|
+
expectedRevision: 0
|
|
927
|
+
})
|
|
928
|
+
});
|
|
929
|
+
pendingInstructions.delete(path);
|
|
930
|
+
};
|
|
931
|
+
tryResolve();
|
|
932
|
+
} catch {}
|
|
933
|
+
})();
|
|
934
|
+
}
|
|
935
|
+
function InstructionPreviewDialog(props) {
|
|
936
|
+
const [text, setText] = (0, react.useState)(props.text);
|
|
937
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
938
|
+
style: {
|
|
939
|
+
position: "fixed",
|
|
940
|
+
inset: 0,
|
|
941
|
+
background: "var(--dsw-alias-overlay-mask, rgba(0,0,0,0.45))",
|
|
942
|
+
display: "flex",
|
|
943
|
+
alignItems: "center",
|
|
944
|
+
justifyContent: "center",
|
|
945
|
+
zIndex: 1100
|
|
946
|
+
},
|
|
947
|
+
onClick: props.onClose,
|
|
948
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
949
|
+
style: {
|
|
950
|
+
width: "min(92vw, 600px)",
|
|
951
|
+
maxWidth: "100%",
|
|
952
|
+
maxHeight: "85vh",
|
|
953
|
+
overflow: "auto",
|
|
954
|
+
overflowX: "hidden",
|
|
955
|
+
background: "var(--dsw-alias-bg-layer-1)",
|
|
956
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
957
|
+
borderRadius: 12,
|
|
958
|
+
padding: 20,
|
|
959
|
+
boxSizing: "border-box",
|
|
960
|
+
color: "var(--dsw-alias-label-primary)"
|
|
961
|
+
},
|
|
962
|
+
onClick: (e) => e.stopPropagation(),
|
|
963
|
+
children: [
|
|
964
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
965
|
+
style: {
|
|
966
|
+
display: "flex",
|
|
967
|
+
justifyContent: "space-between",
|
|
968
|
+
alignItems: "center",
|
|
969
|
+
marginBottom: 14
|
|
970
|
+
},
|
|
971
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
972
|
+
style: {
|
|
973
|
+
margin: 0,
|
|
974
|
+
fontSize: 16,
|
|
975
|
+
color: "var(--dsw-alias-label-primary)"
|
|
976
|
+
},
|
|
977
|
+
children: "项目指令"
|
|
978
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
979
|
+
type: "button",
|
|
980
|
+
onClick: props.onClose,
|
|
981
|
+
style: secondaryButtonStyle$1,
|
|
982
|
+
children: "关闭"
|
|
983
|
+
})]
|
|
984
|
+
}),
|
|
985
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
986
|
+
style: {
|
|
987
|
+
fontSize: 12,
|
|
988
|
+
color: "var(--dsw-alias-label-secondary)",
|
|
989
|
+
marginBottom: 10
|
|
990
|
+
},
|
|
991
|
+
children: "工作区创建后,此指令将自动保存为新工作区的项目指令。新对话首次交互时注入。"
|
|
992
|
+
}),
|
|
993
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
994
|
+
style: {
|
|
995
|
+
width: "100%",
|
|
996
|
+
minHeight: "280px",
|
|
997
|
+
fontFamily: "monospace",
|
|
998
|
+
fontSize: 13,
|
|
999
|
+
padding: "12px",
|
|
1000
|
+
border: "1px solid var(--dsw-alias-border-l2)",
|
|
1001
|
+
borderRadius: 8,
|
|
1002
|
+
background: "var(--dsw-alias-bg-layer-2)",
|
|
1003
|
+
color: "var(--dsw-alias-label-primary)",
|
|
1004
|
+
resize: "vertical",
|
|
1005
|
+
boxSizing: "border-box",
|
|
1006
|
+
outline: "none"
|
|
1007
|
+
},
|
|
1008
|
+
value: text,
|
|
1009
|
+
onChange: (e) => setText(e.target.value),
|
|
1010
|
+
placeholder: "留空则不注入指令...",
|
|
1011
|
+
spellCheck: false
|
|
1012
|
+
}),
|
|
1013
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
1014
|
+
style: {
|
|
1015
|
+
display: "flex",
|
|
1016
|
+
justifyContent: "flex-end",
|
|
1017
|
+
gap: 10,
|
|
1018
|
+
marginTop: 14
|
|
1019
|
+
},
|
|
861
1020
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
862
1021
|
type: "button",
|
|
863
|
-
onClick: props.
|
|
1022
|
+
onClick: props.onClose,
|
|
864
1023
|
style: secondaryButtonStyle$1,
|
|
865
1024
|
children: "取消"
|
|
866
1025
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
867
1026
|
type: "button",
|
|
868
|
-
onClick: () =>
|
|
869
|
-
disabled: busy || props.busy || !nodeId || !path,
|
|
1027
|
+
onClick: () => props.onSave(text),
|
|
870
1028
|
style: primaryButtonStyle$1,
|
|
871
|
-
children: "
|
|
1029
|
+
children: "确定"
|
|
872
1030
|
})]
|
|
873
1031
|
})
|
|
874
1032
|
]
|
|
875
1033
|
})
|
|
876
1034
|
});
|
|
877
1035
|
}
|
|
878
|
-
function dirnameOf(path) {
|
|
879
|
-
const parts = path.split("/").filter(Boolean);
|
|
880
|
-
parts.pop();
|
|
881
|
-
return parts.length === 0 ? "/" : `/${parts.join("/")}`;
|
|
882
|
-
}
|
|
883
1036
|
//#endregion
|
|
884
1037
|
//#region src/client/mobile.tsx
|
|
885
1038
|
/**
|
|
@@ -1762,6 +1915,215 @@ window.__ModuleLoader__.load({
|
|
|
1762
1915
|
}, DrawerToggle));
|
|
1763
1916
|
}
|
|
1764
1917
|
//#endregion
|
|
1918
|
+
//#region src/client/workspace-instruction-menu.tsx
|
|
1919
|
+
let injectedRegistry;
|
|
1920
|
+
function setWorkspaceRegistry(reg) {
|
|
1921
|
+
injectedRegistry = reg;
|
|
1922
|
+
}
|
|
1923
|
+
function resolveWorkspaceByTitle(title) {
|
|
1924
|
+
const wks = injectedRegistry;
|
|
1925
|
+
if (!wks) return void 0;
|
|
1926
|
+
return wks.list.getSnapshot().items.find((w) => w.title === title);
|
|
1927
|
+
}
|
|
1928
|
+
const ROW_INJECT_FLAG = "data-dshb-instr-row-bound";
|
|
1929
|
+
const MENU_INJECT_FLAG = "data-dshb-instr-menu-bound";
|
|
1930
|
+
const MENU_ITEM_FLAG = "data-dshb-instr-menu-item";
|
|
1931
|
+
function mountInstructionDialog(workspaceId, workspaceTitle) {
|
|
1932
|
+
if (!workspaceId) return;
|
|
1933
|
+
const overlay = document.createElement("div");
|
|
1934
|
+
overlay.style.cssText = "position:fixed;inset:0;z-index:1100;display:flex;align-items:center;justify-content:center;background:var(--dsw-alias-overlay-mask,rgba(0,0,0,.45))";
|
|
1935
|
+
const dialog = document.createElement("div");
|
|
1936
|
+
dialog.setAttribute("role", "dialog");
|
|
1937
|
+
dialog.setAttribute("aria-label", `修改 ${workspaceTitle} 的项目指令`);
|
|
1938
|
+
dialog.style.cssText = "box-sizing:border-box;width:min(92vw,600px);max-height:85vh;overflow:auto;padding:20px;border:1px solid var(--dsw-alias-border-l2);border-radius:12px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary)";
|
|
1939
|
+
const title = document.createElement("h2");
|
|
1940
|
+
title.textContent = `项目指令 - ${workspaceTitle}`;
|
|
1941
|
+
title.style.cssText = "margin:0 0 10px;font-size:16px";
|
|
1942
|
+
const description = document.createElement("div");
|
|
1943
|
+
description.textContent = "此内容会注入该工作区的新对话。已有对话继续使用创建时的指令快照。";
|
|
1944
|
+
description.style.cssText = "margin-bottom:10px;font-size:12px;color:var(--dsw-alias-label-secondary)";
|
|
1945
|
+
const textarea = document.createElement("textarea");
|
|
1946
|
+
textarea.placeholder = "留空则不注入指令...";
|
|
1947
|
+
textarea.spellcheck = false;
|
|
1948
|
+
textarea.style.cssText = "box-sizing:border-box;width:100%;min-height:280px;resize:vertical;padding:12px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;outline:none;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);font:13px monospace";
|
|
1949
|
+
const status = document.createElement("div");
|
|
1950
|
+
status.style.cssText = "min-height:18px;margin-top:6px;font-size:12px;color:var(--dsw-alias-label-secondary)";
|
|
1951
|
+
const actions = document.createElement("div");
|
|
1952
|
+
actions.style.cssText = "display:flex;justify-content:flex-end;gap:10px;margin-top:14px";
|
|
1953
|
+
const cancel = document.createElement("button");
|
|
1954
|
+
cancel.type = "button";
|
|
1955
|
+
cancel.textContent = "取消";
|
|
1956
|
+
cancel.style.cssText = "height:36px;padding:0 16px;border:1px solid var(--dsw-alias-border-l2);border-radius:18px;background:transparent;color:var(--dsw-alias-label-primary);cursor:pointer";
|
|
1957
|
+
const save = document.createElement("button");
|
|
1958
|
+
save.type = "button";
|
|
1959
|
+
save.textContent = "保存";
|
|
1960
|
+
save.style.cssText = "height:36px;padding:0 16px;border:0;border-radius:18px;background:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground);cursor:pointer";
|
|
1961
|
+
const close = () => overlay.remove();
|
|
1962
|
+
cancel.addEventListener("click", close);
|
|
1963
|
+
overlay.addEventListener("click", (event) => {
|
|
1964
|
+
if (event.target === overlay) close();
|
|
1965
|
+
});
|
|
1966
|
+
dialog.addEventListener("click", (event) => event.stopPropagation());
|
|
1967
|
+
textarea.addEventListener("input", () => {
|
|
1968
|
+
const bytes = new TextEncoder().encode(textarea.value).length;
|
|
1969
|
+
status.textContent = `${bytes} / 32768 字节`;
|
|
1970
|
+
status.style.color = bytes > 32768 ? "var(--dsw-alias-state-error-primary)" : "var(--dsw-alias-label-secondary)";
|
|
1971
|
+
});
|
|
1972
|
+
save.addEventListener("click", () => {
|
|
1973
|
+
(async () => {
|
|
1974
|
+
const bytes = new TextEncoder().encode(textarea.value).length;
|
|
1975
|
+
if (bytes > 32768) {
|
|
1976
|
+
status.textContent = `指令超过 32768 字节限制(当前 ${bytes} 字节)`;
|
|
1977
|
+
status.style.color = "var(--dsw-alias-state-error-primary)";
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
save.disabled = true;
|
|
1981
|
+
save.textContent = "保存中...";
|
|
1982
|
+
try {
|
|
1983
|
+
const revision = Number(overlay.dataset.revision ?? "0");
|
|
1984
|
+
const response = await fetch(`/api/dshb/instructions/${workspaceId}`, {
|
|
1985
|
+
method: "PUT",
|
|
1986
|
+
headers: { "content-type": "application/json" },
|
|
1987
|
+
body: JSON.stringify({
|
|
1988
|
+
text: textarea.value,
|
|
1989
|
+
expectedRevision: revision
|
|
1990
|
+
})
|
|
1991
|
+
});
|
|
1992
|
+
if (!response.ok) {
|
|
1993
|
+
const body = await response.json().catch(() => ({}));
|
|
1994
|
+
status.textContent = body.error ?? `保存失败 (${response.status})`;
|
|
1995
|
+
status.style.color = "var(--dsw-alias-state-error-primary)";
|
|
1996
|
+
return;
|
|
1997
|
+
}
|
|
1998
|
+
close();
|
|
1999
|
+
} catch {
|
|
2000
|
+
status.textContent = "保存失败";
|
|
2001
|
+
status.style.color = "var(--dsw-alias-state-error-primary)";
|
|
2002
|
+
} finally {
|
|
2003
|
+
save.disabled = false;
|
|
2004
|
+
save.textContent = "保存";
|
|
2005
|
+
}
|
|
2006
|
+
})();
|
|
2007
|
+
});
|
|
2008
|
+
actions.append(cancel, save);
|
|
2009
|
+
dialog.append(title, description, textarea, status, actions);
|
|
2010
|
+
overlay.append(dialog);
|
|
2011
|
+
document.body.append(overlay);
|
|
2012
|
+
status.textContent = "加载中...";
|
|
2013
|
+
fetch(`/api/dshb/instructions/${workspaceId}`).then(async (response) => {
|
|
2014
|
+
if (!response.ok) throw new Error(`读取失败 (${response.status})`);
|
|
2015
|
+
return response.json();
|
|
2016
|
+
}).then((body) => {
|
|
2017
|
+
textarea.value = body.text ?? "";
|
|
2018
|
+
overlay.dataset.revision = String(body.revision ?? 0);
|
|
2019
|
+
textarea.dispatchEvent(new Event("input"));
|
|
2020
|
+
textarea.focus();
|
|
2021
|
+
}).catch((error) => {
|
|
2022
|
+
status.textContent = error instanceof Error ? error.message : "读取失败";
|
|
2023
|
+
status.style.color = "var(--dsw-alias-state-error-primary)";
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
function registerWorkspaceInstructionMenu(ctx) {
|
|
2027
|
+
const wks = ctx.workspaces;
|
|
2028
|
+
if (wks) setWorkspaceRegistry(wks);
|
|
2029
|
+
let activeWorkspaceTitle = "";
|
|
2030
|
+
const findWorkspaceRow = (el) => {
|
|
2031
|
+
let node = el instanceof HTMLElement ? el : null;
|
|
2032
|
+
while (node) {
|
|
2033
|
+
if (node.className && typeof node.className === "string" && node.className.includes("projectRow")) return node;
|
|
2034
|
+
node = node.parentElement;
|
|
2035
|
+
}
|
|
2036
|
+
return null;
|
|
2037
|
+
};
|
|
2038
|
+
const getRowTitle = (row) => {
|
|
2039
|
+
const titleEl = row.querySelector("[class*=\"title\"]");
|
|
2040
|
+
if (!titleEl) return null;
|
|
2041
|
+
return titleEl.textContent ?? null;
|
|
2042
|
+
};
|
|
2043
|
+
const injectMenuItem = (menuContainer) => {
|
|
2044
|
+
if (menuContainer.getAttribute(MENU_INJECT_FLAG) || menuContainer.querySelector(`[${MENU_ITEM_FLAG}]`)) return;
|
|
2045
|
+
menuContainer.setAttribute(MENU_INJECT_FLAG, "1");
|
|
2046
|
+
const existingItems = menuContainer.querySelectorAll("button, [role=\"menuitem\"]");
|
|
2047
|
+
let refItem = null;
|
|
2048
|
+
for (const item of Array.from(existingItems)) {
|
|
2049
|
+
const text = item.textContent ?? "";
|
|
2050
|
+
if (text.includes("重命名") || text.includes("Rename")) {
|
|
2051
|
+
refItem = item;
|
|
2052
|
+
break;
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
if (!refItem) refItem = existingItems[existingItems.length - 1];
|
|
2056
|
+
if (!refItem) return;
|
|
2057
|
+
const btn = document.createElement("button");
|
|
2058
|
+
btn.type = "button";
|
|
2059
|
+
btn.setAttribute("role", "menuitem");
|
|
2060
|
+
btn.setAttribute(MENU_ITEM_FLAG, "1");
|
|
2061
|
+
if (refItem instanceof HTMLElement) {
|
|
2062
|
+
btn.className = refItem.className || "";
|
|
2063
|
+
btn.style.cssText = refItem.style.cssText;
|
|
2064
|
+
}
|
|
2065
|
+
for (const className of Array.from(btn.classList)) if (/danger|delete/i.test(className)) btn.classList.remove(className);
|
|
2066
|
+
btn.style.color = "var(--dsw-alias-label-primary)";
|
|
2067
|
+
btn.style.display = "flex";
|
|
2068
|
+
btn.style.alignItems = "center";
|
|
2069
|
+
btn.style.gap = "8px";
|
|
2070
|
+
const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
2071
|
+
icon.setAttribute("width", "16");
|
|
2072
|
+
icon.setAttribute("height", "16");
|
|
2073
|
+
icon.setAttribute("viewBox", "0 0 16 16");
|
|
2074
|
+
icon.setAttribute("fill", "none");
|
|
2075
|
+
icon.setAttribute("aria-hidden", "true");
|
|
2076
|
+
icon.style.flex = "none";
|
|
2077
|
+
const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
|
|
2078
|
+
path.setAttribute("d", "M3 11.75V13h1.25l7.37-7.37-1.25-1.25L3 11.75Zm9.59-7.34a.66.66 0 0 0 0-.94l-1.06-1.06a.66.66 0 0 0-.94 0l-.83.83 1.25 1.25.83-.83.75.75Z");
|
|
2079
|
+
path.setAttribute("fill", "currentColor");
|
|
2080
|
+
icon.append(path);
|
|
2081
|
+
const label = document.createElement("span");
|
|
2082
|
+
label.textContent = "修改指令";
|
|
2083
|
+
btn.append(icon, label);
|
|
2084
|
+
btn.addEventListener("click", (e) => {
|
|
2085
|
+
e.stopPropagation();
|
|
2086
|
+
e.preventDefault();
|
|
2087
|
+
mountInstructionDialog((activeWorkspaceTitle ? resolveWorkspaceByTitle(activeWorkspaceTitle) : void 0)?.workspaceId ?? "", activeWorkspaceTitle || "Unknown");
|
|
2088
|
+
});
|
|
2089
|
+
refItem.parentElement?.insertBefore(btn, refItem.nextSibling);
|
|
2090
|
+
};
|
|
2091
|
+
new MutationObserver(() => {
|
|
2092
|
+
const projectRows = document.querySelectorAll("[class*=\"projectRow\"]");
|
|
2093
|
+
for (const row of Array.from(projectRows)) {
|
|
2094
|
+
const rowEl = row;
|
|
2095
|
+
if (rowEl.getAttribute(ROW_INJECT_FLAG)) continue;
|
|
2096
|
+
rowEl.setAttribute(ROW_INJECT_FLAG, "1");
|
|
2097
|
+
const iconBtns = rowEl.querySelectorAll("button[class*=\"iconButton\"]");
|
|
2098
|
+
for (const btn of Array.from(iconBtns)) {
|
|
2099
|
+
const label = btn.getAttribute("aria-label") ?? "";
|
|
2100
|
+
if (label.includes("workspace") || label.includes("操作") || label.includes("actions")) btn.addEventListener("click", () => {
|
|
2101
|
+
const title = getRowTitle(rowEl);
|
|
2102
|
+
if (title) activeWorkspaceTitle = title;
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
const menuItems = document.querySelectorAll("[role=\"menuitem\"]");
|
|
2107
|
+
for (const item of Array.from(menuItems)) {
|
|
2108
|
+
const text = item.textContent ?? "";
|
|
2109
|
+
if (text.includes("删除") || text.includes("Delete") || text.includes("重命名") || text.includes("Rename")) {
|
|
2110
|
+
const container = item.closest("[role=\"menu\"]") ?? item.parentElement;
|
|
2111
|
+
if (container instanceof HTMLElement && !container.getAttribute(MENU_INJECT_FLAG)) {
|
|
2112
|
+
const row = findWorkspaceRow(container);
|
|
2113
|
+
if (row) {
|
|
2114
|
+
const title = getRowTitle(row);
|
|
2115
|
+
if (title) activeWorkspaceTitle = title;
|
|
2116
|
+
}
|
|
2117
|
+
injectMenuItem(container);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
}).observe(document.body, {
|
|
2122
|
+
childList: true,
|
|
2123
|
+
subtree: true
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
2126
|
+
//#endregion
|
|
1765
2127
|
//#region src/client/ui.tsx
|
|
1766
2128
|
const SECTION_ID = "dshb-nodes";
|
|
1767
2129
|
const EMPTY_FORM = {
|
|
@@ -2678,6 +3040,7 @@ window.__ModuleLoader__.load({
|
|
|
2678
3040
|
`;
|
|
2679
3041
|
document.head.appendChild(globalStyle);
|
|
2680
3042
|
installMobile(ctx);
|
|
3043
|
+
registerWorkspaceInstructionMenu(ctx);
|
|
2681
3044
|
ctx.plugin({
|
|
2682
3045
|
inject: ["slots", "settingsScope"],
|
|
2683
3046
|
apply: (sub) => {
|