dsh-github-flow 0.2.0 → 0.2.1
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 +31 -12
- package/README.md +33 -14
- package/docs/UPGRADE-NOTES.md +93 -0
- package/docs/architecture.md +33 -18
- package/docs/images/1.png +0 -0
- package/docs/images/2.png +0 -0
- package/docs/images/3.png +0 -0
- package/docs/images/4.png +0 -0
- package/docs/npm-publish-guide.md +15 -4
- package/lib/api/routes-actions.d.ts +18 -4
- package/lib/api/routes-actions.js +143 -19
- package/lib/api/routes-lifecycle.d.ts +1 -1
- package/lib/api/routes-lifecycle.js +53 -2
- package/lib/client.js +411 -44
- package/lib/commands/gh.js +11 -2
- package/lib/executor.js +122 -12
- package/lib/main.js +9 -0
- package/lib/renderers/markdown.d.ts +6 -1
- package/lib/renderers/markdown.js +41 -14
- package/lib/tools/help.js +12 -0
- package/lib/tools/image-utils.d.ts +79 -0
- package/lib/tools/image-utils.js +247 -0
- package/lib/tools/issue.d.ts +9 -4
- package/lib/tools/issue.js +75 -4
- package/lib/tools/pr.d.ts +8 -4
- package/lib/tools/pr.js +80 -6
- package/lib/tools/repo.d.ts +12 -4
- package/lib/tools/repo.js +71 -3
- package/lib/types.d.ts +12 -1
- package/lib/update-settings.d.ts +18 -0
- package/lib/update-settings.js +60 -0
- package/lib/updater.d.ts +32 -1
- package/lib/updater.js +117 -9
- package/lib/upgrade-manager.d.ts +70 -1
- package/lib/upgrade-manager.js +346 -46
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -290,6 +290,40 @@ body[data-ds-dark-theme] .dsh-badge-purple {
|
|
|
290
290
|
border-color: rgba(163, 113, 247, 0.3);
|
|
291
291
|
}
|
|
292
292
|
|
|
293
|
+
/* 插件身份徽标:标识界面由第三方插件 dsh-github-flow 提供(非 DSH/GitHub 官方)。
|
|
294
|
+
* 为什么需要:界面以 GitHub 官方 Octocat 做功能图标,新用户易误认为官方功能;
|
|
295
|
+
* 徽标点击跳插件仓库,与设置「关于」页形成身份闭环。 */
|
|
296
|
+
.dsh-github-plugin-badge {
|
|
297
|
+
display: inline-flex;
|
|
298
|
+
align-items: center;
|
|
299
|
+
font-size: 10px;
|
|
300
|
+
font-weight: 500;
|
|
301
|
+
padding: 1px 7px;
|
|
302
|
+
border-radius: 10px;
|
|
303
|
+
line-height: 14px;
|
|
304
|
+
vertical-align: middle;
|
|
305
|
+
margin-left: 8px;
|
|
306
|
+
text-decoration: none !important;
|
|
307
|
+
background: rgba(125, 133, 144, 0.12);
|
|
308
|
+
color: #656d76;
|
|
309
|
+
border: 1px solid rgba(125, 133, 144, 0.25);
|
|
310
|
+
cursor: pointer;
|
|
311
|
+
transition: all 0.15s ease;
|
|
312
|
+
}
|
|
313
|
+
.dsh-github-plugin-badge:hover {
|
|
314
|
+
background: rgba(125, 133, 144, 0.2);
|
|
315
|
+
color: var(--dsw-alias-label-primary, #1f2328);
|
|
316
|
+
}
|
|
317
|
+
body[data-ds-dark-theme] .dsh-github-plugin-badge {
|
|
318
|
+
background: rgba(139, 148, 158, 0.18);
|
|
319
|
+
color: #8b949e;
|
|
320
|
+
border-color: rgba(139, 148, 158, 0.3);
|
|
321
|
+
}
|
|
322
|
+
body[data-ds-dark-theme] .dsh-github-plugin-badge:hover {
|
|
323
|
+
background: rgba(139, 148, 158, 0.28);
|
|
324
|
+
color: #f0f6fc;
|
|
325
|
+
}
|
|
326
|
+
|
|
293
327
|
/* 7.5. 分类筛选药丸栏 */
|
|
294
328
|
.dsh-github-pills {
|
|
295
329
|
display: flex;
|
|
@@ -1058,6 +1092,45 @@ body[data-ds-dark-theme] .dsh-github-cache-info-box {
|
|
|
1058
1092
|
color: var(--dsw-alias-label-secondary, #8c8c8c);
|
|
1059
1093
|
}
|
|
1060
1094
|
|
|
1095
|
+
/* 关于页求 Star 卡片:与 cache-info-box 同款双主题底色(浅色纸面/暗色深底) */
|
|
1096
|
+
.dsh-github-about-star-box {
|
|
1097
|
+
padding: 12px 14px;
|
|
1098
|
+
border-radius: 8px;
|
|
1099
|
+
background: var(--dsw-alias-bg-layer-1, #f6f8fa);
|
|
1100
|
+
border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
|
|
1101
|
+
}
|
|
1102
|
+
body[data-ds-dark-theme] .dsh-github-about-star-box {
|
|
1103
|
+
background: var(--dsw-alias-bg-base, #161b22);
|
|
1104
|
+
border-color: var(--dsw-alias-border-l1, #30363d);
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
/* 主操作按钮(链接色底):链接色底 + 白字,暗色用亮蓝(对齐 radio/链接的暗色强调色 58a6ff) */
|
|
1108
|
+
.dsh-github-primary-btn {
|
|
1109
|
+
padding: 5px 14px;
|
|
1110
|
+
font-weight: 600;
|
|
1111
|
+
white-space: nowrap;
|
|
1112
|
+
background: var(--dsw-alias-link, #0969da);
|
|
1113
|
+
border-color: var(--dsw-alias-link, #0969da);
|
|
1114
|
+
color: #ffffff;
|
|
1115
|
+
}
|
|
1116
|
+
.dsh-github-primary-btn:hover {
|
|
1117
|
+
background: var(--dsw-alias-link, #0969da);
|
|
1118
|
+
border-color: var(--dsw-alias-link, #0969da);
|
|
1119
|
+
color: #ffffff;
|
|
1120
|
+
opacity: 0.88;
|
|
1121
|
+
}
|
|
1122
|
+
body[data-ds-dark-theme] .dsh-github-primary-btn {
|
|
1123
|
+
background: #58a6ff;
|
|
1124
|
+
border-color: #58a6ff;
|
|
1125
|
+
color: #0d1117;
|
|
1126
|
+
}
|
|
1127
|
+
body[data-ds-dark-theme] .dsh-github-primary-btn:hover {
|
|
1128
|
+
background: #58a6ff;
|
|
1129
|
+
border-color: #58a6ff;
|
|
1130
|
+
color: #0d1117;
|
|
1131
|
+
opacity: 0.88;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1061
1134
|
/* 单选框点强调色 */
|
|
1062
1135
|
.dsh-github-radio {
|
|
1063
1136
|
accent-color: var(--dsw-alias-link, #0969da);
|
|
@@ -1106,6 +1179,24 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1106
1179
|
}
|
|
1107
1180
|
}, renderOctocat(Math.round(size * 0.58), "#ffffff"));
|
|
1108
1181
|
}
|
|
1182
|
+
// 插件身份徽标(h 工厂注入):标注界面由第三方插件 dsh-github-flow 提供。
|
|
1183
|
+
// 点击不直接跳仓库,而是派发全局事件打开「设置-关于」弹窗(仓库/协议/Star
|
|
1184
|
+
// 都在里面),由各宿主面板监听并挂载弹窗——避免从任意界面突兀跳浏览器。
|
|
1185
|
+
function renderPluginBadge(h) {
|
|
1186
|
+
return h("button", {
|
|
1187
|
+
className: "dsh-github-plugin-badge",
|
|
1188
|
+
type: "button",
|
|
1189
|
+
title: "本界面由第三方插件 dsh-github-flow 提供(非 DSH / GitHub 官方)。点击查看关于。",
|
|
1190
|
+
onClick: function (e) {
|
|
1191
|
+
e.stopPropagation();
|
|
1192
|
+
e.preventDefault();
|
|
1193
|
+
try {
|
|
1194
|
+
window.dispatchEvent(new CustomEvent("dsh-github-open-about"));
|
|
1195
|
+
}
|
|
1196
|
+
catch (err) { /* 事件派发失败不阻断点击语义 */ }
|
|
1197
|
+
},
|
|
1198
|
+
}, "插件");
|
|
1199
|
+
}
|
|
1109
1200
|
// 认证与安装引导卡片组件(响应式自适应中央大面板与右侧栏)
|
|
1110
1201
|
function AuthGuideCard(props) {
|
|
1111
1202
|
var auth = props ? props.auth : null;
|
|
@@ -1150,15 +1241,17 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1150
1241
|
});
|
|
1151
1242
|
var json = await res.json();
|
|
1152
1243
|
if (json.ok) {
|
|
1153
|
-
|
|
1154
|
-
|
|
1244
|
+
// 措辞留余地:start 的成败偶受系统会话策略影响,未弹出时用户仍有手动出路
|
|
1245
|
+
setToastMsg("已唤起系统终端,请在弹出窗口中完成操作;若未弹出,可手动打开终端执行上方命令");
|
|
1246
|
+
setTimeout(() => setToastMsg(null), 6000);
|
|
1155
1247
|
}
|
|
1156
1248
|
else {
|
|
1157
|
-
|
|
1249
|
+
// 失败如实展示:后端带回真实原因(如未找到终端/命令失败),并给出手动命令
|
|
1250
|
+
setToastMsg("❌ " + (json.message || "唤起失败,请手动打开终端执行上方命令"));
|
|
1158
1251
|
}
|
|
1159
1252
|
}
|
|
1160
1253
|
catch (e) {
|
|
1161
|
-
setToastMsg("
|
|
1254
|
+
setToastMsg("无法连接后台服务,请手动打开终端执行上方命令");
|
|
1162
1255
|
}
|
|
1163
1256
|
finally {
|
|
1164
1257
|
setLaunching(false);
|
|
@@ -1214,9 +1307,14 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1214
1307
|
className: "dsh-github-action-btn",
|
|
1215
1308
|
onClick: function () {
|
|
1216
1309
|
setToastMsg("正在快速检测登录状态...");
|
|
1217
|
-
|
|
1310
|
+
// 探测超时保护:后端 auth 探针本身 10s 超时,前端同样 10s 兜底——
|
|
1311
|
+
// 无超时时挂起会让 toast 永远停在"正在检测"且自愈刷新不触发
|
|
1312
|
+
var probeController = new AbortController();
|
|
1313
|
+
var probeTimer = setTimeout(function () { probeController.abort(); }, 10000);
|
|
1314
|
+
fetch("/api/github/auth?force=true", { signal: probeController.signal })
|
|
1218
1315
|
.then(function (res) { return res.json(); })
|
|
1219
1316
|
.then(function (json) {
|
|
1317
|
+
clearTimeout(probeTimer);
|
|
1220
1318
|
if (json && json.ok && json.auth && json.auth.loggedIn) {
|
|
1221
1319
|
setToastMsg("✅ 已识别到 GitHub CLI 登录态,正在拉取数据...");
|
|
1222
1320
|
clearStoredGlobalOverview();
|
|
@@ -1228,6 +1326,8 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1228
1326
|
}
|
|
1229
1327
|
})
|
|
1230
1328
|
.catch(function () {
|
|
1329
|
+
clearTimeout(probeTimer);
|
|
1330
|
+
setToastMsg("❌ 检测超时或后端不可达,请稍后重试");
|
|
1231
1331
|
onRefresh();
|
|
1232
1332
|
});
|
|
1233
1333
|
},
|
|
@@ -1289,7 +1389,16 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1289
1389
|
}
|
|
1290
1390
|
else {
|
|
1291
1391
|
setStatus("failed");
|
|
1292
|
-
|
|
1392
|
+
// 失败原因透传:后端 log 的末尾几行含真实报错(如 'dsh' 不是内部命令),
|
|
1393
|
+
// 只给「退出码异常」这类笼统话术用户无从下手
|
|
1394
|
+
var logTail = "";
|
|
1395
|
+
if (json.log && typeof json.log === "string") {
|
|
1396
|
+
var lines = json.log.trim().split("\n").filter(function (l) { return l.trim(); });
|
|
1397
|
+
if (lines.length > 0) {
|
|
1398
|
+
logTail = "\n\n📝 日志末尾: " + lines.slice(-4).join(" ⏎ ").slice(0, 400);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
setMsg((json.message || "升级命令执行失败,请在宿主终端手动执行。") + logTail);
|
|
1293
1402
|
}
|
|
1294
1403
|
}
|
|
1295
1404
|
catch (err) {
|
|
@@ -1305,7 +1414,9 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1305
1414
|
setDismissed(true);
|
|
1306
1415
|
};
|
|
1307
1416
|
var handleCopy = function () {
|
|
1308
|
-
|
|
1417
|
+
// 兜底命令也必须带 --profile(官方 CLI 必填)——正常路径 upgradeCommand
|
|
1418
|
+
// 由后端按真实 profile 生成,此默认值仅在后端数据缺失时兜底
|
|
1419
|
+
var cmd = updateInfo.upgradeCommand || "dsh plugin --profile desktop add dsh-github-flow@latest";
|
|
1309
1420
|
if (navigator.clipboard) {
|
|
1310
1421
|
navigator.clipboard.writeText(cmd);
|
|
1311
1422
|
alert("已复制升级命令到剪贴板:\n" + cmd);
|
|
@@ -1361,7 +1472,17 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1361
1472
|
}, status === "upgrading" ? "升级中..." : "⚡ 一键升级"), h("button", {
|
|
1362
1473
|
className: "dsh-github-btn-sm",
|
|
1363
1474
|
onClick: handleCopy,
|
|
1364
|
-
style: {
|
|
1475
|
+
style: {
|
|
1476
|
+
fontSize: "11px",
|
|
1477
|
+
padding: "4px 8px",
|
|
1478
|
+
// 失败态高亮:与驾驶舱横幅同一引导逻辑(失败文案指向复制动作)
|
|
1479
|
+
...(status === "failed" ? {
|
|
1480
|
+
background: "var(--dsw-alias-link, #0969da)",
|
|
1481
|
+
color: "#ffffff",
|
|
1482
|
+
borderColor: "var(--dsw-alias-link, #0969da)",
|
|
1483
|
+
fontWeight: 600,
|
|
1484
|
+
} : {}),
|
|
1485
|
+
},
|
|
1365
1486
|
}, "📋 复制命令")));
|
|
1366
1487
|
}
|
|
1367
1488
|
// 2. 全景驾驶舱大面板横幅模式
|
|
@@ -1405,8 +1526,17 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1405
1526
|
}, status === "upgrading" ? "正在自动升级中..." : "⚡ 一键自动升级"), h("button", {
|
|
1406
1527
|
className: "dsh-github-btn-sm",
|
|
1407
1528
|
onClick: handleCopy,
|
|
1408
|
-
style: {
|
|
1409
|
-
|
|
1529
|
+
style: {
|
|
1530
|
+
padding: "6px 12px",
|
|
1531
|
+
// 失败态高亮复制按钮:失败文案引导的动作就是它,视觉权重同步提升
|
|
1532
|
+
...(status === "failed" ? {
|
|
1533
|
+
background: "var(--dsw-alias-link, #0969da)",
|
|
1534
|
+
color: "#ffffff",
|
|
1535
|
+
borderColor: "var(--dsw-alias-link, #0969da)",
|
|
1536
|
+
fontWeight: 600,
|
|
1537
|
+
} : {}),
|
|
1538
|
+
},
|
|
1539
|
+
}, status === "failed" ? "📋 复制命令手动升级" : "📋 复制命令"), h("button", {
|
|
1410
1540
|
onClick: handleDismiss,
|
|
1411
1541
|
title: "不再提醒此版本",
|
|
1412
1542
|
style: { background: "none", border: "none", cursor: "pointer", color: "var(--dsw-alias-label-tertiary, #8c8c8c)", fontSize: "16px", padding: "0 4px" },
|
|
@@ -1475,7 +1605,7 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1475
1605
|
}, text);
|
|
1476
1606
|
}
|
|
1477
1607
|
// 渲染 GitHub 仓库信息与操作视图的核心函数
|
|
1478
|
-
function renderGithubRepoView(h, data, loading, error, onRefresh, isRightbar = false, updateInfo = null, syncStatus = "", lastSyncedAt = 0) {
|
|
1608
|
+
function renderGithubRepoView(h, data, loading, error, onRefresh, isRightbar = false, updateInfo = null, syncStatus = "", lastSyncedAt = 0, onOpenSettings = null) {
|
|
1479
1609
|
var auth = data ? data.auth : null;
|
|
1480
1610
|
var repo = data ? data.repo : null;
|
|
1481
1611
|
var prs = (data && data.pullRequests) || [];
|
|
@@ -1485,11 +1615,19 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1485
1615
|
var upgradeBannerEl = effectiveUpdateInfo && effectiveUpdateInfo.hasUpdate
|
|
1486
1616
|
? h(UpgradeNotificationCard, { updateInfo: effectiveUpdateInfo, isRightbar })
|
|
1487
1617
|
: null;
|
|
1488
|
-
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", {
|
|
1618
|
+
var headerEl = h("div", { className: "dsh-github-header" }, h("div", { className: "dsh-github-title" }, renderOctocatBadge(isRightbar ? 26 : 30), h("h2", null, isRightbar ? "GitHub 仓库" : "GitHub 工作台"), renderPluginBadge(h), syncStatus ? h(SyncStatusBadge, { syncStatus, lastSyncedAt, isRightbar }) : null), h("div", { style: { display: "flex", alignItems: "center", gap: "6px" } }, h("button", {
|
|
1489
1619
|
className: "dsh-github-refresh-btn",
|
|
1490
1620
|
onClick: onRefresh,
|
|
1491
1621
|
disabled: loading,
|
|
1492
|
-
}, loading ? "刷新中..." : "🔄 刷新")
|
|
1622
|
+
}, loading ? "刷新中..." : "🔄 刷新"),
|
|
1623
|
+
// 设置按钮:与全景主页复用同一设置弹窗(传入回调时渲染——目前仅右侧栏)。
|
|
1624
|
+
// 右侧栏此前没有设置入口,用户必须切到主页才能改同步/代理/更新设置。
|
|
1625
|
+
onOpenSettings ? h("button", {
|
|
1626
|
+
className: "dsh-github-btn-sm",
|
|
1627
|
+
onClick: onOpenSettings,
|
|
1628
|
+
title: "配置数据同步节奏、本地缓存、网络代理与插件信息",
|
|
1629
|
+
style: { padding: "4px 10px" },
|
|
1630
|
+
}, "⚙️") : null));
|
|
1493
1631
|
// 仓库基础卡片
|
|
1494
1632
|
var repoCardEl = h("div", { className: "dsh-github-card" }, h("div", { className: "dsh-github-card-title" }, "当前工作区 GitHub 仓库"), h("div", { className: "dsh-github-card-value" }, repo
|
|
1495
1633
|
? h("a", { href: repo.url, target: "_blank", className: "dsh-github-repo-link" }, repo.nameWithOwner)
|
|
@@ -1776,10 +1914,10 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1776
1914
|
window.removeEventListener("dsh-github-cache-cleared", onCacheCleared);
|
|
1777
1915
|
};
|
|
1778
1916
|
}, []);
|
|
1779
|
-
// gh
|
|
1917
|
+
// 检查更新与 gh 环境链路(安装/登录/业务数据)零依赖,挂载即并行发起:
|
|
1918
|
+
// 原先门控在 [data] 之后会串行等 5~19 秒,且 gh 冷启动失败时 data 恒空导致横幅永不出。
|
|
1919
|
+
// 仅 300ms 微延迟避让首屏渲染;并发由服务端 inFlightPromise + 24h 缓存合流去重。
|
|
1780
1920
|
React.useEffect(() => {
|
|
1781
|
-
if (!data)
|
|
1782
|
-
return;
|
|
1783
1921
|
var timer = setTimeout(function () {
|
|
1784
1922
|
fetch("/api/github/check-update")
|
|
1785
1923
|
.then(function (res) { return res.json(); })
|
|
@@ -1789,9 +1927,9 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1789
1927
|
}
|
|
1790
1928
|
})
|
|
1791
1929
|
.catch(function () { });
|
|
1792
|
-
},
|
|
1930
|
+
}, 300);
|
|
1793
1931
|
return function () { clearTimeout(timer); };
|
|
1794
|
-
}, [
|
|
1932
|
+
}, []);
|
|
1795
1933
|
// 遵循全局规则的数据拉取核心函数 (仅依赖标量 cwd 与 sessionId,坚决去除 props 引用依赖,彻底终结死循环)
|
|
1796
1934
|
var loadData = React.useCallback(async (forceRefresh = false) => {
|
|
1797
1935
|
// 规则执行:若主页设置为「纯手动模式 (manual)」,且非用户点击强制刷新,且本地已有数据,坚决不打后台网络!
|
|
@@ -1803,9 +1941,15 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1803
1941
|
}
|
|
1804
1942
|
setSyncStatus(data ? "syncing" : "loading");
|
|
1805
1943
|
setError(null);
|
|
1944
|
+
// 与全局面板同规格的超时保护:任一 fetch 挂起(如系统代理劫持回环请求)时
|
|
1945
|
+
// 45 秒 abort,loading 必然清除——未安装 gh 的用户能拿到明确的错误出口
|
|
1946
|
+
// 而非永久转圈。此前本函数完全没有 AbortController,挂起即永久 loading。
|
|
1947
|
+
var controller = new AbortController();
|
|
1948
|
+
var timedOut = false;
|
|
1949
|
+
var timer = setTimeout(function () { timedOut = true; controller.abort(); }, 45000);
|
|
1806
1950
|
try {
|
|
1807
1951
|
if (forceRefresh) {
|
|
1808
|
-
await fetch("/api/github/refresh", { method: "POST" }).catch(() => { });
|
|
1952
|
+
await fetch("/api/github/refresh", { method: "POST", signal: controller.signal }).catch(() => { });
|
|
1809
1953
|
}
|
|
1810
1954
|
var url = "/api/github/overview";
|
|
1811
1955
|
var params = [];
|
|
@@ -1818,7 +1962,7 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1818
1962
|
if (params.length > 0) {
|
|
1819
1963
|
url += "?" + params.join("&");
|
|
1820
1964
|
}
|
|
1821
|
-
var res = await fetch(url);
|
|
1965
|
+
var res = await fetch(url, { signal: controller.signal });
|
|
1822
1966
|
if (!res.ok) {
|
|
1823
1967
|
var errText = await res.text().catch(() => "");
|
|
1824
1968
|
var msg = `后端服务未就绪 (${res.status}): ${errText || res.statusText}`;
|
|
@@ -1849,13 +1993,20 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1849
1993
|
}
|
|
1850
1994
|
catch (err) {
|
|
1851
1995
|
if (data) {
|
|
1996
|
+
// 后台网络故障时,旧数据依然完整可用
|
|
1852
1997
|
setSyncStatus("failed");
|
|
1853
1998
|
}
|
|
1854
1999
|
else {
|
|
1855
|
-
|
|
2000
|
+
if (timedOut || err.name === "AbortError") {
|
|
2001
|
+
setError("获取超时(45 秒):本机 gh 命令响应过慢或网络代理劫持了回环请求。可稍后重试,或在「⚙️ 设置 → 🌐 网络代理」确认代理已放行 127.0.0.1。");
|
|
2002
|
+
}
|
|
2003
|
+
else {
|
|
2004
|
+
setError(err.message || "网络异常");
|
|
2005
|
+
}
|
|
1856
2006
|
}
|
|
1857
2007
|
}
|
|
1858
2008
|
finally {
|
|
2009
|
+
clearTimeout(timer);
|
|
1859
2010
|
setLoading(false);
|
|
1860
2011
|
}
|
|
1861
2012
|
}, [cwd, sessionId, data, syncSettings.strategy]);
|
|
@@ -1890,8 +2041,56 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1890
2041
|
window.addEventListener("focus", onFocus);
|
|
1891
2042
|
return () => window.removeEventListener("focus", onFocus);
|
|
1892
2043
|
}, [data, loadData]);
|
|
2044
|
+
// 设置弹窗(右侧栏独立实例):插件徽标 → 直开「关于」分区;⚙️ 按钮 → 默认
|
|
2045
|
+
// 「数据同步」。与全景主页复用同一弹窗组件,同步设置经事件总线实时对齐两侧。
|
|
2046
|
+
var [aboutModalOpen, setAboutModalOpen] = React.useState(false);
|
|
2047
|
+
var [rightbarModalOpen, setRightbarModalOpen] = React.useState(false);
|
|
2048
|
+
var [rightbarModalSection, setRightbarModalSection] = React.useState("sync");
|
|
2049
|
+
var containerRef = React.useRef(null);
|
|
2050
|
+
React.useEffect(() => {
|
|
2051
|
+
// 多宿主同挂载防叠层:全景主页与右侧栏可能同时在 DOM 中,徽标事件若被
|
|
2052
|
+
// 两处同时响应会出现双层弹窗。只有「自身容器可见」的宿主才响应——
|
|
2053
|
+
// 徽标所在面板必然是用户当前看到的那个。
|
|
2054
|
+
var onOpenAbout = () => {
|
|
2055
|
+
var el = containerRef.current;
|
|
2056
|
+
if (!el)
|
|
2057
|
+
return;
|
|
2058
|
+
var rect = el.getBoundingClientRect();
|
|
2059
|
+
var visible = rect.width > 0 && rect.height > 0;
|
|
2060
|
+
if (visible)
|
|
2061
|
+
setAboutModalOpen(true);
|
|
2062
|
+
};
|
|
2063
|
+
window.addEventListener("dsh-github-open-about", onOpenAbout);
|
|
2064
|
+
return () => window.removeEventListener("dsh-github-open-about", onOpenAbout);
|
|
2065
|
+
}, []);
|
|
2066
|
+
var openRightbarSettings = () => {
|
|
2067
|
+
setRightbarModalSection("sync");
|
|
2068
|
+
setRightbarModalOpen(true);
|
|
2069
|
+
};
|
|
1893
2070
|
var h = React.createElement;
|
|
1894
|
-
return h("div", { className: "dsh-github-rightbar-container" }, ...renderGithubRepoView(h, data, loading, error, () => loadData(true), true, updateInfo, syncStatus, lastSyncedAt)
|
|
2071
|
+
return h("div", { className: "dsh-github-rightbar-container", ref: containerRef }, ...renderGithubRepoView(h, data, loading, error, () => loadData(true), true, updateInfo, syncStatus, lastSyncedAt, openRightbarSettings),
|
|
2072
|
+
// 弹窗一:插件徽标触发的「关于」单页展示
|
|
2073
|
+
h(GlobalSyncSettingsModal, {
|
|
2074
|
+
isOpen: aboutModalOpen,
|
|
2075
|
+
initialSection: "about",
|
|
2076
|
+
onClose: () => setAboutModalOpen(false),
|
|
2077
|
+
currentSettings: syncSettings,
|
|
2078
|
+
onSave: function () { },
|
|
2079
|
+
onClearCache: function () { setData(null); },
|
|
2080
|
+
}),
|
|
2081
|
+
// 弹窗二:⚙️ 按钮触发的完整设置(与主页同一弹窗组件,分区可自由切换)
|
|
2082
|
+
h(GlobalSyncSettingsModal, {
|
|
2083
|
+
isOpen: rightbarModalOpen,
|
|
2084
|
+
initialSection: rightbarModalSection,
|
|
2085
|
+
onClose: () => setRightbarModalOpen(false),
|
|
2086
|
+
currentSettings: syncSettings,
|
|
2087
|
+
onSave: function (newSettings) { setSyncSettings(newSettings); },
|
|
2088
|
+
onClearCache: function () {
|
|
2089
|
+
setData(null);
|
|
2090
|
+
setLastSyncedAt(0);
|
|
2091
|
+
setSyncStatus("loading");
|
|
2092
|
+
},
|
|
2093
|
+
}));
|
|
1895
2094
|
}
|
|
1896
2095
|
// 2. 右侧栏 Tab 标题组件
|
|
1897
2096
|
function GithubRightbarTitle() {
|
|
@@ -1907,22 +2106,51 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1907
2106
|
const active = Boolean(props && props.active);
|
|
1908
2107
|
return React.createElement("div", {
|
|
1909
2108
|
className: "dsh-github-icon" + (active ? " active" : ""),
|
|
1910
|
-
title: "GitHub
|
|
2109
|
+
title: "GitHub 全景驾驶舱(dsh-github-flow 插件)",
|
|
1911
2110
|
}, React.createElement("div", {
|
|
1912
2111
|
className: "dsh-github-icon-inner" + (active ? " active" : "")
|
|
1913
2112
|
}, renderOctocat(18, "currentColor")));
|
|
1914
2113
|
}
|
|
1915
|
-
// 全景驾驶舱「⚙️ 设置」模态弹窗组件:左侧分区导航(数据同步 /
|
|
2114
|
+
// 全景驾驶舱「⚙️ 设置」模态弹窗组件:左侧分区导航(数据同步 / 网络代理 / 更新设置 / 关于)+ 右侧内容区。
|
|
1916
2115
|
// 为什么改多分区:弹窗承载能力从单一「同步偏好」扩展为多项插件设置(后续可继续加),
|
|
1917
2116
|
// 单页滚动堆叠会让用户找不到入口;分区导航让每项设置拥有独立心智空间与保存动作。
|
|
2117
|
+
// initialSection:宿主可指定初始分区——插件徽标点击时直开「关于」,单独展示身份信息。
|
|
1918
2118
|
function GlobalSyncSettingsModal(props) {
|
|
1919
2119
|
if (!props.isOpen)
|
|
1920
2120
|
return null;
|
|
1921
|
-
// ---- 当前激活分区:sync
|
|
1922
|
-
var [activeSection, setActiveSection] = React.useState("sync");
|
|
2121
|
+
// ---- 当前激活分区:sync / proxy / update / about ----
|
|
2122
|
+
var [activeSection, setActiveSection] = React.useState(props.initialSection || "sync");
|
|
1923
2123
|
var [strategy, setStrategy] = React.useState(props.currentSettings.strategy || "swr");
|
|
1924
2124
|
var [cacheMeta, setCacheMeta] = React.useState(getCacheStorageMeta());
|
|
1925
2125
|
var [clearTip, setClearTip] = React.useState("");
|
|
2126
|
+
// ---- 更新设置分区状态 ----
|
|
2127
|
+
// 打开弹窗时从后端拉取真实模式(权威源是 update-settings.json 而非 localStorage),
|
|
2128
|
+
// 与代理分区同范式:避免多窗口/外部手改文件导致 UI 与实际生效配置漂移。
|
|
2129
|
+
var [updateMode, setUpdateMode] = React.useState("official");
|
|
2130
|
+
var [updateBusy, setUpdateBusy] = React.useState(false);
|
|
2131
|
+
var [updateMsg, setUpdateMsg] = React.useState(null);
|
|
2132
|
+
// ---- 关于分区:当前运行版本号(弹窗打开时轻量拉取,失败静默回落占位)----
|
|
2133
|
+
var [aboutVersion, setAboutVersion] = React.useState(null);
|
|
2134
|
+
React.useEffect(function () {
|
|
2135
|
+
if (!props.isOpen)
|
|
2136
|
+
return;
|
|
2137
|
+
fetch("/api/github/update-settings")
|
|
2138
|
+
.then(function (r) { return r.json(); })
|
|
2139
|
+
.then(function (j) {
|
|
2140
|
+
if (j && j.ok && j.data && j.data.mode) {
|
|
2141
|
+
setUpdateMode(j.data.mode);
|
|
2142
|
+
}
|
|
2143
|
+
})
|
|
2144
|
+
.catch(function () { });
|
|
2145
|
+
// 版本号:check-update 命中 24h 缓存时零成本;失败静默(关于页显示占位版本)
|
|
2146
|
+
fetch("/api/github/check-update")
|
|
2147
|
+
.then(function (r) { return r.json(); })
|
|
2148
|
+
.then(function (j) {
|
|
2149
|
+
if (j && j.ok && j.data && j.data.currentVersion)
|
|
2150
|
+
setAboutVersion(j.data.currentVersion);
|
|
2151
|
+
})
|
|
2152
|
+
.catch(function () { });
|
|
2153
|
+
}, [props.isOpen]);
|
|
1926
2154
|
// ---- 网络代理分区状态 ----
|
|
1927
2155
|
// 打开弹窗时从后端拉取真实代理状态(权威源是 proxy.json 而非 localStorage),
|
|
1928
2156
|
// 避免多窗口/外部手改文件导致的 UI 与实际生效配置漂移。
|
|
@@ -1960,6 +2188,55 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
1960
2188
|
window.addEventListener("keydown", onKeyDown);
|
|
1961
2189
|
return function () { window.removeEventListener("keydown", onKeyDown); };
|
|
1962
2190
|
}, [props.onClose]);
|
|
2191
|
+
// initialSection 同步:弹窗实例常驻(return null 在 hooks 后,不卸载),
|
|
2192
|
+
// useState 初值只在首挂载取一次——宿主切换入口重开时(如齿轮→关→徽标),
|
|
2193
|
+
// 必须显式把 activeSection 对齐到新的 initialSection,否则停留在旧分区。
|
|
2194
|
+
React.useEffect(function () {
|
|
2195
|
+
if (props.isOpen && props.initialSection) {
|
|
2196
|
+
setActiveSection(props.initialSection);
|
|
2197
|
+
}
|
|
2198
|
+
}, [props.isOpen, props.initialSection]);
|
|
2199
|
+
// 更新源保存:POST 后端(写盘 + 热切换 checker + 立即用新源真探)。
|
|
2200
|
+
// 返回的 probe 结果如实展示:探测失败提示网络问题而非假报「已生效无异常」。
|
|
2201
|
+
var handleUpdateSave = function () {
|
|
2202
|
+
setUpdateBusy(true);
|
|
2203
|
+
setUpdateMsg(null);
|
|
2204
|
+
fetch("/api/github/update-settings", {
|
|
2205
|
+
method: "POST",
|
|
2206
|
+
headers: { "Content-Type": "application/json" },
|
|
2207
|
+
body: JSON.stringify({ mode: updateMode }),
|
|
2208
|
+
})
|
|
2209
|
+
.then(function (r) { return r.json(); })
|
|
2210
|
+
.then(function (j) {
|
|
2211
|
+
setUpdateBusy(false);
|
|
2212
|
+
if (j && j.ok && j.data) {
|
|
2213
|
+
var d = j.data;
|
|
2214
|
+
var text = "";
|
|
2215
|
+
if (d.mode === "off") {
|
|
2216
|
+
text = "✅ 已关闭自动检查更新(/gh upgrade 与一键升级仍可用),无需重启";
|
|
2217
|
+
}
|
|
2218
|
+
else if (d.probe && d.probe.latestVersion) {
|
|
2219
|
+
text = "✅ 已切换至" + d.description + ",当前最新版本 v" + d.probe.latestVersion
|
|
2220
|
+
+ (d.probe.hasUpdate ? "(有可用更新)" : "(已是最新)") + ",无需重启";
|
|
2221
|
+
}
|
|
2222
|
+
else {
|
|
2223
|
+
// 探测失败如实告知:设置已生效但新源暂不可达(可能是网络或源同步问题)
|
|
2224
|
+
text = "✅ 已切换至" + d.description + ",但新源暂时不可达(版本探测失败,稍后会自动重试);设置已生效,无需重启";
|
|
2225
|
+
}
|
|
2226
|
+
if (d.persisted === false) {
|
|
2227
|
+
text += ";⚠️ 设置写盘失败,重启后需重新选择";
|
|
2228
|
+
}
|
|
2229
|
+
setUpdateMsg({ kind: "ok", text: text });
|
|
2230
|
+
}
|
|
2231
|
+
else {
|
|
2232
|
+
setUpdateMsg({ kind: "err", text: (j && j.message) || "保存失败" });
|
|
2233
|
+
}
|
|
2234
|
+
})
|
|
2235
|
+
.catch(function (e) {
|
|
2236
|
+
setUpdateBusy(false);
|
|
2237
|
+
setUpdateMsg({ kind: "err", text: "网络请求失败: " + (e && e.message ? e.message : e) });
|
|
2238
|
+
});
|
|
2239
|
+
};
|
|
1963
2240
|
var h = React.createElement;
|
|
1964
2241
|
var handleSave = function () {
|
|
1965
2242
|
var updated = {
|
|
@@ -2051,7 +2328,13 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
2051
2328
|
}, "🔄 数据同步"), h("button", {
|
|
2052
2329
|
className: "dsh-github-settings-nav-item" + (activeSection === "proxy" ? " active" : ""),
|
|
2053
2330
|
onClick: function () { setActiveSection("proxy"); },
|
|
2054
|
-
}, "🌐 网络代理")
|
|
2331
|
+
}, "🌐 网络代理"), h("button", {
|
|
2332
|
+
className: "dsh-github-settings-nav-item" + (activeSection === "update" ? " active" : ""),
|
|
2333
|
+
onClick: function () { setActiveSection("update"); },
|
|
2334
|
+
}, "⬆️ 更新设置"), h("button", {
|
|
2335
|
+
className: "dsh-github-settings-nav-item" + (activeSection === "about" ? " active" : ""),
|
|
2336
|
+
onClick: function () { setActiveSection("about"); },
|
|
2337
|
+
}, "ℹ️ 关于")), h("div", { className: "dsh-github-settings-content" }, activeSection === "sync" ? h("div", null, h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "14px", lineHeight: "1.5" } }, "配置 GitHub 全景驾驶舱的数据拉取节奏。启用秒开模式可利用本地持久化缓存消除每次打开时的漫长等待。"),
|
|
2055
2338
|
// 刷新策略选择
|
|
2056
2339
|
h("div", { style: { marginBottom: "16px" } }, h("div", { style: { fontSize: "13px", fontWeight: 600, marginBottom: "8px" } }, "数据刷新策略"),
|
|
2057
2340
|
// 策略 A: SWR 秒开
|
|
@@ -2076,7 +2359,63 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
2076
2359
|
className: "dsh-github-btn-sm",
|
|
2077
2360
|
style: { padding: "3px 8px", fontSize: "11px", color: "var(--dsw-alias-state-error-primary, #cf222e)" },
|
|
2078
2361
|
onClick: handleClear,
|
|
2079
|
-
}, "🗑️ 清空缓存") : null), clearTip ? h("div", { style: { color: "var(--dsw-alias-state-success, #2ea043)", marginTop: "4px", fontWeight: 500 } }, clearTip) : null)) : h("div", null,
|
|
2362
|
+
}, "🗑️ 清空缓存") : null), clearTip ? h("div", { style: { color: "var(--dsw-alias-state-success, #2ea043)", marginTop: "4px", fontWeight: 500 } }, clearTip) : null)) : activeSection === "update" ? h("div", null,
|
|
2363
|
+
// ⬆️ 更新设置分区:选择版本检测的 npm 源(或关闭自动检查)。
|
|
2364
|
+
// 检测与升级流量均遵循「🌐 网络代理」分区设置——代理是全局的。
|
|
2365
|
+
h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "14px", lineHeight: "1.5" } }, "选择插件新版本检测所用的 npm 源。检测与升级流量均遵循「🌐 网络代理」分区设置:启用 http/https 代理时自动经代理访问;socks5 代理下 npm 检测为直连(gh 与升级子进程链路仍走代理)。无论选择哪个源,安装与升级流程本身不受影响。"), h("div", { style: { marginBottom: "16px" } }, h("div", { style: { fontSize: "13px", fontWeight: 600, marginBottom: "8px" } }, "更新源"),
|
|
2366
|
+
// 源 A: npm 官方(默认)
|
|
2367
|
+
h("div", {
|
|
2368
|
+
className: "dsh-github-radio-card" + (updateMode === "official" ? " active" : ""),
|
|
2369
|
+
onClick: function () { setUpdateMode("official"); },
|
|
2370
|
+
}, h("input", { type: "radio", className: "dsh-github-radio", checked: updateMode === "official", readOnly: true }), h("div", null, h("div", { style: { fontWeight: 600, fontSize: "13px" } }, "🌍 npm 官方源 (推荐)"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "版本全球首发最及时,刚发布的新版立即可见;国内直连可能较慢。"))),
|
|
2371
|
+
// 源 B: 国内镜像
|
|
2372
|
+
h("div", {
|
|
2373
|
+
className: "dsh-github-radio-card" + (updateMode === "mirror" ? " active" : ""),
|
|
2374
|
+
onClick: function () { setUpdateMode("mirror"); },
|
|
2375
|
+
}, h("input", { type: "radio", className: "dsh-github-radio", checked: updateMode === "mirror", readOnly: true }), h("div", null, h("div", { style: { fontWeight: 600, fontSize: "13px" } }, "🇨🇳 国内镜像 (npmmirror)"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "阿里云镜像,国内访问快;同步存在分钟级延迟,刚发布的新版可能暂时查不到。"))),
|
|
2376
|
+
// 源 C: 不检查更新
|
|
2377
|
+
h("div", {
|
|
2378
|
+
className: "dsh-github-radio-card" + (updateMode === "off" ? " active" : ""),
|
|
2379
|
+
onClick: function () { setUpdateMode("off"); },
|
|
2380
|
+
}, h("input", { type: "radio", className: "dsh-github-radio", checked: updateMode === "off", readOnly: true }), h("div", null, h("div", { style: { fontWeight: 600, fontSize: "13px" } }, "⛔ 不检查更新"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "关闭自动检查与新版提示横幅;/gh upgrade 命令与一键升级仍然可用。")))),
|
|
2381
|
+
// 保存按钮 + 结果反馈(按钮复用 star-btn 的双主题链接色样式:内联色
|
|
2382
|
+
// 会覆盖 btn-sm 暗色规则,且暗色下亮蓝底刺眼——与关于页修复同源)
|
|
2383
|
+
h("div", { style: { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" } }, h("button", {
|
|
2384
|
+
className: "dsh-github-btn-sm dsh-github-primary-btn",
|
|
2385
|
+
disabled: updateBusy,
|
|
2386
|
+
onClick: handleUpdateSave,
|
|
2387
|
+
}, updateBusy ? "验证中..." : "💾 保存并全局应用"), updateMsg ? h("span", {
|
|
2388
|
+
style: {
|
|
2389
|
+
fontSize: "12px",
|
|
2390
|
+
lineHeight: "1.5",
|
|
2391
|
+
color: updateMsg.kind === "ok"
|
|
2392
|
+
? "var(--dsw-alias-state-success, #2ea043)"
|
|
2393
|
+
: updateMsg.kind === "err"
|
|
2394
|
+
? "var(--dsw-alias-state-error-primary, #cf222e)"
|
|
2395
|
+
: "var(--dsw-alias-label-secondary, #656d76)",
|
|
2396
|
+
},
|
|
2397
|
+
}, updateMsg.text) : null)) : activeSection === "about" ? h("div", null,
|
|
2398
|
+
// ℹ️ 关于分区:项目信息/仓库地址/开源协议/求 Star。纯静态展示,零状态零请求。
|
|
2399
|
+
h("div", { style: { display: "flex", alignItems: "center", gap: "10px", marginBottom: "14px" } }, renderOctocatBadge(40), h("div", null, h("div", { style: { fontSize: "15px", fontWeight: 600 } }, "DSH GitHub Flow"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "当前版本: v" + (aboutVersion || "…")))), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "14px", lineHeight: "1.6" } }, "让 DeepSeek Harness Agent 拥有操作 GitHub 的原生手脚。原生复用宿主机 gh CLI 安全凭据,深度打通本地 Git 工作区与云端协作,提供 9 大领域 Agent 工具、人类 Slash 指令、Web 全景驾驶舱与一键跨平台升级。"),
|
|
2400
|
+
// 信息条目
|
|
2401
|
+
h("div", { style: { display: "flex", flexDirection: "column", gap: "8px", marginBottom: "14px" } }, h("div", { style: { display: "flex", alignItems: "center", gap: "8px", fontSize: "12px" } }, h("span", { style: { minWidth: "64px", color: "var(--dsw-alias-label-secondary, #656d76)" } }, "仓库地址"), h("a", { href: "https://github.com/ItQianChen/dsh-github-flow", target: "_blank", rel: "noopener noreferrer", style: { color: "var(--dsw-alias-link, #0969da)" } }, "github.com/ItQianChen/dsh-github-flow")), h("div", { style: { display: "flex", alignItems: "center", gap: "8px", fontSize: "12px" } }, h("span", { style: { minWidth: "64px", color: "var(--dsw-alias-label-secondary, #656d76)" } }, "npm 包"), h("a", { href: "https://www.npmjs.com/package/dsh-github-flow", target: "_blank", rel: "noopener noreferrer", style: { color: "var(--dsw-alias-link, #0969da)" } }, "npmjs.com/package/dsh-github-flow")), h("div", { style: { display: "flex", alignItems: "center", gap: "8px", fontSize: "12px" } }, h("span", { style: { minWidth: "64px", color: "var(--dsw-alias-label-secondary, #656d76)" } }, "开源协议"), h("a", { href: "https://github.com/ItQianChen/dsh-github-flow/blob/master/LICENSE", target: "_blank", rel: "noopener noreferrer", style: { color: "var(--dsw-alias-link, #0969da)" } }, "MIT License"))),
|
|
2402
|
+
// 求 Star 卡片
|
|
2403
|
+
// 主题兼容:不用内联色——暗色主题的覆盖只能作用于 class 选择器;
|
|
2404
|
+
// 且此前的 canvas-subtle/border-muted 并非宿主真实令牌(全项目无他处使用),
|
|
2405
|
+
// 永远回落亮色值导致暗色下一块刺眼亮底。改用 cache-info-box 同款双主题 class。
|
|
2406
|
+
h("div", {
|
|
2407
|
+
className: "dsh-github-about-star-box",
|
|
2408
|
+
style: {
|
|
2409
|
+
display: "flex",
|
|
2410
|
+
alignItems: "center",
|
|
2411
|
+
gap: "10px",
|
|
2412
|
+
},
|
|
2413
|
+
}, h("span", { style: { fontSize: "18px" } }, "⭐"), h("div", { style: { flex: 1, fontSize: "12px", lineHeight: "1.5" } }, "如果这个插件对你的日常研发有帮助,欢迎到 GitHub 仓库点个 Star 支持一下——你的 Star 是持续迭代的动力!也欢迎提交 Issue 与 PR。"), h("a", {
|
|
2414
|
+
href: "https://github.com/ItQianChen/dsh-github-flow",
|
|
2415
|
+
target: "_blank",
|
|
2416
|
+
rel: "noopener noreferrer",
|
|
2417
|
+
className: "dsh-github-btn-sm dsh-github-primary-btn",
|
|
2418
|
+
}, "🌟 去点 Star"))) : h("div", null,
|
|
2080
2419
|
// 🌐 网络代理分区:设置保存即全局生效(驾驶舱/右侧栏/Agent 工具/升级链路),
|
|
2081
2420
|
// 未启用时回落系统环境代理或直连(「默认走系统设置」语义)。
|
|
2082
2421
|
h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "14px", lineHeight: "1.5" } }, "设置后插件的 GitHub 数据链路(本页面、右侧栏、Agent 工具调用、版本升级)全部经代理访问;未启用时默认使用系统环境代理或直连。保存后立即生效,无需重启。"), h("label", { style: { display: "flex", alignItems: "center", gap: "8px", marginBottom: "10px", cursor: "pointer", fontSize: "13px" } }, h("input", {
|
|
@@ -2137,8 +2476,8 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
2137
2476
|
: "var(--dsw-alias-label-secondary, #656d76)",
|
|
2138
2477
|
},
|
|
2139
2478
|
}, proxyMsg.text) : null))),
|
|
2140
|
-
// 底部操作条:sync 分区提供主保存动作;proxy 分区有自己的「保存并全局应用」,
|
|
2141
|
-
//
|
|
2479
|
+
// 底部操作条:sync 分区提供主保存动作;proxy/update 分区有自己的「保存并全局应用」,
|
|
2480
|
+
// 底部统一为「完成」关闭弹窗,避免多个保存按钮的语义冲突。
|
|
2142
2481
|
h("div", { className: "dsh-github-modal-footer" }, activeSection === "sync" ? h("button", {
|
|
2143
2482
|
className: "dsh-github-btn-sm",
|
|
2144
2483
|
style: {
|
|
@@ -2164,7 +2503,10 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
2164
2503
|
var [loading, setLoading] = React.useState(!initialSnapshot);
|
|
2165
2504
|
var [syncStatus, setSyncStatus] = React.useState(initialSnapshot ? "cached" : "loading");
|
|
2166
2505
|
var [lastSyncedAt, setLastSyncedAt] = React.useState(initialSnapshot?.savedAt || 0);
|
|
2506
|
+
// 弹窗开闭 + 初始分区分离:插件徽标点击直开「关于」分区(initialSection=about),
|
|
2507
|
+
// 而齿轮按钮仍默认打开「数据同步」——同一弹窗两种入口语义。
|
|
2167
2508
|
var [isSettingsModalOpen, setIsSettingsModalOpen] = React.useState(false);
|
|
2509
|
+
var [settingsInitialSection, setSettingsInitialSection] = React.useState("sync");
|
|
2168
2510
|
var [error, setError] = React.useState(null);
|
|
2169
2511
|
var [updateInfo, setUpdateInfo] = React.useState(null);
|
|
2170
2512
|
var [activeTab, setActiveTab] = React.useState("workspaces"); // 'workspaces' | 'repos' | 'mywork'
|
|
@@ -2187,10 +2529,9 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
2187
2529
|
.catch(function () { });
|
|
2188
2530
|
}, []);
|
|
2189
2531
|
React.useEffect(function () { refreshProxyBadge(); }, [refreshProxyBadge]);
|
|
2190
|
-
// gh
|
|
2532
|
+
// 检查更新与 gh 业务数据零依赖,挂载即并行发起(与右侧栏同策略):
|
|
2533
|
+
// 并发由服务端 inFlightPromise + 24h 缓存合流去重,无请求风暴风险。
|
|
2191
2534
|
React.useEffect(() => {
|
|
2192
|
-
if (!data)
|
|
2193
|
-
return;
|
|
2194
2535
|
var timer = setTimeout(function () {
|
|
2195
2536
|
fetch("/api/github/check-update")
|
|
2196
2537
|
.then(function (res) { return res.json(); })
|
|
@@ -2200,9 +2541,31 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
2200
2541
|
}
|
|
2201
2542
|
})
|
|
2202
2543
|
.catch(function () { });
|
|
2203
|
-
},
|
|
2544
|
+
}, 300);
|
|
2204
2545
|
return function () { clearTimeout(timer); };
|
|
2205
|
-
}, [
|
|
2546
|
+
}, []);
|
|
2547
|
+
// 插件徽标事件:标题上的「插件」徽标点击 → 直开设置弹窗的「关于」分区。
|
|
2548
|
+
// 可见性守卫:右侧栏与主页可能同挂载,只有自身容器可见的宿主响应(防双层弹窗)。
|
|
2549
|
+
var panelRef = React.useRef(null);
|
|
2550
|
+
React.useEffect(() => {
|
|
2551
|
+
var onOpenAbout = () => {
|
|
2552
|
+
var el = panelRef.current;
|
|
2553
|
+
if (!el)
|
|
2554
|
+
return;
|
|
2555
|
+
var rect = el.getBoundingClientRect();
|
|
2556
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
2557
|
+
setSettingsInitialSection("about");
|
|
2558
|
+
setIsSettingsModalOpen(true);
|
|
2559
|
+
}
|
|
2560
|
+
};
|
|
2561
|
+
window.addEventListener("dsh-github-open-about", onOpenAbout);
|
|
2562
|
+
return () => window.removeEventListener("dsh-github-open-about", onOpenAbout);
|
|
2563
|
+
}, []);
|
|
2564
|
+
// 齿轮按钮:默认打开「数据同步」分区(既有语义不变)
|
|
2565
|
+
var openSettingsDefault = () => {
|
|
2566
|
+
setSettingsInitialSection("sync");
|
|
2567
|
+
setIsSettingsModalOpen(true);
|
|
2568
|
+
};
|
|
2206
2569
|
var loadData = React.useCallback(async (forceRefresh = false) => {
|
|
2207
2570
|
// 如果是纯手动模式,且非显式强制刷新,且本地已有旧数据,直接跳过后台拉取
|
|
2208
2571
|
if (!forceRefresh && syncSettings.strategy === "manual" && data) {
|
|
@@ -2215,8 +2578,10 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
2215
2578
|
var timedOut = false;
|
|
2216
2579
|
var timer = setTimeout(function () { timedOut = true; controller.abort(); }, 45000);
|
|
2217
2580
|
try {
|
|
2581
|
+
// refresh 预置请求必须共享同一 signal:它一旦挂起(如系统代理劫持回环请求),
|
|
2582
|
+
// 后续 overview fetch 永远执行不到,loading 永不清除——无 gh 用户正是被此卡死。
|
|
2218
2583
|
if (forceRefresh)
|
|
2219
|
-
await fetch("/api/github/refresh", { method: "POST" }).catch(() => { });
|
|
2584
|
+
await fetch("/api/github/refresh", { method: "POST", signal: controller.signal }).catch(() => { });
|
|
2220
2585
|
var res = await fetch("/api/github/global-overview", { signal: controller.signal });
|
|
2221
2586
|
if (!res.ok) {
|
|
2222
2587
|
var errText = await res.text().catch(() => "");
|
|
@@ -2259,7 +2624,7 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
2259
2624
|
}
|
|
2260
2625
|
else {
|
|
2261
2626
|
if (timedOut || err.name === "AbortError") {
|
|
2262
|
-
setError("获取超时(
|
|
2627
|
+
setError("获取超时(45 秒):本机 gh 命令响应过慢或网络代理劫持了回环请求。可稍后重试,或在「⚙️ 设置 → 🌐 网络代理」确认代理已放行 127.0.0.1。");
|
|
2263
2628
|
}
|
|
2264
2629
|
else {
|
|
2265
2630
|
setError("无法连接后端服务:" + (err.message || err));
|
|
@@ -2303,7 +2668,7 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
2303
2668
|
return () => window.removeEventListener("focus", onFocus);
|
|
2304
2669
|
}, [data, loadData]);
|
|
2305
2670
|
var h = React.createElement;
|
|
2306
|
-
var headerEl = h("div", { className: "dsh-github-header" }, h("div", { className: "dsh-github-title" }, renderOctocatBadge(32), h("h2", null, "GitHub 全景驾驶舱"), h(SyncStatusBadge, { syncStatus, lastSyncedAt }),
|
|
2671
|
+
var headerEl = h("div", { className: "dsh-github-header" }, h("div", { className: "dsh-github-title" }, renderOctocatBadge(32), h("h2", null, "GitHub 全景驾驶舱"), renderPluginBadge(h), h(SyncStatusBadge, { syncStatus, lastSyncedAt }),
|
|
2307
2672
|
// 代理生效徽章:启用时可见,hover 展示代理地址,让用户随时确认「当前确实在走代理」
|
|
2308
2673
|
proxyActive && proxyActive.enabled
|
|
2309
2674
|
? h("span", {
|
|
@@ -2328,12 +2693,12 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
2328
2693
|
disabled: loading,
|
|
2329
2694
|
}, loading ? "同步中..." : "🔄 刷新全局数据"), h("button", {
|
|
2330
2695
|
className: "dsh-github-btn-sm",
|
|
2331
|
-
onClick:
|
|
2332
|
-
title: "
|
|
2696
|
+
onClick: openSettingsDefault,
|
|
2697
|
+
title: "配置数据同步节奏、本地缓存、网络代理与插件信息",
|
|
2333
2698
|
style: { padding: "6px 12px" },
|
|
2334
2699
|
}, "⚙️ 设置")));
|
|
2335
2700
|
if (loading && !data) {
|
|
2336
|
-
return h("div", { className: "dsh-github-panel" }, headerEl, h("div", { className: "dsh-github-empty" }, "正在加载全局 GitHub 账号与工作区数据..."));
|
|
2701
|
+
return h("div", { className: "dsh-github-panel", ref: panelRef }, headerEl, h("div", { className: "dsh-github-empty" }, "正在加载全局 GitHub 账号与工作区数据..."));
|
|
2337
2702
|
}
|
|
2338
2703
|
var effectiveUpdateInfo = updateInfo || data?.updateInfo;
|
|
2339
2704
|
var upgradeBannerEl = effectiveUpdateInfo && effectiveUpdateInfo.hasUpdate
|
|
@@ -2341,6 +2706,7 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
2341
2706
|
: null;
|
|
2342
2707
|
var settingsModalEl = h(GlobalSyncSettingsModal, {
|
|
2343
2708
|
isOpen: isSettingsModalOpen,
|
|
2709
|
+
initialSection: settingsInitialSection,
|
|
2344
2710
|
onClose: () => {
|
|
2345
2711
|
setIsSettingsModalOpen(false);
|
|
2346
2712
|
// 弹窗内可能刚保存过代理配置,关闭时同步刷新标题栏徽章
|
|
@@ -2358,7 +2724,7 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
2358
2724
|
});
|
|
2359
2725
|
// 错误拦截阻断:若无本地数据且请求失败,直接呈现错误卡片,杜绝把接口异常伪装成「未安装 CLI」
|
|
2360
2726
|
if (!data && error) {
|
|
2361
|
-
return h("div", { className: "dsh-github-panel" }, headerEl, upgradeBannerEl, h("div", {
|
|
2727
|
+
return h("div", { className: "dsh-github-panel", ref: panelRef }, headerEl, upgradeBannerEl, h("div", {
|
|
2362
2728
|
style: {
|
|
2363
2729
|
padding: "24px",
|
|
2364
2730
|
borderRadius: "8px",
|
|
@@ -2468,7 +2834,7 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
2468
2834
|
})));
|
|
2469
2835
|
}
|
|
2470
2836
|
}
|
|
2471
|
-
return h("div", { className: "dsh-github-panel" }, headerEl, upgradeBannerEl, errorEl, statsCardsEl, authGuideEl, tabsEl, contentEl, settingsModalEl);
|
|
2837
|
+
return h("div", { className: "dsh-github-panel", ref: panelRef }, headerEl, upgradeBannerEl, errorEl, statsCardsEl, authGuideEl, tabsEl, contentEl, settingsModalEl);
|
|
2472
2838
|
}
|
|
2473
2839
|
var inject = ["slots", "sidebarRightTabs", "sessions", "layout", "theme"];
|
|
2474
2840
|
function apply(ctx) {
|
|
@@ -2500,6 +2866,7 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
2500
2866
|
id: "dsh-github-flow",
|
|
2501
2867
|
kind: "github",
|
|
2502
2868
|
priority: "plugin",
|
|
2869
|
+
// 标题保持简洁,身份由面板内徽标标注(Tab 体系不支持内嵌徽标元素)
|
|
2503
2870
|
title: () => "GitHub 仓库",
|
|
2504
2871
|
guide: [
|
|
2505
2872
|
{
|
|
@@ -2507,7 +2874,7 @@ body[data-ds-dark-theme] .dsh-github-radio {
|
|
|
2507
2874
|
commandId: "workspace.github",
|
|
2508
2875
|
order: 40,
|
|
2509
2876
|
title: () => "GitHub 仓库",
|
|
2510
|
-
description: () => "查看与操作当前工作区目录下的 GitHub
|
|
2877
|
+
description: () => "查看与操作当前工作区目录下的 GitHub 仓库(由 dsh-github-flow 插件提供)",
|
|
2511
2878
|
icon: GithubArtwork,
|
|
2512
2879
|
},
|
|
2513
2880
|
],
|