dsh-github-flow 0.1.7 → 0.1.8
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/README.en.md +200 -67
- package/README.md +199 -65
- package/docs/architecture.md +55 -13
- package/docs/images/1.png +0 -0
- package/docs/images/2.png +0 -0
- package/docs/images/3.png +0 -0
- package/lib/api/routes.d.ts +3 -1
- package/lib/api/routes.js +95 -4
- package/lib/client.js +851 -40
- package/lib/commands/gh.d.ts +2 -1
- package/lib/commands/gh.js +40 -3
- package/lib/executor.js +12 -2
- package/lib/main.d.ts +11 -0
- package/lib/main.js +41 -3
- package/lib/types.d.ts +45 -0
- package/lib/updater.d.ts +41 -0
- package/lib/updater.js +160 -0
- package/lib/upgrade-manager.d.ts +54 -0
- package/lib/upgrade-manager.js +299 -0
- package/lib/version.d.ts +13 -0
- package/lib/version.js +13 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -804,6 +804,131 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
|
|
|
804
804
|
margin-top: 10px;
|
|
805
805
|
line-height: 1.4;
|
|
806
806
|
}
|
|
807
|
+
|
|
808
|
+
/* 16. 数据同步设置弹窗 (Sync Settings Modal) */
|
|
809
|
+
.dsh-github-modal-overlay {
|
|
810
|
+
position: fixed;
|
|
811
|
+
top: 0;
|
|
812
|
+
left: 0;
|
|
813
|
+
right: 0;
|
|
814
|
+
bottom: 0;
|
|
815
|
+
background: rgba(0, 0, 0, 0.45);
|
|
816
|
+
display: flex;
|
|
817
|
+
align-items: center;
|
|
818
|
+
justify-content: center;
|
|
819
|
+
z-index: 99999;
|
|
820
|
+
backdrop-filter: blur(2px);
|
|
821
|
+
}
|
|
822
|
+
.dsh-github-modal-dialog {
|
|
823
|
+
width: 90%;
|
|
824
|
+
max-width: 480px;
|
|
825
|
+
background: var(--dsw-alias-bg-base, #ffffff);
|
|
826
|
+
border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
|
|
827
|
+
border-radius: 12px;
|
|
828
|
+
box-shadow: 0 16px 32px rgba(0, 0, 0, 0.18);
|
|
829
|
+
padding: 24px;
|
|
830
|
+
box-sizing: border-box;
|
|
831
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
832
|
+
font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
|
|
833
|
+
}
|
|
834
|
+
body[data-ds-dark-theme] .dsh-github-modal-dialog {
|
|
835
|
+
background: var(--dsw-alias-bg-layer-2, #1e1e22);
|
|
836
|
+
border-color: var(--dsw-alias-border-l1, #30363d);
|
|
837
|
+
box-shadow: 0 16px 36px rgba(0, 0, 0, 0.45);
|
|
838
|
+
}
|
|
839
|
+
.dsh-github-radio-card {
|
|
840
|
+
display: flex;
|
|
841
|
+
align-items: flex-start;
|
|
842
|
+
gap: 10px;
|
|
843
|
+
padding: 10px 12px;
|
|
844
|
+
border-radius: 8px;
|
|
845
|
+
border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
|
|
846
|
+
margin-bottom: 8px;
|
|
847
|
+
cursor: pointer;
|
|
848
|
+
transition: all 0.15s ease;
|
|
849
|
+
background: var(--dsw-alias-bg-layer-1, #f6f8fa);
|
|
850
|
+
}
|
|
851
|
+
.dsh-github-radio-card:hover {
|
|
852
|
+
border-color: var(--dsw-alias-link, #0969da);
|
|
853
|
+
}
|
|
854
|
+
.dsh-github-radio-card.active {
|
|
855
|
+
border-color: var(--dsw-alias-link, #0969da);
|
|
856
|
+
background: rgba(9, 105, 218, 0.08);
|
|
857
|
+
}
|
|
858
|
+
body[data-ds-dark-theme] .dsh-github-radio-card {
|
|
859
|
+
background: var(--dsw-alias-bg-base, #161b22);
|
|
860
|
+
border-color: var(--dsw-alias-border-l1, #30363d);
|
|
861
|
+
}
|
|
862
|
+
body[data-ds-dark-theme] .dsh-github-radio-card.active {
|
|
863
|
+
border-color: #58a6ff;
|
|
864
|
+
background: rgba(88, 166, 255, 0.12);
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/* 下拉菜单及选项在深色/浅色模式下的精准适配 */
|
|
868
|
+
.dsh-github-select {
|
|
869
|
+
width: 100%;
|
|
870
|
+
padding: 8px 12px;
|
|
871
|
+
border-radius: 6px;
|
|
872
|
+
border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
|
|
873
|
+
background: var(--dsw-alias-bg-base, #ffffff);
|
|
874
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
875
|
+
font-size: 13px;
|
|
876
|
+
outline: none;
|
|
877
|
+
cursor: pointer;
|
|
878
|
+
box-sizing: border-box;
|
|
879
|
+
color-scheme: light;
|
|
880
|
+
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
|
881
|
+
}
|
|
882
|
+
.dsh-github-select:focus {
|
|
883
|
+
border-color: var(--dsw-alias-link, #0969da);
|
|
884
|
+
box-shadow: 0 0 0 3px rgba(9, 105, 218, 0.15);
|
|
885
|
+
}
|
|
886
|
+
.dsh-github-select option {
|
|
887
|
+
background-color: #ffffff;
|
|
888
|
+
color: #1f2328;
|
|
889
|
+
padding: 6px 10px;
|
|
890
|
+
}
|
|
891
|
+
body[data-ds-dark-theme] .dsh-github-select {
|
|
892
|
+
border-color: var(--dsw-alias-border-l1, #30363d);
|
|
893
|
+
background: var(--dsw-alias-bg-layer-2, #1e1e22);
|
|
894
|
+
color: var(--dsw-alias-label-primary, #f0f6fc);
|
|
895
|
+
color-scheme: dark;
|
|
896
|
+
}
|
|
897
|
+
body[data-ds-dark-theme] .dsh-github-select:focus {
|
|
898
|
+
border-color: #58a6ff;
|
|
899
|
+
box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.2);
|
|
900
|
+
}
|
|
901
|
+
body[data-ds-dark-theme] .dsh-github-select option {
|
|
902
|
+
background-color: #1e1e22;
|
|
903
|
+
color: #f0f6fc;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
/* 缓存信息展示框 */
|
|
907
|
+
.dsh-github-cache-info-box {
|
|
908
|
+
padding: 10px 12px;
|
|
909
|
+
border-radius: 6px;
|
|
910
|
+
background: var(--dsw-alias-bg-layer-1, #f6f8fa);
|
|
911
|
+
border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
|
|
912
|
+
margin-bottom: 20px;
|
|
913
|
+
font-size: 12px;
|
|
914
|
+
color: var(--dsw-alias-label-secondary, #656d76);
|
|
915
|
+
transition: all 0.15s ease;
|
|
916
|
+
}
|
|
917
|
+
body[data-ds-dark-theme] .dsh-github-cache-info-box {
|
|
918
|
+
background: var(--dsw-alias-bg-base, #161b22);
|
|
919
|
+
border-color: var(--dsw-alias-border-l1, #30363d);
|
|
920
|
+
color: var(--dsw-alias-label-secondary, #8c8c8c);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/* 单选框点强调色 */
|
|
924
|
+
.dsh-github-radio {
|
|
925
|
+
accent-color: var(--dsw-alias-link, #0969da);
|
|
926
|
+
cursor: pointer;
|
|
927
|
+
margin-top: 3px;
|
|
928
|
+
}
|
|
929
|
+
body[data-ds-dark-theme] .dsh-github-radio {
|
|
930
|
+
accent-color: #58a6ff;
|
|
931
|
+
}
|
|
807
932
|
`;
|
|
808
933
|
function injectStyles() {
|
|
809
934
|
var styleId = "dsh-plugin-github-styles";
|
|
@@ -949,7 +1074,25 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
|
|
|
949
1074
|
}, "🌐 官网安装包 (.msi/.pkg)")
|
|
950
1075
|
: null, h("button", {
|
|
951
1076
|
className: "dsh-github-action-btn",
|
|
952
|
-
onClick:
|
|
1077
|
+
onClick: function () {
|
|
1078
|
+
setToastMsg("正在快速检测登录状态...");
|
|
1079
|
+
fetch("/api/github/auth?force=true")
|
|
1080
|
+
.then(function (res) { return res.json(); })
|
|
1081
|
+
.then(function (json) {
|
|
1082
|
+
if (json && json.ok && json.auth && json.auth.loggedIn) {
|
|
1083
|
+
setToastMsg("✅ 已识别到 GitHub CLI 登录态,正在拉取数据...");
|
|
1084
|
+
clearStoredGlobalOverview();
|
|
1085
|
+
onRefresh();
|
|
1086
|
+
}
|
|
1087
|
+
else {
|
|
1088
|
+
setToastMsg("⚠️ 暂未检测到登录凭据,请在终端执行 gh auth login 完成授权");
|
|
1089
|
+
onRefresh();
|
|
1090
|
+
}
|
|
1091
|
+
})
|
|
1092
|
+
.catch(function () {
|
|
1093
|
+
onRefresh();
|
|
1094
|
+
});
|
|
1095
|
+
},
|
|
953
1096
|
title: "重新检测登录与安装状态"
|
|
954
1097
|
}, "🔄 刷新状态")), toastMsg
|
|
955
1098
|
? h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-link, #0969da)", marginTop: "8px" } }, `ℹ️ ${toastMsg}`)
|
|
@@ -957,14 +1100,254 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
|
|
|
957
1100
|
? "💡 提示:使用包管理器安装完成后,若未立即识别,可重启 DeepSeek Harness 应用刷新系统环境变量 PATH。"
|
|
958
1101
|
: "💡 提示:凭证由官方 GitHub CLI 加密保存在操作系统安全凭据库中,授权完毕切回本窗口将自动识别刷新。"));
|
|
959
1102
|
}
|
|
1103
|
+
// 通用升级通知与一键升级卡片组件(自适应右侧栏紧凑模式与全景大面板模式)
|
|
1104
|
+
function UpgradeNotificationCard(props) {
|
|
1105
|
+
var updateInfo = props.updateInfo;
|
|
1106
|
+
var isRightbar = Boolean(props.isRightbar);
|
|
1107
|
+
if (!updateInfo || !updateInfo.hasUpdate)
|
|
1108
|
+
return null;
|
|
1109
|
+
var storageKey = "dsh_dismissed_update_" + (updateInfo.latestVersion || "latest");
|
|
1110
|
+
var [dismissed, setDismissed] = React.useState(function () {
|
|
1111
|
+
try {
|
|
1112
|
+
return localStorage.getItem(storageKey) === "true";
|
|
1113
|
+
}
|
|
1114
|
+
catch (e) {
|
|
1115
|
+
return false;
|
|
1116
|
+
}
|
|
1117
|
+
});
|
|
1118
|
+
var [status, setStatus] = React.useState("idle"); // 'idle' | 'upgrading' | 'success' | 'failed'
|
|
1119
|
+
var [msg, setMsg] = React.useState("");
|
|
1120
|
+
// 窗口切回或切 Tab 重挂载时,主动对齐后端实际的升级状态,防止状态丢失
|
|
1121
|
+
React.useEffect(function () {
|
|
1122
|
+
fetch("/api/github/upgrade-status")
|
|
1123
|
+
.then(function (res) { return res.json(); })
|
|
1124
|
+
.then(function (json) {
|
|
1125
|
+
if (json && json.ok && json.data) {
|
|
1126
|
+
if (json.data.state === "upgrading") {
|
|
1127
|
+
setStatus("upgrading");
|
|
1128
|
+
setMsg("正在拉取最新版本(约需 5~15 秒)...");
|
|
1129
|
+
}
|
|
1130
|
+
else if (json.data.state === "success") {
|
|
1131
|
+
setStatus("success");
|
|
1132
|
+
setMsg(json.data.message || "升级成功!已安装最新依赖。");
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
})
|
|
1136
|
+
.catch(function () { });
|
|
1137
|
+
}, []);
|
|
1138
|
+
if (dismissed)
|
|
1139
|
+
return null;
|
|
1140
|
+
var handleUpgrade = async function () {
|
|
1141
|
+
if (status === "upgrading")
|
|
1142
|
+
return;
|
|
1143
|
+
setStatus("upgrading");
|
|
1144
|
+
setMsg("正在拉取最新版本(约需 5~15 秒)...");
|
|
1145
|
+
try {
|
|
1146
|
+
var res = await fetch("/api/github/upgrade", { method: "POST" });
|
|
1147
|
+
var json = await res.json().catch(function () { return {}; });
|
|
1148
|
+
if (res.ok && json.ok) {
|
|
1149
|
+
setStatus("success");
|
|
1150
|
+
setMsg(json.message || "升级成功!已安装最新依赖。");
|
|
1151
|
+
}
|
|
1152
|
+
else {
|
|
1153
|
+
setStatus("failed");
|
|
1154
|
+
setMsg(json.message || "升级命令执行失败,请在宿主终端手动执行。");
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
catch (err) {
|
|
1158
|
+
setStatus("failed");
|
|
1159
|
+
setMsg("无法连接后端执行升级: " + (err.message || err));
|
|
1160
|
+
}
|
|
1161
|
+
};
|
|
1162
|
+
var handleDismiss = function () {
|
|
1163
|
+
try {
|
|
1164
|
+
localStorage.setItem(storageKey, "true");
|
|
1165
|
+
}
|
|
1166
|
+
catch (e) { }
|
|
1167
|
+
setDismissed(true);
|
|
1168
|
+
};
|
|
1169
|
+
var handleCopy = function () {
|
|
1170
|
+
var cmd = updateInfo.upgradeCommand || "pnpm dsh plugin add dsh-github-flow@latest";
|
|
1171
|
+
if (navigator.clipboard) {
|
|
1172
|
+
navigator.clipboard.writeText(cmd);
|
|
1173
|
+
alert("已复制升级命令到剪贴板:\n" + cmd);
|
|
1174
|
+
}
|
|
1175
|
+
else {
|
|
1176
|
+
prompt("请复制升级命令:", cmd);
|
|
1177
|
+
}
|
|
1178
|
+
};
|
|
1179
|
+
var handleReload = function () {
|
|
1180
|
+
window.location.reload();
|
|
1181
|
+
};
|
|
1182
|
+
var h = React.createElement;
|
|
1183
|
+
// 1. 右侧栏紧凑型模式
|
|
1184
|
+
if (isRightbar) {
|
|
1185
|
+
return h("div", {
|
|
1186
|
+
className: "dsh-github-upgrade-strip",
|
|
1187
|
+
style: {
|
|
1188
|
+
padding: "10px 12px",
|
|
1189
|
+
marginBottom: "12px",
|
|
1190
|
+
borderRadius: "8px",
|
|
1191
|
+
background: status === "success"
|
|
1192
|
+
? "var(--dsw-alias-state-success-subtle, rgba(46, 160, 67, 0.12))"
|
|
1193
|
+
: "var(--dsw-alias-bg-module-platform, rgba(9, 105, 218, 0.08))",
|
|
1194
|
+
border: "1px solid " + (status === "success"
|
|
1195
|
+
? "var(--dsw-alias-state-success-border, rgba(46, 160, 67, 0.35))"
|
|
1196
|
+
: "var(--dsw-alias-border-l2, rgba(9, 105, 218, 0.25))"),
|
|
1197
|
+
fontSize: "12px",
|
|
1198
|
+
},
|
|
1199
|
+
}, h("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "6px" } }, h("span", { style: { fontWeight: 600, color: "var(--dsw-alias-label-primary, #1f2328)" } }, status === "success" ? "🎉 升级完成" : "🚀 发现新版 v" + updateInfo.latestVersion), h("button", {
|
|
1200
|
+
onClick: handleDismiss,
|
|
1201
|
+
title: "不再提醒此版本",
|
|
1202
|
+
style: { background: "none", border: "none", cursor: "pointer", color: "var(--dsw-alias-label-tertiary, #8c8c8c)", fontSize: "14px", padding: 0 },
|
|
1203
|
+
}, "✕")), h("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "8px", lineHeight: "1.4" } }, msg || (status === "success"
|
|
1204
|
+
? "需重启 DSH 服务彻底生效后端工具,或刷新网页预览新界面。"
|
|
1205
|
+
: "当前: v" + updateInfo.currentVersion + ",推荐升级以获取最新功能。")), h("div", { style: { display: "flex", gap: "6px", flexWrap: "wrap" } }, status === "success"
|
|
1206
|
+
? h("button", {
|
|
1207
|
+
className: "dsh-github-btn-sm",
|
|
1208
|
+
onClick: handleReload,
|
|
1209
|
+
style: { flex: 1, justifyContent: "center", background: "#2ea043", color: "#fff", borderColor: "#2ea043" },
|
|
1210
|
+
}, "🔄 刷新页面")
|
|
1211
|
+
: h("button", {
|
|
1212
|
+
className: "dsh-github-btn-sm",
|
|
1213
|
+
disabled: status === "upgrading",
|
|
1214
|
+
onClick: handleUpgrade,
|
|
1215
|
+
style: {
|
|
1216
|
+
flex: 1,
|
|
1217
|
+
justifyContent: "center",
|
|
1218
|
+
background: "var(--dsw-alias-link, #0969da)",
|
|
1219
|
+
color: "#fff",
|
|
1220
|
+
borderColor: "var(--dsw-alias-link, #0969da)",
|
|
1221
|
+
opacity: status === "upgrading" ? 0.7 : 1,
|
|
1222
|
+
},
|
|
1223
|
+
}, status === "upgrading" ? "升级中..." : "⚡ 一键升级"), h("button", {
|
|
1224
|
+
className: "dsh-github-btn-sm",
|
|
1225
|
+
onClick: handleCopy,
|
|
1226
|
+
style: { fontSize: "11px", padding: "4px 8px" },
|
|
1227
|
+
}, "📋 复制命令")));
|
|
1228
|
+
}
|
|
1229
|
+
// 2. 全景驾驶舱大面板横幅模式
|
|
1230
|
+
return h("div", {
|
|
1231
|
+
className: "dsh-github-upgrade-banner",
|
|
1232
|
+
style: {
|
|
1233
|
+
display: "flex",
|
|
1234
|
+
alignItems: "center",
|
|
1235
|
+
justifyContent: "space-between",
|
|
1236
|
+
padding: "12px 18px",
|
|
1237
|
+
marginBottom: "16px",
|
|
1238
|
+
borderRadius: "8px",
|
|
1239
|
+
background: status === "success"
|
|
1240
|
+
? "linear-gradient(90deg, rgba(46, 160, 67, 0.15) 0%, rgba(9, 105, 218, 0.12) 100%)"
|
|
1241
|
+
: "linear-gradient(90deg, rgba(9, 105, 218, 0.12) 0%, rgba(137, 87, 229, 0.12) 100%)",
|
|
1242
|
+
border: "1px solid " + (status === "success" ? "rgba(46, 160, 67, 0.35)" : "rgba(9, 105, 218, 0.3)"),
|
|
1243
|
+
fontSize: "13px",
|
|
1244
|
+
},
|
|
1245
|
+
}, h("div", { style: { display: "flex", alignItems: "center", gap: "10px" } }, h("span", { style: { fontSize: "20px" } }, status === "success" ? "🎉" : "🚀"), h("div", null, h("div", { style: { fontWeight: 600, color: "var(--dsw-alias-label-primary, #1f2328)" } }, status === "success"
|
|
1246
|
+
? "升级完成!新版本 v" + updateInfo.latestVersion + " 已写入"
|
|
1247
|
+
: "发现 dsh-github-flow 新版本 v" + updateInfo.latestVersion + "(当前版本: v" + updateInfo.currentVersion + ")"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, msg || (status === "success"
|
|
1248
|
+
? "💡 提示:DSH 后端驻留了旧版内存,需重启 DSH 服务彻底生效后端工具;若仅预览界面改动可先刷新网页。"
|
|
1249
|
+
: "包含最新 GitHub 工具特性与性能优化,点击右侧可一键自动升级。")))), h("div", { style: { display: "flex", alignItems: "center", gap: "8px", flexShrink: 0 } }, status === "success"
|
|
1250
|
+
? h("button", {
|
|
1251
|
+
className: "dsh-github-btn-sm",
|
|
1252
|
+
onClick: handleReload,
|
|
1253
|
+
style: { background: "#2ea043", color: "#fff", borderColor: "#2ea043", padding: "6px 14px", fontWeight: 600 },
|
|
1254
|
+
}, "🔄 立即刷新页面")
|
|
1255
|
+
: h("button", {
|
|
1256
|
+
className: "dsh-github-btn-sm",
|
|
1257
|
+
disabled: status === "upgrading",
|
|
1258
|
+
onClick: handleUpgrade,
|
|
1259
|
+
style: {
|
|
1260
|
+
background: "var(--dsw-alias-link, #0969da)",
|
|
1261
|
+
color: "#fff",
|
|
1262
|
+
borderColor: "var(--dsw-alias-link, #0969da)",
|
|
1263
|
+
padding: "6px 14px",
|
|
1264
|
+
fontWeight: 600,
|
|
1265
|
+
opacity: status === "upgrading" ? 0.7 : 1,
|
|
1266
|
+
},
|
|
1267
|
+
}, status === "upgrading" ? "正在自动升级中..." : "⚡ 一键自动升级"), h("button", {
|
|
1268
|
+
className: "dsh-github-btn-sm",
|
|
1269
|
+
onClick: handleCopy,
|
|
1270
|
+
style: { padding: "6px 12px" },
|
|
1271
|
+
}, "📋 复制命令"), h("button", {
|
|
1272
|
+
onClick: handleDismiss,
|
|
1273
|
+
title: "不再提醒此版本",
|
|
1274
|
+
style: { background: "none", border: "none", cursor: "pointer", color: "var(--dsw-alias-label-tertiary, #8c8c8c)", fontSize: "16px", padding: "0 4px" },
|
|
1275
|
+
}, "✕")));
|
|
1276
|
+
}
|
|
1277
|
+
// 数据新鲜度与同步状态微标组件 (自适应全景大面板与右侧栏紧凑模式)
|
|
1278
|
+
function SyncStatusBadge(props) {
|
|
1279
|
+
var syncStatus = props.syncStatus;
|
|
1280
|
+
var lastSyncedAt = props.lastSyncedAt;
|
|
1281
|
+
var isRightbar = Boolean(props.isRightbar);
|
|
1282
|
+
var text = "";
|
|
1283
|
+
var bg = "rgba(101, 109, 118, 0.1)";
|
|
1284
|
+
var color = "var(--dsw-alias-label-secondary, #656d76)";
|
|
1285
|
+
var formatTimeAgo = function (ts) {
|
|
1286
|
+
if (!ts)
|
|
1287
|
+
return "刚刚";
|
|
1288
|
+
var diffSec = Math.floor((Date.now() - ts) / 1000);
|
|
1289
|
+
if (diffSec < 60)
|
|
1290
|
+
return "刚刚";
|
|
1291
|
+
var diffMin = Math.floor(diffSec / 60);
|
|
1292
|
+
if (diffMin < 60)
|
|
1293
|
+
return diffMin + "分钟前";
|
|
1294
|
+
var diffHour = Math.floor(diffMin / 60);
|
|
1295
|
+
if (diffHour < 24)
|
|
1296
|
+
return diffHour + "小时前";
|
|
1297
|
+
var diffDay = Math.floor(diffHour / 24);
|
|
1298
|
+
return diffDay + "天前";
|
|
1299
|
+
};
|
|
1300
|
+
if (syncStatus === "syncing") {
|
|
1301
|
+
text = isRightbar ? "⏳ 同步中..." : "⏳ 后台同步中...";
|
|
1302
|
+
bg = "rgba(9, 105, 218, 0.12)";
|
|
1303
|
+
color = "var(--dsw-alias-link, #0969da)";
|
|
1304
|
+
}
|
|
1305
|
+
else if (syncStatus === "fresh") {
|
|
1306
|
+
var timeStr = lastSyncedAt ? new Date(lastSyncedAt).toLocaleTimeString() : "";
|
|
1307
|
+
text = "✅ " + (isRightbar ? "最新" : "已是最新") + (timeStr ? " (" + timeStr + ")" : "");
|
|
1308
|
+
bg = "rgba(46, 160, 67, 0.12)";
|
|
1309
|
+
color = "var(--dsw-alias-state-success, #2ea043)";
|
|
1310
|
+
}
|
|
1311
|
+
else if (syncStatus === "cached") {
|
|
1312
|
+
text = "📦 本地快照 (" + formatTimeAgo(lastSyncedAt) + ")";
|
|
1313
|
+
bg = "rgba(101, 109, 118, 0.12)";
|
|
1314
|
+
color = "var(--dsw-alias-label-secondary, #656d76)";
|
|
1315
|
+
}
|
|
1316
|
+
else if (syncStatus === "failed") {
|
|
1317
|
+
text = isRightbar ? "⚠️ 同步超时" : "⚠️ 同步超时,已保留本地缓存";
|
|
1318
|
+
bg = "rgba(207, 34, 46, 0.1)";
|
|
1319
|
+
color = "var(--dsw-alias-state-error-primary, #cf222e)";
|
|
1320
|
+
}
|
|
1321
|
+
if (!text)
|
|
1322
|
+
return null;
|
|
1323
|
+
return React.createElement("span", {
|
|
1324
|
+
className: "dsh-github-sync-badge",
|
|
1325
|
+
style: {
|
|
1326
|
+
fontSize: isRightbar ? "10px" : "11px",
|
|
1327
|
+
padding: isRightbar ? "2px 6px" : "3px 8px",
|
|
1328
|
+
borderRadius: "12px",
|
|
1329
|
+
background: bg,
|
|
1330
|
+
color: color,
|
|
1331
|
+
marginLeft: isRightbar ? "6px" : "10px",
|
|
1332
|
+
fontWeight: 500,
|
|
1333
|
+
display: "inline-flex",
|
|
1334
|
+
alignItems: "center",
|
|
1335
|
+
whiteSpace: "nowrap",
|
|
1336
|
+
},
|
|
1337
|
+
}, text);
|
|
1338
|
+
}
|
|
960
1339
|
// 渲染 GitHub 仓库信息与操作视图的核心函数
|
|
961
|
-
function renderGithubRepoView(h, data, loading, error, onRefresh, isRightbar = false) {
|
|
1340
|
+
function renderGithubRepoView(h, data, loading, error, onRefresh, isRightbar = false, updateInfo = null, syncStatus = "", lastSyncedAt = 0) {
|
|
962
1341
|
var auth = data ? data.auth : null;
|
|
963
1342
|
var repo = data ? data.repo : null;
|
|
964
1343
|
var prs = (data && data.pullRequests) || [];
|
|
965
1344
|
var issues = (data && data.issues) || [];
|
|
966
1345
|
var runs = (data && data.runs) || [];
|
|
967
|
-
var
|
|
1346
|
+
var effectiveUpdateInfo = updateInfo || data?.updateInfo;
|
|
1347
|
+
var upgradeBannerEl = effectiveUpdateInfo && effectiveUpdateInfo.hasUpdate
|
|
1348
|
+
? h(UpgradeNotificationCard, { updateInfo: effectiveUpdateInfo, isRightbar })
|
|
1349
|
+
: null;
|
|
1350
|
+
var headerEl = h("div", { className: "dsh-github-header" }, h("div", { className: "dsh-github-title" }, renderOctocatBadge(isRightbar ? 26 : 30), h("h2", null, isRightbar ? "GitHub 仓库" : "GitHub 工作台"), syncStatus ? h(SyncStatusBadge, { syncStatus, lastSyncedAt, isRightbar }) : null), h("button", {
|
|
968
1351
|
className: "dsh-github-refresh-btn",
|
|
969
1352
|
onClick: onRefresh,
|
|
970
1353
|
disabled: loading,
|
|
@@ -1075,23 +1458,202 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
|
|
|
1075
1458
|
var authGuideEl = !auth?.loggedIn
|
|
1076
1459
|
? h(AuthGuideCard, { auth, onRefresh, isRightbar })
|
|
1077
1460
|
: null;
|
|
1078
|
-
return [headerEl, authGuideEl, repoCardEl, actionsBarEl, prSectionEl, issueSectionEl, runSectionEl].filter(Boolean);
|
|
1461
|
+
return [headerEl, upgradeBannerEl, authGuideEl, repoCardEl, actionsBarEl, prSectionEl, issueSectionEl, runSectionEl].filter(Boolean);
|
|
1462
|
+
}
|
|
1463
|
+
// 全局持久化缓存与偏好配置键名
|
|
1464
|
+
var GLOBAL_OVERVIEW_CACHE_KEY = "dsh_github_global_overview_snapshot_v1";
|
|
1465
|
+
var SYNC_SETTINGS_STORAGE_KEY = "dsh_github_sync_settings_v1";
|
|
1466
|
+
var DEFAULT_SYNC_SETTINGS = {
|
|
1467
|
+
strategy: "swr", // 'swr' (秒开展示旧数据,后台静默拉取) | 'manual' (仅手动刷新) | 'interval' (定时轮询)
|
|
1468
|
+
intervalMs: 300000, // 默认 5 分钟 (300,000ms)
|
|
1469
|
+
};
|
|
1470
|
+
function getLocalSyncSettings() {
|
|
1471
|
+
try {
|
|
1472
|
+
var raw = localStorage.getItem(SYNC_SETTINGS_STORAGE_KEY);
|
|
1473
|
+
if (!raw)
|
|
1474
|
+
return DEFAULT_SYNC_SETTINGS;
|
|
1475
|
+
var parsed = JSON.parse(raw);
|
|
1476
|
+
return Object.assign({}, DEFAULT_SYNC_SETTINGS, parsed);
|
|
1477
|
+
}
|
|
1478
|
+
catch (e) {
|
|
1479
|
+
return DEFAULT_SYNC_SETTINGS;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
function saveLocalSyncSettings(settings) {
|
|
1483
|
+
try {
|
|
1484
|
+
localStorage.setItem(SYNC_SETTINGS_STORAGE_KEY, JSON.stringify(settings));
|
|
1485
|
+
// 跨组件事件通知:确保主页设置被修改后,右侧栏及所有实例即时同步新规则
|
|
1486
|
+
if (typeof window !== "undefined") {
|
|
1487
|
+
window.dispatchEvent(new CustomEvent("dsh-github-sync-settings-change", { detail: settings }));
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
catch (e) { }
|
|
1491
|
+
}
|
|
1492
|
+
function getStoredGlobalOverview() {
|
|
1493
|
+
try {
|
|
1494
|
+
var raw = localStorage.getItem(GLOBAL_OVERVIEW_CACHE_KEY);
|
|
1495
|
+
if (!raw)
|
|
1496
|
+
return null;
|
|
1497
|
+
var parsed = JSON.parse(raw);
|
|
1498
|
+
// 毒丸缓存自愈清洗:如果缓存中的快照显示「未登录」或未就绪,立即将其从 localStorage 彻底抹除!
|
|
1499
|
+
if (!parsed || !parsed.data || !parsed.data.auth || !parsed.data.auth.loggedIn) {
|
|
1500
|
+
localStorage.removeItem(GLOBAL_OVERVIEW_CACHE_KEY);
|
|
1501
|
+
return null;
|
|
1502
|
+
}
|
|
1503
|
+
if (typeof parsed.savedAt === "number") {
|
|
1504
|
+
return parsed;
|
|
1505
|
+
}
|
|
1506
|
+
return null;
|
|
1507
|
+
}
|
|
1508
|
+
catch (e) {
|
|
1509
|
+
return null;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
function saveStoredGlobalOverview(data) {
|
|
1513
|
+
// 毒丸拦截守卫:绝不持久化存储未登录状态、空数据或异常快照!
|
|
1514
|
+
if (!data || !data.auth || !data.auth.loggedIn) {
|
|
1515
|
+
return;
|
|
1516
|
+
}
|
|
1517
|
+
try {
|
|
1518
|
+
localStorage.setItem(GLOBAL_OVERVIEW_CACHE_KEY, JSON.stringify({
|
|
1519
|
+
data: data,
|
|
1520
|
+
savedAt: Date.now(),
|
|
1521
|
+
}));
|
|
1522
|
+
}
|
|
1523
|
+
catch (e) { }
|
|
1524
|
+
}
|
|
1525
|
+
function clearStoredGlobalOverview() {
|
|
1526
|
+
try {
|
|
1527
|
+
localStorage.removeItem(GLOBAL_OVERVIEW_CACHE_KEY);
|
|
1528
|
+
// 彻底清空所有右侧栏工作区历史快照,确保全局一致
|
|
1529
|
+
if (typeof localStorage !== "undefined") {
|
|
1530
|
+
for (var i = localStorage.length - 1; i >= 0; i--) {
|
|
1531
|
+
var k = localStorage.key(i);
|
|
1532
|
+
if (k && k.indexOf("dsh_github_rightbar_snapshot_") === 0) {
|
|
1533
|
+
localStorage.removeItem(k);
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1537
|
+
if (typeof window !== "undefined") {
|
|
1538
|
+
window.dispatchEvent(new CustomEvent("dsh-github-cache-cleared"));
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
catch (e) { }
|
|
1542
|
+
}
|
|
1543
|
+
function getCacheStorageMeta() {
|
|
1544
|
+
try {
|
|
1545
|
+
var raw = localStorage.getItem(GLOBAL_OVERVIEW_CACHE_KEY);
|
|
1546
|
+
if (!raw)
|
|
1547
|
+
return { exists: false, sizeKb: 0, savedAt: 0 };
|
|
1548
|
+
var parsed = JSON.parse(raw);
|
|
1549
|
+
var sizeKb = Math.round((raw.length * 2) / 1024 * 10) / 10;
|
|
1550
|
+
return { exists: true, sizeKb: sizeKb, savedAt: parsed?.savedAt || 0 };
|
|
1551
|
+
}
|
|
1552
|
+
catch (e) {
|
|
1553
|
+
return { exists: false, sizeKb: 0, savedAt: 0 };
|
|
1554
|
+
}
|
|
1555
|
+
}
|
|
1556
|
+
// 右侧栏专属本地持久化快照工具
|
|
1557
|
+
function getRightbarStorageKey(cwd) {
|
|
1558
|
+
var safe = (cwd || "default").replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1559
|
+
return "dsh_github_rightbar_snapshot_" + safe;
|
|
1560
|
+
}
|
|
1561
|
+
function getStoredRightbarSnapshot(cwd) {
|
|
1562
|
+
try {
|
|
1563
|
+
var key = getRightbarStorageKey(cwd);
|
|
1564
|
+
var raw = localStorage.getItem(key);
|
|
1565
|
+
if (!raw)
|
|
1566
|
+
return null;
|
|
1567
|
+
var parsed = JSON.parse(raw);
|
|
1568
|
+
// 毒丸拦截:若快照未登录,物理删除
|
|
1569
|
+
if (!parsed || !parsed.data || !parsed.data.auth || !parsed.data.auth.loggedIn) {
|
|
1570
|
+
localStorage.removeItem(key);
|
|
1571
|
+
return null;
|
|
1572
|
+
}
|
|
1573
|
+
return { data: parsed.data, savedAt: typeof parsed.savedAt === "number" ? parsed.savedAt : 0 };
|
|
1574
|
+
}
|
|
1575
|
+
catch (e) {
|
|
1576
|
+
return null;
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
function saveStoredRightbarData(cwd, data) {
|
|
1580
|
+
if (!data || !data.auth || !data.auth.loggedIn)
|
|
1581
|
+
return;
|
|
1582
|
+
try {
|
|
1583
|
+
var key = getRightbarStorageKey(cwd);
|
|
1584
|
+
localStorage.setItem(key, JSON.stringify({
|
|
1585
|
+
data: data,
|
|
1586
|
+
savedAt: Date.now(),
|
|
1587
|
+
}));
|
|
1588
|
+
}
|
|
1589
|
+
catch (e) { }
|
|
1079
1590
|
}
|
|
1080
1591
|
// 1. 右侧栏专属视图 (Right Sidebar Tab Body)
|
|
1081
1592
|
function GithubRightbarBody(props) {
|
|
1082
|
-
var
|
|
1083
|
-
var [data, setData] = React.useState(null);
|
|
1084
|
-
var [error, setError] = React.useState(null);
|
|
1085
|
-
// 核心:直接从会话 Props 拿到真实物理工作区路径!
|
|
1593
|
+
var sessionId = props ? props.sessionId : null;
|
|
1086
1594
|
var cwd = null;
|
|
1087
|
-
if (props && props.useSessions &&
|
|
1595
|
+
if (props && typeof props.useSessions === "function" && sessionId) {
|
|
1088
1596
|
try {
|
|
1089
|
-
cwd = props.useSessions((sessions) => sessions?.byId?.[
|
|
1597
|
+
cwd = props.useSessions((sessions) => sessions?.byId?.[sessionId]?.cwd) || null;
|
|
1090
1598
|
}
|
|
1091
1599
|
catch (e) { }
|
|
1092
1600
|
}
|
|
1601
|
+
// 核心原则:右侧栏与全景主页使用同一套在“主页-设置”中配置的缓存与刷新规则事实源
|
|
1602
|
+
var initialSettings = React.useMemo(() => getLocalSyncSettings(), []);
|
|
1603
|
+
var [syncSettings, setSyncSettings] = React.useState(initialSettings);
|
|
1604
|
+
// SWR 秒开机制:初始数据直接从本地快照读取,0ms 瞬间展示!
|
|
1605
|
+
var initialSnapshot = React.useMemo(() => getStoredRightbarSnapshot(cwd), [cwd]);
|
|
1606
|
+
var [data, setData] = React.useState(initialSnapshot?.data || null);
|
|
1607
|
+
var [syncStatus, setSyncStatus] = React.useState(initialSnapshot ? "cached" : "loading");
|
|
1608
|
+
var [lastSyncedAt, setLastSyncedAt] = React.useState(initialSnapshot?.savedAt || 0);
|
|
1609
|
+
var [loading, setLoading] = React.useState(false);
|
|
1610
|
+
var [error, setError] = React.useState(null);
|
|
1611
|
+
var [updateInfo, setUpdateInfo] = React.useState(null);
|
|
1612
|
+
// 跨组件事件监听:当主页修改了同步设置或清空缓存时,右侧栏即时无感对齐
|
|
1613
|
+
React.useEffect(function () {
|
|
1614
|
+
var onSettingsChange = function (e) {
|
|
1615
|
+
if (e && e.detail) {
|
|
1616
|
+
setSyncSettings(e.detail);
|
|
1617
|
+
}
|
|
1618
|
+
};
|
|
1619
|
+
var onCacheCleared = function () {
|
|
1620
|
+
setData(null);
|
|
1621
|
+
setLastSyncedAt(0);
|
|
1622
|
+
setSyncStatus("loading");
|
|
1623
|
+
};
|
|
1624
|
+
window.addEventListener("dsh-github-sync-settings-change", onSettingsChange);
|
|
1625
|
+
window.addEventListener("dsh-github-cache-cleared", onCacheCleared);
|
|
1626
|
+
return function () {
|
|
1627
|
+
window.removeEventListener("dsh-github-sync-settings-change", onSettingsChange);
|
|
1628
|
+
window.removeEventListener("dsh-github-cache-cleared", onCacheCleared);
|
|
1629
|
+
};
|
|
1630
|
+
}, []);
|
|
1631
|
+
// gh 数据拉取完毕后,闲时 1.5 秒异步检测升级,绝对不抢占主业务首屏资源
|
|
1632
|
+
React.useEffect(() => {
|
|
1633
|
+
if (!data)
|
|
1634
|
+
return;
|
|
1635
|
+
var timer = setTimeout(function () {
|
|
1636
|
+
fetch("/api/github/check-update")
|
|
1637
|
+
.then(function (res) { return res.json(); })
|
|
1638
|
+
.then(function (json) {
|
|
1639
|
+
if (json && json.ok && json.data) {
|
|
1640
|
+
setUpdateInfo(json.data);
|
|
1641
|
+
}
|
|
1642
|
+
})
|
|
1643
|
+
.catch(function () { });
|
|
1644
|
+
}, 1500);
|
|
1645
|
+
return function () { clearTimeout(timer); };
|
|
1646
|
+
}, [data]);
|
|
1647
|
+
// 遵循全局规则的数据拉取核心函数 (仅依赖标量 cwd 与 sessionId,坚决去除 props 引用依赖,彻底终结死循环)
|
|
1093
1648
|
var loadData = React.useCallback(async (forceRefresh = false) => {
|
|
1094
|
-
|
|
1649
|
+
// 规则执行:若主页设置为「纯手动模式 (manual)」,且非用户点击强制刷新,且本地已有数据,坚决不打后台网络!
|
|
1650
|
+
if (!forceRefresh && syncSettings.strategy === "manual" && data) {
|
|
1651
|
+
return;
|
|
1652
|
+
}
|
|
1653
|
+
if (forceRefresh || !data) {
|
|
1654
|
+
setLoading(true);
|
|
1655
|
+
}
|
|
1656
|
+
setSyncStatus(data ? "syncing" : "loading");
|
|
1095
1657
|
setError(null);
|
|
1096
1658
|
try {
|
|
1097
1659
|
if (forceRefresh) {
|
|
@@ -1099,8 +1661,8 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
|
|
|
1099
1661
|
}
|
|
1100
1662
|
var url = "/api/github/overview";
|
|
1101
1663
|
var params = [];
|
|
1102
|
-
if (
|
|
1103
|
-
params.push("sessionId=" + encodeURIComponent(
|
|
1664
|
+
if (sessionId) {
|
|
1665
|
+
params.push("sessionId=" + encodeURIComponent(sessionId));
|
|
1104
1666
|
}
|
|
1105
1667
|
if (cwd) {
|
|
1106
1668
|
params.push("cwd=" + encodeURIComponent(cwd));
|
|
@@ -1111,39 +1673,82 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
|
|
|
1111
1673
|
var res = await fetch(url);
|
|
1112
1674
|
if (!res.ok) {
|
|
1113
1675
|
var errText = await res.text().catch(() => "");
|
|
1114
|
-
|
|
1676
|
+
var msg = `后端服务未就绪 (${res.status}): ${errText || res.statusText}`;
|
|
1677
|
+
if (data) {
|
|
1678
|
+
setSyncStatus("failed");
|
|
1679
|
+
}
|
|
1680
|
+
else {
|
|
1681
|
+
setError(msg);
|
|
1682
|
+
}
|
|
1115
1683
|
return;
|
|
1116
1684
|
}
|
|
1117
1685
|
var json = await res.json();
|
|
1118
1686
|
if (json.ok && json.data) {
|
|
1119
1687
|
setData(json.data);
|
|
1688
|
+
saveStoredRightbarData(cwd, json.data); // 写入持久化快照
|
|
1689
|
+
setLastSyncedAt(Date.now());
|
|
1690
|
+
setSyncStatus("fresh");
|
|
1691
|
+
if (json.data.updateInfo) {
|
|
1692
|
+
setUpdateInfo(json.data.updateInfo);
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
else if (data) {
|
|
1696
|
+
setSyncStatus("failed");
|
|
1120
1697
|
}
|
|
1121
1698
|
else {
|
|
1122
1699
|
setError(json.error || "获取状态失败");
|
|
1123
1700
|
}
|
|
1124
1701
|
}
|
|
1125
1702
|
catch (err) {
|
|
1126
|
-
|
|
1703
|
+
if (data) {
|
|
1704
|
+
setSyncStatus("failed");
|
|
1705
|
+
}
|
|
1706
|
+
else {
|
|
1707
|
+
setError(err.message || "网络异常");
|
|
1708
|
+
}
|
|
1127
1709
|
}
|
|
1128
1710
|
finally {
|
|
1129
1711
|
setLoading(false);
|
|
1130
1712
|
}
|
|
1131
|
-
}, [cwd,
|
|
1713
|
+
}, [cwd, sessionId, data, syncSettings.strategy]);
|
|
1714
|
+
// 挂载与策略调度:与主页规则 100% 保持一致
|
|
1715
|
+
React.useEffect(() => {
|
|
1716
|
+
if (syncSettings.strategy === "swr") {
|
|
1717
|
+
var timer = setTimeout(function () { loadData(false); }, 300);
|
|
1718
|
+
return function () { clearTimeout(timer); };
|
|
1719
|
+
}
|
|
1720
|
+
else if (!data) {
|
|
1721
|
+
loadData(false);
|
|
1722
|
+
}
|
|
1723
|
+
}, [cwd, sessionId, syncSettings.strategy]);
|
|
1724
|
+
// 定时轮询调度:与主页完全一致
|
|
1132
1725
|
React.useEffect(() => {
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1726
|
+
if (syncSettings.strategy !== "interval" || !syncSettings.intervalMs || syncSettings.intervalMs <= 0)
|
|
1727
|
+
return;
|
|
1728
|
+
var timer = setInterval(function () {
|
|
1729
|
+
loadData(false);
|
|
1730
|
+
}, syncSettings.intervalMs);
|
|
1731
|
+
return function () { clearInterval(timer); };
|
|
1732
|
+
}, [syncSettings.strategy, syncSettings.intervalMs, loadData]);
|
|
1733
|
+
// 窗口切回自动探测自愈:优先轻量快速探针,检测到已登录立即自愈
|
|
1136
1734
|
React.useEffect(() => {
|
|
1137
1735
|
var onFocus = () => {
|
|
1138
1736
|
if (!data?.auth?.loggedIn) {
|
|
1139
|
-
|
|
1737
|
+
fetch("/api/github/auth?force=true")
|
|
1738
|
+
.then(function (res) { return res.json(); })
|
|
1739
|
+
.then(function (json) {
|
|
1740
|
+
if (json && json.ok && json.auth && json.auth.loggedIn) {
|
|
1741
|
+
loadData(true);
|
|
1742
|
+
}
|
|
1743
|
+
})
|
|
1744
|
+
.catch(function () { });
|
|
1140
1745
|
}
|
|
1141
1746
|
};
|
|
1142
1747
|
window.addEventListener("focus", onFocus);
|
|
1143
1748
|
return () => window.removeEventListener("focus", onFocus);
|
|
1144
1749
|
}, [data, loadData]);
|
|
1145
1750
|
var h = React.createElement;
|
|
1146
|
-
return h("div", { className: "dsh-github-rightbar-container" }, ...renderGithubRepoView(h, data, loading, error, () => loadData(true), true));
|
|
1751
|
+
return h("div", { className: "dsh-github-rightbar-container" }, ...renderGithubRepoView(h, data, loading, error, () => loadData(true), true, updateInfo, syncStatus, lastSyncedAt));
|
|
1147
1752
|
}
|
|
1148
1753
|
// 2. 右侧栏 Tab 标题组件
|
|
1149
1754
|
function GithubRightbarTitle() {
|
|
@@ -1164,76 +1769,282 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
|
|
|
1164
1769
|
className: "dsh-github-icon-inner" + (active ? " active" : "")
|
|
1165
1770
|
}, renderOctocat(18, "currentColor")));
|
|
1166
1771
|
}
|
|
1772
|
+
// 全景驾驶舱「⚙️ 数据同步与缓存设置」模态弹窗组件
|
|
1773
|
+
function GlobalSyncSettingsModal(props) {
|
|
1774
|
+
if (!props.isOpen)
|
|
1775
|
+
return null;
|
|
1776
|
+
var [strategy, setStrategy] = React.useState(props.currentSettings.strategy || "swr");
|
|
1777
|
+
var [intervalMs, setIntervalMs] = React.useState(props.currentSettings.intervalMs || 300000);
|
|
1778
|
+
var [cacheMeta, setCacheMeta] = React.useState(getCacheStorageMeta());
|
|
1779
|
+
var [clearTip, setClearTip] = React.useState("");
|
|
1780
|
+
// 键盘 Escape 快捷关闭支持
|
|
1781
|
+
React.useEffect(function () {
|
|
1782
|
+
var onKeyDown = function (e) {
|
|
1783
|
+
if (e.key === "Escape" || e.keyCode === 27) {
|
|
1784
|
+
props.onClose();
|
|
1785
|
+
}
|
|
1786
|
+
};
|
|
1787
|
+
window.addEventListener("keydown", onKeyDown);
|
|
1788
|
+
return function () { window.removeEventListener("keydown", onKeyDown); };
|
|
1789
|
+
}, [props.onClose]);
|
|
1790
|
+
var h = React.createElement;
|
|
1791
|
+
var handleSave = function () {
|
|
1792
|
+
var updated = {
|
|
1793
|
+
strategy: strategy,
|
|
1794
|
+
intervalMs: Number(intervalMs),
|
|
1795
|
+
};
|
|
1796
|
+
saveLocalSyncSettings(updated);
|
|
1797
|
+
props.onSave(updated);
|
|
1798
|
+
props.onClose();
|
|
1799
|
+
};
|
|
1800
|
+
var handleClear = function () {
|
|
1801
|
+
clearStoredGlobalOverview();
|
|
1802
|
+
props.onClearCache();
|
|
1803
|
+
setCacheMeta({ exists: false, sizeKb: 0, savedAt: 0 });
|
|
1804
|
+
setClearTip("本地缓存已彻底清空!");
|
|
1805
|
+
setTimeout(function () { setClearTip(""); }, 2000);
|
|
1806
|
+
};
|
|
1807
|
+
return h("div", { className: "dsh-github-modal-overlay", onClick: props.onClose }, h("div", {
|
|
1808
|
+
className: "dsh-github-modal-dialog",
|
|
1809
|
+
onClick: function (e) { e.stopPropagation(); },
|
|
1810
|
+
}, h("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "16px" } }, h("h3", { style: { margin: 0, fontSize: "16px", fontWeight: 600 } }, "⚙️ 数据同步与缓存偏好"), h("button", {
|
|
1811
|
+
style: { background: "none", border: "none", cursor: "pointer", fontSize: "18px", color: "var(--dsw-alias-label-secondary, #656d76)" },
|
|
1812
|
+
onClick: props.onClose,
|
|
1813
|
+
}, "✕")), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "14px", lineHeight: "1.5" } }, "配置 GitHub 全景驾驶舱的数据拉取节奏。启用秒开模式可利用本地持久化缓存消除每次打开时的漫长等待。"),
|
|
1814
|
+
// 刷新策略选择
|
|
1815
|
+
h("div", { style: { marginBottom: "16px" } }, h("div", { style: { fontSize: "13px", fontWeight: 600, marginBottom: "8px" } }, "数据刷新策略"),
|
|
1816
|
+
// 策略 A: SWR 秒开
|
|
1817
|
+
h("div", {
|
|
1818
|
+
className: "dsh-github-radio-card" + (strategy === "swr" ? " active" : ""),
|
|
1819
|
+
onClick: function () { setStrategy("swr"); },
|
|
1820
|
+
}, h("input", { type: "radio", className: "dsh-github-radio", checked: strategy === "swr", readOnly: true }), h("div", null, h("div", { style: { fontWeight: 600, fontSize: "13px" } }, "⚡ 秒开展示旧数据 (SWR, 强烈推荐)"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "打开驾驶舱立即展示上次本地缓存(0ms秒开),后台静默同步最新状态。"))),
|
|
1821
|
+
// 策略 B: 纯手动刷新
|
|
1822
|
+
h("div", {
|
|
1823
|
+
className: "dsh-github-radio-card" + (strategy === "manual" ? " active" : ""),
|
|
1824
|
+
onClick: function () { setStrategy("manual"); },
|
|
1825
|
+
}, h("input", { type: "radio", className: "dsh-github-radio", checked: strategy === "manual", readOnly: true }), h("div", null, h("div", { style: { fontWeight: 600, fontSize: "13px" } }, "📋 仅在手动点击时拉取 (Manual Only)"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "打开只浏览本地缓存,绝不自动触发后台 gh 进程,最省电、最省资源。"))),
|
|
1826
|
+
// 策略 C: 定时轮询
|
|
1827
|
+
h("div", {
|
|
1828
|
+
className: "dsh-github-radio-card" + (strategy === "interval" ? " active" : ""),
|
|
1829
|
+
onClick: function () { setStrategy("interval"); },
|
|
1830
|
+
}, h("input", { type: "radio", className: "dsh-github-radio", checked: strategy === "interval", readOnly: true }), h("div", null, h("div", { style: { fontWeight: 600, fontSize: "13px" } }, "⏱️ 定时自动轮询 (Auto Poll)"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "驾驶舱打开期间,按照下方设定的时间间隔在后台自动轮询。")))),
|
|
1831
|
+
// 自动拉取间隔下拉
|
|
1832
|
+
h("div", { style: { marginBottom: "18px" } }, h("div", { style: { fontSize: "13px", fontWeight: 600, marginBottom: "6px" } }, "后台自动拉取间隔"), h("select", {
|
|
1833
|
+
className: "dsh-github-select",
|
|
1834
|
+
value: intervalMs,
|
|
1835
|
+
onChange: function (e) { setIntervalMs(Number(e.target.value)); },
|
|
1836
|
+
}, h("option", { value: 60000 }, "1 分钟 (高频刷新)"), h("option", { value: 300000 }, "5 分钟 (默认推荐)"), h("option", { value: 900000 }, "15 分钟"), h("option", { value: 1800000 }, "30 分钟 (推荐节能)"), h("option", { value: 3600000 }, "1 小时"), h("option", { value: 0 }, "从不自动刷新 (仅手动)"))),
|
|
1837
|
+
// 本地缓存信息与清理
|
|
1838
|
+
h("div", {
|
|
1839
|
+
className: "dsh-github-cache-info-box",
|
|
1840
|
+
}, h("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center" } }, h("span", null, cacheMeta.exists
|
|
1841
|
+
? `📦 本地缓存: 约 ${cacheMeta.sizeKb} KB (上次保存: ${new Date(cacheMeta.savedAt).toLocaleTimeString()})`
|
|
1842
|
+
: "📦 暂无本地持久化缓存"), cacheMeta.exists ? h("button", {
|
|
1843
|
+
className: "dsh-github-btn-sm",
|
|
1844
|
+
style: { padding: "3px 8px", fontSize: "11px", color: "var(--dsw-alias-state-error-primary, #cf222e)" },
|
|
1845
|
+
onClick: handleClear,
|
|
1846
|
+
}, "🗑️ 清空缓存") : null), clearTip ? h("div", { style: { color: "var(--dsw-alias-state-success, #2ea043)", marginTop: "4px", fontWeight: 500 } }, clearTip) : null),
|
|
1847
|
+
// 操作按钮
|
|
1848
|
+
h("div", { style: { display: "flex", justifyContent: "flex-end", gap: "8px" } }, h("button", {
|
|
1849
|
+
className: "dsh-github-btn-sm",
|
|
1850
|
+
style: { padding: "6px 14px" },
|
|
1851
|
+
onClick: props.onClose,
|
|
1852
|
+
}, "取消"), h("button", {
|
|
1853
|
+
className: "dsh-github-btn-sm",
|
|
1854
|
+
style: {
|
|
1855
|
+
padding: "6px 16px",
|
|
1856
|
+
background: "var(--dsw-alias-link, #0969da)",
|
|
1857
|
+
color: "#ffffff",
|
|
1858
|
+
borderColor: "var(--dsw-alias-link, #0969da)",
|
|
1859
|
+
fontWeight: 600,
|
|
1860
|
+
},
|
|
1861
|
+
onClick: handleSave,
|
|
1862
|
+
}, "保存并应用"))));
|
|
1863
|
+
}
|
|
1167
1864
|
function GithubWorkspacePanel() {
|
|
1168
|
-
|
|
1169
|
-
var
|
|
1865
|
+
// 1. 读取本地偏好配置与历史持久化快照 (0ms 纯内存读取)
|
|
1866
|
+
var initialSettings = React.useMemo(() => getLocalSyncSettings(), []);
|
|
1867
|
+
var initialSnapshot = React.useMemo(() => getStoredGlobalOverview(), []);
|
|
1868
|
+
var [syncSettings, setSyncSettings] = React.useState(initialSettings);
|
|
1869
|
+
var [data, setData] = React.useState(initialSnapshot?.data || null);
|
|
1870
|
+
var [loading, setLoading] = React.useState(!initialSnapshot);
|
|
1871
|
+
var [syncStatus, setSyncStatus] = React.useState(initialSnapshot ? "cached" : "loading");
|
|
1872
|
+
var [lastSyncedAt, setLastSyncedAt] = React.useState(initialSnapshot?.savedAt || 0);
|
|
1873
|
+
var [isSettingsModalOpen, setIsSettingsModalOpen] = React.useState(false);
|
|
1170
1874
|
var [error, setError] = React.useState(null);
|
|
1875
|
+
var [updateInfo, setUpdateInfo] = React.useState(null);
|
|
1171
1876
|
var [activeTab, setActiveTab] = React.useState("workspaces"); // 'workspaces' | 'repos' | 'mywork'
|
|
1172
1877
|
var [searchQuery, setSearchQuery] = React.useState("");
|
|
1173
1878
|
var [issueFilter, setIssueFilter] = React.useState("all"); // 'all' | 'reported' | 'assigned' | 'created'
|
|
1879
|
+
// gh 业务数据就绪后,闲时 1.5 秒异步静默检测升级,绝不影响首屏
|
|
1880
|
+
React.useEffect(() => {
|
|
1881
|
+
if (!data)
|
|
1882
|
+
return;
|
|
1883
|
+
var timer = setTimeout(function () {
|
|
1884
|
+
fetch("/api/github/check-update")
|
|
1885
|
+
.then(function (res) { return res.json(); })
|
|
1886
|
+
.then(function (json) {
|
|
1887
|
+
if (json && json.ok && json.data) {
|
|
1888
|
+
setUpdateInfo(json.data);
|
|
1889
|
+
}
|
|
1890
|
+
})
|
|
1891
|
+
.catch(function () { });
|
|
1892
|
+
}, 1500);
|
|
1893
|
+
return function () { clearTimeout(timer); };
|
|
1894
|
+
}, [data]);
|
|
1174
1895
|
var loadData = React.useCallback(async (forceRefresh = false) => {
|
|
1896
|
+
// 如果是纯手动模式,且非显式强制刷新,且本地已有旧数据,直接跳过后台拉取
|
|
1897
|
+
if (!forceRefresh && syncSettings.strategy === "manual" && data) {
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1175
1900
|
setLoading(true);
|
|
1901
|
+
setSyncStatus(data ? "syncing" : "loading");
|
|
1176
1902
|
setError(null);
|
|
1177
|
-
// 全局驾驶舱要 spawn 十几次 gh,冷启动可能超过 10 秒。
|
|
1178
|
-
// 不设显式超时的话浏览器会抛一个与真实原因无关的网络错误,
|
|
1179
|
-
// 早先这里会把所有失败都归因成「后端服务未就绪或接口不存在」——归因错误比没有报错更糟。
|
|
1180
1903
|
var controller = new AbortController();
|
|
1181
1904
|
var timedOut = false;
|
|
1182
|
-
var timer = setTimeout(function () { timedOut = true; controller.abort(); },
|
|
1905
|
+
var timer = setTimeout(function () { timedOut = true; controller.abort(); }, 45000);
|
|
1183
1906
|
try {
|
|
1184
1907
|
if (forceRefresh)
|
|
1185
1908
|
await fetch("/api/github/refresh", { method: "POST" }).catch(() => { });
|
|
1186
1909
|
var res = await fetch("/api/github/global-overview", { signal: controller.signal });
|
|
1187
1910
|
if (!res.ok) {
|
|
1188
1911
|
var errText = await res.text().catch(() => "");
|
|
1189
|
-
|
|
1912
|
+
var failMsg = `获取全局数据失败(HTTP ${res.status})${errText ? ": " + errText : ""}`;
|
|
1913
|
+
if (data) {
|
|
1914
|
+
// 容灾保障:有本地旧数据时绝不红屏崩溃,平滑降级展示离线快照
|
|
1915
|
+
setSyncStatus("failed");
|
|
1916
|
+
}
|
|
1917
|
+
else {
|
|
1918
|
+
setError(failMsg);
|
|
1919
|
+
}
|
|
1190
1920
|
return;
|
|
1191
1921
|
}
|
|
1192
1922
|
var json = await res.json();
|
|
1193
1923
|
if (json.ok && json.data) {
|
|
1194
1924
|
setData(json.data);
|
|
1195
|
-
//
|
|
1925
|
+
saveStoredGlobalOverview(json.data); // 写入持久化快照
|
|
1926
|
+
setLastSyncedAt(Date.now());
|
|
1927
|
+
setSyncStatus("fresh");
|
|
1928
|
+
if (json.data.updateInfo) {
|
|
1929
|
+
setUpdateInfo(json.data.updateInfo);
|
|
1930
|
+
}
|
|
1196
1931
|
if (json.data.workspaceError) {
|
|
1197
1932
|
setError("工作区列表读取失败:" + json.data.workspaceError);
|
|
1198
1933
|
}
|
|
1199
1934
|
}
|
|
1200
1935
|
else {
|
|
1201
|
-
|
|
1936
|
+
if (data) {
|
|
1937
|
+
setSyncStatus("failed");
|
|
1938
|
+
}
|
|
1939
|
+
else {
|
|
1940
|
+
setError(json.error || "获取全局数据失败");
|
|
1941
|
+
}
|
|
1202
1942
|
}
|
|
1203
1943
|
}
|
|
1204
1944
|
catch (err) {
|
|
1205
|
-
if (
|
|
1206
|
-
|
|
1945
|
+
if (data) {
|
|
1946
|
+
// 后台网络故障时,旧数据依然完整可用
|
|
1947
|
+
setSyncStatus("failed");
|
|
1207
1948
|
}
|
|
1208
1949
|
else {
|
|
1209
|
-
|
|
1950
|
+
if (timedOut || err.name === "AbortError") {
|
|
1951
|
+
setError("获取超时(30 秒):本机 gh 命令响应过慢,通常是网络或工作区数量较多所致。可稍后重试,或减少同时打开的工作区。");
|
|
1952
|
+
}
|
|
1953
|
+
else {
|
|
1954
|
+
setError("无法连接后端服务:" + (err.message || err));
|
|
1955
|
+
}
|
|
1210
1956
|
}
|
|
1211
1957
|
}
|
|
1212
1958
|
finally {
|
|
1213
1959
|
clearTimeout(timer);
|
|
1214
1960
|
setLoading(false);
|
|
1215
1961
|
}
|
|
1216
|
-
}, []);
|
|
1217
|
-
|
|
1218
|
-
|
|
1962
|
+
}, [data, syncSettings]);
|
|
1963
|
+
// 挂载加载调度:SWR 模式下初次延迟 300ms 后台平滑发起静默同步
|
|
1964
|
+
React.useEffect(() => {
|
|
1965
|
+
if (syncSettings.strategy === "swr") {
|
|
1966
|
+
var timer = setTimeout(function () { loadData(false); }, 300);
|
|
1967
|
+
return function () { clearTimeout(timer); };
|
|
1968
|
+
}
|
|
1969
|
+
else if (!data) {
|
|
1970
|
+
loadData(false);
|
|
1971
|
+
}
|
|
1972
|
+
}, [syncSettings.strategy]);
|
|
1973
|
+
// 定时轮询调度
|
|
1974
|
+
React.useEffect(() => {
|
|
1975
|
+
if (syncSettings.strategy !== "interval" || !syncSettings.intervalMs || syncSettings.intervalMs <= 0)
|
|
1976
|
+
return;
|
|
1977
|
+
var timer = setInterval(function () {
|
|
1978
|
+
loadData(false);
|
|
1979
|
+
}, syncSettings.intervalMs);
|
|
1980
|
+
return function () { clearInterval(timer); };
|
|
1981
|
+
}, [syncSettings.strategy, syncSettings.intervalMs, loadData]);
|
|
1982
|
+
// 窗口切回自动探测自愈:优先轻量快速探针,检测到已登录立即自愈
|
|
1219
1983
|
React.useEffect(() => {
|
|
1220
1984
|
var onFocus = () => {
|
|
1221
1985
|
if (!data?.auth?.loggedIn) {
|
|
1222
|
-
|
|
1986
|
+
fetch("/api/github/auth?force=true")
|
|
1987
|
+
.then(function (res) { return res.json(); })
|
|
1988
|
+
.then(function (json) {
|
|
1989
|
+
if (json && json.ok && json.auth && json.auth.loggedIn) {
|
|
1990
|
+
clearStoredGlobalOverview();
|
|
1991
|
+
loadData(true);
|
|
1992
|
+
}
|
|
1993
|
+
})
|
|
1994
|
+
.catch(function () { });
|
|
1223
1995
|
}
|
|
1224
1996
|
};
|
|
1225
1997
|
window.addEventListener("focus", onFocus);
|
|
1226
1998
|
return () => window.removeEventListener("focus", onFocus);
|
|
1227
1999
|
}, [data, loadData]);
|
|
1228
2000
|
var h = React.createElement;
|
|
1229
|
-
var headerEl = h("div", { className: "dsh-github-header" }, h("div", { className: "dsh-github-title" }, renderOctocatBadge(32), h("h2", null, "GitHub 全景驾驶舱")), h("button", {
|
|
2001
|
+
var headerEl = h("div", { className: "dsh-github-header" }, h("div", { className: "dsh-github-title" }, renderOctocatBadge(32), h("h2", null, "GitHub 全景驾驶舱"), h(SyncStatusBadge, { syncStatus, lastSyncedAt })), h("div", { style: { display: "flex", alignItems: "center", gap: "8px" } }, h("button", {
|
|
1230
2002
|
className: "dsh-github-refresh-btn",
|
|
1231
2003
|
onClick: () => loadData(true),
|
|
1232
2004
|
disabled: loading,
|
|
1233
|
-
}, loading ? "
|
|
2005
|
+
}, loading ? "同步中..." : "🔄 刷新全局数据"), h("button", {
|
|
2006
|
+
className: "dsh-github-btn-sm",
|
|
2007
|
+
onClick: () => setIsSettingsModalOpen(true),
|
|
2008
|
+
title: "配置数据拉取节奏与本地缓存偏好",
|
|
2009
|
+
style: { padding: "6px 12px" },
|
|
2010
|
+
}, "⚙️ 同步设置")));
|
|
1234
2011
|
if (loading && !data) {
|
|
1235
2012
|
return h("div", { className: "dsh-github-panel" }, headerEl, h("div", { className: "dsh-github-empty" }, "正在加载全局 GitHub 账号与工作区数据..."));
|
|
1236
2013
|
}
|
|
2014
|
+
var effectiveUpdateInfo = updateInfo || data?.updateInfo;
|
|
2015
|
+
var upgradeBannerEl = effectiveUpdateInfo && effectiveUpdateInfo.hasUpdate
|
|
2016
|
+
? h(UpgradeNotificationCard, { updateInfo: effectiveUpdateInfo, isRightbar: false })
|
|
2017
|
+
: null;
|
|
2018
|
+
var settingsModalEl = h(GlobalSyncSettingsModal, {
|
|
2019
|
+
isOpen: isSettingsModalOpen,
|
|
2020
|
+
onClose: () => setIsSettingsModalOpen(false),
|
|
2021
|
+
currentSettings: syncSettings,
|
|
2022
|
+
onSave: (newSettings) => {
|
|
2023
|
+
setSyncSettings(newSettings);
|
|
2024
|
+
},
|
|
2025
|
+
onClearCache: () => {
|
|
2026
|
+
setSyncStatus("loading");
|
|
2027
|
+
setData(null);
|
|
2028
|
+
setLastSyncedAt(0);
|
|
2029
|
+
},
|
|
2030
|
+
});
|
|
2031
|
+
// 错误拦截阻断:若无本地数据且请求失败,直接呈现错误卡片,杜绝把接口异常伪装成「未安装 CLI」
|
|
2032
|
+
if (!data && error) {
|
|
2033
|
+
return h("div", { className: "dsh-github-panel" }, headerEl, upgradeBannerEl, h("div", {
|
|
2034
|
+
style: {
|
|
2035
|
+
padding: "24px",
|
|
2036
|
+
borderRadius: "8px",
|
|
2037
|
+
background: "rgba(248, 81, 73, 0.08)",
|
|
2038
|
+
border: "1px solid rgba(248, 81, 73, 0.3)",
|
|
2039
|
+
margin: "16px 0",
|
|
2040
|
+
textAlign: "center",
|
|
2041
|
+
},
|
|
2042
|
+
}, h("div", { style: { fontSize: "16px", fontWeight: 600, color: "#f85149", marginBottom: "8px" } }, "⚠️ 后端服务加载异常"), h("div", { style: { fontSize: "13px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "16px" } }, error), h("button", {
|
|
2043
|
+
className: "dsh-github-btn-sm",
|
|
2044
|
+
onClick: () => loadData(true),
|
|
2045
|
+
style: { padding: "6px 18px", background: "var(--dsw-alias-link, #0969da)", color: "#ffffff", borderColor: "var(--dsw-alias-link, #0969da)" },
|
|
2046
|
+
}, "🔄 重新请求")), settingsModalEl);
|
|
2047
|
+
}
|
|
1237
2048
|
var auth = data ? data.auth : null;
|
|
1238
2049
|
var userRepos = (data && data.userRepos) || [];
|
|
1239
2050
|
var workspaceMatrix = (data && data.workspaceMatrix) || [];
|
|
@@ -1329,7 +2140,7 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
|
|
|
1329
2140
|
})));
|
|
1330
2141
|
}
|
|
1331
2142
|
}
|
|
1332
|
-
return h("div", { className: "dsh-github-panel" }, headerEl, errorEl, statsCardsEl, authGuideEl, tabsEl, contentEl);
|
|
2143
|
+
return h("div", { className: "dsh-github-panel" }, headerEl, upgradeBannerEl, errorEl, statsCardsEl, authGuideEl, tabsEl, contentEl, settingsModalEl);
|
|
1333
2144
|
}
|
|
1334
2145
|
var inject = ["slots", "sidebarRightTabs", "sessions", "layout", "theme"];
|
|
1335
2146
|
function apply(ctx) {
|